Files
klz-cables.com/components/record-mode/RecordModeContext.tsx
Marc Mintel 3d498f3df8
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
fix(og): enable automatic OG image discovery and refine Traefik whitelist
- Removed manual 'images' metadata overrides.
- This allows Next.js to use built-in automatic discovery.
- Ensures metadata uses the dynamic metadataBase from the environment.
- Refined Traefik public router regex for sub-routes.
- Restored and verified imports in modified page.tsx files.
2026-02-13 01:38:26 +01:00

179 lines
5.1 KiB
TypeScript

'use client';
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
import { RecordEvent, RecordingSession } from '@/types/record-mode';
interface RecordModeContextType {
isActive: boolean;
setIsActive: (active: boolean) => void;
isRecording: boolean;
startRecording: () => void;
stopRecording: () => void;
events: RecordEvent[];
addEvent: (event: Omit<RecordEvent, 'id' | 'timestamp'>) => void;
updateEvent: (id: string, event: Partial<RecordEvent>) => void;
removeEvent: (id: string) => void;
clearEvents: () => void;
isPlaying: boolean;
playEvents: () => void;
stopPlayback: () => void;
currentSession: RecordingSession | null;
saveSession: (name: string) => void;
}
const RecordModeContext = createContext<RecordModeContextType | null>(null);
export function useRecordMode() {
const context = useContext(RecordModeContext);
if (!context) {
throw new Error('useRecordMode must be used within a RecordModeProvider');
}
return context;
}
export function RecordModeProvider({ children }: { children: React.ReactNode }) {
const [isActive, setIsActive] = useState(false);
const [isRecording, setIsRecording] = useState(false);
const [events, setEvents] = useState<RecordEvent[]>([]);
const [isPlaying, setIsPlaying] = useState(false);
const [currentSession, setCurrentSession] = useState<RecordingSession | null>(null);
const startTimeRef = useRef<number>(0);
const startRecording = () => {
setIsRecording(true);
setEvents([]);
startTimeRef.current = Date.now();
};
const stopRecording = () => {
setIsRecording(false);
};
const addEvent = (eventData: Omit<RecordEvent, 'id' | 'timestamp'>) => {
const timestamp = Date.now() - startTimeRef.current;
const newEvent: RecordEvent = {
id: crypto.randomUUID(),
timestamp,
...eventData,
};
setEvents((prev) => [...prev, newEvent]);
};
const updateEvent = (id: string, updates: Partial<RecordEvent>) => {
setEvents((prev) => prev.map((e) => (e.id === id ? { ...e, ...updates } : e)));
};
const removeEvent = (id: string) => {
setEvents((prev) => prev.filter((e) => e.id !== id));
};
const clearEvents = () => {
setEvents([]);
};
const playEvents = async () => {
if (events.length === 0) return;
setIsPlaying(true);
// Simple playback logic mostly for preview
const startPlayTime = Date.now();
// Sort events by timestamp just in case
const sortedEvents = [...events].sort((a, b) => a.timestamp - b.timestamp);
for (const event of sortedEvents) {
if (!isPlaying) break; // Check if stopped
const targetTime = startPlayTime + event.timestamp;
const now = Date.now();
const delay = targetTime - now;
if (delay > 0) {
await new Promise((r) => setTimeout(r, delay));
}
// Execute event visual feedback
if (document) {
if (event.selector) {
const el = document.querySelector(event.selector);
if (el) {
// Highlight or scroll to element
if (event.type === 'scroll') {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (event.type === 'click') {
// Visualize click
const rect = el.getBoundingClientRect();
const clickMarker = document.createElement('div');
clickMarker.style.position = 'fixed';
clickMarker.style.left = `${rect.left + rect.width / 2}px`;
clickMarker.style.top = `${rect.top + rect.height / 2}px`;
clickMarker.style.width = '20px';
clickMarker.style.height = '20px';
clickMarker.style.borderRadius = '50%';
clickMarker.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
clickMarker.style.transform = 'translate(-50%, -50%)';
clickMarker.style.zIndex = '99999';
document.body.appendChild(clickMarker);
setTimeout(() => clickMarker.remove(), 500);
}
}
}
}
}
setIsPlaying(false);
};
const stopPlayback = () => {
setIsPlaying(false);
};
const saveSession = (name: string) => {
const session: RecordingSession = {
id: crypto.randomUUID(),
name,
events,
createdAt: new Date().toISOString(),
};
setCurrentSession(session);
// Ideally save to local storage or API
localStorage.setItem('klz-record-session', JSON.stringify(session));
};
// Load session on mount
useEffect(() => {
const saved = localStorage.getItem('klz-record-session');
if (saved) {
try {
setCurrentSession(JSON.parse(saved));
} catch (e) {
console.error('Failed to load session', e);
}
}
}, []);
return (
<RecordModeContext.Provider
value={{
isActive,
setIsActive,
isRecording,
startRecording,
stopRecording,
events,
addEvent,
updateEvent,
removeEvent,
clearEvents,
isPlaying,
playEvents,
stopPlayback,
currentSession,
saveSession,
}}
>
{children}
</RecordModeContext.Provider>
);
}