Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions client/src/hooks/use-wake-lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useCallback, useEffect, useRef, useState } from "react";

/**
* A custom hook to manage a screen wake lock.
*
* @returns An object with an `isLocked` boolean and `request`/`release` functions.
*/
Comment thread
Copilot marked this conversation as resolved.
export const useWakeLock = () => {
const wakeLock = useRef<WakeLockSentinel | null>(null);
const shouldHoldLock = useRef(false);
const [isLocked, setIsLocked] = useState(false);
Comment thread
Copilot marked this conversation as resolved.
Comment thread
Copilot marked this conversation as resolved.

const request = useCallback(async () => {
if (!("wakeLock" in navigator)) {
console.warn("Screen Wake Lock API not supported");
return;
}

shouldHoldLock.current = true;

try {
const sentinel = await navigator.wakeLock.request("screen");
wakeLock.current = sentinel;
setIsLocked(true);

sentinel.addEventListener(
"release",
() => {
if (wakeLock.current === sentinel) {
wakeLock.current = null;
}
setIsLocked(false);
},
{ once: true }
);
} catch (err) {
console.error(`Wake Lock request failed: ${err}`);
setIsLocked(false);
}
Comment thread
Copilot marked this conversation as resolved.
Comment thread
spring1843 marked this conversation as resolved.
}, []);
Comment thread
Copilot marked this conversation as resolved.

const release = useCallback(async () => {
shouldHoldLock.current = false;

if (!wakeLock.current) {
setIsLocked(false);
return;
}

try {
await wakeLock.current.release();
} catch {
// ignore (it may have already been released by the browser)
} finally {
wakeLock.current = null;
setIsLocked(false);
}
}, []);
Comment thread
Copilot marked this conversation as resolved.

useEffect(() => {
const handleVisibilityChange = () => {
if (
shouldHoldLock.current &&
!isLocked &&
document.visibilityState === "visible"
) {
void request();
}
};

document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener("fullscreenchange", handleVisibilityChange);

return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
document.removeEventListener("fullscreenchange", handleVisibilityChange);
release();
Comment thread
Copilot marked this conversation as resolved.
Outdated
};
Comment thread
Copilot marked this conversation as resolved.
Comment on lines +103 to +108
}, [request, release, isLocked]);

return { isLocked, request, release };
};
16 changes: 16 additions & 0 deletions client/src/pages/tools/timer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "@/lib/time-tools";
import { getParam, updateURL, copyShareableURL } from "@/lib/url-sharing";
import { useToast } from "@/hooks/use-toast";
import { useWakeLock } from "@/hooks/use-wake-lock";
import {
ToolButton,
ResetButton,
Expand Down Expand Up @@ -68,6 +69,21 @@ export default function Timer() {
const audioContextRef = useRef<AudioContext | null>(null);
const alarmIntervalsRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
const { toast } = useToast();
const {
isLocked,
request: requestWakeLock,
release: releaseWakeLock,
} = useWakeLock();

// Effect to manage the wake lock
useEffect(() => {
const hasRunningTimers = timers.some(timer => timer.isRunning);
if (hasRunningTimers && !isLocked) {
requestWakeLock();
} else if (!hasRunningTimers && isLocked) {
Comment thread
spring1843 marked this conversation as resolved.
Outdated
releaseWakeLock();
}
Comment thread
Copilot marked this conversation as resolved.
Outdated
}, [timers, isLocked, requestWakeLock, releaseWakeLock]);
Comment thread
Copilot marked this conversation as resolved.
Comment on lines +78 to +87

// Clear any alarm handle (interval or timeout) robustly
const clearAlarmHandle = useCallback((handle: NodeJS.Timeout) => {
Expand Down
Loading