feat: refactor clicks to generic mouse interactions with click/hover subtypes
This commit is contained in:
@@ -132,7 +132,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
|||||||
if (el) {
|
if (el) {
|
||||||
if (event.type === 'scroll') {
|
if (event.type === 'scroll') {
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
} else if (event.type === 'click') {
|
} else if (event.type === 'mouse') {
|
||||||
const currentRect = el.getBoundingClientRect();
|
const currentRect = el.getBoundingClientRect();
|
||||||
|
|
||||||
// Calculate Point based on Origin
|
// Calculate Point based on Origin
|
||||||
@@ -157,6 +157,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
|||||||
clientX: targetX,
|
clientX: targetX,
|
||||||
clientY: targetY
|
clientY: targetY
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatchMouse = (type: string) => {
|
const dispatchMouse = (type: string) => {
|
||||||
el.dispatchEvent(new MouseEvent(type, {
|
el.dispatchEvent(new MouseEvent(type, {
|
||||||
view: window,
|
view: window,
|
||||||
@@ -165,10 +166,21 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
|||||||
...eventCoords
|
...eventCoords
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
dispatchMouse('mousedown');
|
|
||||||
dispatchMouse('mouseup');
|
if (event.interactionType === 'click') {
|
||||||
dispatchMouse('click');
|
dispatchMouse('mousedown');
|
||||||
el.click();
|
dispatchMouse('mouseup');
|
||||||
|
dispatchMouse('click');
|
||||||
|
|
||||||
|
if (event.realClick) {
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Hover Interaction
|
||||||
|
dispatchMouse('mousemove');
|
||||||
|
dispatchMouse('mouseover');
|
||||||
|
dispatchMouse('mouseenter');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +287,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
|||||||
if (el) {
|
if (el) {
|
||||||
if (event.type === 'scroll') {
|
if (event.type === 'scroll') {
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
} else if (event.type === 'click') {
|
} else if (event.type === 'mouse') {
|
||||||
const currentRect = el.getBoundingClientRect();
|
const currentRect = el.getBoundingClientRect();
|
||||||
|
|
||||||
// Calculate Point based on Origin (same as above for parity)
|
// Calculate Point based on Origin (same as above for parity)
|
||||||
@@ -300,6 +312,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
|||||||
clientX: targetX,
|
clientX: targetX,
|
||||||
clientY: targetY
|
clientY: targetY
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatchMouse = (type: string) => {
|
const dispatchMouse = (type: string) => {
|
||||||
el.dispatchEvent(new MouseEvent(type, {
|
el.dispatchEvent(new MouseEvent(type, {
|
||||||
view: window,
|
view: window,
|
||||||
@@ -308,10 +321,21 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
|||||||
...eventCoords
|
...eventCoords
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
dispatchMouse('mousedown');
|
|
||||||
dispatchMouse('mouseup');
|
if (event.interactionType === 'click') {
|
||||||
dispatchMouse('click');
|
dispatchMouse('mousedown');
|
||||||
el.click();
|
dispatchMouse('mouseup');
|
||||||
|
dispatchMouse('click');
|
||||||
|
|
||||||
|
if (event.realClick) {
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Hover Interaction
|
||||||
|
dispatchMouse('mousemove');
|
||||||
|
dispatchMouse('mouseover');
|
||||||
|
dispatchMouse('mouseenter');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Box,
|
Box,
|
||||||
|
ExternalLink,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { RecordEvent } from '@/types/record-mode';
|
import { RecordEvent } from '@/types/record-mode';
|
||||||
import { PlaybackCursor } from './PlaybackCursor';
|
import { PlaybackCursor } from './PlaybackCursor';
|
||||||
@@ -42,7 +43,8 @@ export function RecordModeOverlay() {
|
|||||||
setEvents, // Added setEvents here
|
setEvents, // Added setEvents here
|
||||||
} = useRecordMode();
|
} = useRecordMode();
|
||||||
|
|
||||||
const [pickingMode, setPickingMode] = useState<'click' | 'scroll' | null>(null);
|
const [pickingMode, setPickingMode] = useState<'mouse' | 'scroll' | null>(null);
|
||||||
|
const [lastInteractionType, setLastInteractionType] = useState<'click' | 'hover'>('click');
|
||||||
const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(null);
|
const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(null);
|
||||||
const [editingEventId, setEditingEventId] = useState<string | null>(null);
|
const [editingEventId, setEditingEventId] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -61,13 +63,14 @@ export function RecordModeOverlay() {
|
|||||||
if (e.data.type === 'ELEMENT_SELECTED') {
|
if (e.data.type === 'ELEMENT_SELECTED') {
|
||||||
const { selector, rect, tagName } = e.data;
|
const { selector, rect, tagName } = e.data;
|
||||||
|
|
||||||
if (pickingMode === 'click') {
|
if (pickingMode === 'mouse') {
|
||||||
addEvent({
|
addEvent({
|
||||||
type: 'click',
|
type: 'mouse',
|
||||||
|
interactionType: lastInteractionType,
|
||||||
selector,
|
selector,
|
||||||
duration: 1000,
|
duration: lastInteractionType === 'click' ? 1000 : 1500,
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
description: `Click on ${tagName}`,
|
description: `${lastInteractionType === 'click' ? 'Click' : 'Hover'} on ${tagName}`,
|
||||||
motionBlur: false,
|
motionBlur: false,
|
||||||
rect,
|
rect,
|
||||||
});
|
});
|
||||||
@@ -156,13 +159,27 @@ export function RecordModeOverlay() {
|
|||||||
{/* Action Tools */}
|
{/* Action Tools */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setPickingMode('click')}
|
onClick={() => {
|
||||||
className={`flex items-center gap-2 px-4 py-2.5 rounded-[16px] transition-all text-xs font-bold uppercase tracking-wide ${pickingMode === 'click' ? 'bg-accent text-primary-dark shadow-lg shadow-accent/20' : 'text-white/40 hover:text-white hover:bg-white/5'}`}
|
setPickingMode('mouse');
|
||||||
|
setLastInteractionType('click');
|
||||||
|
}}
|
||||||
|
className={`flex items-center gap-2 px-4 py-2.5 rounded-[16px] transition-all text-xs font-bold uppercase tracking-wide ${pickingMode === 'mouse' && lastInteractionType === 'click' ? 'bg-accent text-primary-dark shadow-lg shadow-accent/20' : 'text-white/40 hover:text-white hover:bg-white/5'}`}
|
||||||
>
|
>
|
||||||
<MousePointer2 size={16} />
|
<MousePointer2 size={16} />
|
||||||
<span>Click</span>
|
<span>Click</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setPickingMode('mouse');
|
||||||
|
setLastInteractionType('hover');
|
||||||
|
}}
|
||||||
|
className={`flex items-center gap-2 px-4 py-2.5 rounded-[16px] transition-all text-xs font-bold uppercase tracking-wide ${pickingMode === 'mouse' && lastInteractionType === 'hover' ? 'bg-accent text-primary-dark shadow-lg shadow-accent/20' : 'text-white/40 hover:text-white hover:bg-white/5'}`}
|
||||||
|
>
|
||||||
|
<Eye size={16} />
|
||||||
|
<span>Hover</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setPickingMode('scroll')}
|
onClick={() => setPickingMode('scroll')}
|
||||||
className={`flex items-center gap-2 px-4 py-2.5 rounded-[16px] transition-all text-xs font-bold uppercase tracking-wide ${pickingMode === 'scroll' ? 'bg-accent text-primary-dark shadow-lg shadow-accent/20' : 'text-white/40 hover:text-white hover:bg-white/5'}`}
|
className={`flex items-center gap-2 px-4 py-2.5 rounded-[16px] transition-all text-xs font-bold uppercase tracking-wide ${pickingMode === 'scroll' ? 'bg-accent text-primary-dark shadow-lg shadow-accent/20' : 'text-white/40 hover:text-white hover:bg-white/5'}`}
|
||||||
@@ -314,8 +331,10 @@ export function RecordModeOverlay() {
|
|||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-white text-[10px] font-black uppercase tracking-widest">{event.type}</span>
|
<span className="text-white text-[10px] font-black uppercase tracking-widest">
|
||||||
{event.clickOrigin && event.clickOrigin !== 'center' && (
|
{event.type === 'mouse' ? event.interactionType : event.type}
|
||||||
|
</span>
|
||||||
|
{event.clickOrigin && event.clickOrigin !== 'center' && event.interactionType === 'click' && (
|
||||||
<span className="text-[8px] bg-accent/20 text-accent px-1.5 py-0.5 rounded uppercase font-bold">{event.clickOrigin}</span>
|
<span className="text-[8px] bg-accent/20 text-accent px-1.5 py-0.5 rounded uppercase font-bold">{event.clickOrigin}</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-[8px] bg-white/10 px-1.5 py-0.5 rounded text-white/40 font-mono italic">{event.duration}ms</span>
|
<span className="text-[8px] bg-white/10 px-1.5 py-0.5 rounded text-white/40 font-mono italic">{event.duration}ms</span>
|
||||||
@@ -390,17 +409,41 @@ export function RecordModeOverlay() {
|
|||||||
<div className="flex-1 space-y-8 overflow-y-auto pr-2 scrollbar-hide">
|
<div className="flex-1 space-y-8 overflow-y-auto pr-2 scrollbar-hide">
|
||||||
{/* Type Display */}
|
{/* Type Display */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-white/30 leading-none">Action Type</label>
|
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-white/30 leading-none">Interaction Type</label>
|
||||||
<div className="bg-white/5 p-4 rounded-2xl border border-white/5 flex items-center gap-3">
|
<div className="flex gap-2 p-1 bg-white/5 rounded-2xl border border-white/5">
|
||||||
<div className="w-10 h-10 rounded-xl bg-accent/20 text-accent flex items-center justify-center">
|
<button
|
||||||
{editForm.type === 'click' ? <MousePointer2 size={20} /> : editForm.type === 'scroll' ? <Scroll size={20} /> : <Clock size={20} />}
|
onClick={() => setEditForm(prev => ({ ...prev, type: 'mouse', interactionType: 'click' }))}
|
||||||
</div>
|
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl transition-all border ${editForm.type === 'mouse' && editForm.interactionType === 'click' ? 'bg-accent text-primary-dark border-accent' : 'text-white/40 border-transparent hover:border-white/10'}`}
|
||||||
<span className="text-white font-black uppercase tracking-widest text-sm">{editForm.type}</span>
|
>
|
||||||
|
<MousePointer2 size={14} />
|
||||||
|
<span className="text-[10px] font-black uppercase">Click</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditForm(prev => ({ ...prev, type: 'mouse', interactionType: 'hover' }))}
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl transition-all border ${editForm.type === 'mouse' && editForm.interactionType === 'hover' ? 'bg-accent text-primary-dark border-accent' : 'text-white/40 border-transparent hover:border-white/10'}`}
|
||||||
|
>
|
||||||
|
<Eye size={14} />
|
||||||
|
<span className="text-[10px] font-black uppercase">Hover</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditForm(prev => ({ ...prev, type: 'scroll' }))}
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl transition-all border ${editForm.type === 'scroll' ? 'bg-accent text-primary-dark border-accent' : 'text-white/40 border-transparent hover:border-white/10'}`}
|
||||||
|
>
|
||||||
|
<Scroll size={14} />
|
||||||
|
<span className="text-[10px] font-black uppercase">Scroll</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditForm(prev => ({ ...prev, type: 'wait' }))}
|
||||||
|
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl transition-all border ${editForm.type === 'wait' ? 'bg-accent text-primary-dark border-accent' : 'text-white/40 border-transparent hover:border-white/10'}`}
|
||||||
|
>
|
||||||
|
<Clock size={14} />
|
||||||
|
<span className="text-[10px] font-black uppercase">Wait</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Precise Click Origin */}
|
{/* Precise Click Origin */}
|
||||||
{editForm.type === 'click' && (
|
{editForm.type === 'mouse' && editForm.interactionType === 'click' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-white/30 leading-none">Click Origin</label>
|
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-white/30 leading-none">Click Origin</label>
|
||||||
<div className="grid grid-cols-3 gap-2 p-2 bg-white/5 rounded-2xl border border-white/5">
|
<div className="grid grid-cols-3 gap-2 p-2 bg-white/5 rounded-2xl border border-white/5">
|
||||||
@@ -465,6 +508,22 @@ export function RecordModeOverlay() {
|
|||||||
</div>
|
</div>
|
||||||
{editForm.motionBlur ? <Check size={18} /> : <div className="w-[18px]" />}
|
{editForm.motionBlur ? <Check size={18} /> : <div className="w-[18px]" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{editForm.type === 'mouse' && editForm.interactionType === 'click' && (
|
||||||
|
<button
|
||||||
|
onClick={() => setEditForm(prev => ({ ...prev, realClick: !prev.realClick }))}
|
||||||
|
className={`flex items-center justify-between w-full p-4 rounded-2xl border transition-all ${editForm.realClick ? 'bg-orange-500/10 border-orange-500/30 text-orange-400' : 'bg-white/5 border-white/5 text-white/40'}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ExternalLink size={18} />
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
<span className="text-xs font-bold uppercase tracking-wider">Trigger Navigation</span>
|
||||||
|
<span className="text-[8px] opacity-60">Allows URL transitions in Studio</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{editForm.realClick ? <Check size={18} /> : <div className="w-[18px]" />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface RecordEvent {
|
export interface RecordEvent {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'click' | 'scroll' | 'wait' | 'hover';
|
type: 'mouse' | 'scroll' | 'wait';
|
||||||
|
interactionType?: 'click' | 'hover';
|
||||||
selector?: string; // CSS selector
|
selector?: string; // CSS selector
|
||||||
timestamp: number; // Time in ms since start of recording
|
timestamp: number; // Time in ms since start of recording
|
||||||
duration: number; // Duration allocated for this action in playback
|
duration: number; // Duration allocated for this action in playback
|
||||||
@@ -9,6 +10,7 @@ export interface RecordEvent {
|
|||||||
motionBlur?: boolean; // Enable motion blur effect
|
motionBlur?: boolean; // Enable motion blur effect
|
||||||
rect?: { x: number; y: number; width: number; height: number }; // Element position for rendering
|
rect?: { x: number; y: number; width: number; height: number }; // Element position for rendering
|
||||||
clickOrigin?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
clickOrigin?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||||
|
realClick?: boolean; // Trigger real browser action (navigation)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecordingSession {
|
export interface RecordingSession {
|
||||||
|
|||||||
Reference in New Issue
Block a user