services/screensaver.jsxjavascript
/*
Combines idle detection (useIdle) with the configured timeout/fade values
(useConfig) into a single ScreensaverContext. Waking from idle also resets
the kiosk session: the app navigates back to the app-selection screen ("/")
while the screensaver is still fading out, so the next visitor starts fresh
instead of inheriting the previous visitor's route, open menus and
selections. The page unmount is what clears that per-page state - no
explicit reset needed. The reset deliberately happens on wake-up rather
than when going idle: the screensaver video is defined by the current
route's content file (see Screensaver.jsx), and navigating away at idle
start would unload it mid-show.
Must be mounted inside the Router (see src/HashRouter.jsx): useConfig (via
useSearchParams) requires router context, and the session reset needs
useNavigate/useLocation. Same reason ContentProvider now lives in
HashRouter.jsx instead of App.jsx - TrackingProvider/TabProvider stay in
App.jsx since they don't use useConfig.
*/
// allow export of contexts and hooks - shouldn't be flagged as non-component
/* eslint react-refresh/only-export-components: 0 */
import { createContext, useContext, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { useNavigate, useLocation } from "react-router";
import { useConfig } from "./config/config";
import { useIdle } from "../hooks/idle";
const ScreensaverContext = createContext();
/**
* Tracks idle state and exposes it via ScreensaverContext.
*/
export const ScreensaverProvider = ({ children }) => {
const { screensaverTimeout, screensaverFadeDuration } = useConfig();
// screensaverTimeout is in seconds (see config.jsx), useIdle expects ms.
const { isIdle, trigger, forceIdle } = useIdle(screensaverTimeout * 1000);
const navigate = useNavigate();
const { pathname, search } = useLocation();
// session reset on the idle -> active transition only (not on mount, not
// while idle): happens behind the still-opaque fading screensaver, no view
// transition needed; keeps the kiosk config params (config-schema.js) alive
const wasIdleRef = useRef(false);
useEffect(() => {
if (wasIdleRef.current && !isIdle && pathname !== "/") navigate(`/${search}`);
wasIdleRef.current = isIdle;
}, [isIdle, pathname, search, navigate]);
return (
<ScreensaverContext.Provider
value={{ isIdle, trigger, screensaverFadeDuration, showNow: forceIdle }}>
{children}
</ScreensaverContext.Provider>
);
};
ScreensaverProvider.propTypes = {
children: PropTypes.node.isRequired,
};
/**
* React hook to access { isIdle, trigger, screensaverFadeDuration, showNow }.
* Must be used within a `<ScreensaverProvider>`.
*/
export const useScreensaver = () => {
return useContext(ScreensaverContext);
};