diff --git a/web/src/app.html b/web/src/app.html index d1db02f493483..aa8450e9be4ba 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -1,5 +1,5 @@ - + @@ -14,35 +14,96 @@ %sveltekit.head% + + +
+
+
+ + + +
+
+ +
+
+
+
+
+

+ 🚨 {$t('error_title')} +

+
+ handleCopy()} + /> +
+
+ +
+ +
+
+

{error?.message} ({error?.code})

+ {#if error?.stack} + +
{error?.stack || 'No stack'}
+ {/if} +
+
+ +
+ + +
+
+
+
+
diff --git a/web/src/lib/components/forms/admin-registration-form.svelte b/web/src/lib/components/forms/admin-registration-form.svelte index c66b09040fb77..d49ab554397c1 100644 --- a/web/src/lib/components/forms/admin-registration-form.svelte +++ b/web/src/lib/components/forms/admin-registration-form.svelte @@ -6,6 +6,7 @@ import Button from '../elements/buttons/button.svelte'; import PasswordField from '../shared-components/password-field.svelte'; import { t } from 'svelte-i18n'; + import { retrieveServerConfig } from '$lib/stores/server-config.store'; let email = ''; let password = ''; @@ -31,6 +32,7 @@ try { await signUpAdmin({ signUpDto: { email, password, name } }); + await retrieveServerConfig(); await goto(AppRoute.AUTH_LOGIN); } catch (error) { handleError(error, $t('errors.unable_to_create_admin_account')); diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte index 828927a13abab..b1af7a01f4b1a 100644 --- a/web/src/lib/components/forms/login-form.svelte +++ b/web/src/lib/components/forms/login-form.svelte @@ -5,7 +5,7 @@ import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; import { oauth } from '$lib/utils'; import { getServerErrorMessage, handleError } from '$lib/utils/handle-error'; - import { getServerConfig, login } from '@immich/sdk'; + import { login } from '@immich/sdk'; import { onMount } from 'svelte'; import { fade } from 'svelte/transition'; import Button from '../elements/buttons/button.svelte'; @@ -58,11 +58,9 @@ try { errorMessage = ''; loading = true; - const user = await login({ loginCredentialDto: { email, password } }); - const serverConfig = await getServerConfig(); - if (user.isAdmin && !serverConfig.isOnboarded) { + if (user.isAdmin && !$serverConfig.isOnboarded) { await onOnboarding(); return; } diff --git a/web/src/lib/stores/server-config.store.ts b/web/src/lib/stores/server-config.store.ts index 40670df25fb6f..1d3c4bc00eb26 100644 --- a/web/src/lib/stores/server-config.store.ts +++ b/web/src/lib/stores/server-config.store.ts @@ -33,7 +33,7 @@ export const serverConfig = writable({ externalDomain: '', }); -export const loadConfig = async () => { +export const retrieveServerConfig = async () => { const [flags, config] = await Promise.all([getServerFeatures(), getServerConfig()]); featureFlags.update(() => ({ ...flags, loaded: true })); diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index b805cf8132a00..6c3add70ce562 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -33,7 +33,7 @@ interface DownloadRequestOptions { onDownloadProgress?: (event: ProgressEvent) => void; } -export const initApp = async () => { +export const initLanguage = async () => { const preferenceLang = get(lang); for (const { code, loader } of langs) { register(code, loader); diff --git a/web/src/lib/utils/server.ts b/web/src/lib/utils/server.ts new file mode 100644 index 0000000000000..d2c5ab185119c --- /dev/null +++ b/web/src/lib/utils/server.ts @@ -0,0 +1,21 @@ +import { retrieveServerConfig } from '$lib/stores/server-config.store'; +import { initLanguage } from '$lib/utils'; +import { defaults } from '@immich/sdk'; +import { memoize } from 'lodash-es'; + +type fetchType = typeof fetch; + +export function initSDK(fetch: fetchType) { + // set event.fetch on the fetch-client used by @immich/sdk + // https://kit.svelte.dev/docs/load#making-fetch-requests + // https://github.com/oazapfts/oazapfts/blob/main/README.md#fetch-options + defaults.fetch = fetch; +} + +async function _init(fetch: fetchType) { + initSDK(fetch); + await initLanguage(); + await retrieveServerConfig(); +} + +export const init = memoize(_init, () => 'singlevalue'); diff --git a/web/src/routes/+error.svelte b/web/src/routes/+error.svelte index e82605d83ef9a..23e8fd3ff191e 100644 --- a/web/src/routes/+error.svelte +++ b/web/src/routes/+error.svelte @@ -1,106 +1,6 @@ -
-
-
- - - -
-
- -
-
-
-
-
-

- 🚨 {$t('error_title')} -

-
- handleCopy()} - /> -
-
- -
- -
-
-

{$page.error?.message} ({$page.error?.code})

- {#if $page.error?.stack} - -
{$page.error?.stack || 'No stack'}
- {/if} -
-
- -
- - -
-
-
-
-
+ diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 1ad9066c4e1db..b7335dea595d8 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -10,16 +10,18 @@ import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte'; import { Theme } from '$lib/constants'; import { colorTheme, handleToggleTheme, type ThemeSetting } from '$lib/stores/preferences.store'; - import { loadConfig, serverConfig } from '$lib/stores/server-config.store'; + + import { serverConfig } from '$lib/stores/server-config.store'; + import { user } from '$lib/stores/user.store'; import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket'; import { copyToClipboard, setKey } from '$lib/utils'; - import { handleError } from '$lib/utils/handle-error'; import { onDestroy, onMount } from 'svelte'; import '../app.css'; import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation'; import DialogWrapper from '$lib/components/shared-components/dialog/dialog-wrapper.svelte'; import { t } from 'svelte-i18n'; + import Error from '$lib/components/error.svelte'; import { shortcut } from '$lib/actions/shortcut'; let showNavigationLoadingBar = false; @@ -33,8 +35,7 @@ const changeTheme = (theme: ThemeSetting) => { if (theme.system) { - theme.value = - window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.DARK : Theme.LIGHT; + theme.value = window.matchMedia('(prefers-color-scheme: dark)').matches ? Theme.DARK : Theme.LIGHT; } if (theme.value === Theme.LIGHT) { @@ -55,6 +56,8 @@ }; onMount(() => { + const element = document.querySelector('#stencil'); + element?.remove(); // if the browser theme changes, changes the Immich theme too window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handleChangeTheme); }); @@ -77,14 +80,6 @@ afterNavigate(() => { showNavigationLoadingBar = false; }); - - onMount(async () => { - try { - await loadConfig(); - } catch (error) { - handleError(error, $t('errors.unable_to_connect_to_server')); - } - }); @@ -134,7 +129,12 @@ onShortcut: () => copyToClipboard(getMyImmichLink().toString()), }} /> - + +{#if $page.data.error} + +{:else} + +{/if} {#if showNavigationLoadingBar} diff --git a/web/src/routes/+layout.ts b/web/src/routes/+layout.ts index e8f665e0e44af..b5edece09e58a 100644 --- a/web/src/routes/+layout.ts +++ b/web/src/routes/+layout.ts @@ -1,19 +1,19 @@ -import { initApp } from '$lib/utils'; -import { defaults } from '@immich/sdk'; +import { init } from '$lib/utils/server'; import type { LayoutLoad } from './$types'; export const ssr = false; export const csr = true; export const load = (async ({ fetch }) => { - // set event.fetch on the fetch-client used by @immich/sdk - // https://kit.svelte.dev/docs/load#making-fetch-requests - // https://github.com/oazapfts/oazapfts/blob/main/README.md#fetch-options - defaults.fetch = fetch; - - await initApp(); + let error; + try { + await init(fetch); + } catch (initError) { + error = initError; + } return { + error, meta: { title: 'Immich', }, diff --git a/web/src/routes/+page.ts b/web/src/routes/+page.ts index f9897336af75c..bcc854cc3cd4d 100644 --- a/web/src/routes/+page.ts +++ b/web/src/routes/+page.ts @@ -1,26 +1,38 @@ import { AppRoute } from '$lib/constants'; +import { serverConfig } from '$lib/stores/server-config.store'; import { getFormatter } from '$lib/utils/i18n'; -import { getServerConfig } from '@immich/sdk'; +import { init } from '$lib/utils/server'; + import { redirect } from '@sveltejs/kit'; +import { get } from 'svelte/store'; import { loadUser } from '../lib/utils/auth'; import type { PageLoad } from './$types'; export const ssr = false; export const csr = true; -export const load = (async () => { - const authenticated = await loadUser(); - if (authenticated) { - redirect(302, AppRoute.PHOTOS); - } +export const load = (async ({ fetch }) => { + let $t = (arg: string) => arg; + try { + await init(fetch); + const authenticated = await loadUser(); + if (authenticated) { + redirect(302, AppRoute.PHOTOS); + } - const { isInitialized } = await getServerConfig(); - if (isInitialized) { - // Redirect to login page if there exists an admin account (i.e. server is initialized) - redirect(302, AppRoute.AUTH_LOGIN); - } + const { isInitialized } = get(serverConfig); + if (isInitialized) { + // Redirect to login page if there exists an admin account (i.e. server is initialized) + redirect(302, AppRoute.AUTH_LOGIN); + } - const $t = await getFormatter(); + $t = await getFormatter(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (redirectError: any) { + if (redirectError?.status === 302) { + throw redirectError; + } + } return { meta: { diff --git a/web/src/routes/auth/login/+page.ts b/web/src/routes/auth/login/+page.ts index 427287c8eafb9..847992ab20098 100644 --- a/web/src/routes/auth/login/+page.ts +++ b/web/src/routes/auth/login/+page.ts @@ -1,12 +1,15 @@ import { AppRoute } from '$lib/constants'; +import { serverConfig } from '$lib/stores/server-config.store'; import { getFormatter } from '$lib/utils/i18n'; -import { defaults, getServerConfig } from '@immich/sdk'; + import { redirect } from '@sveltejs/kit'; +import { get } from 'svelte/store'; import type { PageLoad } from './$types'; -export const load = (async ({ fetch }) => { - defaults.fetch = fetch; - const { isInitialized } = await getServerConfig(); +export const load = (async ({ parent }) => { + await parent(); + const { isInitialized } = get(serverConfig); + if (!isInitialized) { // Admin not registered redirect(302, AppRoute.AUTH_REGISTER); diff --git a/web/src/routes/auth/onboarding/+page.ts b/web/src/routes/auth/onboarding/+page.ts index 7bd307a3ee9ef..db16c8e51419e 100644 --- a/web/src/routes/auth/onboarding/+page.ts +++ b/web/src/routes/auth/onboarding/+page.ts @@ -1,11 +1,9 @@ -import { loadConfig } from '$lib/stores/server-config.store'; import { authenticate } from '$lib/utils/auth'; import { getFormatter } from '$lib/utils/i18n'; import type { PageLoad } from './$types'; export const load = (async () => { await authenticate({ admin: true }); - await loadConfig(); const $t = await getFormatter(); diff --git a/web/src/routes/auth/register/+page.ts b/web/src/routes/auth/register/+page.ts index 00574043c1381..88b56caa47b6a 100644 --- a/web/src/routes/auth/register/+page.ts +++ b/web/src/routes/auth/register/+page.ts @@ -1,11 +1,13 @@ import { AppRoute } from '$lib/constants'; +import { serverConfig } from '$lib/stores/server-config.store'; import { getFormatter } from '$lib/utils/i18n'; -import { getServerConfig } from '@immich/sdk'; import { redirect } from '@sveltejs/kit'; +import { get } from 'svelte/store'; import type { PageLoad } from './$types'; -export const load = (async () => { - const { isInitialized } = await getServerConfig(); +export const load = (async ({ parent }) => { + await parent(); + const { isInitialized } = get(serverConfig); if (isInitialized) { // Admin has been registered, redirect to login redirect(302, AppRoute.AUTH_LOGIN);