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 (event.type === 'scroll') {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
} else if (event.type === 'click') {
|
||||
} else if (event.type === 'mouse') {
|
||||
const currentRect = el.getBoundingClientRect();
|
||||
|
||||
// Calculate Point based on Origin
|
||||
@@ -157,6 +157,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
||||
clientX: targetX,
|
||||
clientY: targetY
|
||||
};
|
||||
|
||||
const dispatchMouse = (type: string) => {
|
||||
el.dispatchEvent(new MouseEvent(type, {
|
||||
view: window,
|
||||
@@ -165,10 +166,21 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
||||
...eventCoords
|
||||
}));
|
||||
};
|
||||
dispatchMouse('mousedown');
|
||||
dispatchMouse('mouseup');
|
||||
dispatchMouse('click');
|
||||
el.click();
|
||||
|
||||
if (event.interactionType === 'click') {
|
||||
dispatchMouse('mousedown');
|
||||
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 (event.type === 'scroll') {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
} else if (event.type === 'click') {
|
||||
} else if (event.type === 'mouse') {
|
||||
const currentRect = el.getBoundingClientRect();
|
||||
|
||||
// Calculate Point based on Origin (same as above for parity)
|
||||
@@ -300,6 +312,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
||||
clientX: targetX,
|
||||
clientY: targetY
|
||||
};
|
||||
|
||||
const dispatchMouse = (type: string) => {
|
||||
el.dispatchEvent(new MouseEvent(type, {
|
||||
view: window,
|
||||
@@ -308,10 +321,21 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
|
||||
...eventCoords
|
||||
}));
|
||||
};
|
||||
dispatchMouse('mousedown');
|
||||
dispatchMouse('mouseup');
|
||||
dispatchMouse('click');
|
||||
el.click();
|
||||
|
||||
if (event.interactionType === 'click') {
|
||||
dispatchMouse('mousedown');
|
||||
dispatchMouse('mouseup');
|
||||
dispatchMouse('click');
|
||||
|
||||
if (event.realClick) {
|
||||
el.click();
|
||||
}
|
||||
} else {
|
||||
// Hover Interaction
|
||||
dispatchMouse('mousemove');
|
||||
dispatchMouse('mouseover');
|
||||
dispatchMouse('mouseenter');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Clock,
|
||||
Maximize2,
|
||||
Box,
|
||||
ExternalLink,
|
||||
} from 'lucide-react';
|
||||
import { RecordEvent } from '@/types/record-mode';
|
||||
import { PlaybackCursor } from './PlaybackCursor';
|
||||
@@ -42,7 +43,8 @@ export function RecordModeOverlay() {
|
||||
setEvents, // Added setEvents here
|
||||
} = 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 [editingEventId, setEditingEventId] = useState<string | null>(null);
|
||||
|
||||
@@ -61,13 +63,14 @@ export function RecordModeOverlay() {
|
||||
if (e.data.type === 'ELEMENT_SELECTED') {
|
||||
const { selector, rect, tagName } = e.data;
|
||||
|
||||
if (pickingMode === 'click') {
|
||||
if (pickingMode === 'mouse') {
|
||||
addEvent({
|
||||
type: 'click',
|
||||
type: 'mouse',
|
||||
interactionType: lastInteractionType,
|
||||
selector,
|
||||
duration: 1000,
|
||||
duration: lastInteractionType === 'click' ? 1000 : 1500,
|
||||
zoom: 1,
|
||||
description: `Click on ${tagName}`,
|
||||
description: `${lastInteractionType === 'click' ? 'Click' : 'Hover'} on ${tagName}`,
|
||||
motionBlur: false,
|
||||
rect,
|
||||
});
|
||||
@@ -156,13 +159,27 @@ export function RecordModeOverlay() {
|
||||
{/* Action Tools */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setPickingMode('click')}
|
||||
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'}`}
|
||||
onClick={() => {
|
||||
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} />
|
||||
<span>Click</span>
|
||||
</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
|
||||
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'}`}
|
||||
@@ -314,8 +331,10 @@ export function RecordModeOverlay() {
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white text-[10px] font-black uppercase tracking-widest">{event.type}</span>
|
||||
{event.clickOrigin && event.clickOrigin !== 'center' && (
|
||||
<span className="text-white text-[10px] font-black uppercase tracking-widest">
|
||||
{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-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">
|
||||
{/* Type Display */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-white/30 leading-none">Action Type</label>
|
||||
<div className="bg-white/5 p-4 rounded-2xl border border-white/5 flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-accent/20 text-accent flex items-center justify-center">
|
||||
{editForm.type === 'click' ? <MousePointer2 size={20} /> : editForm.type === 'scroll' ? <Scroll size={20} /> : <Clock size={20} />}
|
||||
</div>
|
||||
<span className="text-white font-black uppercase tracking-widest text-sm">{editForm.type}</span>
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-white/30 leading-none">Interaction Type</label>
|
||||
<div className="flex gap-2 p-1 bg-white/5 rounded-2xl border border-white/5">
|
||||
<button
|
||||
onClick={() => setEditForm(prev => ({ ...prev, type: 'mouse', interactionType: 'click' }))}
|
||||
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'}`}
|
||||
>
|
||||
<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>
|
||||
|
||||
{/* Precise Click Origin */}
|
||||
{editForm.type === 'click' && (
|
||||
{editForm.type === 'mouse' && editForm.interactionType === 'click' && (
|
||||
<div className="space-y-4">
|
||||
<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">
|
||||
@@ -465,6 +508,22 @@ export function RecordModeOverlay() {
|
||||
</div>
|
||||
{editForm.motionBlur ? <Check size={18} /> : <div className="w-[18px]" />}
|
||||
</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>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface RecordEvent {
|
||||
id: string;
|
||||
type: 'click' | 'scroll' | 'wait' | 'hover';
|
||||
type: 'mouse' | 'scroll' | 'wait';
|
||||
interactionType?: 'click' | 'hover';
|
||||
selector?: string; // CSS selector
|
||||
timestamp: number; // Time in ms since start of recording
|
||||
duration: number; // Duration allocated for this action in playback
|
||||
@@ -9,6 +10,7 @@ export interface RecordEvent {
|
||||
motionBlur?: boolean; // Enable motion blur effect
|
||||
rect?: { x: number; y: number; width: number; height: number }; // Element position for rendering
|
||||
clickOrigin?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
realClick?: boolean; // Trigger real browser action (navigation)
|
||||
}
|
||||
|
||||
export interface RecordingSession {
|
||||
|
||||
Reference in New Issue
Block a user