mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
refactor(web): routes (#25313)
This commit is contained in:
parent
07675a2de4
commit
8196bd9bbd
87 changed files with 425 additions and 383 deletions
|
|
@ -1,14 +1,12 @@
|
||||||
import { navigating } from '$app/stores';
|
import { navigating } from '$app/stores';
|
||||||
import { AppRoute, SessionStorageKey } from '$lib/constants';
|
import { SessionStorageKey } from '$lib/constants';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
/**
|
/**
|
||||||
* {@link AppRoute} for subpages that scroll state should be kept while visiting.
|
|
||||||
*
|
|
||||||
* This must be kept the same in all subpages of this route for the scroll memory clearer to work.
|
* This must be kept the same in all subpages of this route for the scroll memory clearer to work.
|
||||||
*/
|
*/
|
||||||
routeStartsWith: AppRoute;
|
routeStartsWith: string;
|
||||||
/**
|
/**
|
||||||
* Function to clear additional data/state before scrolling (ex infinite scroll).
|
* Function to clear additional data/state before scrolling (ex infinite scroll).
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
import QueueCardBadge from '$lib/components/QueueCardBadge.svelte';
|
import QueueCardBadge from '$lib/components/QueueCardBadge.svelte';
|
||||||
import QueueCardButton from '$lib/components/QueueCardButton.svelte';
|
import QueueCardButton from '$lib/components/QueueCardButton.svelte';
|
||||||
import Badge from '$lib/elements/Badge.svelte';
|
import Badge from '$lib/elements/Badge.svelte';
|
||||||
import { asQueueItem, getQueueDetailUrl } from '$lib/services/queue.service';
|
import { Route } from '$lib/route';
|
||||||
|
import { asQueueItem } from '$lib/services/queue.service';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { QueueCommand, type QueueCommandDto, type QueueResponseDto } from '@immich/sdk';
|
import { QueueCommand, type QueueCommandDto, type QueueResponseDto } from '@immich/sdk';
|
||||||
import { Icon, IconButton, Link } from '@immich/ui';
|
import { Icon, IconButton, Link } from '@immich/ui';
|
||||||
|
|
@ -50,7 +51,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col gap-2 p-5 sm:p-7 md:p-9">
|
<div class="flex flex-col gap-2 p-5 sm:p-7 md:p-9">
|
||||||
<div class="flex items-center gap-2 text-xl font-semibold text-primary">
|
<div class="flex items-center gap-2 text-xl font-semibold text-primary">
|
||||||
<Link class="flex items-center gap-2 hover:underline" href={getQueueDetailUrl(queue)} underline={false}>
|
<Link class="flex items-center gap-2 hover:underline" href={Route.viewQueue(queue)} underline={false}>
|
||||||
<Icon {icon} size="1.25em" class="hidden shrink-0 sm:block" />
|
<Icon {icon} size="1.25em" class="hidden shrink-0 sm:block" />
|
||||||
<span class="uppercase">{title}</span>
|
<span class="uppercase">{title}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -60,7 +61,7 @@
|
||||||
aria-label={$t('view_details')}
|
aria-label={$t('view_details')}
|
||||||
size="small"
|
size="small"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
href={getQueueDetailUrl(queue)}
|
href={Route.viewQueue(queue)}
|
||||||
/>
|
/>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
{#if statistics.failed > 0}
|
{#if statistics.failed > 0}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AppRoute, OpenSettingQueryParameterValue, QueryParameter } from '$lib/constants';
|
import { OpenQueryParam } from '$lib/constants';
|
||||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -9,10 +10,7 @@
|
||||||
values={{ template: $t('admin.storage_template_settings') }}
|
values={{ template: $t('admin.storage_template_settings') }}
|
||||||
>
|
>
|
||||||
{#snippet children({ message })}
|
{#snippet children({ message })}
|
||||||
<a
|
<a href={Route.systemSettings({ isOpen: OpenQueryParam.STORAGE_TEMPLATE })} class="text-primary">
|
||||||
href="{AppRoute.ADMIN_SETTINGS}?{QueryParameter.IS_OPEN}={OpenSettingQueryParameterValue.STORAGE_TEMPLATE}"
|
|
||||||
class="text-primary"
|
|
||||||
>
|
|
||||||
{message}
|
{message}
|
||||||
</a>
|
</a>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import SupportedDatetimePanel from '$lib/components/admin-settings/SupportedDatetimePanel.svelte';
|
import SupportedDatetimePanel from '$lib/components/admin-settings/SupportedDatetimePanel.svelte';
|
||||||
import SupportedVariablesPanel from '$lib/components/admin-settings/SupportedVariablesPanel.svelte';
|
import SupportedVariablesPanel from '$lib/components/admin-settings/SupportedVariablesPanel.svelte';
|
||||||
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
|
||||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||||
import { AppRoute, SettingInputFieldType } from '$lib/constants';
|
import { SettingInputFieldType } from '$lib/constants';
|
||||||
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
import FormatMessage from '$lib/elements/FormatMessage.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { handleSystemConfigSave } from '$lib/services/system-config.service';
|
import { handleSystemConfigSave } from '$lib/services/system-config.service';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { getStorageTemplateOptions, type SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
|
import { getStorageTemplateOptions, type SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
|
||||||
|
|
@ -257,9 +257,7 @@
|
||||||
values={{ job: $t('admin.storage_template_migration_job') }}
|
values={{ job: $t('admin.storage_template_migration_job') }}
|
||||||
>
|
>
|
||||||
{#snippet children({ message })}
|
{#snippet children({ message })}
|
||||||
<a href={resolve(AppRoute.ADMIN_QUEUES)} class="text-primary">
|
<a href={Route.queues()} class="text-primary">{message}</a>
|
||||||
{message}
|
|
||||||
</a>
|
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FormatMessage>
|
</FormatMessage>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import AlbumCard from '$lib/components/album-page/album-card.svelte';
|
import AlbumCard from '$lib/components/album-page/album-card.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { albumViewSettings } from '$lib/stores/preferences.store';
|
import { albumViewSettings } from '$lib/stores/preferences.store';
|
||||||
import { type AlbumGroup, isAlbumGroupCollapsed, toggleAlbumGroupCollapsing } from '$lib/utils/album-utils';
|
import { type AlbumGroup, isAlbumGroupCollapsed, toggleAlbumGroupCollapsing } from '$lib/utils/album-utils';
|
||||||
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
|
|
@ -65,7 +64,7 @@
|
||||||
<div class="grid grid-auto-fill-56 gap-y-4" transition:slide={{ duration: 300 }}>
|
<div class="grid grid-auto-fill-56 gap-y-4" transition:slide={{ duration: 300 }}>
|
||||||
{#each albums as album, index (album.id)}
|
{#each albums as album, index (album.id)}
|
||||||
<a
|
<a
|
||||||
href={resolve(`${AppRoute.ALBUMS}/${album.id}`)}
|
href={Route.viewAlbum(album)}
|
||||||
animate:flip={{ duration: 400 }}
|
animate:flip={{ duration: 400 }}
|
||||||
oncontextmenu={(event) => oncontextmenu(event, album)}
|
oncontextmenu={(event) => oncontextmenu(event, album)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { resolve } from '$app/paths';
|
import { dateFormats } from '$lib/constants';
|
||||||
import { AppRoute, dateFormats } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
<tr
|
<tr
|
||||||
class="flex w-full place-items-center border-3 border-transparent p-2 text-center even:bg-subtle/20 odd:bg-subtle/80 hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:px-5 md:py-2"
|
class="flex w-full place-items-center border-3 border-transparent p-2 text-center even:bg-subtle/20 odd:bg-subtle/80 hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:px-5 md:py-2"
|
||||||
onclick={() => goto(resolve(`${AppRoute.ALBUMS}/${album.id}`))}
|
onclick={() => goto(Route.viewAlbum(album))}
|
||||||
{oncontextmenu}
|
{oncontextmenu}
|
||||||
>
|
>
|
||||||
<td class="text-md text-ellipsis text-start w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%] items-center">
|
<td class="text-md text-ellipsis text-start w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%] items-center">
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import { shortcut } from '$lib/actions/shortcut';
|
import { shortcut } from '$lib/actions/shortcut';
|
||||||
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 MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import { AppRoute, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getAssetType } from '$lib/utils/asset-utils';
|
import { getAssetType } from '$lib/utils/asset-utils';
|
||||||
|
|
@ -139,10 +139,7 @@
|
||||||
|
|
||||||
<div class="w-full leading-4 overflow-hidden self-center wrap-break-word text-sm">{reaction.comment}</div>
|
<div class="w-full leading-4 overflow-hidden self-center wrap-break-word text-sm">{reaction.comment}</div>
|
||||||
{#if assetId === undefined && reaction.assetId}
|
{#if assetId === undefined && reaction.assetId}
|
||||||
<a
|
<a class="aspect-square w-19 h-19" href={Route.viewAlbumAsset({ albumId, assetId: reaction.assetId })}>
|
||||||
class="aspect-square w-19 h-19"
|
|
||||||
href={resolve(`${AppRoute.ALBUMS}/${albumId}/photos/${reaction.assetId}`)}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
class="rounded-lg w-19 h-19 object-cover"
|
class="rounded-lg w-19 h-19 object-cover"
|
||||||
src={getAssetThumbnailUrl(reaction.assetId)}
|
src={getAssetThumbnailUrl(reaction.assetId)}
|
||||||
|
|
@ -194,7 +191,7 @@
|
||||||
{#if assetId === undefined && reaction.assetId}
|
{#if assetId === undefined && reaction.assetId}
|
||||||
<a
|
<a
|
||||||
class="aspect-square w-19 h-19"
|
class="aspect-square w-19 h-19"
|
||||||
href={resolve(`${AppRoute.ALBUMS}/${albumId}/photos/${reaction.assetId}`)}
|
href={Route.viewAlbumAsset({ albumId, assetId: reaction.assetId })}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="rounded-lg w-19 h-19 object-cover"
|
class="rounded-lg w-19 h-19 object-cover"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import ActionButton from '$lib/components/ActionButton.svelte';
|
import ActionButton from '$lib/components/ActionButton.svelte';
|
||||||
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
||||||
import type { OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
|
import type { OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
|
||||||
|
|
@ -20,8 +19,8 @@
|
||||||
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
|
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.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 MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.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 { getGlobalActions } from '$lib/services/app.service';
|
import { getGlobalActions } from '$lib/services/app.service';
|
||||||
import { getAssetActions, handleReplaceAsset } from '$lib/services/asset.service';
|
import { getAssetActions, handleReplaceAsset } from '$lib/services/asset.service';
|
||||||
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
|
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
|
||||||
|
|
@ -250,7 +249,7 @@
|
||||||
{#if !asset.isArchived && !asset.isTrashed}
|
{#if !asset.isArchived && !asset.isTrashed}
|
||||||
<MenuOption
|
<MenuOption
|
||||||
icon={mdiImageSearch}
|
icon={mdiImageSearch}
|
||||||
onClick={() => goto(resolve(`${AppRoute.PHOTOS}?at=${stack?.primaryAssetId ?? asset.id}`))}
|
onClick={() => goto(Route.photos({ at: stack?.primaryAssetId ?? asset.id }))}
|
||||||
text={$t('view_in_timeline')}
|
text={$t('view_in_timeline')}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -258,8 +257,7 @@
|
||||||
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
|
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
|
||||||
<MenuOption
|
<MenuOption
|
||||||
icon={mdiCompare}
|
icon={mdiCompare}
|
||||||
onClick={() =>
|
onClick={() => goto(Route.search({ queryAssetId: stack?.primaryAssetId ?? asset.id }))}
|
||||||
goto(resolve(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`))}
|
|
||||||
text={$t('view_similar_photos')}
|
text={$t('view_similar_photos')}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@
|
||||||
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
||||||
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
|
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
|
||||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||||
import { AppRoute, AssetAction, ProjectionType } from '$lib/constants';
|
import { AssetAction, ProjectionType } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
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 { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte';
|
import { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte';
|
||||||
import { preloadManager } from '$lib/managers/PreloadManager.svelte';
|
import { preloadManager } from '$lib/managers/PreloadManager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { ocrManager } from '$lib/stores/ocr.svelte';
|
import { ocrManager } from '$lib/stores/ocr.svelte';
|
||||||
import { alwaysLoadOriginalVideo } from '$lib/stores/preferences.store';
|
import { alwaysLoadOriginalVideo } from '$lib/stores/preferences.store';
|
||||||
|
|
@ -395,7 +396,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((promise) => setTimeout(promise, 500));
|
await new Promise((promise) => setTimeout(promise, 500));
|
||||||
await goto(`${AppRoute.PHOTOS}/${newAssetId}`);
|
await goto(Route.viewAsset({ id: newAssetId }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAssetUpdate = (update: AssetResponseDto) => {
|
const onAssetUpdate = (update: AssetResponseDto) => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
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';
|
||||||
import AssetChangeDateModal from '$lib/modals/AssetChangeDateModal.svelte';
|
import AssetChangeDateModal from '$lib/modals/AssetChangeDateModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
|
@ -17,7 +18,6 @@
|
||||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { delay, getDimensions } from '$lib/utils/asset-utils';
|
import { delay, getDimensions } from '$lib/utils/asset-utils';
|
||||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
|
||||||
import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
|
import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { getParentPath } from '$lib/utils/tree-utils';
|
import { getParentPath } from '$lib/utils/tree-utils';
|
||||||
import { AssetMediaSize, getAssetInfo, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, getAssetInfo, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -207,7 +207,7 @@
|
||||||
class="w-22"
|
class="w-22"
|
||||||
href={resolve(
|
href={resolve(
|
||||||
`${AppRoute.PEOPLE}/${person.id}?${QueryParameter.PREVIOUS_ROUTE}=${
|
`${AppRoute.PEOPLE}/${person.id}?${QueryParameter.PREVIOUS_ROUTE}=${
|
||||||
currentAlbum?.id ? `${AppRoute.ALBUMS}/${currentAlbum?.id}` : AppRoute.PHOTOS
|
currentAlbum?.id ? Route.viewAlbum(currentAlbum) : Route.photos()
|
||||||
}`,
|
}`,
|
||||||
)}
|
)}
|
||||||
onfocus={() => ($boundingBoxesArray = people[index].faces)}
|
onfocus={() => ($boundingBoxesArray = people[index].faces)}
|
||||||
|
|
@ -385,12 +385,10 @@
|
||||||
{#if asset.exifInfo?.make || asset.exifInfo?.model}
|
{#if asset.exifInfo?.make || asset.exifInfo?.model}
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
href={resolve(
|
href={Route.search({
|
||||||
`${AppRoute.SEARCH}?${getMetadataSearchQuery({
|
make: asset.exifInfo?.make ?? undefined,
|
||||||
...(asset.exifInfo?.make ? { make: asset.exifInfo.make } : {}),
|
model: asset.exifInfo?.model ?? undefined,
|
||||||
...(asset.exifInfo?.model ? { model: asset.exifInfo.model } : {}),
|
})}
|
||||||
})}`,
|
|
||||||
)}
|
|
||||||
title="{$t('search_for')} {asset.exifInfo.make || ''} {asset.exifInfo.model || ''}"
|
title="{$t('search_for')} {asset.exifInfo.make || ''} {asset.exifInfo.model || ''}"
|
||||||
class="hover:text-primary"
|
class="hover:text-primary"
|
||||||
>
|
>
|
||||||
|
|
@ -421,7 +419,7 @@
|
||||||
{#if asset.exifInfo?.lensModel}
|
{#if asset.exifInfo?.lensModel}
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
href={resolve(`${AppRoute.SEARCH}?${getMetadataSearchQuery({ lensModel: asset.exifInfo.lensModel })}`)}
|
href={Route.search({ lensModel: asset.exifInfo.lensModel })}
|
||||||
title="{$t('search_for')} {asset.exifInfo.lensModel}"
|
title="{$t('search_for')} {asset.exifInfo.lensModel}"
|
||||||
class="hover:text-primary line-clamp-1"
|
class="hover:text-primary line-clamp-1"
|
||||||
>
|
>
|
||||||
|
|
@ -515,7 +513,7 @@
|
||||||
<section class="px-6 py-6 dark:text-immich-dark-fg">
|
<section class="px-6 py-6 dark:text-immich-dark-fg">
|
||||||
<p class="uppercase pb-4 text-sm">{$t('appears_in')}</p>
|
<p class="uppercase pb-4 text-sm">{$t('appears_in')}</p>
|
||||||
{#each albums as album (album.id)}
|
{#each albums as album (album.id)}
|
||||||
<a href={resolve(`${AppRoute.ALBUMS}/${album.id}`)}>
|
<a href={Route.viewAlbum(album)}>
|
||||||
<div class="flex gap-4 pt-2 hover:cursor-pointer items-center">
|
<div class="flex gap-4 pt-2 hover:cursor-pointer items-center">
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import BreadcrumbActionPage from '$lib/components/BreadcrumbActionPage.svelte';
|
import BreadcrumbActionPage from '$lib/components/BreadcrumbActionPage.svelte';
|
||||||
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
||||||
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 { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
||||||
import type { HeaderButtonActionItem } from '$lib/types';
|
import type { HeaderButtonActionItem } from '$lib/types';
|
||||||
import { AppShell, AppShellHeader, AppShellSidebar, MenuItemType, NavbarItem, type BreadcrumbItem } from '@immich/ui';
|
import { AppShell, AppShellHeader, AppShellSidebar, MenuItemType, NavbarItem, type BreadcrumbItem } from '@immich/ui';
|
||||||
|
|
@ -28,11 +28,11 @@
|
||||||
class="border-none shadow-none h-full flex flex-col justify-between gap-2"
|
class="border-none shadow-none h-full flex flex-col justify-between gap-2"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col pt-8 pe-4 gap-1">
|
<div class="flex flex-col pt-8 pe-4 gap-1">
|
||||||
<NavbarItem title={$t('users')} href={AppRoute.ADMIN_USERS} icon={mdiAccountMultipleOutline} />
|
<NavbarItem title={$t('users')} href={Route.users()} icon={mdiAccountMultipleOutline} />
|
||||||
<NavbarItem title={$t('external_libraries')} href={AppRoute.ADMIN_LIBRARIES} icon={mdiBookshelf} />
|
<NavbarItem title={$t('external_libraries')} href={Route.libraries()} icon={mdiBookshelf} />
|
||||||
<NavbarItem title={$t('admin.queues')} href={AppRoute.ADMIN_QUEUES} icon={mdiTrayFull} />
|
<NavbarItem title={$t('admin.queues')} href={Route.queues()} icon={mdiTrayFull} />
|
||||||
<NavbarItem title={$t('settings')} href={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
|
<NavbarItem title={$t('settings')} href={Route.systemSettings()} icon={mdiCog} />
|
||||||
<NavbarItem title={$t('server_stats')} href={AppRoute.ADMIN_STATS} icon={mdiServer} />
|
<NavbarItem title={$t('server_stats')} href={Route.systemStatistics()} icon={mdiServer} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-2 me-4">
|
<div class="mb-2 me-4">
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@
|
||||||
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 { QueryParameter } from '$lib/constants';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset, 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 { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte';
|
import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte';
|
||||||
|
|
@ -111,7 +112,7 @@
|
||||||
const handlePreviousAsset = () => handleNavigate(current?.previous?.asset);
|
const handlePreviousAsset = () => handleNavigate(current?.previous?.asset);
|
||||||
const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
|
const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
|
||||||
const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]);
|
const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]);
|
||||||
const handleEscape = async () => goto(AppRoute.PHOTOS);
|
const handleEscape = async () => goto(Route.photos());
|
||||||
const handleSelectAll = () =>
|
const handleSelectAll = () =>
|
||||||
assetInteraction.selectAssets(current?.memory.assets.map((a) => toTimelineAsset(a)) || []);
|
assetInteraction.selectAssets(current?.memory.assets.map((a) => toTimelineAsset(a)) || []);
|
||||||
|
|
||||||
|
|
@ -238,7 +239,7 @@
|
||||||
|
|
||||||
const init = (target: Page | NavigationTarget | null) => {
|
const init = (target: Page | NavigationTarget | null) => {
|
||||||
if (memoryStore.memories.length === 0) {
|
if (memoryStore.memories.length === 0) {
|
||||||
return handlePromiseError(goto(AppRoute.PHOTOS));
|
return handlePromiseError(goto(Route.photos()));
|
||||||
}
|
}
|
||||||
|
|
||||||
current = loadFromParams(target);
|
current = loadFromParams(target);
|
||||||
|
|
@ -362,7 +363,7 @@
|
||||||
use:resizeObserver={({ height, width }) => ((viewport.height = height), (viewport.width = width))}
|
use:resizeObserver={({ height, width }) => ((viewport.height = height), (viewport.width = width))}
|
||||||
>
|
>
|
||||||
{#if current}
|
{#if current}
|
||||||
<ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} forceDark multiRow>
|
<ControlAppBar onClose={() => goto(Route.photos())} forceDark multiRow>
|
||||||
{#snippet leading()}
|
{#snippet leading()}
|
||||||
{#if current}
|
{#if current}
|
||||||
<p class="text-lg">
|
<p class="text-lg">
|
||||||
|
|
@ -532,7 +533,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<IconButton
|
<IconButton
|
||||||
href="{AppRoute.PHOTOS}?at={current.asset.id}"
|
href={Route.photos({ at: current.asset.id })}
|
||||||
icon={mdiImageSearch}
|
icon={mdiImageSearch}
|
||||||
aria-label={$t('view_in_timeline')}
|
aria-label={$t('view_in_timeline')}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { placesViewSettings } from '$lib/stores/preferences.store';
|
import { placesViewSettings } from '$lib/stores/preferences.store';
|
||||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
|
||||||
import { type PlacesGroup, isPlacesGroupCollapsed, togglePlacesGroupCollapsing } from '$lib/utils/places-utils';
|
import { type PlacesGroup, isPlacesGroupCollapsed, togglePlacesGroupCollapsing } from '$lib/utils/places-utils';
|
||||||
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon } from '@immich/ui';
|
||||||
|
|
@ -41,7 +40,7 @@
|
||||||
<div class="flex flex-row flex-wrap gap-4">
|
<div class="flex flex-row flex-wrap gap-4">
|
||||||
{#each places as item (item.id)}
|
{#each places as item (item.id)}
|
||||||
{@const city = item.exifInfo?.city}
|
{@const city = item.exifInfo?.city}
|
||||||
<a class="relative" href="{AppRoute.SEARCH}?{getMetadataSearchQuery({ city })}" draggable="false">
|
<a class="relative" href={Route.search({ city })} draggable="false">
|
||||||
<div
|
<div
|
||||||
class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-39 justify-center overflow-hidden rounded-xl brightness-75 filter"
|
class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-39 justify-center overflow-hidden rounded-xl brightness-75 filter"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@
|
||||||
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
|
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
|
||||||
import RemoveFromSharedLink from '$lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte';
|
import RemoveFromSharedLink from '$lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.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 { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||||
|
|
@ -76,7 +77,7 @@
|
||||||
case AssetAction.ARCHIVE:
|
case AssetAction.ARCHIVE:
|
||||||
case AssetAction.DELETE:
|
case AssetAction.DELETE:
|
||||||
case AssetAction.TRASH: {
|
case AssetAction.TRASH: {
|
||||||
await goto(AppRoute.PHOTOS);
|
await goto(Route.photos());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +107,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
{:else}
|
{:else}
|
||||||
<ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} backIcon={mdiArrowLeft} showBackButton={false}>
|
<ControlAppBar onClose={() => goto(Route.photos())} backIcon={mdiArrowLeft} showBackButton={false}>
|
||||||
{#snippet leading()}
|
{#snippet leading()}
|
||||||
<a data-sveltekit-preload-data="hover" class="ms-4" href="/">
|
<a data-sveltekit-preload-data="hover" class="ms-4" href="/">
|
||||||
<Logo variant={mobileDevice.maxMd ? 'icon' : 'inline'} class="min-w-10" />
|
<Logo variant={mobileDevice.maxMd ? 'icon' : 'inline'} class="min-w-10" />
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
|
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
|
||||||
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
||||||
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
import Portal from '$lib/elements/Portal.svelte';
|
import Portal from '$lib/elements/Portal.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
||||||
import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte';
|
import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte';
|
||||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { showDeleteModal } from '$lib/stores/preferences.store';
|
import { showDeleteModal } from '$lib/stores/preferences.store';
|
||||||
|
|
@ -256,7 +257,7 @@
|
||||||
|
|
||||||
const shortcuts: ShortcutOptions[] = [
|
const shortcuts: ShortcutOptions[] = [
|
||||||
{ shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal },
|
{ shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal },
|
||||||
{ shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) },
|
{ shortcut: { key: '/' }, onShortcut: () => goto(Route.explore()) },
|
||||||
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets() },
|
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets() },
|
||||||
...(arrowNavigation
|
...(arrowNavigation
|
||||||
? [
|
? [
|
||||||
|
|
@ -306,7 +307,7 @@
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
if (assets.length === 0) {
|
if (assets.length === 0) {
|
||||||
return await goto(AppRoute.PHOTOS);
|
return await goto(Route.photos());
|
||||||
}
|
}
|
||||||
if (assetCursor.nextAsset) {
|
if (assetCursor.nextAsset) {
|
||||||
await navigateToAsset(assetCursor.nextAsset);
|
await navigateToAsset(assetCursor.nextAsset);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { focusTrap } from '$lib/actions/focus-trap';
|
import { focusTrap } from '$lib/actions/focus-trap';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import AvatarEditModal from '$lib/modals/AvatarEditModal.svelte';
|
import AvatarEditModal from '$lib/modals/AvatarEditModal.svelte';
|
||||||
import HelpAndFeedbackModal from '$lib/modals/HelpAndFeedbackModal.svelte';
|
import HelpAndFeedbackModal from '$lib/modals/HelpAndFeedbackModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { userInteraction } from '$lib/stores/user.svelte';
|
import { userInteraction } from '$lib/stores/user.svelte';
|
||||||
import { getAboutInfo, type ServerAboutResponseDto } from '@immich/sdk';
|
import { getAboutInfo, type ServerAboutResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<Button
|
<Button
|
||||||
href={AppRoute.USER_SETTINGS}
|
href={Route.userSettings()}
|
||||||
onclick={onClose}
|
onclick={onClose}
|
||||||
size="small"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
{#if $user.isAdmin}
|
{#if $user.isAdmin}
|
||||||
<Button
|
<Button
|
||||||
href={AppRoute.ADMIN_SETTINGS}
|
href={Route.systemSettings()}
|
||||||
onclick={onClose}
|
onclick={onClose}
|
||||||
shape="round"
|
shape="round"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
import ActionButton from '$lib/components/ActionButton.svelte';
|
import ActionButton from '$lib/components/ActionButton.svelte';
|
||||||
import NotificationPanel from '$lib/components/shared-components/navigation-bar/notification-panel.svelte';
|
import NotificationPanel from '$lib/components/shared-components/navigation-bar/notification-panel.svelte';
|
||||||
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import SkipLink from '$lib/elements/SkipLink.svelte';
|
import SkipLink from '$lib/elements/SkipLink.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';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { getGlobalActions } from '$lib/services/app.service';
|
import { getGlobalActions } from '$lib/services/app.service';
|
||||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||||
import { notificationManager } from '$lib/stores/notification-manager.svelte';
|
import { notificationManager } from '$lib/stores/notification-manager.svelte';
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
}}
|
}}
|
||||||
class="sidebar:hidden"
|
class="sidebar:hidden"
|
||||||
/>
|
/>
|
||||||
<a data-sveltekit-preload-data="hover" href={AppRoute.PHOTOS}>
|
<a data-sveltekit-preload-data="hover" href={Route.photos()}>
|
||||||
<Logo variant={mobileDevice.isFullSidebar ? 'inline' : 'icon'} class="max-md:h-12" />
|
<Logo variant={mobileDevice.isFullSidebar ? 'inline' : 'icon'} class="max-md:h-12" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="medium"
|
size="medium"
|
||||||
icon={mdiMagnify}
|
icon={mdiMagnify}
|
||||||
href={AppRoute.SEARCH}
|
href={Route.search()}
|
||||||
id="search-button"
|
id="search-button"
|
||||||
class="sm:hidden"
|
class="sm:hidden"
|
||||||
aria-label={$t('go_to_search')}
|
aria-label={$t('go_to_search')}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { focusOutside } from '$lib/actions/focus-outside';
|
import { focusOutside } from '$lib/actions/focus-outside';
|
||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import SearchFilterModal from '$lib/modals/SearchFilterModal.svelte';
|
import SearchFilterModal from '$lib/modals/SearchFilterModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { searchStore } from '$lib/stores/search.svelte';
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { generateId } from '$lib/utils/generate-id';
|
import { generateId } from '$lib/utils/generate-id';
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
|
||||||
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
||||||
import { Button, IconButton, modalManager } from '@immich/ui';
|
import { Button, IconButton, modalManager } from '@immich/ui';
|
||||||
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
|
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
|
||||||
|
|
@ -42,11 +41,9 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSearch = async (payload: SmartSearchDto | MetadataSearchDto) => {
|
const handleSearch = async (payload: SmartSearchDto | MetadataSearchDto) => {
|
||||||
const params = getMetadataSearchQuery(payload);
|
|
||||||
|
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
searchStore.isSearchEnabled = false;
|
searchStore.isSearchEnabled = false;
|
||||||
await goto(`${AppRoute.SEARCH}?${params}`);
|
await goto(Route.search(payload));
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSearchTerm = (searchTerm: string) => {
|
const clearSearchTerm = (searchTerm: string) => {
|
||||||
|
|
@ -256,7 +253,7 @@
|
||||||
draggable="false"
|
draggable="false"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="select-text text-sm"
|
class="select-text text-sm"
|
||||||
action={AppRoute.SEARCH}
|
action={Route.search()}
|
||||||
onreset={() => (value = '')}
|
onreset={() => (value = '')}
|
||||||
{onsubmit}
|
{onsubmit}
|
||||||
onfocusin={onFocusIn}
|
onfocusin={onFocusIn}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { OpenQueryParam } from '$lib/constants';
|
||||||
import Portal from '$lib/elements/Portal.svelte';
|
import Portal from '$lib/elements/Portal.svelte';
|
||||||
import PurchaseModal from '$lib/modals/PurchaseModal.svelte';
|
import PurchaseModal from '$lib/modals/PurchaseModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { purchaseStore } from '$lib/stores/purchase.store';
|
import { purchaseStore } from '$lib/stores/purchase.store';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { getAccountAge } from '$lib/utils/auth';
|
import { getAccountAge } from '$lib/utils/auth';
|
||||||
|
|
@ -73,7 +74,7 @@
|
||||||
<div class="license-status ps-4 text-sm">
|
<div class="license-status ps-4 text-sm">
|
||||||
{#if $isPurchased && $preferences.purchase.showSupportBadge}
|
{#if $isPurchased && $preferences.purchase.showSupportBadge}
|
||||||
<button
|
<button
|
||||||
onclick={() => goto(`${AppRoute.USER_SETTINGS}?isOpen=user-purchase-settings`)}
|
onclick={() => goto(Route.userSettings({ isOpen: OpenQueryParam.PURCHASE_SETTINGS }))}
|
||||||
class="w-full mt-2"
|
class="w-full mt-2"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { userInteraction } from '$lib/stores/user.svelte';
|
import { userInteraction } from '$lib/stores/user.svelte';
|
||||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
|
@ -25,7 +26,7 @@
|
||||||
|
|
||||||
{#each albums as album (album.id)}
|
{#each albums as album (album.id)}
|
||||||
<a
|
<a
|
||||||
href={'/albums/' + album.id}
|
href={Route.viewAlbum(album)}
|
||||||
title={album.albumName}
|
title={album.albumName}
|
||||||
class="flex w-full place-items-center justify-between gap-4 rounded-e-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-subtle hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary ps-10 group-hover:sm:px-10 md:px-10"
|
class="flex w-full place-items-center justify-between gap-4 rounded-e-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-subtle hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary ps-10 group-hover:sm:px-10 md:px-10"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
|
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
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 { recentAlbumsDropdown } from '$lib/stores/preferences.store';
|
import { recentAlbumsDropdown } from '$lib/stores/preferences.store';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { NavbarGroup, NavbarItem } from '@immich/ui';
|
import { NavbarGroup, NavbarItem } from '@immich/ui';
|
||||||
|
|
@ -37,15 +38,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Sidebar ariaLabel={$t('primary')}>
|
<Sidebar ariaLabel={$t('primary')}>
|
||||||
<NavbarItem
|
<NavbarItem title={$t('photos')} href={Route.photos()} icon={mdiImageMultipleOutline} activeIcon={mdiImageMultiple} />
|
||||||
title={$t('photos')}
|
|
||||||
href={AppRoute.PHOTOS}
|
|
||||||
icon={mdiImageMultipleOutline}
|
|
||||||
activeIcon={mdiImageMultiple}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if featureFlagsManager.value.search}
|
{#if featureFlagsManager.value.search}
|
||||||
<NavbarItem title={$t('explore')} href={AppRoute.EXPLORE} icon={mdiMagnify} />
|
<NavbarItem title={$t('explore')} href={Route.explore()} icon={mdiMagnify} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if featureFlagsManager.value.map}
|
{#if featureFlagsManager.value.map}
|
||||||
|
|
@ -57,23 +53,23 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $preferences.sharedLinks.enabled && $preferences.sharedLinks.sidebarWeb}
|
{#if $preferences.sharedLinks.enabled && $preferences.sharedLinks.sidebarWeb}
|
||||||
<NavbarItem title={$t('shared_links')} href={AppRoute.SHARED_LINKS} icon={mdiLink} />
|
<NavbarItem title={$t('shared_links')} href={Route.sharedLinks()} icon={mdiLink} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<NavbarItem
|
<NavbarItem
|
||||||
title={$t('sharing')}
|
title={$t('sharing')}
|
||||||
href={AppRoute.SHARING}
|
href={Route.sharing()}
|
||||||
icon={mdiAccountMultipleOutline}
|
icon={mdiAccountMultipleOutline}
|
||||||
activeIcon={mdiAccountMultiple}
|
activeIcon={mdiAccountMultiple}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavbarGroup title={$t('library')} size="tiny" />
|
<NavbarGroup title={$t('library')} size="tiny" />
|
||||||
|
|
||||||
<NavbarItem title={$t('favorites')} href={AppRoute.FAVORITES} icon={mdiHeartOutline} activeIcon={mdiHeart} />
|
<NavbarItem title={$t('favorites')} href={Route.favorites()} icon={mdiHeartOutline} activeIcon={mdiHeart} />
|
||||||
|
|
||||||
<NavbarItem
|
<NavbarItem
|
||||||
title={$t('albums')}
|
title={$t('albums')}
|
||||||
href={AppRoute.ALBUMS}
|
href={Route.albums()}
|
||||||
icon={{ icon: mdiImageAlbum, flipped: true }}
|
icon={{ icon: mdiImageAlbum, flipped: true }}
|
||||||
bind:expanded={$recentAlbumsDropdown}
|
bind:expanded={$recentAlbumsDropdown}
|
||||||
>
|
>
|
||||||
|
|
@ -92,19 +88,19 @@
|
||||||
<NavbarItem title={$t('folders')} href={AppRoute.FOLDERS} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
<NavbarItem title={$t('folders')} href={AppRoute.FOLDERS} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<NavbarItem title={$t('utilities')} href={AppRoute.UTILITIES} icon={mdiToolboxOutline} activeIcon={mdiToolbox} />
|
<NavbarItem title={$t('utilities')} href={Route.utilities()} icon={mdiToolboxOutline} activeIcon={mdiToolbox} />
|
||||||
|
|
||||||
<NavbarItem
|
<NavbarItem
|
||||||
title={$t('archive')}
|
title={$t('archive')}
|
||||||
href={AppRoute.ARCHIVE}
|
href={Route.archive()}
|
||||||
icon={mdiArchiveArrowDownOutline}
|
icon={mdiArchiveArrowDownOutline}
|
||||||
activeIcon={mdiArchiveArrowDown}
|
activeIcon={mdiArchiveArrowDown}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavbarItem title={$t('locked_folder')} href={AppRoute.LOCKED} icon={mdiLockOutline} activeIcon={mdiLock} />
|
<NavbarItem title={$t('locked_folder')} href={Route.locked()} icon={mdiLockOutline} activeIcon={mdiLock} />
|
||||||
|
|
||||||
{#if featureFlagsManager.value.trash}
|
{#if featureFlagsManager.value.trash}
|
||||||
<NavbarItem title={$t('trash')} href={AppRoute.TRASH} icon={mdiTrashCanOutline} activeIcon={mdiTrashCan} />
|
<NavbarItem title={$t('trash')} href={Route.trash()} icon={mdiTrashCanOutline} activeIcon={mdiTrashCan} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<BottomInfo />
|
<BottomInfo />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
import type { UploadAsset } from '$lib/types';
|
import type { UploadAsset } from '$lib/types';
|
||||||
|
|
@ -34,10 +34,6 @@
|
||||||
uploadAssetsStore.removeItem(uploadAsset.id);
|
uploadAssetsStore.removeItem(uploadAsset.id);
|
||||||
await fileUploadHandler({ files: [uploadAsset.file], albumId: uploadAsset.albumId });
|
await fileUploadHandler({ files: [uploadAsset.file], albumId: uploadAsset.albumId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const asLink = (asset: UploadAsset) => {
|
|
||||||
return asset.isTrashed ? `${AppRoute.TRASH}/${asset.assetId}` : `${AppRoute.PHOTOS}/${uploadAsset.assetId}`;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -69,7 +65,9 @@
|
||||||
{#if uploadAsset.state === UploadState.DUPLICATED && uploadAsset.assetId}
|
{#if uploadAsset.state === UploadState.DUPLICATED && uploadAsset.assetId}
|
||||||
<div class="flex items-center justify-between gap-1">
|
<div class="flex items-center justify-between gap-1">
|
||||||
<a
|
<a
|
||||||
href={asLink(uploadAsset)}
|
href={uploadAsset.isTrashed
|
||||||
|
? Route.viewTrashedAsset({ id: uploadAsset.assetId })
|
||||||
|
: Route.viewAsset({ id: uploadAsset.assetId })}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class=""
|
class=""
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ActionButton from '$lib/components/ActionButton.svelte';
|
import ActionButton from '$lib/components/ActionButton.svelte';
|
||||||
import ShareCover from '$lib/components/sharedlinks-page/covers/share-cover.svelte';
|
import ShareCover from '$lib/components/sharedlinks-page/covers/share-cover.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { getSharedLinkActions } from '$lib/services/shared-link.service';
|
import { getSharedLinkActions } from '$lib/services/shared-link.service';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
|
import { SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
>
|
>
|
||||||
<svelte:element
|
<svelte:element
|
||||||
this={isExpired ? 'div' : 'a'}
|
this={isExpired ? 'div' : 'a'}
|
||||||
href={isExpired ? undefined : `${AppRoute.SHARE}/${sharedLink.key}`}
|
href={isExpired ? undefined : Route.viewSharedLink(sharedLink)}
|
||||||
class="flex gap-4 w-full py-4"
|
class="flex gap-4 w-full py-4"
|
||||||
>
|
>
|
||||||
<ShareCover class="transition-all duration-300 hover:shadow-lg" {sharedLink} />
|
<ShareCover class="transition-all duration-300 hover:shadow-lg" {sharedLink} />
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
setFocusToAsset as setFocusAssetInit,
|
setFocusToAsset as setFocusAssetInit,
|
||||||
setFocusTo as setFocusToInit,
|
setFocusTo as setFocusToInit,
|
||||||
} from '$lib/components/timeline/actions/focus-actions';
|
} from '$lib/components/timeline/actions/focus-actions';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
|
|
@ -13,6 +12,7 @@
|
||||||
import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte';
|
import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte';
|
||||||
import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte';
|
import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte';
|
||||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { showDeleteModal } from '$lib/stores/preferences.store';
|
import { showDeleteModal } from '$lib/stores/preferences.store';
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
|
|
||||||
const shortcuts: ShortcutOptions[] = [
|
const shortcuts: ShortcutOptions[] = [
|
||||||
{ shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal },
|
{ shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal },
|
||||||
{ shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) },
|
{ shortcut: { key: '/' }, onShortcut: () => goto(Route.explore()) },
|
||||||
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(timelineManager, assetInteraction) },
|
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(timelineManager, assetInteraction) },
|
||||||
{ shortcut: { key: 'ArrowRight' }, onShortcut: () => setFocusTo('earlier', 'asset') },
|
{ shortcut: { key: 'ArrowRight' }, onShortcut: () => setFocusTo('earlier', 'asset') },
|
||||||
{ shortcut: { key: 'ArrowLeft' }, onShortcut: () => setFocusTo('later', 'asset') },
|
{ shortcut: { key: 'ArrowLeft' }, onShortcut: () => setFocusTo('later', 'asset') },
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import NotificationsSettings from '$lib/components/user-settings-page/notifications-settings.svelte';
|
import NotificationsSettings from '$lib/components/user-settings-page/notifications-settings.svelte';
|
||||||
import UserPurchaseSettings from '$lib/components/user-settings-page/user-purchase-settings.svelte';
|
import UserPurchaseSettings from '$lib/components/user-settings-page/user-purchase-settings.svelte';
|
||||||
import UserUsageStatistic from '$lib/components/user-settings-page/user-usage-statistic.svelte';
|
import UserUsageStatistic from '$lib/components/user-settings-page/user-usage-statistic.svelte';
|
||||||
import { OpenSettingQueryParameterValue, QueryParameter } from '$lib/constants';
|
import { OpenQueryParam, QueryParameter } from '$lib/constants';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { oauth } from '$lib/utils';
|
import { oauth } from '$lib/utils';
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
let oauthOpen =
|
let oauthOpen =
|
||||||
oauth.isCallback(globalThis.location) ||
|
oauth.isCallback(globalThis.location) ||
|
||||||
$page.url.searchParams.get(QueryParameter.OPEN_SETTING) === OpenSettingQueryParameterValue.OAUTH;
|
$page.url.searchParams.get(QueryParameter.OPEN_SETTING) === OpenQueryParam.OAUTH;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
|
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
|
|
||||||
<SettingAccordion
|
<SettingAccordion
|
||||||
icon={mdiBellOutline}
|
icon={mdiBellOutline}
|
||||||
key="notifications"
|
key={OpenQueryParam.NOTIFICATIONS}
|
||||||
title={$t('notifications')}
|
title={$t('notifications')}
|
||||||
subtitle={$t('notifications_setting_description')}
|
subtitle={$t('notifications_setting_description')}
|
||||||
>
|
>
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
{#if featureFlagsManager.value.oauth}
|
{#if featureFlagsManager.value.oauth}
|
||||||
<SettingAccordion
|
<SettingAccordion
|
||||||
icon={mdiTwoFactorAuthentication}
|
icon={mdiTwoFactorAuthentication}
|
||||||
key="oauth"
|
key={OpenQueryParam.OAUTH}
|
||||||
title={$t('oauth')}
|
title={$t('oauth')}
|
||||||
subtitle={$t('manage_your_oauth_connection')}
|
subtitle={$t('manage_your_oauth_connection')}
|
||||||
isOpen={oauthOpen || undefined}
|
isOpen={oauthOpen || undefined}
|
||||||
|
|
@ -154,7 +154,7 @@
|
||||||
|
|
||||||
<SettingAccordion
|
<SettingAccordion
|
||||||
icon={mdiKeyOutline}
|
icon={mdiKeyOutline}
|
||||||
key="user-purchase-settings"
|
key={OpenQueryParam.PURCHASE_SETTINGS}
|
||||||
title={$t('user_purchase_settings')}
|
title={$t('user_purchase_settings')}
|
||||||
subtitle={$t('user_purchase_settings_description')}
|
subtitle={$t('user_purchase_settings_description')}
|
||||||
autoScrollTo={true}
|
autoScrollTo={true}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import AppDownloadModal from '$lib/modals/AppDownloadModal.svelte';
|
import AppDownloadModal from '$lib/modals/AppDownloadModal.svelte';
|
||||||
import ObtainiumConfigModal from '$lib/modals/ObtainiumConfigModal.svelte';
|
import ObtainiumConfigModal from '$lib/modals/ObtainiumConfigModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { Icon, modalManager } from '@immich/ui';
|
import { Icon, modalManager } from '@immich/ui';
|
||||||
import {
|
import {
|
||||||
mdiCellphoneArrowDownVariant,
|
mdiCellphoneArrowDownVariant,
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{ href: AppRoute.DUPLICATES, icon: mdiContentDuplicate, label: $t('review_duplicates') },
|
{ href: Route.duplicatesUtility(), icon: mdiContentDuplicate, label: $t('review_duplicates') },
|
||||||
{ href: AppRoute.LARGE_FILES, icon: mdiImageSizeSelectLarge, label: $t('review_large_files') },
|
{ href: Route.largeFileUtility(), icon: mdiImageSizeSelectLarge, label: $t('review_large_files') },
|
||||||
{ href: AppRoute.GEOLOCATION, icon: mdiCrosshairsGps, label: $t('manage_geolocation') },
|
{ href: Route.geolocationUtility(), icon: mdiCrosshairsGps, label: $t('manage_geolocation') },
|
||||||
{ href: AppRoute.WORKFLOWS, icon: mdiStateMachine, label: $t('workflows') },
|
{ href: Route.workflows(), icon: mdiStateMachine, label: $t('workflows') },
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,52 +20,17 @@ export enum AssetAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
ADMIN_USERS = '/admin/users',
|
|
||||||
ADMIN_USERS_NEW = '/admin/users/new',
|
|
||||||
ADMIN_LIBRARIES = '/admin/library-management',
|
|
||||||
ADMIN_LIBRARIES_NEW = '/admin/library-management/new',
|
|
||||||
ADMIN_SETTINGS = '/admin/system-settings',
|
|
||||||
ADMIN_STATS = '/admin/server-status',
|
|
||||||
ADMIN_QUEUES = '/admin/queues',
|
|
||||||
ADMIN_REPAIR = '/admin/repair',
|
|
||||||
|
|
||||||
ALBUMS = '/albums',
|
|
||||||
ARCHIVE = '/archive',
|
|
||||||
FAVORITES = '/favorites',
|
|
||||||
PEOPLE = '/people',
|
PEOPLE = '/people',
|
||||||
PLACES = '/places',
|
|
||||||
PHOTOS = '/photos',
|
|
||||||
EXPLORE = '/explore',
|
|
||||||
SHARE = '/share',
|
|
||||||
SHARING = '/sharing',
|
|
||||||
SHARED_LINKS = '/shared-links',
|
|
||||||
SEARCH = '/search',
|
SEARCH = '/search',
|
||||||
MAP = '/map',
|
MAP = '/map',
|
||||||
USER_SETTINGS = '/user-settings',
|
|
||||||
MEMORY = '/memory',
|
|
||||||
TRASH = '/trash',
|
|
||||||
PARTNERS = '/partners',
|
|
||||||
BUY = '/buy',
|
BUY = '/buy',
|
||||||
|
|
||||||
AUTH_LOGIN = '/auth/login',
|
|
||||||
AUTH_REGISTER = '/auth/register',
|
|
||||||
AUTH_CHANGE_PASSWORD = '/auth/change-password',
|
|
||||||
AUTH_ONBOARDING = '/auth/onboarding',
|
|
||||||
AUTH_PIN_PROMPT = '/auth/pin-prompt',
|
|
||||||
|
|
||||||
UTILITIES = '/utilities',
|
|
||||||
DUPLICATES = '/utilities/duplicates',
|
|
||||||
LARGE_FILES = '/utilities/large-files',
|
|
||||||
GEOLOCATION = '/utilities/geolocation',
|
|
||||||
WORKFLOWS = '/utilities/workflows',
|
|
||||||
|
|
||||||
FOLDERS = '/folders',
|
FOLDERS = '/folders',
|
||||||
TAGS = '/tags',
|
TAGS = '/tags',
|
||||||
LOCKED = '/locked',
|
|
||||||
|
|
||||||
MAINTENANCE = '/maintenance',
|
MAINTENANCE = '/maintenance',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SharedLinkTab = 'all' | 'album' | 'individual';
|
||||||
|
|
||||||
export enum ProjectionType {
|
export enum ProjectionType {
|
||||||
EQUIRECTANGULAR = 'EQUIRECTANGULAR',
|
EQUIRECTANGULAR = 'EQUIRECTANGULAR',
|
||||||
CUBEMAP = 'CUBEMAP',
|
CUBEMAP = 'CUBEMAP',
|
||||||
|
|
@ -94,7 +59,6 @@ export enum QueryParameter {
|
||||||
ACTION = 'action',
|
ACTION = 'action',
|
||||||
ID = 'id',
|
ID = 'id',
|
||||||
IS_OPEN = 'isOpen',
|
IS_OPEN = 'isOpen',
|
||||||
ONBOARDING_STEP = 'step',
|
|
||||||
OPEN_SETTING = 'openSetting',
|
OPEN_SETTING = 'openSetting',
|
||||||
PREVIOUS_ROUTE = 'previousRoute',
|
PREVIOUS_ROUTE = 'previousRoute',
|
||||||
QUERY = 'query',
|
QUERY = 'query',
|
||||||
|
|
@ -109,10 +73,13 @@ export enum SessionStorageKey {
|
||||||
SCROLL_POSITION = 'scrollPosition',
|
SCROLL_POSITION = 'scrollPosition',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OpenSettingQueryParameterValue {
|
// TODO split into user settings vs system settings
|
||||||
|
export enum OpenQueryParam {
|
||||||
OAUTH = 'oauth',
|
OAUTH = 'oauth',
|
||||||
JOB = 'job',
|
JOB = 'job',
|
||||||
STORAGE_TEMPLATE = 'storage-template',
|
STORAGE_TEMPLATE = 'storage-template',
|
||||||
|
NOTIFICATIONS = 'notifications',
|
||||||
|
PURCHASE_SETTINGS = 'user-purchase-settings',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ActionQueryParameterValue {
|
export enum ActionQueryParameterValue {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { isSharedLinkRoute } from '$lib/utils/navigation';
|
import { isSharedLinkRoute } from '$lib/utils/navigation';
|
||||||
import { logout } from '@immich/sdk';
|
import { logout } from '@immich/sdk';
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ class AuthManager {
|
||||||
console.log('Error logging out:', error);
|
console.log('Error logging out:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri = redirectUri ?? AppRoute.AUTH_LOGIN;
|
redirectUri = redirectUri ?? Route.login();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (redirectUri.startsWith('/')) {
|
if (redirectUri.startsWith('/')) {
|
||||||
|
|
|
||||||
36
web/src/lib/route.spec.ts
Normal file
36
web/src/lib/route.spec.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { OpenQueryParam } from '$lib/constants';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
|
|
||||||
|
describe('Route', () => {
|
||||||
|
describe(Route.login.name, () => {
|
||||||
|
it('should encode continue', () => {
|
||||||
|
expect(Route.login({ continue: '/some/path?with=query', autoLaunch: 1 })).toBe(
|
||||||
|
'/auth/login?continue=%2Fsome%2Fpath%3Fwith%3Dquery&autoLaunch=1',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(Route.search.name, () => {
|
||||||
|
it('should work', () => {
|
||||||
|
expect(Route.search({})).toBe('/search');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work', () => {
|
||||||
|
expect(Route.search({ make: undefined, model: 'Immich' })).toBe('/search?query=%7B%22model%22%3A%22Immich%22%7D');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support query parameters', () => {
|
||||||
|
expect(Route.systemSettings({ isOpen: OpenQueryParam.OAUTH })).toBe('/admin/system-settings?isOpen=oauth');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(Route.systemSettings.name, () => {
|
||||||
|
it('should work', () => {
|
||||||
|
expect(Route.systemSettings()).toBe('/admin/system-settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support query parameters', () => {
|
||||||
|
expect(Route.systemSettings({ isOpen: OpenQueryParam.OAUTH })).toBe('/admin/system-settings?isOpen=oauth');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
105
web/src/lib/route.ts
Normal file
105
web/src/lib/route.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { OpenQueryParam, type SharedLinkTab } from '$lib/constants';
|
||||||
|
import { QueueName, type MetadataSearchDto, type SmartSearchDto } from '@immich/sdk';
|
||||||
|
import { omitBy } from 'lodash-es';
|
||||||
|
|
||||||
|
const asQueueSlug = (name: QueueName) => {
|
||||||
|
return name.replaceAll(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fromQueueSlug = (slug: string): QueueName | undefined => {
|
||||||
|
const name = slug.replaceAll(/-([a-z])/g, (_, c) => c.toUpperCase());
|
||||||
|
if (Object.values(QueueName).includes(name as QueueName)) {
|
||||||
|
return name as QueueName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type QueryValue = number | string;
|
||||||
|
const asQueryString = (params?: Record<string, QueryValue | undefined>) => {
|
||||||
|
const items = Object.entries(params ?? {})
|
||||||
|
.filter((item): item is [string, QueryValue] => item[1] !== undefined)
|
||||||
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||||
|
|
||||||
|
return items.length === 0 ? '' : `?${items.join('&')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = {
|
||||||
|
// auth
|
||||||
|
login: (params?: { continue?: string; autoLaunch?: 0 | 1 }) => '/auth/login' + asQueryString(params),
|
||||||
|
register: () => '/auth/register',
|
||||||
|
changePassword: () => '/auth/change-password',
|
||||||
|
onboarding: (params?: { step?: string }) => '/auth/onboarding' + asQueryString(params),
|
||||||
|
pinPrompt: (params?: { continue?: string }) => '/auth/pin-prompt' + asQueryString({ continue: params?.continue }),
|
||||||
|
|
||||||
|
// albums
|
||||||
|
albums: () => '/albums',
|
||||||
|
viewAlbum: ({ id }: { id: string }) => `/albums/${id}`,
|
||||||
|
viewAlbumAsset: ({ albumId, assetId }: { albumId: string; assetId: string }) =>
|
||||||
|
`/albums/${albumId}/photos/${assetId}`,
|
||||||
|
|
||||||
|
// explore
|
||||||
|
explore: () => '/explore',
|
||||||
|
places: () => '/places',
|
||||||
|
|
||||||
|
// libraries
|
||||||
|
libraries: () => '/admin/library-management',
|
||||||
|
newLibrary: () => '/admin/library-management/new',
|
||||||
|
viewLibrary: ({ id }: { id: string }) => `/admin/library-management/${id}`,
|
||||||
|
editLibrary: ({ id }: { id: string }) => `/admin/library-management/${id}/edit`,
|
||||||
|
|
||||||
|
// memories
|
||||||
|
memories: (params?: { id?: string }) => '/memory' + asQueryString(params),
|
||||||
|
|
||||||
|
// partners
|
||||||
|
viewPartner: ({ id }: { id: string }) => `/partners/${id}`,
|
||||||
|
|
||||||
|
// photos
|
||||||
|
photos: (params?: { at?: string }) => '/photos' + asQueryString(params),
|
||||||
|
viewAsset: ({ id }: { id: string }) => `/photos/${id}`,
|
||||||
|
archive: () => '/archive',
|
||||||
|
favorites: () => '/favorites',
|
||||||
|
locked: () => '/locked',
|
||||||
|
trash: () => '/trash',
|
||||||
|
viewTrashedAsset: ({ id }: { id: string }) => `/trash/photos/${id}`,
|
||||||
|
|
||||||
|
// search
|
||||||
|
search: (dto?: MetadataSearchDto | SmartSearchDto) => {
|
||||||
|
const metadata = omitBy(dto ?? {}, (value) => value === undefined);
|
||||||
|
const query = Object.keys(metadata).length === 0 ? undefined : JSON.stringify(metadata);
|
||||||
|
return `/search` + asQueryString({ query });
|
||||||
|
},
|
||||||
|
|
||||||
|
// sharing
|
||||||
|
sharing: () => '/sharing',
|
||||||
|
|
||||||
|
// shared links
|
||||||
|
sharedLinks: (params?: { filter?: SharedLinkTab }) => '/shared-links' + asQueryString(params),
|
||||||
|
editSharedLink: ({ id }: { id: string }) => `/shared-links/${id}/edit`,
|
||||||
|
viewSharedLink: ({ slug, key }: { slug?: string | null; key: string }) => (slug ? `/s/${slug}` : `/share/${key}`),
|
||||||
|
|
||||||
|
// settings
|
||||||
|
userSettings: (params?: { isOpen?: OpenQueryParam }) => '/user-settings' + asQueryString(params),
|
||||||
|
|
||||||
|
// system
|
||||||
|
systemSettings: (params?: { isOpen?: OpenQueryParam }) => '/admin/system-settings' + asQueryString(params),
|
||||||
|
systemStatistics: () => '/admin/server-status',
|
||||||
|
|
||||||
|
// users
|
||||||
|
users: () => '/admin/users',
|
||||||
|
newUser: () => `/admin/users/new`,
|
||||||
|
viewUser: ({ id }: { id: string }) => `/admin/users/${id}`,
|
||||||
|
editUser: ({ id }: { id: string }) => `/admin/users/${id}/edit`,
|
||||||
|
|
||||||
|
// utilities
|
||||||
|
utilities: () => '/utilities',
|
||||||
|
duplicatesUtility: (params?: { index?: number }) => '/utilities/duplicates' + asQueryString(params),
|
||||||
|
largeFileUtility: () => '/utilities/large-files',
|
||||||
|
geolocationUtility: () => '/utilities/geolocation',
|
||||||
|
|
||||||
|
// workflows
|
||||||
|
workflows: () => '/utilities/workflows',
|
||||||
|
viewWorkflow: ({ id }: { id: string }) => `/utilities/workflows/${id}`,
|
||||||
|
|
||||||
|
// queues
|
||||||
|
queues: () => '/admin/queues',
|
||||||
|
viewQueue: ({ name }: { name: QueueName }) => `/admin/queues/${asQueueSlug(name)}`,
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import ToastAction from '$lib/components/ToastAction.svelte';
|
import ToastAction from '$lib/components/ToastAction.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import AlbumAddUsersModal from '$lib/modals/AlbumAddUsersModal.svelte';
|
import AlbumAddUsersModal from '$lib/modals/AlbumAddUsersModal.svelte';
|
||||||
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
|
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
|
||||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { downloadArchive } from '$lib/utils/asset-utils';
|
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
|
|
@ -161,9 +161,7 @@ export const handleUpdateAlbum = async ({ id }: { id: string }, dto: UpdateAlbum
|
||||||
button: {
|
button: {
|
||||||
text: $t('view_album'),
|
text: $t('view_album'),
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
onClick() {
|
onClick: () => goto(Route.viewAlbum({ id })),
|
||||||
return goto(`${AppRoute.ALBUMS}/${id}`);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte';
|
import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte';
|
||||||
import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte';
|
import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte';
|
||||||
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
|
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
|
||||||
import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte';
|
import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import {
|
import {
|
||||||
|
|
@ -37,7 +37,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp
|
||||||
title: $t('create_library'),
|
title: $t('create_library'),
|
||||||
type: $t('command'),
|
type: $t('command'),
|
||||||
icon: mdiPlusBoxOutline,
|
icon: mdiPlusBoxOutline,
|
||||||
onAction: () => goto(AppRoute.ADMIN_LIBRARIES_NEW),
|
onAction: () => goto(Route.newLibrary()),
|
||||||
shortcuts: { shift: true, key: 'n' },
|
shortcuts: { shift: true, key: 'n' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse
|
||||||
icon: mdiPencilOutline,
|
icon: mdiPencilOutline,
|
||||||
type: $t('command'),
|
type: $t('command'),
|
||||||
title: $t('edit'),
|
title: $t('edit'),
|
||||||
onAction: () => goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}/edit`),
|
onAction: () => goto(Route.editLibrary(library)),
|
||||||
shortcuts: { key: 'r' },
|
shortcuts: { key: 'r' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -148,10 +148,6 @@ const handleScanLibrary = async (library: LibraryResponseDto) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleViewLibrary = async (library: LibraryResponseDto) => {
|
|
||||||
await goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleCreateLibrary = async (dto: CreateLibraryDto) => {
|
export const handleCreateLibrary = async (dto: CreateLibraryDto) => {
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { OpenQueryParam } from '$lib/constants';
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import { queueManager } from '$lib/managers/queue-manager.svelte';
|
import { queueManager } from '$lib/managers/queue-manager.svelte';
|
||||||
import JobCreateModal from '$lib/modals/JobCreateModal.svelte';
|
import JobCreateModal from '$lib/modals/JobCreateModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import type { HeaderButtonActionItem } from '$lib/types';
|
import type { HeaderButtonActionItem } from '$lib/types';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
|
|
@ -73,7 +74,7 @@ export const getQueuesActions = ($t: MessageFormatter, queues: QueueResponseDto[
|
||||||
title: $t('admin.manage_concurrency'),
|
title: $t('admin.manage_concurrency'),
|
||||||
description: $t('admin.manage_concurrency_description'),
|
description: $t('admin.manage_concurrency_description'),
|
||||||
type: $t('page'),
|
type: $t('page'),
|
||||||
onAction: () => goto(`${AppRoute.ADMIN_SETTINGS}?isOpen=job`),
|
onAction: () => goto(Route.systemSettings({ isOpen: OpenQueryParam.JOB })),
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ResumePaused, ManageConcurrency, CreateJob };
|
return { ResumePaused, ManageConcurrency, CreateJob };
|
||||||
|
|
@ -250,22 +251,3 @@ export const asQueueItem = ($t: MessageFormatter, queue: { name: QueueName }): Q
|
||||||
|
|
||||||
return items[queue.name];
|
return items[queue.name];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const asQueueSlug = (name: QueueName) => {
|
|
||||||
return name.replaceAll(/[A-Z]/g, (m) => '-' + m.toLowerCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fromQueueSlug = (slug: string): QueueName | undefined => {
|
|
||||||
const name = slug.replaceAll(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
||||||
if (Object.values(QueueName).includes(name as QueueName)) {
|
|
||||||
return name as QueueName;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getQueueDetailUrl = (queue: QueueResponseDto) => {
|
|
||||||
return `${AppRoute.ADMIN_QUEUES}/${asQueueSlug(queue.name)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleViewQueue = (queue: QueueResponseDto) => {
|
|
||||||
return goto(getQueueDetailUrl(queue));
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
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 { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { copyToClipboard } from '$lib/utils';
|
import { copyToClipboard } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
|
|
@ -25,7 +25,7 @@ export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLin
|
||||||
const Edit: ActionItem = {
|
const Edit: ActionItem = {
|
||||||
title: $t('edit_link'),
|
title: $t('edit_link'),
|
||||||
icon: mdiPencilOutline,
|
icon: mdiPencilOutline,
|
||||||
onAction: () => goto(`${AppRoute.SHARED_LINKS}/${sharedLink.id}/edit`),
|
onAction: () => goto(Route.editSharedLink(sharedLink)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Delete: ActionItem = {
|
const Delete: ActionItem = {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
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 PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
|
import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
|
||||||
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
|
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
|
||||||
import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte';
|
import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { user as authUser } from '$lib/stores/user.store';
|
import { user as authUser } from '$lib/stores/user.store';
|
||||||
import type { HeaderButtonActionItem } from '$lib/types';
|
import type { HeaderButtonActionItem } from '$lib/types';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
|
@ -38,7 +38,7 @@ export const getUserAdminsActions = ($t: MessageFormatter) => {
|
||||||
title: $t('create_user'),
|
title: $t('create_user'),
|
||||||
type: $t('command'),
|
type: $t('command'),
|
||||||
icon: mdiPlusBoxOutline,
|
icon: mdiPlusBoxOutline,
|
||||||
onAction: () => goto(AppRoute.ADMIN_USERS_NEW),
|
onAction: () => goto(Route.newUser()),
|
||||||
shortcuts: { shift: true, key: 'n' },
|
shortcuts: { shift: true, key: 'n' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons
|
||||||
const Update: ActionItem = {
|
const Update: ActionItem = {
|
||||||
icon: mdiPencilOutline,
|
icon: mdiPencilOutline,
|
||||||
title: $t('edit'),
|
title: $t('edit'),
|
||||||
onAction: () => goto(`${AppRoute.ADMIN_USERS}/${user.id}/edit`),
|
onAction: () => goto(Route.editUser(user)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Delete: ActionItem = {
|
const Delete: ActionItem = {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import {
|
import {
|
||||||
|
|
@ -331,7 +331,7 @@ export const getWorkflowActions = ($t: MessageFormatter, workflow: WorkflowRespo
|
||||||
const Edit: ActionItem = {
|
const Edit: ActionItem = {
|
||||||
title: $t('edit'),
|
title: $t('edit'),
|
||||||
icon: mdiPencil,
|
icon: mdiPencil,
|
||||||
onAction: () => handleNavigateToWorkflow(workflow),
|
onAction: () => goto(Route.viewWorkflow(workflow)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Delete: ActionItem = {
|
const Delete: ActionItem = {
|
||||||
|
|
@ -370,7 +370,7 @@ export const handleCreateWorkflow = async (): Promise<WorkflowResponseDto | unde
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
|
await goto(Route.viewWorkflow(workflow));
|
||||||
return workflow;
|
return workflow;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_create'));
|
handleError(error, $t('errors.unable_to_create'));
|
||||||
|
|
@ -419,10 +419,6 @@ export const handleDeleteWorkflow = async (workflow: WorkflowResponseDto): Promi
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleNavigateToWorkflow = async (workflow: WorkflowResponseDto): Promise<void> => {
|
|
||||||
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchPickerMetadata = async (
|
export const fetchPickerMetadata = async (
|
||||||
value: string | string[] | undefined,
|
value: string | string[] | undefined,
|
||||||
subType: PickerSubType,
|
subType: PickerSubType,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import {
|
import {
|
||||||
AlbumFilter,
|
AlbumFilter,
|
||||||
AlbumGroupBy,
|
AlbumGroupBy,
|
||||||
|
|
@ -39,7 +39,7 @@ export const createAlbum = async (name?: string, assetIds?: string[]) => {
|
||||||
export const createAlbumAndRedirect = async (name?: string, assetIds?: string[]) => {
|
export const createAlbumAndRedirect = async (name?: string, assetIds?: string[]) => {
|
||||||
const newAlbum = await createAlbum(name, assetIds);
|
const newAlbum = await createAlbum(name, assetIds);
|
||||||
if (newAlbum) {
|
if (newAlbum) {
|
||||||
await goto(`${AppRoute.ALBUMS}/${newAlbum.id}`);
|
await goto(Route.viewAlbum(newAlbum));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import ToastAction from '$lib/components/ToastAction.svelte';
|
import ToastAction from '$lib/components/ToastAction.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||||
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 { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
|
@ -73,7 +73,7 @@ export const addAssetsToAlbum = async (albumId: string, assetIds: string[], show
|
||||||
text: $t('view_album'),
|
text: $t('view_album'),
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
onClick() {
|
onClick() {
|
||||||
return goto(`${AppRoute.ALBUMS}/${albumId}`);
|
return goto(Route.viewAlbum({ id: albumId }));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { purchaseStore } from '$lib/stores/purchase.store';
|
import { purchaseStore } from '$lib/stores/purchase.store';
|
||||||
import { preferences as preferences$, user as user$ } from '$lib/stores/user.store';
|
import { preferences as preferences$, user as user$ } from '$lib/stores/user.store';
|
||||||
import { userInteraction } from '$lib/stores/user.svelte';
|
import { userInteraction } from '$lib/stores/user.svelte';
|
||||||
|
|
@ -7,7 +8,6 @@ import { getAboutInfo, getMyPreferences, getMyUser, getStorage } from '@immich/s
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { AppRoute } from '../constants';
|
|
||||||
|
|
||||||
export interface AuthOptions {
|
export interface AuthOptions {
|
||||||
admin?: true;
|
admin?: true;
|
||||||
|
|
@ -62,11 +62,11 @@ export const authenticate = async (url: URL, options?: AuthOptions) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(307, `${AppRoute.AUTH_LOGIN}?continue=${encodeURIComponent(url.pathname + url.search)}`);
|
redirect(307, Route.login({ continue: url.pathname + url.search }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adminRoute && !user.isAdmin) {
|
if (adminRoute && !user.isAdmin) {
|
||||||
redirect(307, AppRoute.PHOTOS);
|
redirect(307, Route.photos());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { QueryParameter } from '$lib/constants';
|
|
||||||
import type { MetadataSearchDto } from '@immich/sdk';
|
|
||||||
|
|
||||||
export function getMetadataSearchQuery(metadata: MetadataSearchDto) {
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
[QueryParameter.QUERY]: JSON.stringify(metadata),
|
|
||||||
});
|
|
||||||
return searchParams.toString();
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import type { RouteId } from '$app/types';
|
import type { RouteId } from '$app/types';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte';
|
import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export type AssetGridRouteSearchParams = {
|
export type AssetGridRouteSearchParams = {
|
||||||
|
|
@ -33,7 +33,7 @@ function currentUrlWithoutAsset() {
|
||||||
// This contains special casing for the /photos/:assetId route, which hangs directly
|
// This contains special casing for the /photos/:assetId route, which hangs directly
|
||||||
// off / instead of a subpath, unlike every other asset-containing route.
|
// off / instead of a subpath, unlike every other asset-containing route.
|
||||||
return isPhotosRoute($page.route.id)
|
return isPhotosRoute($page.route.id)
|
||||||
? AppRoute.PHOTOS + $page.url.search
|
? Route.photos() + $page.url.search
|
||||||
: $page.url.pathname.replace(/(\/photos.*)$/, '') + $page.url.search;
|
: $page.url.pathname.replace(/(\/photos.*)$/, '') + $page.url.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ export function currentUrlReplaceAssetId(assetId: string) {
|
||||||
// this contains special casing for the /photos/:assetId photos route, which hangs directly
|
// this contains special casing for the /photos/:assetId photos route, which hangs directly
|
||||||
// off / instead of a subpath, unlike every other asset-containing route.
|
// off / instead of a subpath, unlike every other asset-containing route.
|
||||||
return isPhotosRoute($page.route.id)
|
return isPhotosRoute($page.route.id)
|
||||||
? `${AppRoute.PHOTOS}/${assetId}${searchparams}`
|
? `${Route.viewAsset({ id: assetId })}${searchparams}`
|
||||||
: `${$page.url.pathname.replace(/\/photos\/[^/]+$/, '')}/photos/${assetId}${searchparams}`;
|
: `${$page.url.pathname.replace(/\/photos\/[^/]+$/, '')}/photos/${assetId}${searchparams}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
import Albums from '$lib/components/album-page/albums-list.svelte';
|
import Albums from '$lib/components/album-page/albums-list.svelte';
|
||||||
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 { AppRoute } from '$lib/constants';
|
|
||||||
import GroupTab from '$lib/elements/GroupTab.svelte';
|
import GroupTab from '$lib/elements/GroupTab.svelte';
|
||||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { AlbumFilter, albumViewSettings } from '$lib/stores/preferences.store';
|
import { AlbumFilter, albumViewSettings } from '$lib/stores/preferences.store';
|
||||||
import { createAlbumAndRedirect } from '$lib/utils/album-utils';
|
import { createAlbumAndRedirect } from '$lib/utils/album-utils';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
let albumGroups: string[] = $state([]);
|
let albumGroups: string[] = $state([]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout title={data.meta.title} use={[[scrollMemory, { routeStartsWith: AppRoute.ALBUMS }]]}>
|
<UserPageLayout title={data.meta.title} use={[[scrollMemory, { routeStartsWith: Route.albums() }]]}>
|
||||||
{#snippet buttons()}
|
{#snippet buttons()}
|
||||||
<div class="flex place-items-center gap-2">
|
<div class="flex place-items-center gap-2">
|
||||||
<AlbumsControls {albumGroups} bind:searchQuery />
|
<AlbumsControls {albumGroups} bind:searchQuery />
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,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 { AlbumPageViewMode, AppRoute } from '$lib/constants';
|
import { AlbumPageViewMode } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
|
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
|
||||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import {
|
import {
|
||||||
getAlbumActions,
|
getAlbumActions,
|
||||||
getAlbumAssetsActions,
|
getAlbumAssetsActions,
|
||||||
|
|
@ -90,7 +91,7 @@
|
||||||
|
|
||||||
let oldAt: AssetGridRouteSearchParams | null | undefined = $state();
|
let oldAt: AssetGridRouteSearchParams | null | undefined = $state();
|
||||||
|
|
||||||
let backUrl: string = $state(AppRoute.ALBUMS);
|
let backUrl: string = $state(Route.albums());
|
||||||
let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW);
|
let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW);
|
||||||
|
|
||||||
let timelineManager = $state<TimelineManager>() as TimelineManager;
|
let timelineManager = $state<TimelineManager>() as TimelineManager;
|
||||||
|
|
@ -108,13 +109,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAlbumsRoute(route) || isPeopleRoute(route)) {
|
if (isAlbumsRoute(route) || isPeopleRoute(route)) {
|
||||||
url = AppRoute.ALBUMS;
|
url = Route.albums();
|
||||||
}
|
}
|
||||||
|
|
||||||
backUrl = url || AppRoute.ALBUMS;
|
backUrl = url || Route.albums();
|
||||||
|
|
||||||
if (backUrl === AppRoute.SHARED_LINKS) {
|
if (backUrl === Route.sharedLinks()) {
|
||||||
backUrl = history.state?.backUrl || AppRoute.ALBUMS;
|
backUrl = history.state?.backUrl || Route.albums();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -347,7 +348,7 @@
|
||||||
/>
|
/>
|
||||||
<CommandPaletteDefaultProvider name={$t('album')} actions={[AddAssets, Upload]} />
|
<CommandPaletteDefaultProvider name={$t('album')} actions={[AddAssets, Upload]} />
|
||||||
|
|
||||||
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
|
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: Route.albums() }}>
|
||||||
<div class="relative w-full shrink">
|
<div class="relative w-full shrink">
|
||||||
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)">
|
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)">
|
||||||
<Timeline
|
<Timeline
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import LicenseActivationSuccess from '$lib/components/shared-components/purchasing/purchase-activation-success.svelte';
|
import LicenseActivationSuccess from '$lib/components/shared-components/purchasing/purchase-activation-success.svelte';
|
||||||
import LicenseContent from '$lib/components/shared-components/purchasing/purchase-content.svelte';
|
import LicenseContent from '$lib/components/shared-components/purchasing/purchase-content.svelte';
|
||||||
import SupporterBadge from '$lib/components/shared-components/side-bar/supporter-badge.svelte';
|
import SupporterBadge from '$lib/components/shared-components/side-bar/supporter-badge.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { purchaseStore } from '$lib/stores/purchase.store';
|
import { purchaseStore } from '$lib/stores/purchase.store';
|
||||||
import { Alert, Container, Stack } from '@immich/ui';
|
import { Alert, Container, Stack } from '@immich/ui';
|
||||||
import { mdiAlertCircleOutline } from '@mdi/js';
|
import { mdiAlertCircleOutline } from '@mdi/js';
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showLicenseActivated || data.isActivated === true}
|
{#if showLicenseActivated || data.isActivated === true}
|
||||||
<LicenseActivationSuccess onDone={() => goto(AppRoute.PHOTOS, { replaceState: false })} />
|
<LicenseActivationSuccess onDone={() => goto(Route.photos(), { replaceState: false })} />
|
||||||
{:else}
|
{:else}
|
||||||
<LicenseContent
|
<LicenseContent
|
||||||
onActivate={() => {
|
onActivate={() => {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
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 { AppRoute } from '$lib/constants';
|
||||||
|
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';
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
|
||||||
import { AssetMediaSize, type SearchExploreResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, type SearchExploreResponseDto } from '@immich/sdk';
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon } from '@immich/ui';
|
||||||
import { mdiHeart } from '@mdi/js';
|
import { mdiHeart } from '@mdi/js';
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('places')}</p>
|
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('places')}</p>
|
||||||
<a
|
<a
|
||||||
href={AppRoute.PLACES}
|
href={Route.places()}
|
||||||
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
|
||||||
>
|
>
|
||||||
|
|
@ -89,11 +89,7 @@
|
||||||
<SingleGridRow class="grid grid-flow-col md:grid-auto-fill-36 grid-auto-fill-28 gap-x-4">
|
<SingleGridRow class="grid grid-flow-col md:grid-auto-fill-36 grid-auto-fill-28 gap-x-4">
|
||||||
{#snippet children({ itemCount })}
|
{#snippet children({ itemCount })}
|
||||||
{#each places.slice(0, itemCount) as item (item.data.id)}
|
{#each places.slice(0, itemCount) as item (item.data.id)}
|
||||||
<a
|
<a class="relative" href={Route.search({ city: item.value })} draggable="false">
|
||||||
class="relative"
|
|
||||||
href="{AppRoute.SEARCH}?{getMetadataSearchQuery({ city: item.value })}"
|
|
||||||
draggable="false"
|
|
||||||
>
|
|
||||||
<div class="flex justify-center overflow-hidden rounded-xl brightness-75 filter">
|
<div class="flex justify-center overflow-hidden rounded-xl brightness-75 filter">
|
||||||
<img
|
<img
|
||||||
src={getAssetThumbnailUrl({ id: item.data.id, size: AssetMediaSize.Thumbnail })}
|
src={getAssetThumbnailUrl({ id: item.data.id, size: AssetMediaSize.Thumbnail })}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@
|
||||||
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
|
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.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, AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
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 { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { AssetVisibility, lockAuthSession } from '@immich/sdk';
|
import { AssetVisibility, lockAuthSession } from '@immich/sdk';
|
||||||
import { Button } from '@immich/ui';
|
import { Button } from '@immich/ui';
|
||||||
|
|
@ -45,7 +46,7 @@
|
||||||
|
|
||||||
const handleLock = async () => {
|
const handleLock = async () => {
|
||||||
await lockAuthSession();
|
await lockAuthSession();
|
||||||
await goto(AppRoute.PHOTOS);
|
await goto(Route.photos());
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getAuthStatus } from '@immich/sdk';
|
import { getAuthStatus } from '@immich/sdk';
|
||||||
|
|
@ -10,7 +10,7 @@ export const load = (async ({ url }) => {
|
||||||
|
|
||||||
const { isElevated, pinCode } = await getAuthStatus();
|
const { isElevated, pinCode } = await getAuthStatus();
|
||||||
if (!isElevated || !pinCode) {
|
if (!isElevated || !pinCode) {
|
||||||
redirect(307, `${AppRoute.AUTH_PIN_PROMPT}?continue=${encodeURIComponent(url.pathname + url.search)}`);
|
redirect(307, Route.pinPrompt({ continue: url.pathname + url.search }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import type { AssetCursor } from '$lib/components/asset-viewer/asset-viewer.svelte';
|
import type { AssetCursor } from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import { AppRoute, timeToLoadTheMap } from '$lib/constants';
|
import { timeToLoadTheMap } from '$lib/constants';
|
||||||
import Portal from '$lib/elements/Portal.svelte';
|
import Portal from '$lib/elements/Portal.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';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { delay } from '$lib/utils/asset-utils';
|
import { delay } from '$lib/utils/asset-utils';
|
||||||
|
|
@ -30,7 +31,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!featureFlagsManager.value.map) {
|
if (!featureFlagsManager.value.map) {
|
||||||
handlePromiseError(goto(AppRoute.PHOTOS));
|
handlePromiseError(goto(Route.photos()));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onViewAssets(assetIds: string[]) {
|
async function onViewAssets(assetIds: string[]) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
|
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.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 } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { AssetVisibility } from '@immich/sdk';
|
import { AssetVisibility } from '@immich/sdk';
|
||||||
import { mdiArrowLeft, mdiPlus } from '@mdi/js';
|
import { mdiArrowLeft, mdiPlus } from '@mdi/js';
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
<DownloadAction />
|
<DownloadAction />
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
{:else}
|
{:else}
|
||||||
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(AppRoute.SHARING)}>
|
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(Route.sharing())}>
|
||||||
{#snippet leading()}
|
{#snippet leading()}
|
||||||
<p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg">
|
<p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg">
|
||||||
{data.partner.name}'s photos
|
{data.partner.name}'s photos
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
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';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { getPersonActions } from '$lib/services/person.service';
|
import { getPersonActions } from '$lib/services/person.service';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
|
@ -73,7 +74,7 @@
|
||||||
|
|
||||||
let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS);
|
let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS);
|
||||||
let isEditingName = $state(false);
|
let isEditingName = $state(false);
|
||||||
let previousRoute: string = $state(AppRoute.EXPLORE);
|
let previousRoute = $state<string>(Route.explore());
|
||||||
let personMerge1: PersonResponseDto | undefined = $state();
|
let personMerge1: PersonResponseDto | undefined = $state();
|
||||||
let personMerge2: PersonResponseDto | undefined = $state();
|
let personMerge2: PersonResponseDto | undefined = $state();
|
||||||
let potentialMergePeople: PersonResponseDto[] = $state([]);
|
let potentialMergePeople: PersonResponseDto[] = $state([]);
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@
|
||||||
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, AssetAction, QueryParameter } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
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 { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||||
|
|
@ -95,7 +96,7 @@
|
||||||
memoryStore.memories.map((memory) => ({
|
memoryStore.memories.map((memory) => ({
|
||||||
id: memory.id,
|
id: memory.id,
|
||||||
title: $memoryLaneTitle(memory),
|
title: $memoryLaneTitle(memory),
|
||||||
href: `${AppRoute.MEMORY}?${QueryParameter.ID}=${memory.assets[0].id}`,
|
href: Route.memories({ id: memory.assets[0].id }),
|
||||||
alt: $t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } }),
|
alt: $t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } }),
|
||||||
src: getAssetThumbnailUrl(memory.assets[0].id),
|
src: getAssetThumbnailUrl(memory.assets[0].id),
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,10 @@
|
||||||
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 AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { QueryParameter } from '$lib/constants';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.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 { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { lang, locale } from '$lib/stores/preferences.store';
|
import { lang, locale } from '$lib/stores/preferences.store';
|
||||||
|
|
@ -55,7 +56,7 @@
|
||||||
// The GalleryViewer pushes it's own history state, which causes weird
|
// The GalleryViewer pushes it's own history state, which causes weird
|
||||||
// behavior for history.back(). To prevent that we store the previous page
|
// behavior for history.back(). To prevent that we store the previous page
|
||||||
// manually and navigate back to that.
|
// manually and navigate back to that.
|
||||||
let previousRoute = $state(AppRoute.EXPLORE as string);
|
let previousRoute = $state<string>(Route.explore());
|
||||||
|
|
||||||
let nextPage = $state(1);
|
let nextPage = $state(1);
|
||||||
let searchResultAlbums: AlbumResponseDto[] = $state([]);
|
let searchResultAlbums: AlbumResponseDto[] = $state([]);
|
||||||
|
|
@ -108,11 +109,11 @@
|
||||||
const route = from?.route?.id;
|
const route = from?.route?.id;
|
||||||
|
|
||||||
if (isPeopleRoute(route)) {
|
if (isPeopleRoute(route)) {
|
||||||
previousRoute = AppRoute.PHOTOS;
|
previousRoute = Route.photos();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAlbumsRoute(route)) {
|
if (isAlbumsRoute(route)) {
|
||||||
previousRoute = AppRoute.EXPLORE;
|
previousRoute = Route.explore();
|
||||||
}
|
}
|
||||||
|
|
||||||
tick()
|
tick()
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
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 SharedLinkCard from '$lib/components/sharedlinks-page/SharedLinkCard.svelte';
|
import SharedLinkCard from '$lib/components/sharedlinks-page/SharedLinkCard.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { type SharedLinkTab } from '$lib/constants';
|
||||||
import GroupTab from '$lib/elements/GroupTab.svelte';
|
import GroupTab from '$lib/elements/GroupTab.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { getAllSharedLinks, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
|
import { getAllSharedLinks, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
import { Container } from '@immich/ui';
|
import { Container } from '@immich/ui';
|
||||||
import { onMount, type Snippet } from 'svelte';
|
import { onMount, type Snippet } from 'svelte';
|
||||||
|
|
@ -29,9 +30,7 @@
|
||||||
await refresh();
|
await refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
type Filter = 'all' | 'album' | 'individual';
|
const filterMap: Record<SharedLinkTab, string> = {
|
||||||
|
|
||||||
const filterMap: Record<Filter, string> = {
|
|
||||||
all: $t('all'),
|
all: $t('all'),
|
||||||
album: $t('albums'),
|
album: $t('albums'),
|
||||||
individual: $t('individual_shares'),
|
individual: $t('individual_shares'),
|
||||||
|
|
@ -46,9 +45,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let selectedTab = $derived(getActiveTab(page.url));
|
let selectedTab = $derived(getActiveTab(page.url));
|
||||||
const handleSelectTab = async (value: string) => {
|
|
||||||
await goto(`${AppRoute.SHARED_LINKS}?filter=${value}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
let filteredSharedLinks = $derived(
|
let filteredSharedLinks = $derived(
|
||||||
sharedLinks.filter(
|
sharedLinks.filter(
|
||||||
|
|
@ -76,7 +72,13 @@
|
||||||
<UserPageLayout title={data.meta.title}>
|
<UserPageLayout title={data.meta.title}>
|
||||||
{#snippet buttons()}
|
{#snippet buttons()}
|
||||||
<div class="hidden xl:block h-10">
|
<div class="hidden xl:block h-10">
|
||||||
<GroupTab label={$t('show_shared_links')} {filters} {labels} selected={selectedTab} onSelect={handleSelectTab} />
|
<GroupTab
|
||||||
|
label={$t('show_shared_links')}
|
||||||
|
{filters}
|
||||||
|
{labels}
|
||||||
|
selected={selectedTab}
|
||||||
|
onSelect={(value) => goto(Route.sharedLinks({ filter: value as SharedLinkTab }))}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { AppRoute, UUID_REGEX } from '$lib/constants';
|
import { UUID_REGEX } from '$lib/constants';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getAllSharedLinks } from '@immich/sdk';
|
import { getAllSharedLinks } from '@immich/sdk';
|
||||||
|
|
@ -9,12 +10,12 @@ export const load = (async ({ params, url }) => {
|
||||||
await authenticate(url);
|
await authenticate(url);
|
||||||
|
|
||||||
if (!UUID_REGEX.test(params.id)) {
|
if (!UUID_REGEX.test(params.id)) {
|
||||||
redirect(307, AppRoute.SHARED_LINKS);
|
redirect(307, Route.sharedLinks());
|
||||||
}
|
}
|
||||||
|
|
||||||
const [sharedLink] = await getAllSharedLinks({ id: params.id });
|
const [sharedLink] = await getAllSharedLinks({ id: params.id });
|
||||||
if (!sharedLink) {
|
if (!sharedLink) {
|
||||||
redirect(307, AppRoute.SHARED_LINKS);
|
redirect(307, Route.sharedLinks());
|
||||||
}
|
}
|
||||||
|
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import SharedLinkExpiration from '$lib/components/SharedLinkExpiration.svelte';
|
import SharedLinkExpiration from '$lib/components/SharedLinkExpiration.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { handleUpdateSharedLink } from '$lib/services/shared-link.service';
|
import { handleUpdateSharedLink } from '$lib/services/shared-link.service';
|
||||||
import { SharedLinkType } from '@immich/sdk';
|
import { SharedLinkType } from '@immich/sdk';
|
||||||
import { Field, FormModal, Input, PasswordInput, Switch, Text } from '@immich/ui';
|
import { Field, FormModal, Input, PasswordInput, Switch, Text } from '@immich/ui';
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
let expiresAt = $state(sharedLink.expiresAt);
|
let expiresAt = $state(sharedLink.expiresAt);
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
await goto(`${AppRoute.SHARED_LINKS}`);
|
await goto(Route.sharedLinks());
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
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 UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import {
|
import {
|
||||||
AlbumFilter,
|
AlbumFilter,
|
||||||
AlbumGroupBy,
|
AlbumGroupBy,
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
>
|
>
|
||||||
<Text class="hidden md:block">{$t('create_album')}</Text>
|
<Text class="hidden md:block">{$t('create_album')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Button leadingIcon={mdiLink} href={AppRoute.SHARED_LINKS} size="small" variant="ghost" color="secondary">
|
<Button leadingIcon={mdiLink} href={Route.sharedLinks()} size="small" variant="ghost" color="secondary">
|
||||||
<Text class="hidden md:block">{$t('shared_links')}</Text>
|
<Text class="hidden md:block">{$t('shared_links')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
<div class="flex flex-row flex-wrap gap-4">
|
<div class="flex flex-row flex-wrap gap-4">
|
||||||
{#each data.partners as partner (partner.id)}
|
{#each data.partners as partner (partner.id)}
|
||||||
<a
|
<a
|
||||||
href="{AppRoute.PARTNERS}/{partner.id}"
|
href={Route.viewPartner(partner)}
|
||||||
class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||||
>
|
>
|
||||||
<UserAvatar user={partner} size="lg" />
|
<UserAvatar user={partner} size="lg" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
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';
|
||||||
|
|
||||||
export const load = (() => {
|
export const load = (() => redirect(307, Route.sharedLinks())) satisfies PageLoad;
|
||||||
redirect(307, AppRoute.SHARED_LINKS);
|
|
||||||
}) satisfies PageLoad;
|
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
|
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.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 } from '$lib/constants';
|
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.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 { getTrashActions } from '$lib/services/trash.service';
|
import { getTrashActions } from '$lib/services/trash.service';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
const assetInteraction = new AssetInteraction();
|
const assetInteraction = new AssetInteraction();
|
||||||
|
|
||||||
if (!featureFlagsManager.value.trash) {
|
if (!featureFlagsManager.value.trash) {
|
||||||
handlePromiseError(goto(AppRoute.PHOTOS));
|
handlePromiseError(goto(Route.photos()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEscape = () => {
|
const handleEscape = () => {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import DuplicatesCompareControl from '$lib/components/utilities-page/duplicates/duplicates-compare-control.svelte';
|
import DuplicatesCompareControl from '$lib/components/utilities-page/duplicates/duplicates-compare-control.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 DuplicatesInformationModal from '$lib/modals/DuplicatesInformationModal.svelte';
|
import DuplicatesInformationModal from '$lib/modals/DuplicatesInformationModal.svelte';
|
||||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { stackAssets } from '$lib/utils/asset-utils';
|
import { stackAssets } from '$lib/utils/asset-utils';
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
|
duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
|
||||||
|
|
||||||
deletedNotification(trashIds.length);
|
deletedNotification(trashIds.length);
|
||||||
await correctDuplicatesIndexAndGo(duplicatesIndex);
|
await navigateToIndex(duplicatesIndex);
|
||||||
},
|
},
|
||||||
trashIds.length > 0 && !featureFlagsManager.value.trash ? $t('delete_duplicates_confirmation') : undefined,
|
trashIds.length > 0 && !featureFlagsManager.value.trash ? $t('delete_duplicates_confirmation') : undefined,
|
||||||
trashIds.length > 0 && !featureFlagsManager.value.trash ? $t('permanently_delete') : undefined,
|
trashIds.length > 0 && !featureFlagsManager.value.trash ? $t('permanently_delete') : undefined,
|
||||||
|
|
@ -119,7 +119,7 @@
|
||||||
const duplicateAssetIds = assets.map((asset) => asset.id);
|
const duplicateAssetIds = assets.map((asset) => asset.id);
|
||||||
await updateAssets({ assetBulkUpdateDto: { ids: duplicateAssetIds, duplicateId: null } });
|
await updateAssets({ assetBulkUpdateDto: { ids: duplicateAssetIds, duplicateId: null } });
|
||||||
duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
|
duplicates = duplicates.filter((duplicate) => duplicate.duplicateId !== duplicateId);
|
||||||
await correctDuplicatesIndexAndGo(duplicatesIndex);
|
await navigateToIndex(duplicatesIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeduplicateAll = async () => {
|
const handleDeduplicateAll = async () => {
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
deletedNotification(idsToDelete.length);
|
deletedNotification(idsToDelete.length);
|
||||||
|
|
||||||
page.url.searchParams.delete('index');
|
page.url.searchParams.delete('index');
|
||||||
await goto(`${AppRoute.DUPLICATES}`);
|
await goto(Route.duplicatesUtility());
|
||||||
},
|
},
|
||||||
prompt,
|
prompt,
|
||||||
confirmText,
|
confirmText,
|
||||||
|
|
@ -169,41 +169,32 @@
|
||||||
|
|
||||||
toastManager.success($t('resolved_all_duplicates'));
|
toastManager.success($t('resolved_all_duplicates'));
|
||||||
page.url.searchParams.delete('index');
|
page.url.searchParams.delete('index');
|
||||||
await goto(`${AppRoute.DUPLICATES}`);
|
await goto(Route.duplicatesUtility());
|
||||||
},
|
},
|
||||||
$t('bulk_keep_duplicates_confirmation', { values: { count: ids.length } }),
|
$t('bulk_keep_duplicates_confirmation', { values: { count: ids.length } }),
|
||||||
$t('confirm'),
|
$t('confirm'),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFirst = async () => {
|
const handleFirst = () => navigateToIndex(0);
|
||||||
await correctDuplicatesIndexAndGo(0);
|
const handlePrevious = () => navigateToIndex(Math.max(duplicatesIndex - 1, 0));
|
||||||
};
|
|
||||||
const handlePrevious = async () => {
|
|
||||||
await correctDuplicatesIndexAndGo(Math.max(duplicatesIndex - 1, 0));
|
|
||||||
};
|
|
||||||
const handlePreviousShortcut = async () => {
|
const handlePreviousShortcut = async () => {
|
||||||
if ($showAssetViewer) {
|
if ($showAssetViewer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await handlePrevious();
|
await handlePrevious();
|
||||||
};
|
};
|
||||||
const handleNext = async () => {
|
const handleNext = async () => navigateToIndex(Math.min(duplicatesIndex + 1, duplicates.length - 1));
|
||||||
await correctDuplicatesIndexAndGo(Math.min(duplicatesIndex + 1, duplicates.length - 1));
|
|
||||||
};
|
|
||||||
const handleNextShortcut = async () => {
|
const handleNextShortcut = async () => {
|
||||||
if ($showAssetViewer) {
|
if ($showAssetViewer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await handleNext();
|
await handleNext();
|
||||||
};
|
};
|
||||||
const handleLast = async () => {
|
const handleLast = () => navigateToIndex(duplicates.length - 1);
|
||||||
await correctDuplicatesIndexAndGo(duplicates.length - 1);
|
|
||||||
};
|
const navigateToIndex = async (index: number) =>
|
||||||
const correctDuplicatesIndexAndGo = async (index: number) => {
|
goto(Route.duplicatesUtility({ index: correctDuplicatesIndex(index) }));
|
||||||
page.url.searchParams.set('index', correctDuplicatesIndex(index).toString());
|
|
||||||
await goto(`${AppRoute.DUPLICATES}?${page.url.searchParams.toString()}`);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document
|
<svelte:document
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
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';
|
||||||
|
|
||||||
export const load = (({ params }) => {
|
export const load = (({ params }) => redirect(307, Route.viewAsset({ id: params.photoId }))) satisfies PageLoad;
|
||||||
const photoId = params.photoId;
|
|
||||||
return redirect(307, `${AppRoute.PHOTOS}/${photoId}`);
|
|
||||||
}) satisfies PageLoad;
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
import WorkflowJsonEditor from '$lib/components/workflows/WorkflowJsonEditor.svelte';
|
import WorkflowJsonEditor from '$lib/components/workflows/WorkflowJsonEditor.svelte';
|
||||||
import WorkflowSummarySidebar from '$lib/components/workflows/WorkflowSummary.svelte';
|
import WorkflowSummarySidebar from '$lib/components/workflows/WorkflowSummary.svelte';
|
||||||
import WorkflowTriggerCard from '$lib/components/workflows/WorkflowTriggerCard.svelte';
|
import WorkflowTriggerCard from '$lib/components/workflows/WorkflowTriggerCard.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import AddWorkflowStepModal from '$lib/modals/AddWorkflowStepModal.svelte';
|
import AddWorkflowStepModal from '$lib/modals/AddWorkflowStepModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import {
|
import {
|
||||||
buildWorkflowPayload,
|
buildWorkflowPayload,
|
||||||
getActionsByContext,
|
getActionsByContext,
|
||||||
|
|
@ -580,7 +580,7 @@
|
||||||
<WorkflowSummarySidebar trigger={selectedTrigger} filters={selectedFilters} actions={selectedActions} />
|
<WorkflowSummarySidebar trigger={selectedTrigger} filters={selectedFilters} actions={selectedActions} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<ControlAppBar onClose={() => goto(AppRoute.WORKFLOWS)} backIcon={mdiArrowLeft} tailwindClasses="fixed! top-0! w-full">
|
<ControlAppBar onClose={() => goto(Route.workflows())} backIcon={mdiArrowLeft} tailwindClasses="fixed! top-0! w-full">
|
||||||
{#snippet leading()}
|
{#snippet leading()}
|
||||||
<Text>{data.meta.title}</Text>
|
<Text>{data.meta.title}</Text>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
||||||
import ServerRestartingModal from '$lib/modals/ServerRestartingModal.svelte';
|
import ServerRestartingModal from '$lib/modals/ServerRestartingModal.svelte';
|
||||||
import VersionAnnouncementModal from '$lib/modals/VersionAnnouncementModal.svelte';
|
import VersionAnnouncementModal from '$lib/modals/VersionAnnouncementModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
import { sidebarStore } from '$lib/stores/sidebar.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { closeWebsocketConnection, openWebsocketConnection, websocketStore } from '$lib/stores/websocket';
|
import { closeWebsocketConnection, openWebsocketConnection, websocketStore } from '$lib/stores/websocket';
|
||||||
|
|
@ -155,32 +156,32 @@
|
||||||
title: $t('users'),
|
title: $t('users'),
|
||||||
description: $t('admin.users_page_description'),
|
description: $t('admin.users_page_description'),
|
||||||
icon: mdiAccountMultipleOutline,
|
icon: mdiAccountMultipleOutline,
|
||||||
onAction: () => goto(AppRoute.ADMIN_USERS),
|
onAction: () => goto(Route.users()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: $t('settings'),
|
title: $t('settings'),
|
||||||
description: $t('admin.settings_page_description'),
|
description: $t('admin.settings_page_description'),
|
||||||
icon: mdiCog,
|
icon: mdiCog,
|
||||||
onAction: () => goto(AppRoute.ADMIN_SETTINGS),
|
onAction: () => goto(Route.systemSettings()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: $t('admin.queues'),
|
title: $t('admin.queues'),
|
||||||
description: $t('admin.queues_page_description'),
|
description: $t('admin.queues_page_description'),
|
||||||
icon: mdiSync,
|
icon: mdiSync,
|
||||||
type: $t('page'),
|
type: $t('page'),
|
||||||
onAction: () => goto(AppRoute.ADMIN_QUEUES),
|
onAction: () => goto(Route.queues()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: $t('external_libraries'),
|
title: $t('external_libraries'),
|
||||||
description: $t('admin.external_libraries_page_description'),
|
description: $t('admin.external_libraries_page_description'),
|
||||||
icon: mdiBookshelf,
|
icon: mdiBookshelf,
|
||||||
onAction: () => goto(AppRoute.ADMIN_LIBRARIES),
|
onAction: () => goto(Route.libraries()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: $t('server_stats'),
|
title: $t('server_stats'),
|
||||||
description: $t('admin.server_stats_page_description'),
|
description: $t('admin.server_stats_page_description'),
|
||||||
icon: mdiServer,
|
icon: mdiServer,
|
||||||
onAction: () => goto(AppRoute.ADMIN_STATS),
|
onAction: () => goto(Route.systemStatistics()),
|
||||||
},
|
},
|
||||||
].map((route) => ({ ...route, type: $t('page'), $if: () => $user?.isAdmin }));
|
].map((route) => ({ ...route, type: $t('page'), $if: () => $user?.isAdmin }));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { Button, Heading } from '@immich/ui';
|
import { Button, Heading } from '@immich/ui';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="flex flex-col place-items-center text-center gap-12">
|
<div class="flex flex-col place-items-center text-center gap-12">
|
||||||
<Heading size="large" color="primary" tag="h1">{$t('welcome_to_immich')}</Heading>
|
<Heading size="large" color="primary" tag="h1">{$t('welcome_to_immich')}</Heading>
|
||||||
<div>
|
<div>
|
||||||
<Button href={AppRoute.AUTH_REGISTER} size="medium" shape="round">
|
<Button href={Route.register()} size="medium" shape="round">
|
||||||
<span class="px-2 font-semibold">{$t('getting_started')}</span>
|
<span class="px-2 font-semibold">{$t('getting_started')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
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 { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { init } from '$lib/utils/server';
|
import { init } from '$lib/utils/server';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
@ -19,12 +20,12 @@ export const load = (async ({ fetch }) => {
|
||||||
|
|
||||||
const authenticated = await loadUser();
|
const authenticated = await loadUser();
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
redirect(307, AppRoute.PHOTOS);
|
redirect(307, Route.photos());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverConfigManager.value.isInitialized) {
|
if (serverConfigManager.value.isInitialized) {
|
||||||
// Redirect to login page if there exists an admin account (i.e. server is initialized)
|
// Redirect to login page if there exists an admin account (i.e. server is initialized)
|
||||||
redirect(307, AppRoute.AUTH_LOGIN);
|
redirect(307, Route.login());
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
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';
|
||||||
|
|
||||||
export const load = (() => {
|
export const load = (() => redirect(307, Route.systemSettings())) satisfies PageLoad;
|
||||||
redirect(307, AppRoute.ADMIN_SETTINGS);
|
|
||||||
}) satisfies PageLoad;
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
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';
|
||||||
|
|
||||||
export const load = (() => redirect(307, AppRoute.ADMIN_QUEUES)) satisfies PageLoad;
|
export const load = (() => redirect(307, Route.queues())) satisfies PageLoad;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { getLibrariesActions, handleViewLibrary } from '$lib/services/library.service';
|
import { getLibrariesActions } from '$lib/services/library.service';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { getBytesWithUnit } from '$lib/utils/byte-units';
|
import { getBytesWithUnit } from '$lib/utils/byte-units';
|
||||||
import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk';
|
import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
let owners = $state(data.owners);
|
let owners = $state(data.owners);
|
||||||
|
|
||||||
const onLibraryCreate = async (library: LibraryResponseDto) => {
|
const onLibraryCreate = async (library: LibraryResponseDto) => {
|
||||||
await goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}`);
|
await goto(Route.viewLibrary(library));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLibraryUpdate = async (library: LibraryResponseDto) => {
|
const onLibraryUpdate = async (library: LibraryResponseDto) => {
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
<TableCell class={classes.column4}>{videos.toLocaleString($locale)}</TableCell>
|
<TableCell class={classes.column4}>{videos.toLocaleString($locale)}</TableCell>
|
||||||
<TableCell class={classes.column5}>{diskUsage} {diskUsageUnit}</TableCell>
|
<TableCell class={classes.column5}>{diskUsage} {diskUsageUnit}</TableCell>
|
||||||
<TableCell class={classes.column6}>
|
<TableCell class={classes.column6}>
|
||||||
<Button size="small" onclick={() => handleViewLibrary(library)}>{$t('view')}</Button>
|
<Button size="small" href={Route.viewLibrary(library)}>{$t('view')}</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
<EmptyPlaceholder
|
<EmptyPlaceholder
|
||||||
fullWidth
|
fullWidth
|
||||||
text={$t('no_libraries_message')}
|
text={$t('no_libraries_message')}
|
||||||
onClick={() => goto(AppRoute.ADMIN_LIBRARIES_NEW)}
|
onClick={() => goto(Route.newLibrary())}
|
||||||
class="mt-10 mx-auto"
|
class="mt-10 mx-auto"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { handleCreateLibrary } from '$lib/services/library.service';
|
import { handleCreateLibrary } from '$lib/services/library.service';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { Field, FormModal, HelperText, Select } from '@immich/ui';
|
import { Field, FormModal, HelperText, Select } from '@immich/ui';
|
||||||
|
|
@ -18,13 +18,13 @@
|
||||||
const users = $state(data.allUsers);
|
const users = $state(data.allUsers);
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
await goto(AppRoute.ADMIN_LIBRARIES);
|
await goto(Route.libraries());
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
const library = await handleCreateLibrary({ ownerId });
|
const library = await handleCreateLibrary({ ownerId });
|
||||||
if (library) {
|
if (library) {
|
||||||
await goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}`, { replaceState: true });
|
await goto(Route.viewLibrary(library), { replaceState: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
|
import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import TableButton from '$lib/components/TableButton.svelte';
|
import TableButton from '$lib/components/TableButton.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
|
import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import {
|
import {
|
||||||
getLibraryActions,
|
getLibraryActions,
|
||||||
getLibraryExclusionPatternActions,
|
getLibraryExclusionPatternActions,
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
const onLibraryDelete = async ({ id }: { id: string }) => {
|
const onLibraryDelete = async ({ id }: { id: string }) => {
|
||||||
if (id === library.id) {
|
if (id === library.id) {
|
||||||
await goto(AppRoute.ADMIN_LIBRARIES);
|
await goto(Route.libraries());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
<CommandPaletteDefaultProvider name={$t('library')} actions={[Edit, Delete, AddFolder, AddExclusionPattern, Scan]} />
|
<CommandPaletteDefaultProvider name={$t('library')} actions={[Edit, Delete, AddFolder, AddExclusionPattern, Scan]} />
|
||||||
|
|
||||||
<AdminPageLayout
|
<AdminPageLayout
|
||||||
breadcrumbs={[{ title: $t('external_libraries'), href: AppRoute.ADMIN_LIBRARIES }, { title: library.name }]}
|
breadcrumbs={[{ title: $t('external_libraries'), href: Route.libraries() }, { title: library.name }]}
|
||||||
actions={[Scan, Edit, Delete]}
|
actions={[Scan, Edit, Delete]}
|
||||||
>
|
>
|
||||||
<Container size="large" center>
|
<Container size="large" center>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk';
|
import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -13,7 +13,7 @@ export const load = (async ({ params: { id }, url }) => {
|
||||||
try {
|
try {
|
||||||
library = await getLibrary({ id });
|
library = await getLibrary({ id });
|
||||||
} catch {
|
} catch {
|
||||||
redirect(307, AppRoute.ADMIN_LIBRARIES);
|
redirect(307, Route.libraries());
|
||||||
}
|
}
|
||||||
|
|
||||||
const statistics = await getLibraryStatistics({ id });
|
const statistics = await getLibraryStatistics({ id });
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { handleUpdateLibrary } from '$lib/services/library.service';
|
import { handleUpdateLibrary } from '$lib/services/library.service';
|
||||||
import { Field, FormModal, Input } from '@immich/ui';
|
import { Field, FormModal, Input } from '@immich/ui';
|
||||||
import { mdiRenameOutline } from '@mdi/js';
|
import { mdiRenameOutline } from '@mdi/js';
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
let name = $state(library.name);
|
let name = $state(library.name);
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
await goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}`);
|
await goto(Route.viewLibrary(library));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||||
import QueueGraph from '$lib/components/QueueGraph.svelte';
|
import QueueGraph from '$lib/components/QueueGraph.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { queueManager } from '$lib/managers/queue-manager.svelte';
|
import { queueManager } from '$lib/managers/queue-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { asQueueItem, getQueueActions } from '$lib/services/queue.service';
|
import { asQueueItem, getQueueActions } from '$lib/services/queue.service';
|
||||||
import { type QueueResponseDto } from '@immich/sdk';
|
import { type QueueResponseDto } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
<OnEvents {onQueueUpdate} />
|
<OnEvents {onQueueUpdate} />
|
||||||
|
|
||||||
<AdminPageLayout
|
<AdminPageLayout
|
||||||
breadcrumbs={[{ title: $t('admin.queues'), href: AppRoute.ADMIN_QUEUES }, { title: item.title }]}
|
breadcrumbs={[{ title: $t('admin.queues'), href: Route.queues() }, { title: item.title }]}
|
||||||
actions={[Pause, Resume, Empty, MenuItemType.Divider, RemoveFailedJobs]}
|
actions={[Pause, Resume, Empty, MenuItemType.Divider, RemoveFailedJobs]}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { fromQueueSlug, Route } from '$lib/route';
|
||||||
import { fromQueueSlug } from '$lib/services/queue.service';
|
|
||||||
import { authenticate, requestServerInfo } from '$lib/utils/auth';
|
import { authenticate, requestServerInfo } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getQueue, getQueueJobs, QueueJobStatus } from '@immich/sdk';
|
import { getQueue, getQueueJobs, QueueJobStatus } from '@immich/sdk';
|
||||||
|
|
@ -12,7 +11,7 @@ export const load = (async ({ params, url }) => {
|
||||||
|
|
||||||
const name = fromQueueSlug(params.name);
|
const name = fromQueueSlug(params.name);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
redirect(307, AppRoute.ADMIN_QUEUES);
|
redirect(307, Route.queues());
|
||||||
}
|
}
|
||||||
|
|
||||||
const [queue, failedJobs] = await Promise.all([
|
const [queue, failedJobs] = await Promise.all([
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
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';
|
||||||
|
|
||||||
export const load = (() => redirect(307, AppRoute.ADMIN_USERS)) satisfies PageLoad;
|
export const load = (() => redirect(307, Route.users())) satisfies PageLoad;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
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 { handleCreateUserAdmin } from '$lib/services/user-admin.service';
|
import { handleCreateUserAdmin } from '$lib/services/user-admin.service';
|
||||||
import { userInteraction } from '$lib/stores/user.svelte';
|
import { userInteraction } from '$lib/stores/user.svelte';
|
||||||
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
const valid = $derived(!passwordMismatch && !isCreatingUser);
|
const valid = $derived(!passwordMismatch && !isCreatingUser);
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
await goto(AppRoute.ADMIN_USERS);
|
await goto(Route.users());
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (event: Event) => {
|
const onSubmit = async (event: Event) => {
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await goto(`${AppRoute.ADMIN_USERS}/${user.id}`, { replaceState: true });
|
await goto(Route.viewUser(user), { replaceState: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
isCreatingUser = false;
|
isCreatingUser = false;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import DeviceCard from '$lib/components/user-settings-page/device-card.svelte';
|
import DeviceCard from '$lib/components/user-settings-page/device-card.svelte';
|
||||||
import FeatureSetting from '$lib/components/users/FeatureSetting.svelte';
|
import FeatureSetting from '$lib/components/users/FeatureSetting.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { getUserAdminActions } from '$lib/services/user-admin.service';
|
import { getUserAdminActions } from '$lib/services/user-admin.service';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { createDateFormatter, findLocale } from '$lib/utils';
|
import { createDateFormatter, findLocale } from '$lib/utils';
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
|
|
||||||
const onUserAdminDeleted = async ({ id }: { id: string }) => {
|
const onUserAdminDeleted = async ({ id }: { id: string }) => {
|
||||||
if (id === user.id) {
|
if (id === user.id) {
|
||||||
await goto(AppRoute.ADMIN_USERS);
|
await goto(Route.users());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
<CommandPaletteDefaultProvider name={$t('user')} actions={[ResetPassword, ResetPinCode, Update, Delete, Restore]} />
|
<CommandPaletteDefaultProvider name={$t('user')} actions={[ResetPassword, ResetPinCode, Update, Delete, Restore]} />
|
||||||
|
|
||||||
<AdminPageLayout
|
<AdminPageLayout
|
||||||
breadcrumbs={[{ title: $t('admin.user_management'), href: AppRoute.ADMIN_USERS }, { title: user.name }]}
|
breadcrumbs={[{ title: $t('admin.user_management'), href: Route.users() }, { title: user.name }]}
|
||||||
actions={[ResetPassword, ResetPinCode, Update, Restore, MenuItemType.Divider, Delete]}
|
actions={[ResetPassword, ResetPinCode, Update, Restore, MenuItemType.Divider, Delete]}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { AppRoute, UUID_REGEX } from '$lib/constants';
|
import { UUID_REGEX } from '$lib/constants';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { authenticate, requestServerInfo } from '$lib/utils/auth';
|
import { authenticate, requestServerInfo } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getUserPreferencesAdmin, getUserSessionsAdmin, getUserStatisticsAdmin, searchUsersAdmin } from '@immich/sdk';
|
import { getUserPreferencesAdmin, getUserSessionsAdmin, getUserStatisticsAdmin, searchUsersAdmin } from '@immich/sdk';
|
||||||
|
|
@ -10,12 +11,12 @@ export const load = (async ({ params, url }) => {
|
||||||
await requestServerInfo();
|
await requestServerInfo();
|
||||||
|
|
||||||
if (!UUID_REGEX.test(params.id)) {
|
if (!UUID_REGEX.test(params.id)) {
|
||||||
redirect(307, AppRoute.ADMIN_USERS);
|
redirect(307, Route.users());
|
||||||
}
|
}
|
||||||
|
|
||||||
const [user] = await searchUsersAdmin({ id: params.id, withDeleted: true }).catch(() => []);
|
const [user] = await searchUsersAdmin({ id: params.id, withDeleted: true }).catch(() => []);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(307, AppRoute.ADMIN_USERS);
|
redirect(307, Route.users());
|
||||||
}
|
}
|
||||||
|
|
||||||
const [userPreferences, userStatistics, userSessions] = await Promise.all([
|
const [userPreferences, userStatistics, userSessions] = await Promise.all([
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { handleUpdateUserAdmin } from '$lib/services/user-admin.service';
|
import { handleUpdateUserAdmin } from '$lib/services/user-admin.service';
|
||||||
import { user as authUser } from '$lib/stores/user.store';
|
import { user as authUser } from '$lib/stores/user.store';
|
||||||
import { userInteraction } from '$lib/stores/user.svelte';
|
import { userInteraction } from '$lib/stores/user.svelte';
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
await goto(`${AppRoute.ADMIN_USERS}/${user.id}`);
|
await goto(Route.viewUser(user));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (event: Event) => {
|
const onSubmit = async (event: Event) => {
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
|
|
||||||
<Text size="small" class="mt-2" color="muted">
|
<Text size="small" class="mt-2" color="muted">
|
||||||
{$t('admin.note_apply_storage_label_previous_assets')}
|
{$t('admin.note_apply_storage_label_previous_assets')}
|
||||||
<Link href={AppRoute.ADMIN_QUEUES}>
|
<Link href={Route.queues()}>
|
||||||
{$t('admin.storage_template_migration_job')}
|
{$t('admin.storage_template_migration_job')}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
|
|
@ -9,7 +9,7 @@ import type { PageLoad } from './$types';
|
||||||
export const load = (async ({ url }) => {
|
export const load = (async ({ url }) => {
|
||||||
await authenticate(url);
|
await authenticate(url);
|
||||||
if (!get(user).shouldChangePassword) {
|
if (!get(user).shouldChangePassword) {
|
||||||
redirect(307, AppRoute.PHOTOS);
|
redirect(307, Route.photos());
|
||||||
}
|
}
|
||||||
|
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { oauth } from '$lib/utils';
|
import { oauth } from '$lib/utils';
|
||||||
import { getServerErrorMessage, handleError } from '$lib/utils/handle-error';
|
import { getServerErrorMessage, handleError } from '$lib/utils/handle-error';
|
||||||
import { login, type LoginResponseDto } from '@immich/sdk';
|
import { login, type LoginResponseDto } from '@immich/sdk';
|
||||||
|
|
@ -33,8 +33,8 @@
|
||||||
eventManager.emit('AuthLogin', user);
|
eventManager.emit('AuthLogin', user);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFirstLogin = () => goto(AppRoute.AUTH_CHANGE_PASSWORD);
|
const onFirstLogin = () => goto(Route.changePassword());
|
||||||
const onOnboarding = () => goto(AppRoute.AUTH_ONBOARDING);
|
const onOnboarding = () => goto(Route.onboarding());
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!featureFlagsManager.value.oauth) {
|
if (!featureFlagsManager.value.oauth) {
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
(featureFlagsManager.value.oauthAutoLaunch && !oauth.isAutoLaunchDisabled(globalThis.location)) ||
|
(featureFlagsManager.value.oauthAutoLaunch && !oauth.isAutoLaunchDisabled(globalThis.location)) ||
|
||||||
oauth.isAutoLaunchEnabled(globalThis.location)
|
oauth.isAutoLaunchEnabled(globalThis.location)
|
||||||
) {
|
) {
|
||||||
await goto(`${AppRoute.AUTH_LOGIN}?autoLaunch=0`, { replaceState: true });
|
await goto(Route.login({ autoLaunch: 0 }), { replaceState: true });
|
||||||
await oauth.authorize(globalThis.location);
|
await oauth.authorize(globalThis.location);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
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 { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
@ -9,7 +9,7 @@ export const load = (async ({ parent, url }) => {
|
||||||
|
|
||||||
if (!serverConfigManager.value.isInitialized) {
|
if (!serverConfigManager.value.isInitialized) {
|
||||||
// Admin not registered
|
// Admin not registered
|
||||||
redirect(307, AppRoute.AUTH_REGISTER);
|
redirect(307, Route.register());
|
||||||
}
|
}
|
||||||
|
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
@ -17,6 +17,6 @@ export const load = (async ({ parent, url }) => {
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('login'),
|
title: $t('login'),
|
||||||
},
|
},
|
||||||
continueUrl: url.searchParams.get('continue') || AppRoute.PHOTOS,
|
continueUrl: url.searchParams.get('continue') || Route.photos(),
|
||||||
};
|
};
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
import OnboardingStorageTemplate from '$lib/components/onboarding-page/onboarding-storage-template.svelte';
|
import OnboardingStorageTemplate from '$lib/components/onboarding-page/onboarding-storage-template.svelte';
|
||||||
import OnboardingTheme from '$lib/components/onboarding-page/onboarding-theme.svelte';
|
import OnboardingTheme from '$lib/components/onboarding-page/onboarding-theme.svelte';
|
||||||
import OnboardingUserPrivacy from '$lib/components/onboarding-page/onboarding-user-privacy.svelte';
|
import OnboardingUserPrivacy from '$lib/components/onboarding-page/onboarding-user-privacy.svelte';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
|
||||||
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
||||||
|
import { Route } from '$lib/route';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { OnboardingRole } from '$lib/types';
|
import { OnboardingRole } from '$lib/types';
|
||||||
import { setUserOnboarding, updateAdminOnboarding } from '@immich/sdk';
|
import { setUserOnboarding, updateAdminOnboarding } from '@immich/sdk';
|
||||||
|
|
@ -137,11 +137,9 @@
|
||||||
onboardingDto: { isOnboarded: true },
|
onboardingDto: { isOnboarded: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
await goto(AppRoute.PHOTOS);
|
await goto(Route.photos());
|
||||||
} else {
|
} else {
|
||||||
await goto(
|
await goto(Route.onboarding({ step: onboardingSteps[nextStepIndex].name }));
|
||||||
`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[nextStepIndex].name}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -150,9 +148,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await goto(
|
await goto(Route.onboarding({ step: onboardingSteps[previousStepIndex].name }));
|
||||||
`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[previousStepIndex].name}`,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const OnboardingStep = $derived(onboardingSteps[index].component);
|
const OnboardingStep = $derived(onboardingSteps[index].component);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||||
import PinCodeCreateForm from '$lib/components/user-settings-page/PinCodeCreateForm.svelte';
|
import PinCodeCreateForm from '$lib/components/user-settings-page/PinCodeCreateForm.svelte';
|
||||||
import PincodeInput from '$lib/components/user-settings-page/PinCodeInput.svelte';
|
import PincodeInput from '$lib/components/user-settings-page/PinCodeInput.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { unlockAuthSession } from '@immich/sdk';
|
import { unlockAuthSession } from '@immich/sdk';
|
||||||
import { Button, Icon } from '@immich/ui';
|
import { Button, Icon } from '@immich/ui';
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
onFilled={handleUnlockSession}
|
onFilled={handleUnlockSession}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type="button" color="secondary" onclick={() => goto(AppRoute.PHOTOS)}>{$t('cancel')}</Button>
|
<Button type="button" color="secondary" onclick={() => goto(Route.photos())}>{$t('cancel')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { Route } from '$lib/route';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getAuthStatus } from '@immich/sdk';
|
import { getAuthStatus } from '@immich/sdk';
|
||||||
|
|
@ -16,6 +16,6 @@ export const load = (async ({ url }) => {
|
||||||
title: $t('pin_verification'),
|
title: $t('pin_verification'),
|
||||||
},
|
},
|
||||||
hasPinCode: !!pinCode,
|
hasPinCode: !!pinCode,
|
||||||
continueUrl: url.searchParams.get('continue') || AppRoute.LOCKED,
|
continueUrl: url.searchParams.get('continue') || Route.locked(),
|
||||||
};
|
};
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
|
||||||
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 { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { signUpAdmin } from '@immich/sdk';
|
import { signUpAdmin } from '@immich/sdk';
|
||||||
import { Alert, Button, Field, Input, PasswordInput, Text } from '@immich/ui';
|
import { Alert, Button, Field, Input, PasswordInput, Text } from '@immich/ui';
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
try {
|
try {
|
||||||
await signUpAdmin({ signUpDto: { email, password, name } });
|
await signUpAdmin({ signUpDto: { email, password, name } });
|
||||||
await serverConfigManager.loadServerConfig();
|
await serverConfigManager.loadServerConfig();
|
||||||
await goto(AppRoute.AUTH_LOGIN);
|
await goto(Route.login());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_create_admin_account'));
|
handleError(error, $t('errors.unable_to_create_admin_account'));
|
||||||
errorMessage = $t('errors.unable_to_create_admin_account');
|
errorMessage = $t('errors.unable_to_create_admin_account');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
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 { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
@ -8,7 +8,7 @@ export const load = (async ({ parent }) => {
|
||||||
await parent();
|
await parent();
|
||||||
if (serverConfigManager.value.isInitialized) {
|
if (serverConfigManager.value.isInitialized) {
|
||||||
// Admin has been registered, redirect to login
|
// Admin has been registered, redirect to login
|
||||||
redirect(307, AppRoute.AUTH_LOGIN);
|
redirect(307, Route.login());
|
||||||
}
|
}
|
||||||
|
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute, OpenQueryParam } from '$lib/constants';
|
||||||
|
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';
|
||||||
|
|
||||||
|
|
@ -14,17 +15,17 @@ export const load = (({ url }) => {
|
||||||
const target = queryParams.get('target') as LinkTarget;
|
const target = queryParams.get('target') as LinkTarget;
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case LinkTarget.HOME: {
|
case LinkTarget.HOME: {
|
||||||
return redirect(307, AppRoute.PHOTOS);
|
return redirect(307, Route.photos());
|
||||||
}
|
}
|
||||||
|
|
||||||
case LinkTarget.UNSUBSCRIBE: {
|
case LinkTarget.UNSUBSCRIBE: {
|
||||||
return redirect(307, `${AppRoute.USER_SETTINGS}?isOpen=notifications`);
|
return redirect(307, Route.userSettings({ isOpen: OpenQueryParam.NOTIFICATIONS }));
|
||||||
}
|
}
|
||||||
|
|
||||||
case LinkTarget.VIEW_ASSET: {
|
case LinkTarget.VIEW_ASSET: {
|
||||||
const id = queryParams.get('id');
|
const id = queryParams.get('id');
|
||||||
if (id) {
|
if (id) {
|
||||||
return redirect(307, `${AppRoute.PHOTOS}/${id}`);
|
return redirect(307, Route.viewAsset({ id }));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -49,5 +50,5 @@ export const load = (({ url }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect(307, AppRoute.PHOTOS);
|
return redirect(307, Route.photos());
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue