beeta/frontend/src/lib/stores/screensaver.js

171 lines
4.9 KiB
JavaScript
Raw Normal View History

2026-01-07 16:27:43 -05:00
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);