diff --git a/electron/main.ts b/electron/main.ts index 2775a00..94c465a 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -4,14 +4,12 @@ import * as fs from 'fs'; import { ScreenManager } from './services/ScreenManager'; import { PresenceDetector } from './services/PresenceDetector'; import { FrigateStreamer } from './services/FrigateStreamer'; -import { MotionDetector } from './services/MotionDetector'; import { PhotoManager } from './services/PhotoManager'; let mainWindow: BrowserWindow | null = null; let screenManager: ScreenManager | null = null; let presenceDetector: PresenceDetector | null = null; let frigateStreamer: FrigateStreamer | null = null; -let motionDetector: MotionDetector | null = null; let photoManager: PhotoManager | null = null; let powerSaveBlockerId: number | null = null; @@ -63,23 +61,6 @@ function createWindow(): void { // Initialize services screenManager = new ScreenManager(mainWindow); - // Initialize motion detector (runs in main process, not throttled by browser) - // Uses file size comparison which is more reliable for JPEG streams - motionDetector = new MotionDetector({ - go2rtcUrl: 'http://192.168.1.241:1985', - cameraName: 'Kitchen_Panel', - sensitivityThreshold: 5, // % file size change to trigger (5% = significant motion) - checkIntervalMs: 2000, // Check every 2 seconds for responsiveness - }); - - motionDetector.on('motion', () => { - console.log('MotionDetector: Motion detected, waking screen'); - screenManager?.wakeScreen(); - mainWindow?.webContents.send('motion:detected'); - }); - - motionDetector.start(); - // Photo frame slideshow source photoManager = new PhotoManager(resolvePhotosDir()); console.log(`PhotoManager: watching ${photoManager.getDir()}`); @@ -243,7 +224,6 @@ app.on('window-all-closed', () => { } presenceDetector?.stop(); frigateStreamer?.stop(); - motionDetector?.stop(); if (process.platform !== 'darwin') { app.quit(); diff --git a/electron/preload.ts b/electron/preload.ts index 5bb9500..5fb4674 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -17,9 +17,6 @@ export interface ElectronAPI { startStream: (rtspUrl: string) => Promise; stopStream: () => Promise; }; - motion: { - onDetected: (callback: () => void) => () => void; - }; app: { quit: () => void; toggleFullscreen: () => void; @@ -61,13 +58,6 @@ const electronAPI: ElectronAPI = { startStream: (rtspUrl: string) => ipcRenderer.invoke('frigate:startStream', rtspUrl), stopStream: () => ipcRenderer.invoke('frigate:stopStream'), }, - motion: { - onDetected: (callback: () => void) => { - const handler = (_event: IpcRendererEvent) => callback(); - ipcRenderer.on('motion:detected', handler); - return () => ipcRenderer.removeListener('motion:detected', handler); - }, - }, app: { quit: () => ipcRenderer.invoke('app:quit'), toggleFullscreen: () => ipcRenderer.invoke('app:toggleFullscreen'), diff --git a/src/App.tsx b/src/App.tsx index 621aa27..2098f3a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,6 @@ import { Dashboard } from '@/components/layout'; import { ThermostatOverlay } from '@/components/climate'; import { LightsOverlay } from '@/components/lights'; import { LocksOverlay } from '@/components/locks'; -import { AlarmoPanel } from '@/components/alarm'; import { CalendarWidget } from '@/components/calendar'; import { TodoWidget } from '@/components/todo'; import { SettingsPanel, ConnectionModal } from '@/components/settings'; @@ -16,7 +15,7 @@ import { useHomeAssistant } from '@/hooks'; import { useIdle } from '@/hooks/useIdle'; // Motion detection now runs in Electron main process (MotionDetector.ts) // import { useSimpleMotion } from '@/hooks/useSimpleMotion'; -import { useHAStore, useEntityAttribute } from '@/stores/haStore'; +import { useHAStore } from '@/stores/haStore'; import { useUIStore, useCameraOverlay } from '@/stores/uiStore'; import { useSettingsStore } from '@/stores/settingsStore'; import { env } from '@/config/environment'; @@ -71,12 +70,6 @@ function PersonAlert({ cameraName, onClose }: { cameraName: string; onClose: () ); } -// Simple thermostat temp display -function ThermostatTemp({ entityId }: { entityId: string }) { - const currentTemp = useEntityAttribute(entityId, 'current_temperature'); - return <>{currentTemp?.toFixed(0) ?? '--'}°; -} - function ConnectionPrompt() { const openSettings = useUIStore((state) => state.openSettings); @@ -104,72 +97,20 @@ function ConnectionPrompt() { function DashboardContent() { const config = useSettingsStore((state) => state.config); - const openLightsOverlay = useUIStore((state) => state.openLightsOverlay); - const openLocksOverlay = useUIStore((state) => state.openLocksOverlay); - const openThermostatsOverlay = useUIStore((state) => state.openThermostatsOverlay); return ( - <> - {/* Left Column - Calendar (spans 2 columns) */} -
- {config.calendar && ( -
- -
- )} -
- - {/* Right Column - Controls, Alarm, Todo */} -
- {/* Control Buttons Row - Lights, Locks, Thermostats */} -
- {config.lights.length > 0 && ( - - )} - {config.locks.length > 0 && ( - - )} - {config.thermostats.map((thermostat) => ( - - ))} +
+ {config.calendar && ( +
+
- - {/* Alarm Panel */} - {config.alarm && } - - {/* Todo List */} - {config.todoList && ( -
- -
- )} -
- + )} + {config.todoList && ( +
+ +
+ )} +
); } @@ -191,9 +132,6 @@ export default function App() { const [alertCamera, setAlertCamera] = useState(null); const alertShownForRef = useRef>(new Set()); - // Motion detection now runs in the Electron main process (MotionDetector.ts) - // This prevents browser throttling when the screensaver is active - // Report touch/click activity to main process for screen wake on Wayland useEffect(() => { const handleActivity = () => { diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index a596a6b..1b93707 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -82,13 +82,50 @@ function PackageStatus() { export function Header() { const connectionState = useConnectionState(); + const cameraOverlayOpen = useUIStore((state) => state.cameraOverlayOpen); + const mediaOverlayOpen = useUIStore((state) => state.mediaOverlayOpen); + const settingsOpen = useUIStore((state) => state.settingsOpen); const openCameraOverlay = useUIStore((state) => state.openCameraOverlay); + const closeCameraOverlay = useUIStore((state) => state.closeCameraOverlay); const openMediaOverlay = useUIStore((state) => state.openMediaOverlay); + const closeMediaOverlay = useUIStore((state) => state.closeMediaOverlay); const openSettings = useUIStore((state) => state.openSettings); + const closeSettings = useUIStore((state) => state.closeSettings); const people = useSettingsStore((state) => state.config.people); const cameras = useSettingsStore((state) => state.config.cameras); const [currentTime, setCurrentTime] = useState(new Date()); + const activeTab: 'home' | 'media' | 'cameras' | 'settings' = + settingsOpen ? 'settings' : cameraOverlayOpen ? 'cameras' : mediaOverlayOpen ? 'media' : 'home'; + + const goHome = () => { + closeCameraOverlay(); + closeMediaOverlay(); + closeSettings(); + }; + const goMedia = () => { + closeCameraOverlay(); + closeSettings(); + openMediaOverlay(); + }; + const goCameras = () => { + closeMediaOverlay(); + closeSettings(); + openCameraOverlay(); + }; + const goSettings = () => { + closeMediaOverlay(); + closeCameraOverlay(); + openSettings(); + }; + + const tabClass = (name: typeof activeTab) => + `flex items-center gap-2 px-4 py-2 rounded-2xl font-semibold text-sm transition-all ${ + activeTab === name + ? 'bg-accent text-white shadow-card' + : 'bg-transparent text-ink-muted hover:bg-dark-hover' + }`; + useEffect(() => { const timer = setInterval(() => { setCurrentTime(new Date()); @@ -122,23 +159,18 @@ export function Header() { }; return ( -
- {/* Left - Time and Date */} -
- - {format(currentTime, 'h:mm')} - - - {format(currentTime, 'EEE, MMM d')} - -
- - {/* Center - Status Icons */} -
- {/* Package Status */} +
+ {/* Left - Time, Date, People */} +
+
+ + {format(currentTime, 'h:mm')} + + + {format(currentTime, 'EEE, MMM d')} + +
- - {/* People */} {people.length > 0 && (
{people.map((person) => ( @@ -153,53 +185,44 @@ export function Header() { )}
- {/* Right - Connection Status, Cameras, Settings */} -
- {/* Connection Status */} -
-
- {getConnectionText()} -
- - {/* Media Button */} -
); diff --git a/src/hooks/useIdle.ts b/src/hooks/useIdle.ts index ec57d12..cb5ea71 100644 --- a/src/hooks/useIdle.ts +++ b/src/hooks/useIdle.ts @@ -1,14 +1,9 @@ import { useEffect } from 'react'; import { useUIStore } from '@/stores/uiStore'; -type ElectronAPILike = { - motion?: { onDetected: (cb: () => void) => () => void }; -}; - /** * Tracks touch/mouse/keyboard activity. After `timeoutMs` of no activity, * flips the UI into idle mode (photo frame). Any activity exits idle. - * Motion detected by Electron's MotionDetector also cancels idle. */ export function useIdle(timeoutMs: number) { const isIdle = useUIStore((s) => s.isIdle); @@ -34,15 +29,11 @@ export function useIdle(timeoutMs: number) { ]; for (const e of events) document.addEventListener(e, reset, { passive: true }); - const api = (window as unknown as { electronAPI?: ElectronAPILike }).electronAPI; - const unsubMotion = api?.motion?.onDetected?.(() => reset()); - reset(); return () => { if (timer) clearTimeout(timer); for (const e of events) document.removeEventListener(e, reset); - if (unsubMotion) unsubMotion(); }; }, [timeoutMs, setIdle]); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 2ae7b3c..f8ecd44 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -51,9 +51,6 @@ interface ElectronAPI { getDir: () => Promise; getUrl: (relative: string) => Promise; }; - motion: { - onDetected: (callback: () => void) => () => void; - }; } interface Window {