diff --git a/e2e/src/specs/web/shared-link.e2e-spec.ts b/e2e/src/specs/web/shared-link.e2e-spec.ts index acd793039..67f6de1f3 100644 --- a/e2e/src/specs/web/shared-link.e2e-spec.ts +++ b/e2e/src/specs/web/shared-link.e2e-spec.ts @@ -103,7 +103,7 @@ test.describe('Shared Links', () => { test('show error for invalid shared link', async ({ page }) => { await page.goto('/share/invalid'); - await page.getByRole('heading', { name: 'Invalid share key' }).waitFor(); + await page.getByRole('heading', { name: 'Page not found' }).waitFor(); }); test('auth on navigation from shared link to timeline', async ({ context, page }) => { diff --git a/web/.prettierrc b/web/.prettierrc index e809bd509..c19bf13b1 100644 --- a/web/.prettierrc +++ b/web/.prettierrc @@ -1,5 +1,5 @@ { - "importOrder": ["", "", "^\\$(.*)$", "^@test-data(.*)$", "^[./]"], + "importOrder": ["", "", "^\\$(.*)$", "^#test-data(.*)$", "^[./]"], "importOrderSeparation": false, "jsonRecursiveSort": true, "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }], diff --git a/web/eslint.config.js b/web/eslint.config.js index 048f42d3a..d9097cd46 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -53,7 +53,7 @@ export default defineConfig( tsconfigRootDir: import.meta.dirname, }, }, - // ignores: ['**/service-worker/**'], + ignores: ['**/service-worker/**'], }, { ignores: [ @@ -74,6 +74,7 @@ export default defineConfig( 'tailwind.config.js', 'coverage', 'vite.config.ts', + '**/service-worker/**', ], }, typescriptEslint.configs.recommended, diff --git a/web/package.json b/web/package.json index 9855cca44..c151ebe5b 100644 --- a/web/package.json +++ b/web/package.json @@ -3,6 +3,14 @@ "version": "3.3.5", "license": "GNU Affero General Public License version 3", "type": "module", + "imports": { + "#lib": "./src/lib/index.js", + "#lib/*": "./src/lib/*", + "#tests": "./src/../tests", + "#tests/*": "./src/../tests/*", + "#i18n/*": "../i18n/*", + "#test-data/*": "./src/test-data/*" + }, "scripts": { "dev": "vite dev --host 0.0.0.0 --port 3000", "build": "vite build", @@ -78,9 +86,9 @@ "@faker-js/faker": "^10.0.0", "@koddsson/eslint-plugin-tscompat": "^0.2.0", "@socket.io/component-emitter": "^3.1.0", - "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/adapter-static": "^4.0.0", "@sveltejs/enhanced-img": "^0.11.0", - "@sveltejs/kit": "^2.56.1", + "@sveltejs/kit": "^3.0.0", "@sveltejs/vite-plugin-svelte": "7.3.1", "@tailwindcss/vite": "^4.2.4", "@testing-library/jest-dom": "^7.0.0", @@ -110,13 +118,13 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^4.0.0", "rollup-plugin-visualizer": "^7.0.0", - "svelte": "5.57.1", - "svelte-check": "^4.4.6", + "svelte": "5.57.2", + "svelte-check": "^4.7.5", "svelte-eslint-parser": "^1.8.1", "tailwindcss": "^4.2.4", "typescript": "npm:@typescript/typescript6@^6.0.2", "typescript-eslint": "8.71.2", - "vite": "^8.0.0", + "vite": "^8.0.12", "vitest": "^5.0.0" } } diff --git a/web/src/app.d.ts b/web/src/app.d.ts index 1a0c85be0..e2bdac7a8 100644 --- a/web/src/app.d.ts +++ b/web/src/app.d.ts @@ -1,5 +1,5 @@ +import type en from '#i18n/en.json'; import 'svelte-i18n'; -import type en from '$i18n/en.json'; /// @@ -34,7 +34,7 @@ type NestedKeys = K extends keyof T & string : never; declare module 'svelte-i18n' { - import type { InterpolationValues } from '$lib/elements/format-message'; + import type { InterpolationValues } from '#lib/elements/format-message.js'; import type { Readable } from 'svelte/store'; type Translations = NestedKeys; diff --git a/web/src/env.ts b/web/src/env.ts new file mode 100644 index 000000000..a772b33e3 --- /dev/null +++ b/web/src/env.ts @@ -0,0 +1,6 @@ +import { defineEnvVars } from '@sveltejs/kit/env'; + +export const variables = defineEnvVars({ + PUBLIC_IMMICH_BUY_HOST: { public: true, static: true }, + PUBLIC_IMMICH_PAY_HOST: { public: true, static: true }, +}); diff --git a/web/src/hooks.client.ts b/web/src/hooks.client.ts index 3593ffadc..8755278b5 100644 --- a/web/src/hooks.client.ts +++ b/web/src/hooks.client.ts @@ -1,5 +1,5 @@ import { isHttpError, type ApiHttpError } from '@immich/sdk'; -import type { HandleClientError } from '@sveltejs/kit'; +import type { HandleClientError } from '@sveltejs/kit/hooks'; const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?'; @@ -19,19 +19,23 @@ const parseHTTPError = (httpError: ApiHttpError) => { }; }; -const parseError = (error: unknown, status: number, message: string) => { +const parseError = (error: App.Error | { status: number; message: string }) => { if (isHttpError(error)) { return parseHTTPError(error); } return { - message: (error as Error)?.message || message || DEFAULT_MESSAGE, - code: status, + message: error.message || DEFAULT_MESSAGE, + code: error.status, }; }; -export const handleError: HandleClientError = ({ error, status, message }) => { - const result = parseError(error, status, message); +export const handleError: HandleClientError = ({ kind, error }) => { + if (kind === 'unknown') { + console.error(error); + return; + } + const result = parseError(error); console.error(`[hooks.client.ts]:handleError ${result.message}`, error); return result; }; diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts index 17b94eda1..ef35dd349 100644 --- a/web/src/hooks.server.ts +++ b/web/src/hooks.server.ts @@ -1,6 +1,6 @@ -import type { Handle } from '@sveltejs/kit'; -import GoogleSans from '$lib/assets/fonts/GoogleSans/GoogleSans.ttf?url'; -import GoogleSansCode from '$lib/assets/fonts/GoogleSansCode/GoogleSansCode.ttf?url'; +import GoogleSans from '#lib/assets/fonts/GoogleSans/GoogleSans.ttf?url'; +import GoogleSansCode from '#lib/assets/fonts/GoogleSansCode/GoogleSansCode.ttf?url'; +import type { Handle } from '@sveltejs/kit/hooks'; // only used during the build to replace the variables from app.html export const handle = (async ({ event, resolve }) => { diff --git a/web/src/lib/actions/__test__/FocusTrap.spec.ts b/web/src/lib/actions/__test__/FocusTrap.spec.ts index 6e9957d8b..b8a0da845 100644 --- a/web/src/lib/actions/__test__/FocusTrap.spec.ts +++ b/web/src/lib/actions/__test__/FocusTrap.spec.ts @@ -1,8 +1,8 @@ +import FocusTrapTest from '#lib/actions/__test__/FocusTrapTest.svelte'; +import { setDefaultTabbleOptions } from '#lib/utils/focus-util.js'; import { render, screen } from '@testing-library/svelte'; import userEvent from '@testing-library/user-event'; import { tick } from 'svelte'; -import FocusTrapTest from '$lib/actions/__test__/FocusTrapTest.svelte'; -import { setDefaultTabbleOptions } from '$lib/utils/focus-util'; setDefaultTabbleOptions({ displayCheck: 'none' }); diff --git a/web/src/lib/actions/__test__/FocusTrapTest.svelte b/web/src/lib/actions/__test__/FocusTrapTest.svelte index a19d2b75d..f24adb465 100644 --- a/web/src/lib/actions/__test__/FocusTrapTest.svelte +++ b/web/src/lib/actions/__test__/FocusTrapTest.svelte @@ -1,5 +1,5 @@ refreshAlbum()} /> +
@@ -351,6 +356,7 @@
{/if} + {/if} @@ -393,13 +399,15 @@ {const Actions = $derived(getAssetBulkActions($t, album))} + + {#if assetMultiSelectManager.isAllUserOwned} timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} - > + /> {/if} @@ -447,7 +455,7 @@ oldAt = { at: assetViewerManager.gridScrollTarget?.at }; await navigate( { targetRoute: 'current', assetId: null, assetGridRouteSearchParams: { at: null } }, - { replaceState: true }, + { replace: true }, ); }} icon={mdiImagePlusOutline} diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts index 1f4bea58a..469054612 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,6 +1,6 @@ -import { getAlbumInfoQuery } from '$lib/queries/album.query'; -import { queryClient } from '$lib/queries/client'; -import { authenticate } from '$lib/utils/auth'; +import { getAlbumInfoQuery } from '#lib/queries/album.query.js'; +import { queryClient } from '#lib/queries/client.js'; +import { authenticate } from '#lib/utils/auth.js'; import type { PageLoad } from './$types'; export const load = (async ({ params, url }) => { diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/AlbumDescription.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/AlbumDescription.svelte index d8556a0c3..6b90f3702 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/AlbumDescription.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/AlbumDescription.svelte @@ -1,7 +1,7 @@ diff --git a/web/src/routes/(user)/s/[slug]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/s/[slug]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 25c24ab02..7bfa85b21 100644 --- a/web/src/routes/(user)/s/[slug]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/s/[slug]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,5 +1,5 @@ - {#if searchTermKeys.length > 0} @@ -379,7 +380,9 @@ + +
diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts index 56a8a33c1..ce42516cc 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,5 +1,5 @@ -import { authenticate } from '$lib/utils/auth'; -import { getFormatter } from '$lib/utils/i18n'; +import { authenticate } from '#lib/utils/auth.js'; +import { getFormatter } from '#lib/utils/i18n.js'; import type { PageLoad } from './$types'; export const load = (async ({ url }) => { diff --git a/web/src/routes/(user)/share/[key]/+error.svelte b/web/src/routes/(user)/share/[key]/+error.svelte index 2c4d2a4d0..e6b9d5320 100644 --- a/web/src/routes/(user)/share/[key]/+error.svelte +++ b/web/src/routes/(user)/share/[key]/+error.svelte @@ -1,5 +1,5 @@ diff --git a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 25c24ab02..7bfa85b21 100644 --- a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,5 +1,5 @@ - import { getHeatmapRange } from '$lib'; - import CalendarHeatmap from '$lib/components/CalendarHeatmap.svelte'; - import Skeleton from '$lib/elements/Skeleton.svelte'; - import { getAlbumStatisticsQuery } from '$lib/queries/album.query'; - import { locale } from '$lib/stores/preferences.store'; + import { getHeatmapRange } from '#lib'; + import CalendarHeatmap from '#lib/components/CalendarHeatmap.svelte'; + import Skeleton from '#lib/elements/Skeleton.svelte'; + import { getAlbumStatisticsQuery } from '#lib/queries/album.query.js'; + import { locale } from '#lib/stores/preferences.store.js'; import { AssetVisibility, CalendarHeatmapType, diff --git a/web/src/routes/(user)/utilities/+page.svelte b/web/src/routes/(user)/utilities/+page.svelte index a1cc50cb4..09950c2ae 100644 --- a/web/src/routes/(user)/utilities/+page.svelte +++ b/web/src/routes/(user)/utilities/+page.svelte @@ -1,5 +1,5 @@ diff --git a/web/src/routes/admin/queues/[name]/+page.svelte b/web/src/routes/admin/queues/[name]/+page.svelte index ed579445a..a2c5a50d6 100644 --- a/web/src/routes/admin/queues/[name]/+page.svelte +++ b/web/src/routes/admin/queues/[name]/+page.svelte @@ -1,9 +1,9 @@ diff --git a/web/src/routes/auth/onboarding/OnboardingMobileApp.svelte b/web/src/routes/auth/onboarding/OnboardingMobileApp.svelte index 27e24d17e..736a57332 100644 --- a/web/src/routes/auth/onboarding/OnboardingMobileApp.svelte +++ b/web/src/routes/auth/onboarding/OnboardingMobileApp.svelte @@ -1,6 +1,6 @@ diff --git a/web/src/routes/auth/onboarding/OnboardingTheme.svelte b/web/src/routes/auth/onboarding/OnboardingTheme.svelte index fba2a6d4d..50b4e3a91 100644 --- a/web/src/routes/auth/onboarding/OnboardingTheme.svelte +++ b/web/src/routes/auth/onboarding/OnboardingTheme.svelte @@ -1,5 +1,5 @@ diff --git a/web/src/routes/auth/onboarding/OnboardingUserPrivacy.svelte b/web/src/routes/auth/onboarding/OnboardingUserPrivacy.svelte index 600e6abea..eb3a4acee 100644 --- a/web/src/routes/auth/onboarding/OnboardingUserPrivacy.svelte +++ b/web/src/routes/auth/onboarding/OnboardingUserPrivacy.svelte @@ -1,7 +1,7 @@