feat: enhance Recording Studio with reorderable events, origin options, and hover previews

This commit is contained in:
2026-02-15 18:13:25 +01:00
parent 40fa2a7721
commit 90cdd7e713
4 changed files with 247 additions and 27 deletions

View File

@@ -11,6 +11,7 @@ interface RecordModeContextType {
updateEvent: (id: string, event: Partial<RecordEvent>) => void;
removeEvent: (id: string) => void;
clearEvents: () => void;
setEvents: (events: RecordEvent[]) => void;
isPlaying: boolean;
playEvents: () => void;
stopPlayback: () => void;
@@ -21,6 +22,9 @@ interface RecordModeContextType {
saveSession: (name: string) => void;
isFeedbackActive: boolean;
setIsFeedbackActive: (active: boolean) => void;
reorderEvents: (startIndex: number, endIndex: number) => void;
hoveredEventId: string | null;
setHoveredEventId: (id: string | null) => void;
}
const RecordModeContext = createContext<RecordModeContextType | null>(null);
@@ -47,6 +51,10 @@ export function useRecordMode(): RecordModeContextType {
isFeedbackActive: false,
setIsFeedbackActive: () => { },
saveSession: () => { },
reorderEvents: () => { },
hoveredEventId: null,
setHoveredEventId: () => { },
setEvents: () => { },
};
}
return context;
@@ -60,6 +68,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
const [zoomLevel, setZoomLevel] = useState(1);
const [isBlurry, setIsBlurry] = useState(false);
const [isFeedbackActive, setIsFeedbackActiveState] = useState(false);
const [hoveredEventId, setHoveredEventId] = useState<string | null>(null);
const [isEmbedded, setIsEmbedded] = useState(false);
useEffect(() => {
@@ -124,13 +133,30 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
if (event.type === 'scroll') {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (event.type === 'click') {
// Get CURRENT rect for industrial precision
const currentRect = el.getBoundingClientRect();
const eventCoords = {
clientX: currentRect.left + currentRect.width / 2,
clientY: currentRect.top + currentRect.height / 2
};
// Calculate Point based on Origin
let targetX = currentRect.left + currentRect.width / 2;
let targetY = currentRect.top + currentRect.height / 2;
if (event.clickOrigin === 'top-left') {
targetX = currentRect.left + 5;
targetY = currentRect.top + 5;
} else if (event.clickOrigin === 'top-right') {
targetX = currentRect.right - 5;
targetY = currentRect.top + 5;
} else if (event.clickOrigin === 'bottom-left') {
targetX = currentRect.left + 5;
targetY = currentRect.bottom - 5;
} else if (event.clickOrigin === 'bottom-right') {
targetX = currentRect.right - 5;
targetY = currentRect.bottom - 5;
}
const eventCoords = {
clientX: targetX,
clientY: targetY
};
const dispatchMouse = (type: string) => {
el.dispatchEvent(new MouseEvent(type, {
view: window,
@@ -153,6 +179,27 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
return () => window.removeEventListener('message', handlePlaybackMessage);
}, [isEmbedded]);
// Sync Hover Preview to Iframe
useEffect(() => {
if (isEmbedded || !isActive) return;
const event = events.find(e => e.id === hoveredEventId);
const iframe = document.querySelector('iframe[name="record-mode-iframe"]') as HTMLIFrameElement;
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage({
type: 'SET_HOVER_SELECTOR',
selector: event?.selector || null
}, '*');
}
}, [hoveredEventId, events, isActive, isEmbedded]);
const reorderEvents = (startIndex: number, endIndex: number) => {
const result = Array.from(events);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
setEvents(result);
};
const addEvent = (event: Omit<RecordEvent, 'id' | 'timestamp'>) => {
const newEvent: RecordEvent = {
...event,
@@ -230,9 +277,28 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (event.type === 'click') {
const currentRect = el.getBoundingClientRect();
// Calculate Point based on Origin (same as above for parity)
let targetX = currentRect.left + currentRect.width / 2;
let targetY = currentRect.top + currentRect.height / 2;
if (event.clickOrigin === 'top-left') {
targetX = currentRect.left + 5;
targetY = currentRect.top + 5;
} else if (event.clickOrigin === 'top-right') {
targetX = currentRect.right - 5;
targetY = currentRect.top + 5;
} else if (event.clickOrigin === 'bottom-left') {
targetX = currentRect.left + 5;
targetY = currentRect.bottom - 5;
} else if (event.clickOrigin === 'bottom-right') {
targetX = currentRect.right - 5;
targetY = currentRect.bottom - 5;
}
const eventCoords = {
clientX: currentRect.left + currentRect.width / 2,
clientY: currentRect.top + currentRect.height / 2
clientX: targetX,
clientY: targetY
};
const dispatchMouse = (type: string) => {
el.dispatchEvent(new MouseEvent(type, {
@@ -282,6 +348,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
updateEvent,
removeEvent,
clearEvents,
setEvents,
isPlaying,
playEvents,
stopPlayback,
@@ -292,6 +359,9 @@ export function RecordModeProvider({ children }: { children: React.ReactNode })
saveSession,
isFeedbackActive,
setIsFeedbackActive,
reorderEvents,
hoveredEventId,
setHoveredEventId,
}}
>
{children}