{
const { data } = await api.systemConfigApi.getMapStyle({
- theme: $mapSettings.allowDarkMode ? $colorTheme : 'light',
+ theme: $mapSettings.allowDarkMode ? $colorTheme.value : Theme.LIGHT,
});
return data as StyleSpecification;
})();
diff --git a/web/src/lib/components/shared-components/theme-button.svelte b/web/src/lib/components/shared-components/theme-button.svelte
index 5f4f6a10e4..0b586a2088 100644
--- a/web/src/lib/components/shared-components/theme-button.svelte
+++ b/web/src/lib/components/shared-components/theme-button.svelte
@@ -1,29 +1,17 @@
-
- {#if $colorTheme === 'light'}
-
- {:else}
-
- {/if}
-
+{#if !$colorTheme.system}
+
+ {#if $colorTheme.value === Theme.LIGHT}
+
+ {:else}
+
+ {/if}
+
+{/if}
diff --git a/web/src/lib/components/user-settings-page/appearance-settings.svelte b/web/src/lib/components/user-settings-page/appearance-settings.svelte
new file mode 100644
index 0000000000..99f00a4b0f
--- /dev/null
+++ b/web/src/lib/components/user-settings-page/appearance-settings.svelte
@@ -0,0 +1,24 @@
+
+
+
diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte
index 7a51c657c0..29901177c0 100644
--- a/web/src/lib/components/user-settings-page/user-settings-list.svelte
+++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte
@@ -14,6 +14,7 @@
import UserAPIKeyList from './user-api-key-list.svelte';
import UserProfileSettings from './user-profile-settings.svelte';
import { user } from '$lib/stores/user.store';
+ import AppearanceSettings from './appearance-settings.svelte';
export let keys: APIKeyResponseDto[] = [];
export let devices: AuthDeviceResponseDto[] = [];
@@ -24,6 +25,10 @@
}
+
+
+
+
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index e755d82971..b5539c3c2c 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -57,3 +57,9 @@ export const dateFormats = {
year: 'numeric',
},
};
+
+// should be the same values as the ones in the app.html
+export enum Theme {
+ LIGHT = 'light',
+ DARK = 'dark',
+}
diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts
index 05a90b2380..8c59b83a0a 100644
--- a/web/src/lib/stores/preferences.store.ts
+++ b/web/src/lib/stores/preferences.store.ts
@@ -1,13 +1,42 @@
import { browser } from '$app/environment';
+import { Theme } from '$lib/constants';
import { persisted } from 'svelte-local-storage-store';
+import { get } from 'svelte/store';
-const initialTheme = browser && !window.matchMedia('(prefers-color-scheme: dark)').matches ? 'light' : 'dark';
+export interface ThemeSetting {
+ value: Theme;
+ system: boolean;
+}
+
+export const handleToggleTheme = () => {
+ const theme = get(colorTheme);
+ theme.value = theme.value === Theme.DARK ? Theme.LIGHT : Theme.DARK;
+ colorTheme.set(theme);
+};
+
+const initTheme = (): ThemeSetting => {
+ if (browser) {
+ if (!window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ return { value: Theme.LIGHT, system: false };
+ }
+ }
+ return { value: Theme.DARK, system: false };
+};
+
+const initialTheme = initTheme();
// The 'color-theme' key is also used by app.html to prevent FOUC on page load.
-export const colorTheme = persisted<'dark' | 'light'>('color-theme', initialTheme, {
+export const colorTheme = persisted
('color-theme', initialTheme, {
serializer: {
- parse: (text) => (text === 'light' ? text : 'dark'),
- stringify: (obj) => obj,
+ parse: (text: string): ThemeSetting => {
+ const parsedText: ThemeSetting = JSON.parse(text);
+ if (Object.values(Theme).includes(parsedText.value)) {
+ return parsedText;
+ } else {
+ return initTheme();
+ }
+ },
+ stringify: (obj) => JSON.stringify(obj),
},
});
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index 74195da175..cfad3f6d04 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -11,15 +11,15 @@
import UploadCover from '$lib/components/shared-components/drag-and-drop-upload-overlay.svelte';
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
- import { onMount } from 'svelte';
+ import { onDestroy, onMount } from 'svelte';
import { loadConfig } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error';
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
import { api } from '@api';
import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
import { user } from '$lib/stores/user.store';
- import { browser } from '$app/environment';
- import { colorTheme } from '$lib/stores/preferences.store';
+ import { ThemeSetting, colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
+ import { Theme } from '$lib/constants';
let showNavigationLoadingBar = false;
let albumId: string | undefined;
@@ -27,15 +27,35 @@
const isSharedLinkRoute = (route: string | null) => route?.startsWith('/(user)/share/[key]');
const isAuthRoute = (route?: string) => route?.startsWith('/auth');
- $: {
- if (browser) {
- if ($colorTheme === 'light') {
- document.documentElement.classList.remove('dark');
- } else {
- document.documentElement.classList.add('dark');
- }
+ $: changeTheme($colorTheme);
+
+ const changeTheme = (theme: ThemeSetting) => {
+ if (theme.system) {
+ theme.value =
+ window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.DARK : Theme.LIGHT;
}
- }
+
+ if (theme.value === Theme.LIGHT) {
+ document.documentElement.classList.remove('dark');
+ } else {
+ document.documentElement.classList.add('dark');
+ }
+ };
+
+ const handleChangeTheme = () => {
+ if ($colorTheme.system) {
+ handleToggleTheme();
+ }
+ };
+
+ onMount(() => {
+ // if the browser theme changes, changes the Immich theme too
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handleChangeTheme);
+ });
+
+ onDestroy(() => {
+ document.removeEventListener('change', handleChangeTheme);
+ });
if (isSharedLinkRoute($page.route?.id)) {
api.setKey($page.params.key);