mirror of
https://github.com/immich-app/immich.git
synced 2026-07-16 01:47:37 +00:00
refactor(web): routes (#25365)
This commit is contained in:
parent
a8198f9934
commit
4a7c4b6d15
18 changed files with 96 additions and 78 deletions
|
|
@ -1,9 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import { shortcut } from '$lib/actions/shortcut';
|
import { shortcut } from '$lib/actions/shortcut';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
|
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { removeTag } from '$lib/utils/asset-utils';
|
import { removeTag } from '$lib/utils/asset-utils';
|
||||||
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
|
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { Icon, modalManager } from '@immich/ui';
|
import { Icon, modalManager } from '@immich/ui';
|
||||||
|
|
@ -46,7 +45,7 @@
|
||||||
<div class="flex group transition-all">
|
<div class="flex group transition-all">
|
||||||
<a
|
<a
|
||||||
class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-primary rounded-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-primary rounded-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||||
href={resolve(`${AppRoute.TAGS}/?path=${encodeURI(tag.value)}`)}
|
href={Route.tags({ path: tag.value })}
|
||||||
>
|
>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
{tag.value}
|
{tag.value}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import DetailPanelDescription from '$lib/components/asset-viewer/detail-panel-description.svelte';
|
import DetailPanelDescription from '$lib/components/asset-viewer/detail-panel-description.svelte';
|
||||||
import DetailPanelLocation from '$lib/components/asset-viewer/detail-panel-location.svelte';
|
import DetailPanelLocation from '$lib/components/asset-viewer/detail-panel-location.svelte';
|
||||||
import DetailPanelRating from '$lib/components/asset-viewer/detail-panel-star-rating.svelte';
|
import DetailPanelRating from '$lib/components/asset-viewer/detail-panel-star-rating.svelte';
|
||||||
import DetailPanelTags from '$lib/components/asset-viewer/detail-panel-tags.svelte';
|
import DetailPanelTags from '$lib/components/asset-viewer/detail-panel-tags.svelte';
|
||||||
import { AppRoute, QueryParameter, timeToLoadTheMap } from '$lib/constants';
|
import { timeToLoadTheMap } from '$lib/constants';
|
||||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
|
|
@ -73,6 +72,7 @@
|
||||||
})(),
|
})(),
|
||||||
);
|
);
|
||||||
let previousId: string | undefined = $state();
|
let previousId: string | undefined = $state();
|
||||||
|
let previousRoute = $derived(currentAlbum?.id ? Route.viewAlbum(currentAlbum) : Route.photos());
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!previousId) {
|
if (!previousId) {
|
||||||
|
|
@ -100,11 +100,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAssetFolderHref = (asset: AssetResponseDto) => {
|
const getAssetFolderHref = (asset: AssetResponseDto) => {
|
||||||
const folderUrl = new URL(AppRoute.FOLDERS, globalThis.location.href);
|
|
||||||
// Remove the last part of the path to get the parent path
|
// Remove the last part of the path to get the parent path
|
||||||
const assetParentPath = getParentPath(asset.originalPath);
|
return Route.folders({ path: getParentPath(asset.originalPath) });
|
||||||
folderUrl.searchParams.set(QueryParameter.PATH, assetParentPath);
|
|
||||||
return folderUrl.href;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAssetPath = () => (showAssetPath = !showAssetPath);
|
const toggleAssetPath = () => (showAssetPath = !showAssetPath);
|
||||||
|
|
@ -205,11 +202,7 @@
|
||||||
{#if showingHiddenPeople || !person.isHidden}
|
{#if showingHiddenPeople || !person.isHidden}
|
||||||
<a
|
<a
|
||||||
class="w-22"
|
class="w-22"
|
||||||
href={resolve(
|
href={Route.viewPerson(person, { previousRoute })}
|
||||||
`${AppRoute.PEOPLE}/${person.id}?${QueryParameter.PREVIOUS_ROUTE}=${
|
|
||||||
currentAlbum?.id ? Route.viewAlbum(currentAlbum) : Route.photos()
|
|
||||||
}`,
|
|
||||||
)}
|
|
||||||
onfocus={() => ($boundingBoxesArray = people[index].faces)}
|
onfocus={() => ($boundingBoxesArray = people[index].faces)}
|
||||||
onblur={() => ($boundingBoxesArray = [])}
|
onblur={() => ($boundingBoxesArray = [])}
|
||||||
onmouseover={() => ($boundingBoxesArray = people[index].faces)}
|
onmouseover={() => ($boundingBoxesArray = people[index].faces)}
|
||||||
|
|
@ -472,7 +465,7 @@
|
||||||
simplified
|
simplified
|
||||||
useLocationPin
|
useLocationPin
|
||||||
showSimpleControls={!showEditFaces}
|
showSimpleControls={!showEditFaces}
|
||||||
onOpenInMapView={() => goto(resolve(`${AppRoute.MAP}#12.5/${latlng.lat}/${latlng.lng}`))}
|
onOpenInMapView={() => goto(Route.map({ ...latlng, zoom: 12.5 }))}
|
||||||
>
|
>
|
||||||
{#snippet popup({ marker })}
|
{#snippet popup({ marker })}
|
||||||
{@const { lat, lon } = marker}
|
{@const { lat, lon } = marker}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { Route } from '$lib/route';
|
||||||
import { ActionQueryParameterValue, AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
|
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
|
||||||
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
|
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
|
||||||
|
|
@ -39,8 +38,7 @@
|
||||||
|
|
||||||
const handleSwapPeople = async () => {
|
const handleSwapPeople = async () => {
|
||||||
[person, selectedPeople[0]] = [selectedPeople[0], person];
|
[person, selectedPeople[0]] = [selectedPeople[0], person];
|
||||||
page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE);
|
await goto(Route.viewPerson(person, { previousRoute: Route.people(), action: 'merge' }));
|
||||||
await goto(`${AppRoute.PEOPLE}/${person.id}?${page.url.searchParams.toString()}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = async (selected: PersonResponseDto) => {
|
const onSelect = async (selected: PersonResponseDto) => {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { focusOutside } from '$lib/actions/focus-outside';
|
import { focusOutside } from '$lib/actions/focus-outside';
|
||||||
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { getPersonActions } from '$lib/services/person.service';
|
import { getPersonActions } from '$lib/services/person.service';
|
||||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { type PersonResponseDto } from '@immich/sdk';
|
import { type PersonResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
use:focusOutside={{ onFocusOut: () => (showVerticalDots = false) }}
|
use:focusOutside={{ onFocusOut: () => (showVerticalDots = false) }}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="{AppRoute.PEOPLE}/{person.id}?{QueryParameter.PREVIOUS_ROUTE}={AppRoute.PEOPLE}"
|
href={Route.viewPerson(person, { previousRoute: Route.people() })}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
onfocus={() => (showVerticalDots = true)}
|
onfocus={() => (showVerticalDots = true)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
||||||
import RecentAlbums from '$lib/components/shared-components/side-bar/recent-albums.svelte';
|
import RecentAlbums from '$lib/components/shared-components/side-bar/recent-albums.svelte';
|
||||||
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
|
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { Route } from '$lib/route';
|
import { Route } from '$lib/route';
|
||||||
import { recentAlbumsDropdown } from '$lib/stores/preferences.store';
|
import { recentAlbumsDropdown } from '$lib/stores/preferences.store';
|
||||||
|
|
@ -45,11 +44,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if featureFlagsManager.value.map}
|
{#if featureFlagsManager.value.map}
|
||||||
<NavbarItem title={$t('map')} href={AppRoute.MAP} icon={mdiMapOutline} activeIcon={mdiMap} />
|
<NavbarItem title={$t('map')} href={Route.map()} icon={mdiMapOutline} activeIcon={mdiMap} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $preferences.people.enabled && $preferences.people.sidebarWeb}
|
{#if $preferences.people.enabled && $preferences.people.sidebarWeb}
|
||||||
<NavbarItem title={$t('people')} href={AppRoute.PEOPLE} icon={mdiAccountOutline} activeIcon={mdiAccount} />
|
<NavbarItem title={$t('people')} href={Route.people()} icon={mdiAccountOutline} activeIcon={mdiAccount} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $preferences.sharedLinks.enabled && $preferences.sharedLinks.sidebarWeb}
|
{#if $preferences.sharedLinks.enabled && $preferences.sharedLinks.sidebarWeb}
|
||||||
|
|
@ -81,11 +80,11 @@
|
||||||
</NavbarItem>
|
</NavbarItem>
|
||||||
|
|
||||||
{#if $preferences.tags.enabled && $preferences.tags.sidebarWeb}
|
{#if $preferences.tags.enabled && $preferences.tags.sidebarWeb}
|
||||||
<NavbarItem title={$t('tags')} href={AppRoute.TAGS} icon={{ icon: mdiTagMultipleOutline, flipped: true }} />
|
<NavbarItem title={$t('tags')} href={Route.tags()} icon={{ icon: mdiTagMultipleOutline, flipped: true }} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $preferences.folders.enabled && $preferences.folders.sidebarWeb}
|
{#if $preferences.folders.enabled && $preferences.folders.sidebarWeb}
|
||||||
<NavbarItem title={$t('folders')} href={AppRoute.FOLDERS} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
<NavbarItem title={$t('folders')} href={Route.folders()} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<NavbarItem title={$t('utilities')} href={Route.utilities()} icon={mdiToolboxOutline} activeIcon={mdiToolbox} />
|
<NavbarItem title={$t('utilities')} href={Route.utilities()} icon={mdiToolboxOutline} activeIcon={mdiToolbox} />
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,6 @@ export enum AssetAction {
|
||||||
RATING = 'rating',
|
RATING = 'rating',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppRoute {
|
|
||||||
PEOPLE = '/people',
|
|
||||||
SEARCH = '/search',
|
|
||||||
MAP = '/map',
|
|
||||||
BUY = '/buy',
|
|
||||||
FOLDERS = '/folders',
|
|
||||||
TAGS = '/tags',
|
|
||||||
MAINTENANCE = '/maintenance',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SharedLinkTab = 'all' | 'album' | 'individual';
|
export type SharedLinkTab = 'all' | 'album' | 'individual';
|
||||||
|
|
||||||
export enum ProjectionType {
|
export enum ProjectionType {
|
||||||
|
|
@ -82,10 +72,6 @@ export enum OpenQueryParam {
|
||||||
PURCHASE_SETTINGS = 'user-purchase-settings',
|
PURCHASE_SETTINGS = 'user-purchase-settings',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ActionQueryParameterValue {
|
|
||||||
MERGE = 'merge',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const maximumLengthSearchPeople = 1000;
|
export const maximumLengthSearchPeople = 1000;
|
||||||
|
|
||||||
// time to load the map before displaying the loading spinner
|
// time to load the map before displaying the loading spinner
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,20 @@ describe('Route', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(Route.tags.name, () => {
|
||||||
|
it('should work', () => {
|
||||||
|
expect(Route.tags()).toBe('/tags');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support query parameters', () => {
|
||||||
|
expect(Route.tags({ path: '/some/path' })).toBe('/tags?path=%2Fsome%2Fpath');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore an empty path', () => {
|
||||||
|
expect(Route.tags({ path: '' })).toBe('/tags');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe(Route.systemSettings.name, () => {
|
describe(Route.systemSettings.name, () => {
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(Route.systemSettings()).toBe('/admin/system-settings');
|
expect(Route.systemSettings()).toBe('/admin/system-settings');
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,29 @@ export const fromQueueSlug = (slug: string): QueueName | undefined => {
|
||||||
};
|
};
|
||||||
|
|
||||||
type QueryValue = number | string;
|
type QueryValue = number | string;
|
||||||
const asQueryString = (params?: Record<string, QueryValue | undefined>) => {
|
const asQueryString = (
|
||||||
|
params?: Record<string, QueryValue | undefined>,
|
||||||
|
options?: { skipEmptyStrings?: boolean; skipNullValues?: boolean },
|
||||||
|
) => {
|
||||||
|
const { skipEmptyStrings = true, skipNullValues = true } = options ?? {};
|
||||||
const items = Object.entries(params ?? {})
|
const items = Object.entries(params ?? {})
|
||||||
.filter((item): item is [string, QueryValue] => item[1] !== undefined)
|
.filter((item): item is [string, QueryValue] => {
|
||||||
|
const value = item[1];
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipNullValues && value === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipEmptyStrings && value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||||
|
|
||||||
return items.length === 0 ? '' : `?${items.join('&')}`;
|
return items.length === 0 ? '' : `?${items.join('&')}`;
|
||||||
|
|
@ -36,22 +56,40 @@ export const Route = {
|
||||||
viewAlbumAsset: ({ albumId, assetId }: { albumId: string; assetId: string }) =>
|
viewAlbumAsset: ({ albumId, assetId }: { albumId: string; assetId: string }) =>
|
||||||
`/albums/${albumId}/photos/${assetId}`,
|
`/albums/${albumId}/photos/${assetId}`,
|
||||||
|
|
||||||
|
// buy
|
||||||
|
buy: () => '/buy',
|
||||||
|
|
||||||
// explore
|
// explore
|
||||||
explore: () => '/explore',
|
explore: () => '/explore',
|
||||||
places: () => '/places',
|
places: () => '/places',
|
||||||
|
|
||||||
|
// folders
|
||||||
|
folders: (params?: { path?: string }) => '/folders' + asQueryString(params),
|
||||||
|
|
||||||
// libraries
|
// libraries
|
||||||
libraries: () => '/admin/library-management',
|
libraries: () => '/admin/library-management',
|
||||||
newLibrary: () => '/admin/library-management/new',
|
newLibrary: () => '/admin/library-management/new',
|
||||||
viewLibrary: ({ id }: { id: string }) => `/admin/library-management/${id}`,
|
viewLibrary: ({ id }: { id: string }) => `/admin/library-management/${id}`,
|
||||||
editLibrary: ({ id }: { id: string }) => `/admin/library-management/${id}/edit`,
|
editLibrary: ({ id }: { id: string }) => `/admin/library-management/${id}/edit`,
|
||||||
|
|
||||||
|
// maintenance
|
||||||
|
maintenanceMode: (params?: { continue?: string }) => '/maintenance' + asQueryString(params),
|
||||||
|
|
||||||
|
// map
|
||||||
|
map: (point?: { zoom: number; lat: number; lng: number }) =>
|
||||||
|
'/map' + (point ? `#${point.zoom}/${point.lat}/${point.lng}` : ''),
|
||||||
|
|
||||||
// memories
|
// memories
|
||||||
memories: (params?: { id?: string }) => '/memory' + asQueryString(params),
|
memories: (params?: { id?: string }) => '/memory' + asQueryString(params),
|
||||||
|
|
||||||
// partners
|
// partners
|
||||||
viewPartner: ({ id }: { id: string }) => `/partners/${id}`,
|
viewPartner: ({ id }: { id: string }) => `/partners/${id}`,
|
||||||
|
|
||||||
|
// people
|
||||||
|
people: () => '/people',
|
||||||
|
viewPerson: ({ id }: { id: string }, params?: { previousRoute?: string; action?: 'merge' }) =>
|
||||||
|
`/people/${id}` + asQueryString(params),
|
||||||
|
|
||||||
// photos
|
// photos
|
||||||
photos: (params?: { at?: string }) => '/photos' + asQueryString(params),
|
photos: (params?: { at?: string }) => '/photos' + asQueryString(params),
|
||||||
viewAsset: ({ id }: { id: string }) => `/photos/${id}`,
|
viewAsset: ({ id }: { id: string }) => `/photos/${id}`,
|
||||||
|
|
@ -82,6 +120,10 @@ export const Route = {
|
||||||
// system
|
// system
|
||||||
systemSettings: (params?: { isOpen?: OpenQueryParam }) => '/admin/system-settings' + asQueryString(params),
|
systemSettings: (params?: { isOpen?: OpenQueryParam }) => '/admin/system-settings' + asQueryString(params),
|
||||||
systemStatistics: () => '/admin/server-status',
|
systemStatistics: () => '/admin/server-status',
|
||||||
|
systemMaintenance: (params?: { continue?: string }) => '/admin/maintenance' + asQueryString(params),
|
||||||
|
|
||||||
|
// tags
|
||||||
|
tags: (params?: { path?: string }) => '/tags' + asQueryString(params),
|
||||||
|
|
||||||
// users
|
// users
|
||||||
users: () => '/admin/users',
|
users: () => '/admin/users',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { notificationManager } from '$lib/stores/notification-manager.svelte';
|
import { notificationManager } from '$lib/stores/notification-manager.svelte';
|
||||||
import type { ReleaseEvent } from '$lib/types';
|
import type { ReleaseEvent } from '$lib/types';
|
||||||
import { createEventEmitter } from '$lib/utils/eventemitter';
|
import { createEventEmitter } from '$lib/utils/eventemitter';
|
||||||
|
|
@ -63,7 +63,7 @@ websocket
|
||||||
|
|
||||||
export const openWebsocketConnection = () => {
|
export const openWebsocketConnection = () => {
|
||||||
try {
|
try {
|
||||||
if (get(user) || page.url.pathname.startsWith(AppRoute.MAINTENANCE)) {
|
if (get(user) || page.url.pathname.startsWith(Route.maintenanceMode())) {
|
||||||
websocket.connect();
|
websocket.connect();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { maintenanceAuth as maintenanceAuth$ } from '$lib/stores/maintenance.store';
|
import { maintenanceAuth as maintenanceAuth$ } from '$lib/stores/maintenance.store';
|
||||||
import { maintenanceLogin } from '@immich/sdk';
|
import { maintenanceLogin } from '@immich/sdk';
|
||||||
|
|
||||||
export function maintenanceCreateUrl(url: URL) {
|
export function maintenanceCreateUrl(url: URL) {
|
||||||
const target = new URL(AppRoute.MAINTENANCE, url.origin);
|
return new URL(Route.maintenanceMode({ continue: url.pathname + url.search }), url.origin).href;
|
||||||
target.searchParams.set('continue', url.pathname + url.search);
|
|
||||||
return target.href;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maintenanceReturnUrl(searchParams: URLSearchParams) {
|
export function maintenanceReturnUrl(searchParams: URLSearchParams) {
|
||||||
|
|
@ -13,7 +11,7 @@ export function maintenanceReturnUrl(searchParams: URLSearchParams) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maintenanceShouldRedirect(maintenanceMode: boolean, currentUrl: URL | Location) {
|
export function maintenanceShouldRedirect(maintenanceMode: boolean, currentUrl: URL | Location) {
|
||||||
return maintenanceMode !== currentUrl.pathname.startsWith(AppRoute.MAINTENANCE);
|
return maintenanceMode !== currentUrl.pathname.startsWith(Route.maintenanceMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadMaintenanceAuth = async () => {
|
export const loadMaintenanceAuth = async () => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import SingleGridRow from '$lib/components/shared-components/single-grid-row.svelte';
|
import SingleGridRow from '$lib/components/shared-components/single-grid-row.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { Route } from '$lib/route';
|
import { Route } from '$lib/route';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
|
@ -47,7 +46,7 @@
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('people')}</p>
|
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('people')}</p>
|
||||||
<a
|
<a
|
||||||
href={AppRoute.PEOPLE}
|
href={Route.people()}
|
||||||
class="pe-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
class="pe-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
||||||
draggable="false">{$t('view_all')}</a
|
draggable="false">{$t('view_all')}</a
|
||||||
>
|
>
|
||||||
|
|
@ -55,7 +54,7 @@
|
||||||
<SingleGridRow class="grid grid-flow-col md:grid-auto-fill-28 grid-auto-fill-20 gap-x-4">
|
<SingleGridRow class="grid grid-flow-col md:grid-auto-fill-28 grid-auto-fill-20 gap-x-4">
|
||||||
{#snippet children({ itemCount })}
|
{#snippet children({ itemCount })}
|
||||||
{#each people.slice(0, itemCount) as person (person.id)}
|
{#each people.slice(0, itemCount) as person (person.id)}
|
||||||
<a href="{AppRoute.PEOPLE}/{person.id}" class="text-center relative">
|
<a href={Route.viewPerson(person)} class="text-center relative">
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
import FavoriteAction from '$lib/components/timeline/actions/FavoriteAction.svelte';
|
import FavoriteAction from '$lib/components/timeline/actions/FavoriteAction.svelte';
|
||||||
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import SkipLink from '$lib/elements/SkipLink.svelte';
|
import SkipLink from '$lib/elements/SkipLink.svelte';
|
||||||
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { foldersStore } from '$lib/stores/folders.svelte';
|
import { foldersStore } from '$lib/stores/folders.svelte';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
|
@ -44,11 +44,7 @@
|
||||||
|
|
||||||
const handleNavigateToFolder = (folderName: string) => navigateToView(joinPaths(data.tree.path, folderName));
|
const handleNavigateToFolder = (folderName: string) => navigateToView(joinPaths(data.tree.path, folderName));
|
||||||
|
|
||||||
function getLinkForPath(path: string) {
|
const getLinkForPath = (path: string) => Route.folders({ path });
|
||||||
const url = new URL(AppRoute.FOLDERS, globalThis.location.href);
|
|
||||||
url.searchParams.set(QueryParameter.PATH, path);
|
|
||||||
return url.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
afterNavigate(function clearAssetSelection() {
|
afterNavigate(function clearAssetSelection() {
|
||||||
// Clear the asset selection when we navigate (like going to another folder)
|
// Clear the asset selection when we navigate (like going to another folder)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@
|
||||||
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||||
import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants';
|
import { QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||||
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
|
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
|
@ -205,9 +206,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMergePeople = async (detail: PersonResponseDto) => {
|
const handleMergePeople = async (detail: PersonResponseDto) => {
|
||||||
await goto(
|
await goto(Route.viewPerson(detail, { previousRoute: Route.people(), action: 'merge' }));
|
||||||
`${AppRoute.PEOPLE}/${detail.id}?${QueryParameter.ACTION}=${ActionQueryParameterValue.MERGE}&${QueryParameter.PREVIOUS_ROUTE}=${AppRoute.PEOPLE}`,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResetSearchBar = async () => {
|
const onResetSearchBar = async () => {
|
||||||
|
|
@ -300,7 +299,7 @@
|
||||||
[
|
[
|
||||||
scrollMemory,
|
scrollMemory,
|
||||||
{
|
{
|
||||||
routeStartsWith: AppRoute.PEOPLE,
|
routeStartsWith: Route.people(),
|
||||||
beforeSave: () => {
|
beforeSave: () => {
|
||||||
if (currentPage) {
|
if (currentPage) {
|
||||||
sessionStorage.setItem(SessionStorageKey.INFINITE_SCROLL_PAGE, currentPage.toString());
|
sessionStorage.setItem(SessionStorageKey.INFINITE_SCROLL_PAGE, currentPage.toString());
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||||
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
|
import { PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
|
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
|
||||||
|
|
@ -219,7 +219,7 @@
|
||||||
await updateAssetCount();
|
await updateAssetCount();
|
||||||
return { merged: true };
|
return { merged: true };
|
||||||
}
|
}
|
||||||
await goto(`${AppRoute.PEOPLE}/${personToBeMergedInto.id}`, { replaceState: true });
|
await goto(Route.viewPerson(personToBeMergedInto), { replaceState: true });
|
||||||
return { merged: true };
|
return { merged: true };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -339,7 +339,7 @@
|
||||||
<main
|
<main
|
||||||
class="relative z-0 h-dvh overflow-hidden px-2 md:px-6 md:pt-(--navbar-height-md) pt-(--navbar-height)"
|
class="relative z-0 h-dvh overflow-hidden px-2 md:px-6 md:pt-(--navbar-height-md) pt-(--navbar-height)"
|
||||||
use:scrollMemoryClearer={{
|
use:scrollMemoryClearer={{
|
||||||
routeStartsWith: AppRoute.PEOPLE,
|
routeStartsWith: Route.people(),
|
||||||
beforeClear: () => {
|
beforeClear: () => {
|
||||||
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@
|
||||||
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
|
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
|
||||||
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
|
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
|
||||||
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
||||||
import { AppRoute, AssetAction, QueryParameter } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
import SkipLink from '$lib/elements/SkipLink.svelte';
|
import SkipLink from '$lib/elements/SkipLink.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { getTagActions } from '$lib/services/tag.service';
|
import { getTagActions } from '$lib/services/tag.service';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { preferences, user } from '$lib/stores/user.store';
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
|
|
@ -50,11 +51,7 @@
|
||||||
|
|
||||||
const handleNavigation = (tag: string) => navigateToView(joinPaths(data.path, tag));
|
const handleNavigation = (tag: string) => navigateToView(joinPaths(data.path, tag));
|
||||||
|
|
||||||
const getLink = (path: string) => {
|
const getLink = (path: string) => Route.tags({ path });
|
||||||
const url = new URL(AppRoute.TAGS, globalThis.location.href);
|
|
||||||
url.searchParams.set(QueryParameter.PATH, path);
|
|
||||||
return url.href;
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateToView = (path: string) => goto(getLink(path));
|
const navigateToView = (path: string) => goto(getLink(path));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
|
import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
|
||||||
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
|
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
|
||||||
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
|
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
||||||
|
|
@ -89,7 +88,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect.pre(() => {
|
$effect.pre(() => {
|
||||||
if ($user || page.url.pathname.startsWith(AppRoute.MAINTENANCE)) {
|
if ($user || page.url.pathname.startsWith(Route.maintenanceMode())) {
|
||||||
openWebsocketConnection();
|
openWebsocketConnection();
|
||||||
} else {
|
} else {
|
||||||
closeWebsocketConnection();
|
closeWebsocketConnection();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
import { Route } from '$lib/route';
|
import { Route } from '$lib/route';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
|
|
@ -15,7 +14,7 @@ export const load = (async ({ fetch }) => {
|
||||||
await init(fetch);
|
await init(fetch);
|
||||||
|
|
||||||
if (serverConfigManager.value.maintenanceMode) {
|
if (serverConfigManager.value.maintenanceMode) {
|
||||||
redirect(307, AppRoute.MAINTENANCE);
|
redirect(307, Route.maintenanceMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
const authenticated = await loadUser();
|
const authenticated = await loadUser();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppRoute, OpenQueryParam } from '$lib/constants';
|
import { OpenQueryParam } from '$lib/constants';
|
||||||
import { Route } from '$lib/route';
|
import { Route } from '$lib/route';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
@ -34,7 +34,7 @@ export const load = (({ url }) => {
|
||||||
// https://my.immich.app/link?target=activate_license&licenseKey=IMCL-9XC3-T4S3-37BU-GGJ5-8MWP-F2Y1-BGEX-AQTF
|
// https://my.immich.app/link?target=activate_license&licenseKey=IMCL-9XC3-T4S3-37BU-GGJ5-8MWP-F2Y1-BGEX-AQTF
|
||||||
const licenseKey = queryParams.get('licenseKey');
|
const licenseKey = queryParams.get('licenseKey');
|
||||||
const activationKey = queryParams.get('activationKey');
|
const activationKey = queryParams.get('activationKey');
|
||||||
const redirectUrl = new URL(AppRoute.BUY, url.origin);
|
const redirectUrl = new URL(Route.buy(), url.origin);
|
||||||
|
|
||||||
if (licenseKey) {
|
if (licenseKey) {
|
||||||
redirectUrl.searchParams.append('licenseKey', licenseKey);
|
redirectUrl.searchParams.append('licenseKey', licenseKey);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue