From 92009c1a6374114371806518d0b84c66e7e60c16 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Mon, 8 Jun 2026 18:24:46 -0400 Subject: [PATCH] feat(App): add Reality Override Admin page and integrate into app navigation --- src/app/App.tsx | 8 +- .../components/RealityOverrideAdminPage.tsx | 223 ++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/app/components/RealityOverrideAdminPage.tsx diff --git a/src/app/App.tsx b/src/app/App.tsx index 3341367..bb918cc 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -11,6 +11,7 @@ import ToolsPage from './components/ToolsPage'; import SearchOverlay from './components/SearchOverlay'; import MediaManager from './components/MediaManager'; import RealityOverridePage from './components/RealityOverridePage'; +import RealityOverrideAdminPage from './components/RealityOverrideAdminPage'; import logoSvg from '../imports/logo.svg'; import { useSettings } from './settings'; @@ -37,7 +38,8 @@ type AppId = | 'petscii-editor' | 'idle-animation' | 'loading-animation' - | 'reality-override'; + | 'reality-override' + | 'reality-override-admin'; export default function App() { const [currentPage, setCurrentPage] = useState('status'); @@ -108,6 +110,7 @@ export default function App() { } label="Idle Animation" onClick={() => setCurrentPage('idle-animation')} /> } label="Loading Animation" onClick={() => setCurrentPage('loading-animation')} /> } label="Reality Override" onClick={() => setCurrentPage('reality-override')} /> + } label="Override Admin" onClick={() => setCurrentPage('reality-override-admin')} /> @@ -145,7 +148,8 @@ export default function App() { 'petscii-editor': setCurrentPage('apps')} />, 'idle-animation': setCurrentPage('apps')} />, 'loading-animation': setCurrentPage('apps')} />, - 'reality-override': setCurrentPage('apps')} /> + 'reality-override': setCurrentPage('apps')} />, + 'reality-override-admin': setCurrentPage('apps')} /> }; // AppCard component for app grid diff --git a/src/app/components/RealityOverrideAdminPage.tsx b/src/app/components/RealityOverrideAdminPage.tsx new file mode 100644 index 0000000..9ab6323 --- /dev/null +++ b/src/app/components/RealityOverrideAdminPage.tsx @@ -0,0 +1,223 @@ +import { useEffect, useRef, useState } from 'react'; +import { + ChevronLeft, Loader2, Wifi, WifiOff, Send, + Image as ImageIcon, Film, Music, + Crop, RotateCcw, Minimize2, + Scissors, FileOutput, LayoutGrid, + Mic, Clapperboard, Wand2, FlipHorizontal, +} from 'lucide-react'; + +// ── Command definitions ─────────────────────────────────────────────────────── + +interface Cmd { id: string; label: string; icon: React.ReactNode; payload: string; } +interface Group { id: string; label: string; color: string; border: string; icon: React.ReactNode; commands: Cmd[]; } + +const img = (cls = 'w-4 h-4') => ; +const aud = (cls = 'w-4 h-4') => ; +const vid = (cls = 'w-4 h-4') => ; + +const GROUPS: Group[] = [ + { + id: 'image', label: 'Image Conversion', + color: 'text-violet-400', border: 'border-violet-800/50', + icon: , + commands: [ + { id: 'img-jpg', label: '→ JPEG', icon: img(), payload: 'CONVERT IMAGE → JPEG' }, + { id: 'img-png', label: '→ PNG', icon: img(), payload: 'CONVERT IMAGE → PNG' }, + { id: 'img-webp', label: '→ WebP', icon: img(), payload: 'CONVERT IMAGE → WEBP' }, + { id: 'img-gif', label: '→ GIF', icon: img(), payload: 'CONVERT IMAGE → GIF' }, + { id: 'img-bmp', label: '→ BMP', icon: img(), payload: 'CONVERT IMAGE → BMP' }, + { id: 'img-avif', label: '→ AVIF', icon: img(), payload: 'CONVERT IMAGE → AVIF' }, + { id: 'img-resize', label: 'Resize', icon: , payload: 'IMAGE: RESIZE' }, + { id: 'img-crop', label: 'Crop', icon: , payload: 'IMAGE: CROP' }, + { id: 'img-rotate', label: 'Rotate 90°', icon: , payload: 'IMAGE: ROTATE 90°' }, + { id: 'img-fliph', label: 'Flip H', icon: , payload: 'IMAGE: FLIP HORIZONTAL' }, + { id: 'img-magic', label: 'Auto Enhance', icon: , payload: 'IMAGE: AUTO ENHANCE' }, + ], + }, + { + id: 'audio', label: 'Audio Conversion', + color: 'text-teal-400', border: 'border-teal-800/50', + icon: , + commands: [ + { id: 'aud-mp3', label: '→ MP3', icon: aud(), payload: 'CONVERT AUDIO → MP3' }, + { id: 'aud-wav', label: '→ WAV', icon: aud(), payload: 'CONVERT AUDIO → WAV' }, + { id: 'aud-ogg', label: '→ OGG', icon: aud(), payload: 'CONVERT AUDIO → OGG' }, + { id: 'aud-flac', label: '→ FLAC', icon: aud(), payload: 'CONVERT AUDIO → FLAC' }, + { id: 'aud-aac', label: '→ AAC', icon: aud(), payload: 'CONVERT AUDIO → AAC' }, + { id: 'aud-opus', label: '→ Opus', icon: aud(), payload: 'CONVERT AUDIO → OPUS' }, + { id: 'aud-norm', label: 'Normalize', icon: , payload: 'AUDIO: NORMALIZE' }, + { id: 'aud-extract', label: 'Extract from Video', icon: , payload: 'AUDIO: EXTRACT FROM VIDEO' }, + ], + }, + { + id: 'video', label: 'Video Conversion', + color: 'text-rose-400', border: 'border-rose-800/50', + icon: , + commands: [ + { id: 'vid-mp4', label: '→ MP4', icon: vid(), payload: 'CONVERT VIDEO → MP4' }, + { id: 'vid-webm', label: '→ WebM', icon: vid(), payload: 'CONVERT VIDEO → WEBM' }, + { id: 'vid-avi', label: '→ AVI', icon: vid(), payload: 'CONVERT VIDEO → AVI' }, + { id: 'vid-mkv', label: '→ MKV', icon: vid(), payload: 'CONVERT VIDEO → MKV' }, + { id: 'vid-mov', label: '→ MOV', icon: vid(), payload: 'CONVERT VIDEO → MOV' }, + { id: 'vid-gif', label: '→ GIF', icon: vid(), payload: 'CONVERT VIDEO → GIF' }, + { id: 'vid-audio', label: 'Extract Audio', icon: , payload: 'VIDEO: EXTRACT AUDIO' }, + { id: 'vid-frames', label: 'Extract Frames', icon: , payload: 'VIDEO: EXTRACT FRAMES' }, + { id: 'vid-clip', label: 'Trim Clip', icon: , payload: 'VIDEO: TRIM CLIP' }, + { id: 'vid-thumb', label: 'Thumbnail', icon: , payload: 'VIDEO: GENERATE THUMBNAIL' }, + { id: 'vid-export', label: 'Export Subtitles',icon: , payload: 'VIDEO: EXPORT SUBTITLES' }, + ], + }, +]; + +// ── Color maps keyed by group id ───────────────────────────────────────────── + +const GROUP_BTN: Record = { + image: 'border-violet-800/40 hover:border-violet-600/70 hover:bg-violet-900/20 active:bg-violet-800/30', + audio: 'border-teal-800/40 hover:border-teal-600/70 hover:bg-teal-900/20 active:bg-teal-800/30', + video: 'border-rose-800/40 hover:border-rose-600/70 hover:bg-rose-900/20 active:bg-rose-800/30', +}; +const GROUP_ICON: Record = { + image: 'text-violet-400', audio: 'text-teal-400', video: 'text-rose-400', +}; +const GROUP_HEAD: Record = { + image: 'text-violet-300 border-violet-800/50', + audio: 'text-teal-300 border-teal-800/50', + video: 'text-rose-300 border-rose-800/50', +}; + +// ── Component ───────────────────────────────────────────────────────────────── + +export default function RealityOverrideAdminPage({ onBack }: { onBack: () => void }) { + const wsRef = useRef(null); + const timerRef = useRef | undefined>(undefined); + + const [wsStatus, setWsStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting'); + const [lastSent, setLastSent] = useState(null); + const [flashId, setFlashId] = useState(0); + const [freeform, setFreeform] = useState(''); + + // ── WebSocket ─────────────────────────────────────────────────────────────── + useEffect(() => { + let cancelled = false; + let ws: WebSocket | null = null; + + const connect = () => { + if (cancelled) return; + ws = new WebSocket(`ws://${window.location.hostname}/ws`); + wsRef.current = ws; + setWsStatus('connecting'); + ws.onopen = () => { if (!cancelled) setWsStatus('connected'); }; + ws.onclose = () => { if (!cancelled) { setWsStatus('disconnected'); setTimeout(connect, 3000); } }; + ws.onerror = () => ws?.close(); + }; + + connect(); + return () => { cancelled = true; ws?.close(); }; + }, []); + + // ── Send command ──────────────────────────────────────────────────────────── + const send = (payload: string) => { + if (wsRef.current?.readyState !== WebSocket.OPEN) return; + wsRef.current.send(payload); + setLastSent(payload); + setFlashId(n => n + 1); + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => setLastSent(null), 3000); + }; + + return ( +
+ + {/* CSS for sent flash */} + + + {/* ── Header ── */} +
+ + + Reality Override — Admin + +
+ {wsStatus === 'connecting' && <>Connecting…} + {wsStatus === 'connected' && <>Connected} + {wsStatus === 'disconnected' && <>Offline} +
+
+ + {/* ── Freeform input ── */} +
+ setFreeform(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter' && freeform.trim()) { send(freeform.trim()); setFreeform(''); } }} + placeholder="Send custom command…" + disabled={wsStatus !== 'connected'} + className="flex-1 bg-neutral-800 border border-neutral-700 rounded px-3 py-1.5 text-sm font-mono text-neutral-200 placeholder-neutral-600 focus:outline-none focus:border-neutral-500 disabled:opacity-40" + /> + +
+ + {/* ── Sent indicator ── */} +
+ {lastSent && ( +
+ + ✓ Sent: {lastSent} + +
+ )} +
+ + {/* ── Command palette ── */} +
+ {GROUPS.map(group => ( +
+
+ {group.icon} +

{group.label}

+
+
+ {group.commands.map(cmd => ( + + ))} +
+
+ ))} +
+
+ ); +}