mirror of
https://github.com/immich-app/immich.git
synced 2025-07-01 21:40:10 +02:00
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 <main> 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 <alex.tran1502@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
parent
1e004611e4
commit
a78260296c
48 changed files with 289 additions and 190 deletions
web/src/lib/utils
|
@ -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<T extends Route>(change: T): Promise<void> {
|
||||
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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue