From a78260296c0dfcb6df6fbccc1f3b525ee922142a Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Wed, 24 Apr 2024 15:24:19 -0400 Subject: [PATCH] feat(web): assets now have a permanent URL (#8532) * Remove asest redirect pages * Rename route paths to handle optional assetId * Update old references to new routes * Load and display asset from all routes that can show assetId * Add
in base layout, update portals to target it * Wire up updating navigation in response to open/close/prev/next * Replace events with navigation functions * Add types to param matcher * misc cleanup * Fix reload on /search pages * Avoid loading bar between photos nav. Delay loading bar by 200ms for all navigations * Update url for maps routes. Note: on page reload, next/prev is not available * Dynamically load asset-viewer on map page * When reloading a url with assetUrl, hide background page to prevent flash during load * Mostly style, review comments * Load buckets for assets on demand * Forgot this update call * typo * fix test * Fix carelessness * Review comment * merge main * remove assets * fix submodule --------- Co-authored-by: Alex Co-authored-by: Daniel Dietzler --- .../asset-viewer/asset-viewer.svelte | 13 +++- .../components/photos-page/asset-grid.svelte | 19 +++-- .../drag-and-drop-upload-overlay.svelte | 5 +- .../gallery-viewer/gallery-viewer.svelte | 54 ++++---------- .../navigation-loading-bar.svelte | 24 ++++-- web/src/lib/stores/asset.store.spec.ts | 14 ++-- web/src/lib/stores/assets.store.ts | 66 +++++++++++------ web/src/lib/utils/navigation.ts | 74 +++++++++++++++++++ web/src/params/id.ts | 6 ++ web/src/params/photos.ts | 5 ++ web/src/routes/(user)/+layout.svelte | 24 +++++- .../[[assetId=id]]}/+page.svelte | 10 +-- .../[[assetId=id]]}/+page.ts | 7 +- .../[albumId]/photos/[assetId]/+page.svelte | 0 .../[albumId]/photos/[assetId]/+page.ts | 13 ---- .../[[assetId=id]]}/+page.svelte | 0 .../[[assetId=id]]}/+page.ts | 5 +- .../archive/photos/[assetId]/+page.svelte | 0 .../(user)/archive/photos/[assetId]/+page.ts | 7 -- .../[[assetId=id]]}/+page.svelte | 0 .../[[assetId=id]]}/+page.ts | 6 +- .../(user)/favorites/[assetId]/+page.svelte | 0 .../(user)/favorites/[assetId]/+page.ts | 7 -- .../[[assetId=id]]}/+page.svelte | 30 ++++---- .../[[assetId=id]]}/+page.ts | 6 +- .../[[assetId=id]]}/+page.svelte | 0 .../[[assetId=id]]}/+page.ts | 6 +- .../routes/(user)/memory/photos/+page.svelte | 0 web/src/routes/(user)/memory/photos/+page.ts | 9 --- .../memory/photos/[assetId]/+page.svelte | 0 .../(user)/memory/photos/[assetId]/+page.ts | 7 -- .../[[assetId=id]]}/+page.svelte | 0 .../[[assetId=id]]}/+page.ts | 5 +- .../[personId]/photos/[assetId]/+page.svelte | 0 .../[personId]/photos/[assetId]/+page.ts | 7 -- .../photos/{ => [[assetId=id]]}/+page.svelte | 0 .../photos/{ => [[assetId=id]]}/+page.ts | 5 +- .../(user)/photos/[assetId]/+page.svelte | 0 .../routes/(user)/photos/[assetId]/+page.ts | 7 -- .../[[assetId=id]]}/+page.svelte | 6 +- .../[[assetId=id]]}/+page.ts | 5 +- .../search/photos/[assetId]/+page.svelte | 0 .../(user)/search/photos/[assetId]/+page.ts | 7 -- .../[[assetId=id]]}/+page.svelte | 0 .../[[assetId=id]]}/+page.ts | 5 +- .../trash/photos/[assetId]/+page.svelte | 0 .../(user)/trash/photos/[assetId]/+page.ts | 7 -- web/src/routes/+layout.svelte | 8 +- 48 files changed, 289 insertions(+), 190 deletions(-) create mode 100644 web/src/params/id.ts create mode 100644 web/src/params/photos.ts rename web/src/routes/(user)/albums/{[albumId] => [albumId=id]/[[photos=photos]]/[[assetId=id]]}/+page.svelte (99%) rename web/src/routes/(user)/albums/{[albumId] => [albumId=id]/[[photos=photos]]/[[assetId=id]]}/+page.ts (57%) delete mode 100644 web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts rename web/src/routes/(user)/archive/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (100%) rename web/src/routes/(user)/archive/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (53%) delete mode 100644 web/src/routes/(user)/archive/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/archive/photos/[assetId]/+page.ts rename web/src/routes/(user)/favorites/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (100%) rename web/src/routes/(user)/favorites/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (53%) delete mode 100644 web/src/routes/(user)/favorites/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/favorites/[assetId]/+page.ts rename web/src/routes/(user)/map/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (82%) rename web/src/routes/(user)/map/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (52%) rename web/src/routes/(user)/memory/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (100%) rename web/src/routes/(user)/memory/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (55%) delete mode 100644 web/src/routes/(user)/memory/photos/+page.svelte delete mode 100644 web/src/routes/(user)/memory/photos/+page.ts delete mode 100644 web/src/routes/(user)/memory/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/memory/photos/[assetId]/+page.ts rename web/src/routes/(user)/people/[personId]/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (100%) rename web/src/routes/(user)/people/[personId]/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (72%) delete mode 100644 web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts rename web/src/routes/(user)/photos/{ => [[assetId=id]]}/+page.svelte (100%) rename web/src/routes/(user)/photos/{ => [[assetId=id]]}/+page.ts (53%) delete mode 100644 web/src/routes/(user)/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/photos/[assetId]/+page.ts rename web/src/routes/(user)/search/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (98%) rename web/src/routes/(user)/search/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (53%) delete mode 100644 web/src/routes/(user)/search/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/search/photos/[assetId]/+page.ts rename web/src/routes/(user)/trash/{ => [[photos=photos]]/[[assetId=id]]}/+page.svelte (100%) rename web/src/routes/(user)/trash/{ => [[photos=photos]]/[[assetId=id]]}/+page.ts (53%) delete mode 100644 web/src/routes/(user)/trash/photos/[assetId]/+page.svelte delete mode 100644 web/src/routes/(user)/trash/photos/[assetId]/+page.ts diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 28899a7525..2fcf904000 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -51,6 +51,7 @@ import PhotoViewer from './photo-viewer.svelte'; import SlideshowBar from './slideshow-bar.svelte'; import VideoViewer from './video-wrapper-viewer.svelte'; + import { navigate } from '$lib/utils/navigation'; export let assetStore: AssetStore | null = null; export let asset: AssetResponseDto; @@ -191,6 +192,7 @@ } onMount(async () => { + await navigate({ targetRoute: 'current', assetId: asset.id }); slideshowStateUnsubscribe = slideshowState.subscribe((value) => { if (value === SlideshowState.PlaySlideshow) { slideshowHistory.reset(); @@ -263,11 +265,14 @@ $isShowDetail = !$isShowDetail; }; - const handleCloseViewer = () => { - closeViewer(); + const handleCloseViewer = async () => { + await closeViewer(); }; - const closeViewer = () => dispatch('close'); + const closeViewer = async () => { + dispatch('close'); + await navigate({ targetRoute: 'current', assetId: null }); + }; const navigateAssetRandom = async () => { if (!assetStore) { @@ -300,7 +305,7 @@ if ($slideshowState === SlideshowState.PlaySlideshow && assetStore) { const hasNext = - order === 'previous' ? await assetStore.getPreviousAsset(asset.id) : await assetStore.getNextAsset(asset.id); + order === 'previous' ? await assetStore.getPreviousAsset(asset) : await assetStore.getNextAsset(asset); if (hasNext) { $restartSlideshowProgress = true; } else { diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 8cfb0b8b16..920e833a9e 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -22,6 +22,7 @@ import DeleteAssetDialog from './delete-asset-dialog.svelte'; import { handlePromiseError } from '$lib/utils'; import { selectAllAssets } from '$lib/utils/asset-utils'; + import { navigate } from '$lib/utils/navigation'; export let isSelectionMode = false; export let singleSelect = false; @@ -48,6 +49,10 @@ $: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0; $: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id); + $: { + void assetStore.updateViewport(viewport); + } + const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>(); onMount(async () => { @@ -142,22 +147,24 @@ } const handlePrevious = async () => { - const previousAsset = await assetStore.getPreviousAsset($viewingAsset.id); + const previousAsset = await assetStore.getPreviousAsset($viewingAsset); if (previousAsset) { - const preloadAsset = await assetStore.getPreviousAsset(previousAsset.id); + const preloadAsset = await assetStore.getPreviousAsset(previousAsset); assetViewingStore.setAsset(previousAsset, preloadAsset ? [preloadAsset] : []); + await navigate({ targetRoute: 'current', assetId: previousAsset.id }); } return !!previousAsset; }; const handleNext = async () => { - const nextAsset = await assetStore.getNextAsset($viewingAsset.id); + const nextAsset = await assetStore.getNextAsset($viewingAsset); if (nextAsset) { - const preloadAsset = await assetStore.getNextAsset(nextAsset.id); + const preloadAsset = await assetStore.getNextAsset(nextAsset); assetViewingStore.setAsset(nextAsset, preloadAsset ? [preloadAsset] : []); + await navigate({ targetRoute: 'current', assetId: nextAsset.id }); } return !!nextAsset; @@ -462,8 +469,8 @@ {#if $showAssetViewer} - {#await import('../asset-viewer/asset-viewer.svelte') then AssetViewer} - - import { page } from '$app/stores'; + import Portal from '../portal/portal.svelte'; import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import type { BucketPosition, Viewport } from '$lib/stores/assets.store'; @@ -10,7 +10,7 @@ import justifiedLayout from 'justified-layout'; import { getAssetRatio } from '$lib/utils/asset-utils'; import { calculateWidth } from '$lib/utils/timeline-util'; - import { pushState, replaceState } from '$app/navigation'; + import { navigate } from '$lib/utils/navigation'; const dispatch = createEventDispatcher<{ intersected: { container: HTMLDivElement; position: BucketPosition } }>(); @@ -20,17 +20,15 @@ export let showArchiveIcon = false; export let viewport: Viewport; - let { isViewing: showAssetViewer } = assetViewingStore; + let { isViewing: showAssetViewer, asset: viewingAsset, setAsset } = assetViewingStore; - let selectedAsset: AssetResponseDto; let currentViewAssetIndex = 0; $: isMultiSelectionMode = selectedAssets.size > 0; - const viewAssetHandler = (asset: AssetResponseDto) => { + const viewAssetHandler = async (asset: AssetResponseDto) => { currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id); - selectedAsset = assets[currentViewAssetIndex]; - $showAssetViewer = true; - updateAssetState(selectedAsset.id, false); + setAsset(assets[currentViewAssetIndex]); + await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); }; const selectAssetHandler = (asset: AssetResponseDto) => { @@ -45,45 +43,28 @@ selectedAssets = temporary; }; - const navigateAssetForward = () => { + const navigateAssetForward = async () => { try { if (currentViewAssetIndex < assets.length - 1) { - currentViewAssetIndex++; - selectedAsset = assets[currentViewAssetIndex]; - updateAssetState(selectedAsset.id); + setAsset(assets[++currentViewAssetIndex]); + await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); } } catch (error) { handleError(error, 'Cannot navigate to the next asset'); } }; - const navigateAssetBackward = () => { + const navigateAssetBackward = async () => { try { if (currentViewAssetIndex > 0) { - currentViewAssetIndex--; - selectedAsset = assets[currentViewAssetIndex]; - updateAssetState(selectedAsset.id); + setAsset(assets[--currentViewAssetIndex]); + await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); } } catch (error) { handleError(error, 'Cannot navigate to previous asset'); } }; - const updateAssetState = (assetId: string, replace = true) => { - const route = `${$page.url.pathname}/photos/${assetId}`; - - if (replace) { - replaceState(route, {}); - } else { - pushState(route, {}); - } - }; - - const closeViewer = () => { - $showAssetViewer = false; - pushState(`${$page.url.pathname}${$page.url.search}`, {}); - }; - onDestroy(() => { $showAssetViewer = false; }); @@ -107,8 +88,6 @@ })(); - - {#if assets.length > 0}
{#each assets as asset, i (i)} @@ -136,10 +115,7 @@ {#if $showAssetViewer} - + + + {/if} diff --git a/web/src/lib/components/shared-components/navigation-loading-bar.svelte b/web/src/lib/components/shared-components/navigation-loading-bar.svelte index c1d39ece57..bed9b8d7ad 100644 --- a/web/src/lib/components/shared-components/navigation-loading-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-loading-bar.svelte @@ -3,16 +3,30 @@ import { cubicOut } from 'svelte/easing'; import { tweened } from 'svelte/motion'; + let showing = false; + + // delay showing any progress for a little bit so very fast loads + // do not cause flicker + const delay = 100; + const progress = tweened(0, { duration: 1000, easing: cubicOut, }); - onMount(async () => { - await progress.set(90); + function animate() { + showing = true; + void progress.set(90); + } + + onMount(() => { + const timer = setTimeout(animate, delay); + return () => clearTimeout(timer); }); -
- -
+{#if showing} +
+ +
+{/if} diff --git a/web/src/lib/stores/asset.store.spec.ts b/web/src/lib/stores/asset.store.spec.ts index e2f702fbab..025362faae 100644 --- a/web/src/lib/stores/asset.store.spec.ts +++ b/web/src/lib/stores/asset.store.spec.ts @@ -307,15 +307,15 @@ describe('AssetStore', () => { }); it('returns null for invalid assetId', async () => { - expect(() => assetStore.getPreviousAsset('invalid')).not.toThrow(); - expect(await assetStore.getPreviousAsset('invalid')).toBeNull(); + expect(() => assetStore.getPreviousAsset({ id: 'invalid' } as AssetResponseDto)).not.toThrow(); + expect(await assetStore.getPreviousAsset({ id: 'invalid' } as AssetResponseDto)).toBeNull(); }); it('returns previous assetId', async () => { await assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Visible); const bucket = assetStore.getBucketByDate('2024-01-01T00:00:00.000Z'); - expect(await assetStore.getPreviousAsset(bucket!.assets[1].id)).toEqual(bucket!.assets[0]); + expect(await assetStore.getPreviousAsset(bucket!.assets[1])).toEqual(bucket!.assets[0]); }); it('returns previous assetId spanning multiple buckets', async () => { @@ -324,7 +324,7 @@ describe('AssetStore', () => { const bucket = assetStore.getBucketByDate('2024-02-01T00:00:00.000Z'); const previousBucket = assetStore.getBucketByDate('2024-03-01T00:00:00.000Z'); - expect(await assetStore.getPreviousAsset(bucket!.assets[0].id)).toEqual(previousBucket!.assets[0]); + expect(await assetStore.getPreviousAsset(bucket!.assets[0])).toEqual(previousBucket!.assets[0]); }); it('loads previous bucket', async () => { @@ -333,7 +333,7 @@ describe('AssetStore', () => { const loadBucketSpy = vi.spyOn(assetStore, 'loadBucket'); const bucket = assetStore.getBucketByDate('2024-02-01T00:00:00.000Z'); const previousBucket = assetStore.getBucketByDate('2024-03-01T00:00:00.000Z'); - expect(await assetStore.getPreviousAsset(bucket!.assets[0].id)).toEqual(previousBucket!.assets[0]); + expect(await assetStore.getPreviousAsset(bucket!.assets[0])).toEqual(previousBucket!.assets[0]); expect(loadBucketSpy).toBeCalledTimes(1); }); @@ -344,12 +344,12 @@ describe('AssetStore', () => { const [assetOne, assetTwo, assetThree] = assetStore.assets; assetStore.removeAssets([assetTwo.id]); - expect(await assetStore.getPreviousAsset(assetThree.id)).toEqual(assetOne); + expect(await assetStore.getPreviousAsset(assetThree)).toEqual(assetOne); }); it('returns null when no more assets', async () => { await assetStore.loadBucket('2024-03-01T00:00:00.000Z', BucketPosition.Visible); - expect(await assetStore.getPreviousAsset(assetStore.assets[0].id)).toBeNull(); + expect(await assetStore.getPreviousAsset(assetStore.assets[0])).toBeNull(); }); }); diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 019a70401f..b1f91466b3 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -1,4 +1,5 @@ import { getKey } from '$lib/utils'; +import { fromLocalDateTime } from '$lib/utils/timeline-util'; import { TimeBucketSize, getTimeBucket, getTimeBuckets, type AssetResponseDto } from '@immich/sdk'; import { throttle } from 'lodash-es'; import { DateTime } from 'luxon'; @@ -188,32 +189,40 @@ export class AssetStore { this.assetToBucket = {}; this.albumAssets = new Set(); - const buckets = await getTimeBuckets({ + const timebuckets = await getTimeBuckets({ ...this.options, key: getKey(), }); this.initialized = true; - this.buckets = buckets.map((bucket) => { - const unwrappedWidth = (3 / 2) * bucket.count * THUMBNAIL_HEIGHT * (7 / 10); + this.buckets = timebuckets.map((bucket) => ({ + bucketDate: bucket.timeBucket, + bucketHeight: 0, + bucketCount: bucket.count, + assets: [], + cancelToken: null, + position: BucketPosition.Unknown, + })); + + // if loading an asset, the grid-view may be hidden, which means + // it has 0 width and height. No need to update bucket or timeline + // heights in this case. Later, updateViewport will be called to + // update the heights. + if (viewport.height !== 0 && viewport.width !== 0) { + await this.updateViewport(viewport); + } + } + + async updateViewport(viewport: Viewport) { + for (const bucket of this.buckets) { + const unwrappedWidth = (3 / 2) * bucket.bucketCount * THUMBNAIL_HEIGHT * (7 / 10); const rows = Math.ceil(unwrappedWidth / viewport.width); const height = rows * THUMBNAIL_HEIGHT; - - return { - bucketDate: bucket.timeBucket, - bucketHeight: height, - bucketCount: bucket.count, - assets: [], - cancelToken: null, - position: BucketPosition.Unknown, - }; - }); - + bucket.bucketHeight = height; + } this.timelineHeight = this.buckets.reduce((accumulator, b) => accumulator + b.bucketHeight, 0); - this.emit(false); - let height = 0; const loaders = []; for (const bucket of this.buckets) { @@ -222,10 +231,10 @@ export class AssetStore { loaders.push(this.loadBucket(bucket.bucketDate, BucketPosition.Visible)); continue; } - break; } await Promise.all(loaders); + this.emit(false); } async loadBucket(bucketDate: string, position: BucketPosition): Promise { @@ -380,8 +389,19 @@ export class AssetStore { return this.buckets.find((bucket) => bucket.bucketDate === bucketDate) || null; } - getBucketInfoForAssetId(assetId: string) { - return this.assetToBucket[assetId] || null; + async getBucketInfoForAssetId({ id, localDateTime }: Pick) { + const bucketInfo = this.assetToBucket[id]; + if (bucketInfo) { + return bucketInfo; + } + let date = fromLocalDateTime(localDateTime); + if (this.options.size == TimeBucketSize.Month) { + date = date.set({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }); + } else if (this.options.size == TimeBucketSize.Day) { + date = date.set({ hour: 0, minute: 0, second: 0, millisecond: 0 }); + } + await this.loadBucket(date.toISO()!, BucketPosition.Unknown); + return this.assetToBucket[id] || null; } getBucketIndexByAssetId(assetId: string) { @@ -451,8 +471,8 @@ export class AssetStore { this.emit(true); } - async getPreviousAsset(assetId: string): Promise { - const info = this.getBucketInfoForAssetId(assetId); + async getPreviousAsset(asset: AssetResponseDto): Promise { + const info = await this.getBucketInfoForAssetId(asset); if (!info) { return null; } @@ -472,8 +492,8 @@ export class AssetStore { return previousBucket.assets.at(-1) || null; } - async getNextAsset(assetId: string): Promise { - const info = this.getBucketInfoForAssetId(assetId); + async getNextAsset(asset: AssetResponseDto): Promise { + const info = await this.getBucketInfoForAssetId(asset); if (!info) { return null; } diff --git a/web/src/lib/utils/navigation.ts b/web/src/lib/utils/navigation.ts index a91c4a739f..1745240da3 100644 --- a/web/src/lib/utils/navigation.ts +++ b/web/src/lib/utils/navigation.ts @@ -1,9 +1,83 @@ import { goto } from '$app/navigation'; +import { page } from '$app/stores'; +import { AppRoute } from '$lib/constants'; +import { getAssetInfo } from '@immich/sdk'; +import type { NavigationTarget } from '@sveltejs/kit'; +import { get } from 'svelte/store'; export const isExternalUrl = (url: string): boolean => { return new URL(url, window.location.href).origin !== window.location.origin; }; +export const isPhotosRoute = (route?: string | null) => !!route?.startsWith('/(user)/photos/[[assetId=id]]'); +export const isSharedLinkRoute = (route?: string | null) => !!route?.startsWith('/(user)/share/[key]'); +export const isSearchRoute = (route?: string | null) => !!route?.startsWith('/(user)/search'); +export const isAlbumsRoute = (route?: string | null) => !!route?.startsWith('/(user)/albums/[albumId=id]'); +export const isPeopleRoute = (route?: string | null) => !!route?.startsWith('/(user)/people/[personId]'); + +export const isAssetViewerRoute = (target?: NavigationTarget | null) => + !!(target?.route.id?.endsWith('/[[assetId=id]]') && 'assetId' in (target?.params || {})); + +export function getAssetInfoFromParam({ assetId }: { assetId?: string }) { + return assetId && getAssetInfo({ id: assetId }); +} + +function currentUrlWithoutAsset() { + const $page = get(page); + // This contains special casing for the /photos/:assetId route, which hangs directly + // off / instead of a subpath, unlike every other asset-containing route. + return isPhotosRoute($page.route.id) + ? AppRoute.PHOTOS + $page.url.search + : $page.url.pathname.replace(/(\/photos.*)$/, '') + $page.url.search; +} + +function currentUrlReplaceAssetId(assetId: string) { + const $page = get(page); + // this contains special casing for the /photos/:assetId photos route, which hangs directly + // off / instead of a subpath, unlike every other asset-containing route. + return isPhotosRoute($page.route.id) + ? `${AppRoute.PHOTOS}/${assetId}${$page.url.search}` + : `${$page.url.pathname.replace(/(\/photos.*)$/, '')}/photos/${assetId}${$page.url.search}`; +} + +function currentUrl() { + const $page = get(page); + const current = $page.url; + return current.pathname + current.search; +} + +interface Route { + /** + * The route to target, or 'current' to stay on current route. + */ + targetRoute: string | 'current'; +} + +interface AssetRoute extends Route { + targetRoute: 'current'; + assetId: string | null; +} + +function isAssetRoute(route: Route): route is AssetRoute { + return route.targetRoute === 'current' && 'assetId' in route; +} + +async function navigateAssetRoute(route: AssetRoute) { + const { assetId } = route; + const next = assetId ? currentUrlReplaceAssetId(assetId) : currentUrlWithoutAsset(); + if (next !== currentUrl()) { + await goto(next, { replaceState: false }); + } +} + +export function navigate(change: T): Promise { + if (isAssetRoute(change)) { + return navigateAssetRoute(change); + } + // future navigation requests here + throw `Invalid navigation: ${JSON.stringify(change)}`; +} + export const clearQueryParam = async (queryParam: string, url: URL) => { if (url.searchParams.has(queryParam)) { url.searchParams.delete(queryParam); diff --git a/web/src/params/id.ts b/web/src/params/id.ts new file mode 100644 index 0000000000..6b16a651d1 --- /dev/null +++ b/web/src/params/id.ts @@ -0,0 +1,6 @@ +import type { ParamMatcher } from '@sveltejs/kit'; + +/* Returns true if the given param matches UUID format */ +export const match: ParamMatcher = (param: string) => { + return /^[\dA-Fa-f]{8}(?:\b-[\dA-Fa-f]{4}){3}\b-[\dA-Fa-f]{12}$/.test(param); +}; diff --git a/web/src/params/photos.ts b/web/src/params/photos.ts new file mode 100644 index 0000000000..cd0105bb63 --- /dev/null +++ b/web/src/params/photos.ts @@ -0,0 +1,5 @@ +import type { ParamMatcher } from '@sveltejs/kit'; + +export const match: ParamMatcher = (param: string) => { + return param === 'photos'; +}; diff --git a/web/src/routes/(user)/+layout.svelte b/web/src/routes/(user)/+layout.svelte index 53c2514c80..23f38b86f4 100644 --- a/web/src/routes/(user)/+layout.svelte +++ b/web/src/routes/(user)/+layout.svelte @@ -1,6 +1,28 @@ - +
+ +
+ + diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 99% rename from web/src/routes/(user)/albums/[albumId]/+page.svelte rename to web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 87471810c5..3879fc26f4 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -79,6 +79,7 @@ import AlbumDescription from '$lib/components/album-page/album-description.svelte'; import { handlePromiseError } from '$lib/utils'; import AlbumSummary from '$lib/components/album-page/album-summary.svelte'; + import { isAlbumsRoute, isPeopleRoute, isSearchRoute } from '$lib/utils/navigation'; export let data: PageData; @@ -137,15 +138,14 @@ album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0); afterNavigate(({ from }) => { - assetViewingStore.showAssetViewer(false); - let url: string | undefined = from?.url?.pathname; - if (from?.route.id === '/(user)/search') { - url = from.url.href; + const route = from?.route?.id; + if (isSearchRoute(route)) { + url = from?.url.href; } - if (from?.route.id === '/(user)/albums/[albumId]' || from?.route.id === '/(user)/people/[personId]') { + if (isAlbumsRoute(route) || isPeopleRoute(route)) { url = AppRoute.ALBUMS; } diff --git a/web/src/routes/(user)/albums/[albumId]/+page.ts b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 57% rename from web/src/routes/(user)/albums/[albumId]/+page.ts rename to web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts index 3772f28623..0143390974 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.ts +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,13 +1,18 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import { getAlbumInfo } from '@immich/sdk'; import type { PageLoad } from './$types'; export const load = (async ({ params }) => { await authenticate(); - const album = await getAlbumInfo({ id: params.albumId, withoutAssets: true }); + const [album, asset] = await Promise.all([ + getAlbumInfo({ id: params.albumId, withoutAssets: true }), + getAssetInfoFromParam(params), + ]); return { album, + asset, meta: { title: album.albumName, }, diff --git a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts deleted file mode 100644 index 5c0ff2677d..0000000000 --- a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load: PageLoad = ({ params }) => { - const albumId = params.albumId; - - if (albumId) { - redirect(302, `${AppRoute.ALBUMS}/${albumId}`); - } else { - redirect(302, AppRoute.PHOTOS); - } -}; diff --git a/web/src/routes/(user)/archive/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 100% rename from web/src/routes/(user)/archive/+page.svelte rename to web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte diff --git a/web/src/routes/(user)/archive/+page.ts b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 53% rename from web/src/routes/(user)/archive/+page.ts rename to web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts index 7669c123ad..0c608506c2 100644 --- a/web/src/routes/(user)/archive/+page.ts +++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,10 +1,13 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { await authenticate(); + const asset = await getAssetInfoFromParam(params); return { + asset, meta: { title: 'Archive', }, diff --git a/web/src/routes/(user)/archive/photos/[assetId]/+page.svelte b/web/src/routes/(user)/archive/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts deleted file mode 100644 index 6a39c40daf..0000000000 --- a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load: PageLoad = () => { - redirect(302, AppRoute.ARCHIVE); -}; diff --git a/web/src/routes/(user)/favorites/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 100% rename from web/src/routes/(user)/favorites/+page.svelte rename to web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte diff --git a/web/src/routes/(user)/favorites/+page.ts b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 53% rename from web/src/routes/(user)/favorites/+page.ts rename to web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts index 9fc993fcae..f11e352bd9 100644 --- a/web/src/routes/(user)/favorites/+page.ts +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,9 +1,13 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { await authenticate(); + const asset = await getAssetInfoFromParam(params); + return { + asset, meta: { title: 'Favorites', }, diff --git a/web/src/routes/(user)/favorites/[assetId]/+page.svelte b/web/src/routes/(user)/favorites/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/favorites/[assetId]/+page.ts b/web/src/routes/(user)/favorites/[assetId]/+page.ts deleted file mode 100644 index 5125ef1e45..0000000000 --- a/web/src/routes/(user)/favorites/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load: PageLoad = () => { - redirect(302, AppRoute.FAVORITES); -}; diff --git a/web/src/routes/(user)/map/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 82% rename from web/src/routes/(user)/map/+page.svelte rename to web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 257c0ad89d..593250a7c9 100644 --- a/web/src/routes/(user)/map/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,6 +1,5 @@ @@ -113,14 +115,16 @@ > {#if $showAssetViewer} - 1} - on:next={navigateNext} - on:previous={navigatePrevious} - on:close={() => assetViewingStore.showAssetViewer(false)} - isShared={false} - /> + {#await import('../../../../../lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} + 1} + on:next={navigateNext} + on:previous={navigatePrevious} + on:close={() => assetViewingStore.showAssetViewer(false)} + isShared={false} + /> + {/await} {/if} diff --git a/web/src/routes/(user)/map/+page.ts b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 52% rename from web/src/routes/(user)/map/+page.ts rename to web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts index 6890cd91f0..4367dd9896 100644 --- a/web/src/routes/(user)/map/+page.ts +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,9 +1,13 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { await authenticate(); + const asset = await getAssetInfoFromParam(params); + return { + asset, meta: { title: 'Map', }, diff --git a/web/src/routes/(user)/memory/+page.svelte b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 100% rename from web/src/routes/(user)/memory/+page.svelte rename to web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.svelte diff --git a/web/src/routes/(user)/memory/+page.ts b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 55% rename from web/src/routes/(user)/memory/+page.ts rename to web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts index e56dd392d6..2e5e891579 100644 --- a/web/src/routes/(user)/memory/+page.ts +++ b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,10 +1,14 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { const user = await authenticate(); + const asset = await getAssetInfoFromParam(params); + return { user, + asset, meta: { title: 'Memory', }, diff --git a/web/src/routes/(user)/memory/photos/+page.svelte b/web/src/routes/(user)/memory/photos/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/memory/photos/+page.ts b/web/src/routes/(user)/memory/photos/+page.ts deleted file mode 100644 index eb578117ed..0000000000 --- a/web/src/routes/(user)/memory/photos/+page.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { authenticate } from '$lib/utils/auth'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load = (async () => { - await authenticate(); - redirect(302, AppRoute.MEMORY); -}) satisfies PageLoad; diff --git a/web/src/routes/(user)/memory/photos/[assetId]/+page.svelte b/web/src/routes/(user)/memory/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/memory/photos/[assetId]/+page.ts b/web/src/routes/(user)/memory/photos/[assetId]/+page.ts deleted file mode 100644 index f90b109917..0000000000 --- a/web/src/routes/(user)/memory/photos/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load = (() => { - redirect(302, AppRoute.PHOTOS); -}) satisfies PageLoad; diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 100% rename from web/src/routes/(user)/people/[personId]/+page.svelte rename to web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte diff --git a/web/src/routes/(user)/people/[personId]/+page.ts b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 72% rename from web/src/routes/(user)/people/[personId]/+page.ts rename to web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts index 9f20e6f003..1f9f9e26c9 100644 --- a/web/src/routes/(user)/people/[personId]/+page.ts +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,18 +1,21 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import { getPerson, getPersonStatistics } from '@immich/sdk'; import type { PageLoad } from './$types'; export const load = (async ({ params }) => { await authenticate(); - const [person, statistics] = await Promise.all([ + const [person, statistics, asset] = await Promise.all([ getPerson({ id: params.personId }), getPersonStatistics({ id: params.personId }), + getAssetInfoFromParam(params), ]); return { person, statistics, + asset, meta: { title: person.name || 'Person', }, diff --git a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.svelte b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts deleted file mode 100644 index bb2e587da4..0000000000 --- a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load = (({ params }) => { - redirect(302, `${AppRoute.PEOPLE}/${params.personId}`); -}) satisfies PageLoad; diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte similarity index 100% rename from web/src/routes/(user)/photos/+page.svelte rename to web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte diff --git a/web/src/routes/(user)/photos/+page.ts b/web/src/routes/(user)/photos/[[assetId=id]]/+page.ts similarity index 53% rename from web/src/routes/(user)/photos/+page.ts rename to web/src/routes/(user)/photos/[[assetId=id]]/+page.ts index 3cfa2dbe69..663e3cebba 100644 --- a/web/src/routes/(user)/photos/+page.ts +++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.ts @@ -1,9 +1,12 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { await authenticate(); + const asset = await getAssetInfoFromParam(params); return { + asset, meta: { title: 'Photos', }, diff --git a/web/src/routes/(user)/photos/[assetId]/+page.svelte b/web/src/routes/(user)/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/photos/[assetId]/+page.ts b/web/src/routes/(user)/photos/[assetId]/+page.ts deleted file mode 100644 index f90b109917..0000000000 --- a/web/src/routes/(user)/photos/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load = (() => { - redirect(302, AppRoute.PHOTOS); -}) satisfies PageLoad; diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 98% rename from web/src/routes/(user)/search/+page.svelte rename to web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index 46ed276064..ed7b281a0c 100644 --- a/web/src/routes/(user)/search/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -38,6 +38,7 @@ import { featureFlags } from '$lib/stores/server-config.store'; import { handleError } from '$lib/utils/handle-error'; import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte'; + import { isAlbumsRoute, isPeopleRoute } from '$lib/utils/navigation'; const MAX_ASSET_COUNT = 5000; let { isViewing: showAssetViewer } = assetViewingStore; @@ -73,12 +74,13 @@ if (from?.url && from.route.id !== $page.route.id) { previousRoute = from.url.href; } + const route = from?.route?.id; - if (from?.route.id === '/(user)/people/[personId]') { + if (isPeopleRoute(route)) { previousRoute = AppRoute.PHOTOS; } - if (from?.route.id === '/(user)/albums/[albumId]') { + if (isAlbumsRoute(route)) { previousRoute = AppRoute.EXPLORE; } }); diff --git a/web/src/routes/(user)/search/+page.ts b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 53% rename from web/src/routes/(user)/search/+page.ts rename to web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts index 64635424ef..c0384cf38d 100644 --- a/web/src/routes/(user)/search/+page.ts +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,9 +1,12 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { await authenticate(); + const asset = await getAssetInfoFromParam(params); return { + asset, meta: { title: 'Search', }, diff --git a/web/src/routes/(user)/search/photos/[assetId]/+page.svelte b/web/src/routes/(user)/search/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/search/photos/[assetId]/+page.ts b/web/src/routes/(user)/search/photos/[assetId]/+page.ts deleted file mode 100644 index f1e5126931..0000000000 --- a/web/src/routes/(user)/search/photos/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load = (() => { - redirect(302, AppRoute.SEARCH); -}) satisfies PageLoad; diff --git a/web/src/routes/(user)/trash/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte similarity index 100% rename from web/src/routes/(user)/trash/+page.svelte rename to web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte diff --git a/web/src/routes/(user)/trash/+page.ts b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.ts similarity index 53% rename from web/src/routes/(user)/trash/+page.ts rename to web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.ts index ea0647c286..3fb1757cc1 100644 --- a/web/src/routes/(user)/trash/+page.ts +++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.ts @@ -1,9 +1,12 @@ import { authenticate } from '$lib/utils/auth'; +import { getAssetInfoFromParam } from '$lib/utils/navigation'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (async ({ params }) => { await authenticate(); + const asset = await getAssetInfoFromParam(params); return { + asset, meta: { title: 'Trash', }, diff --git a/web/src/routes/(user)/trash/photos/[assetId]/+page.svelte b/web/src/routes/(user)/trash/photos/[assetId]/+page.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/web/src/routes/(user)/trash/photos/[assetId]/+page.ts b/web/src/routes/(user)/trash/photos/[assetId]/+page.ts deleted file mode 100644 index eb3a453d24..0000000000 --- a/web/src/routes/(user)/trash/photos/[assetId]/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AppRoute } from '$lib/constants'; -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; - -export const load = (() => { - redirect(302, AppRoute.TRASH); -}) satisfies PageLoad; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 8925ee340b..6fb98a4576 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -17,11 +17,10 @@ import { handleError } from '$lib/utils/handle-error'; import { onDestroy, onMount } from 'svelte'; import '../app.css'; + import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation'; let showNavigationLoadingBar = false; - const isSharedLinkRoute = (route: string | null) => route?.startsWith('/(user)/share/[key]'); - $: changeTheme($colorTheme); $: if ($user) { @@ -62,7 +61,10 @@ setKey($page.params.key); } - beforeNavigate(() => { + beforeNavigate(({ from, to }) => { + if (isAssetViewerRoute(from) && isAssetViewerRoute(to)) { + return; + } showNavigationLoadingBar = true; });