mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-25 08:59:13 +00:00
feat: Keep screen awake (#1788)
* feat keepAwake hook * reaqure lock after view change * refactor: move it to a navigation view setting * save to search params
This commit is contained in:
@@ -8,6 +8,7 @@ import IdentifyOverlay from './common/components/identify-overlay/IdentifyOverla
|
|||||||
import { AppContextProvider } from './common/context/AppContext';
|
import { AppContextProvider } from './common/context/AppContext';
|
||||||
import { ontimeQueryClient } from './common/queryClient';
|
import { ontimeQueryClient } from './common/queryClient';
|
||||||
import { connectSocket } from './common/utils/socket';
|
import { connectSocket } from './common/utils/socket';
|
||||||
|
import KeepAwake from './features/keep-awake/KeepAwake';
|
||||||
import { TranslationProvider } from './translation/TranslationProvider';
|
import { TranslationProvider } from './translation/TranslationProvider';
|
||||||
import AppRouter from './AppRouter';
|
import AppRouter from './AppRouter';
|
||||||
import { baseURI } from './externals';
|
import { baseURI } from './externals';
|
||||||
@@ -24,6 +25,7 @@ function App() {
|
|||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<TranslationProvider>
|
<TranslationProvider>
|
||||||
<IdentifyOverlay />
|
<IdentifyOverlay />
|
||||||
|
<KeepAwake />
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
</TranslationProvider>
|
</TranslationProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { IoClose, IoContract, IoExpand, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
|
import { IoClose, IoContract, IoExpand, IoEye, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
import { Dialog } from '@base-ui-components/react/dialog';
|
import { Dialog } from '@base-ui-components/react/dialog';
|
||||||
import { useDisclosure, useFullscreen } from '@mantine/hooks';
|
import { useDisclosure, useFullscreen } from '@mantine/hooks';
|
||||||
|
|
||||||
import { isLocalhost } from '../../../externals';
|
import { isLocalhost } from '../../../externals';
|
||||||
|
import { useKeepAwakeOptions } from '../../../features/keep-awake/KeepAwake';
|
||||||
import { navigatorConstants } from '../../../viewerConfig';
|
import { navigatorConstants } from '../../../viewerConfig';
|
||||||
import { useClientStore } from '../../stores/clientStore';
|
import { useClientStore } from '../../stores/clientStore';
|
||||||
import { useViewOptionsStore } from '../../stores/viewOptions';
|
import { useViewOptionsStore } from '../../stores/viewOptions';
|
||||||
@@ -31,6 +32,7 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
|||||||
const [isRenameOpen, handlers] = useDisclosure(false);
|
const [isRenameOpen, handlers] = useDisclosure(false);
|
||||||
const { fullscreen, toggle } = useFullscreen();
|
const { fullscreen, toggle } = useFullscreen();
|
||||||
const { mirror, toggleMirror } = useViewOptionsStore();
|
const { mirror, toggleMirror } = useViewOptionsStore();
|
||||||
|
const { keepAwake, toggleKeepAwake } = useKeepAwakeOptions();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -62,6 +64,13 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
|
|||||||
<IoSwapVertical />
|
<IoSwapVertical />
|
||||||
{mirror && <span className={style.note}>Active</span>}
|
{mirror && <span className={style.note}>Active</span>}
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
{window.isSecureContext && (
|
||||||
|
<NavigationMenuItem active={keepAwake} onClick={toggleKeepAwake}>
|
||||||
|
Keep Awake
|
||||||
|
<IoEye />
|
||||||
|
{keepAwake && <span className={style.note}>Active</span>}
|
||||||
|
</NavigationMenuItem>
|
||||||
|
)}
|
||||||
<NavigationMenuItem onClick={handlers.open}>Rename Client</NavigationMenuItem>
|
<NavigationMenuItem onClick={handlers.open}>Rename Client</NavigationMenuItem>
|
||||||
|
|
||||||
<hr className={style.separator} />
|
<hr className={style.separator} />
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { use, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
|
import { PresetContext } from '../../common/context/PresetContext';
|
||||||
|
|
||||||
|
/** @url https://developer.mozilla.org/en-US/docs/Web/API/WakeLock */
|
||||||
|
export default function KeepAwake() {
|
||||||
|
const { keepAwake } = useKeepAwakeOptions();
|
||||||
|
const [wakeLockSentinel, setWakeLockSentinel] = useState<WakeLockSentinel | null>(null);
|
||||||
|
|
||||||
|
const removeLock = () => {
|
||||||
|
if (wakeLockSentinel) wakeLockSentinel.release().finally(() => setWakeLockSentinel(null));
|
||||||
|
};
|
||||||
|
|
||||||
|
const acquireLock = () => {
|
||||||
|
if (!wakeLockSentinel || wakeLockSentinel.released) {
|
||||||
|
setWakeLockSentinel(null);
|
||||||
|
navigator.wakeLock
|
||||||
|
.request('screen')
|
||||||
|
.then((sentinel) => {
|
||||||
|
setWakeLockSentinel(sentinel);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
if (keepAwake) {
|
||||||
|
acquireLock();
|
||||||
|
document.addEventListener(
|
||||||
|
'visibilitychange',
|
||||||
|
() => {
|
||||||
|
if (wakeLockSentinel !== null && document.visibilityState === 'visible') {
|
||||||
|
acquireLock();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ signal: controller.signal },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
removeLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
removeLock();
|
||||||
|
};
|
||||||
|
}, [keepAwake]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keepAwakeKey = 'keep-awake';
|
||||||
|
|
||||||
|
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams) {
|
||||||
|
// Helper to get value from either source, prioritizing defaultValues
|
||||||
|
return defaultValues?.has(keepAwakeKey) || searchParams.has(keepAwakeKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook exposes the keep awake options
|
||||||
|
*/
|
||||||
|
export function useKeepAwakeOptions() {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const maybePreset = use(PresetContext);
|
||||||
|
|
||||||
|
const keepAwake = useMemo(() => {
|
||||||
|
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
|
||||||
|
return getOptionsFromParams(searchParams, defaultValues);
|
||||||
|
}, [maybePreset, searchParams]);
|
||||||
|
|
||||||
|
const toggleKeepAwake = useCallback(() => {
|
||||||
|
setSearchParams((searchParams) => {
|
||||||
|
if (keepAwake) {
|
||||||
|
searchParams.delete(keepAwakeKey);
|
||||||
|
} else {
|
||||||
|
searchParams.set(keepAwakeKey, '1');
|
||||||
|
}
|
||||||
|
return searchParams;
|
||||||
|
});
|
||||||
|
}, [keepAwake]);
|
||||||
|
|
||||||
|
return { keepAwake, toggleKeepAwake };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user