import { writable, derived } from 'svelte/store'; import { browser } from '$app/environment'; const defaultState = { // User settings (from backend) enabled: false, timeoutMinutes: 5, // Runtime state active: false, // Is screensaver currently showing idleTime: 0, // Current idle time in seconds tabVisible: true, // Is tab currently visible mediaPlaying: false // Is any media currently playing }; function createScreensaverStore() { const { subscribe, set, update } = writable(defaultState); let idleTimer = null; let activityListenersAdded = false; // Activity events to track const activityEvents = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll']; function resetIdleTimer() { update(state => { if (state.active) { // Dismiss screensaver on any activity return { ...state, active: false, idleTime: 0 }; } return { ...state, idleTime: 0 }; }); } function checkMediaPlaying() { if (!browser) return false; // Check for playing video elements const videos = document.querySelectorAll('video'); for (const video of videos) { if (!video.paused && !video.ended) return true; } // Check for playing audio elements const audios = document.querySelectorAll('audio'); for (const audio of audios) { if (!audio.paused && !audio.ended) return true; } return false; } function handleVisibilityChange() { update(state => ({ ...state, tabVisible: document.visibilityState === 'visible', idleTime: 0 // Reset idle time on visibility change })); } function startTracking() { if (!browser || activityListenersAdded) return; // Handle visibility changes document.addEventListener('visibilitychange', handleVisibilityChange); // Handle activity events activityEvents.forEach(event => { document.addEventListener(event, resetIdleTimer, { passive: true }); }); activityListenersAdded = true; // Start idle timer (check every second) if (idleTimer) clearInterval(idleTimer); idleTimer = setInterval(() => { update(state => { // Don't do anything if disabled if (!state.enabled) return state; const mediaPlaying = checkMediaPlaying(); const newIdleTime = state.idleTime + 1; const newState = { ...state, idleTime: newIdleTime, mediaPlaying }; // Check if should activate // Don't activate if: disabled, tab not visible, media playing, already active if (!state.enabled || !state.tabVisible || mediaPlaying || state.active) { return newState; } // Check if idle time exceeds timeout if (newIdleTime >= state.timeoutMinutes * 60) { return { ...newState, active: true }; } return newState; }); }, 1000); } function stopTracking() { if (!browser) return; document.removeEventListener('visibilitychange', handleVisibilityChange); activityEvents.forEach(event => { document.removeEventListener(event, resetIdleTimer); }); activityListenersAdded = false; if (idleTimer) { clearInterval(idleTimer); idleTimer = null; } } return { subscribe, // Initialize from user settings init(settings) { const enabled = settings?.screensaverEnabled || false; const timeoutMinutes = settings?.screensaverTimeoutMinutes || 5; update(state => ({ ...state, enabled, timeoutMinutes })); if (browser && enabled) { startTracking(); } }, // Update settings from API response updateSettings(enabled, timeoutMinutes) { update(state => ({ ...state, enabled, timeoutMinutes })); if (browser) { if (enabled) { startTracking(); } else { stopTracking(); update(state => ({ ...state, active: false })); } } }, // Manually dismiss the screensaver dismiss() { update(state => ({ ...state, active: false, idleTime: 0 })); }, // Cleanup on component destroy cleanup() { stopTracking(); } }; } export const screensaver = createScreensaverStore(); // Derived store for whether screensaver is active export const isScreensaverActive = derived(screensaver, $s => $s.active);