From c50a807bc1e1d61b99832c06db2baf6ed649b06c Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Thu, 16 Jul 2026 11:41:37 +0100 Subject: [PATCH] feat(web): add wake lock when uploading assets (#29820) --- web/src/lib/utils/wakelock.svelte.ts | 50 ++++++++++++++++++++++++++++ web/src/routes/UploadPanel.svelte | 11 ++++++ 2 files changed, 61 insertions(+) create mode 100644 web/src/lib/utils/wakelock.svelte.ts diff --git a/web/src/lib/utils/wakelock.svelte.ts b/web/src/lib/utils/wakelock.svelte.ts new file mode 100644 index 0000000000..4c69a397a5 --- /dev/null +++ b/web/src/lib/utils/wakelock.svelte.ts @@ -0,0 +1,50 @@ +import { on } from 'svelte/events'; +import { browser } from '$app/environment'; + +const isSupported = browser && 'wakeLock' in navigator; + +let sentinel: WakeLockSentinel | undefined; +let acquiring = false; + +export async function acquireWakeLock() { + if (!isSupported) { + return; + } + // eslint-disable-next-line tscompat/tscompat + if (sentinel && !sentinel.released) { + return; + } + if (acquiring) { + return; + } + acquiring = true; + try { + // eslint-disable-next-line tscompat/tscompat + sentinel = await navigator.wakeLock.request('screen'); + } catch (error) { + console.warn('Failed to acquire wake lock:', error); + } finally { + acquiring = false; + } +} + +export async function releaseWakeLock() { + if (sentinel) { + const toReleaseSentinel = sentinel; + // Unset first to avoid race condition after await + sentinel = undefined; + + // eslint-disable-next-line tscompat/tscompat + await toReleaseSentinel.release(); + } +} + +if (isSupported) { + // Wake lock is cleared when user changes to a different tab, + // so we need to reacquire the wake lock when they come back. + on(globalThis, 'visibilitychange', () => { + if (sentinel && document.visibilityState === 'visible') { + void acquireWakeLock(); + } + }); +} diff --git a/web/src/routes/UploadPanel.svelte b/web/src/routes/UploadPanel.svelte index ad6d4d9ecb..a13c03cabf 100644 --- a/web/src/routes/UploadPanel.svelte +++ b/web/src/routes/UploadPanel.svelte @@ -2,6 +2,7 @@ import { locale } from '$lib/stores/preferences.store'; import { uploadAssetsStore } from '$lib/stores/upload'; import { uploadExecutionQueue } from '$lib/utils/file-uploader'; + import { acquireWakeLock, releaseWakeLock } from '$lib/utils/wakelock.svelte'; import { Icon, IconButton, toastManager } from '@immich/ui'; import { mdiCancel, mdiCloudUploadOutline, mdiCog, mdiWindowMinimize } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -15,11 +16,21 @@ let { stats, isDismissible, isUploading, remainingUploads } = uploadAssetsStore; + let hasRemaining = $derived($remainingUploads > 0); + $effect(() => { if ($isUploading) { showDetail = true; } }); + + $effect(() => { + if (hasRemaining) { + void acquireWakeLock(); + } else { + void releaseWakeLock(); + } + }); {#if $isUploading}