diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index 52e42d4..8393c99 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -6,8 +6,8 @@ import { ThemedDropdown } from "./ThemedDropdown"; type SettingsSection = "workspace" | "general"; const SECTIONS: { id: SettingsSection; label: string; detail: string }[] = [ - { id: "workspace", label: "AI Workspace", detail: "Tagging models and queue targets" }, { id: "general", label: "General", detail: "App data and diagnostics" }, + { id: "workspace", label: "AI Workspace", detail: "Tagging models and queue targets" }, ]; function formatBytesShort(bytes: number): string { @@ -122,7 +122,7 @@ function TaggerAccelerationButton({ acceleration, current, onSelect, children }: } export function SettingsModal() { - const [activeSection, setActiveSection] = useState("workspace"); + const [activeSection, setActiveSection] = useState("general"); const [taggerQueueStatus, setTaggerQueueStatus] = useState(null); const [taggerQueueing, setTaggerQueueing] = useState(false); const [taggerClearing, setTaggerClearing] = useState(false); @@ -195,6 +195,10 @@ export function SettingsModal() { const openOnboarding = useGalleryStore((state) => state.openOnboarding); const theme = useGalleryStore((state) => state.theme); const setTheme = useGalleryStore((state) => state.setTheme); + const lightboxAutoplay = useGalleryStore((state) => state.lightboxAutoplay); + const setLightboxAutoplay = useGalleryStore((state) => state.setLightboxAutoplay); + const lightboxAutoMute = useGalleryStore((state) => state.lightboxAutoMute); + const setLightboxAutoMute = useGalleryStore((state) => state.setLightboxAutoMute); useEffect(() => { if (!settingsOpen) return; @@ -618,6 +622,35 @@ export function SettingsModal() { + + + + + + + + + ([]); const [volume, setVolume] = useState(persistedVolume); - const [muted, setMuted] = useState(persistedMuted); + const [muted, setMuted] = useState( + () => useGalleryStore.getState().lightboxAutoMute || persistedMuted, + ); const [playbackRate, setPlaybackRate] = useState(1); const [loop, setLoop] = useState(false); const [fullscreen, setFullscreen] = useState(false); @@ -89,11 +92,16 @@ export function VideoPlayer({ src }: { src: string }) { useEffect(() => { const video = videoRef.current; if (!video) return; + // Read playback prefs fresh for each opened video — not reactive, so a + // settings change applies to the next video rather than the current one. + const { lightboxAutoplay, lightboxAutoMute } = useGalleryStore.getState(); + const startMuted = lightboxAutoMute || persistedMuted; video.volume = persistedVolume; - video.muted = persistedMuted; - // Autoplay; if the webview blocks it the catch leaves us paused with - // controls visible, which is a fine fallback. - video.play().catch(() => {}); + video.muted = startMuted; + setMuted(startMuted); + // Autoplay when enabled; if the webview blocks it the catch leaves us paused + // with controls visible, which is a fine fallback. + if (lightboxAutoplay) video.play().catch(() => {}); }, [src]); const readBuffered = useCallback(() => { diff --git a/src/store.ts b/src/store.ts index 2daca96..e08f483 100644 --- a/src/store.ts +++ b/src/store.ts @@ -12,6 +12,8 @@ import { notifyTaskComplete } from "./notifications"; const notificationTimers = new Map>(); const NOTIFICATION_DEBOUNCE_MS = 6000; const THEME_KEY = "phokus-theme"; +const LIGHTBOX_AUTOPLAY_KEY = "phokus.lightboxAutoplay"; +const LIGHTBOX_AUTO_MUTE_KEY = "phokus.lightboxAutoMute"; export interface Folder { id: number; @@ -346,6 +348,8 @@ interface GalleryState { mutedFolderIds: number[]; notificationsPaused: boolean; theme: AppTheme; + lightboxAutoplay: boolean; + lightboxAutoMute: boolean; // Per-folder background-worker pause flags, shared by the BackgroundTasks // bar and the sidebar folder context menu. workerPaused: Record>; @@ -445,6 +449,8 @@ interface GalleryState { loadNotificationsPaused: () => Promise; setNotificationsPaused: (paused: boolean) => void; setTheme: (theme: AppTheme) => void; + setLightboxAutoplay: (enabled: boolean) => void; + setLightboxAutoMute: (enabled: boolean) => void; loadWorkerStates: () => Promise; setWorkerPaused: (folderId: number, worker: WorkerKey, paused: boolean) => void; setAllWorkersPaused: (folderId: number, paused: boolean) => void; @@ -515,6 +521,12 @@ function initialAiCaptionsEnabled(): boolean { return window.localStorage.getItem(AI_CAPTIONS_ENABLED_KEY) === "true"; } +function initialBoolSetting(key: string, fallback: boolean): boolean { + if (typeof window === "undefined") return fallback; + const stored = window.localStorage.getItem(key); + return stored === null ? fallback : stored === "true"; +} + function initialTheme(): AppTheme { if (typeof window === "undefined") return "phokus"; const saved = window.localStorage.getItem(THEME_KEY); @@ -765,6 +777,8 @@ export const useGalleryStore = create((set, get) => ({ mutedFolderIds: [], notificationsPaused: false, theme: initialTheme(), + lightboxAutoplay: initialBoolSetting(LIGHTBOX_AUTOPLAY_KEY, true), + lightboxAutoMute: initialBoolSetting(LIGHTBOX_AUTO_MUTE_KEY, false), workerPaused: {}, appVersion: null, @@ -1649,6 +1663,16 @@ export const useGalleryStore = create((set, get) => ({ set({ theme }); }, + setLightboxAutoplay: (enabled) => { + window.localStorage.setItem(LIGHTBOX_AUTOPLAY_KEY, String(enabled)); + set({ lightboxAutoplay: enabled }); + }, + + setLightboxAutoMute: (enabled) => { + window.localStorage.setItem(LIGHTBOX_AUTO_MUTE_KEY, String(enabled)); + set({ lightboxAutoMute: enabled }); + }, + loadWorkerStates: async () => { const folderIds = get().folders.map((folder) => folder.id); if (folderIds.length === 0) {