+
+
{@render children?.()}
diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index aa535a30ee..fe9d3bd718 100644
--- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -34,6 +34,7 @@
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
@@ -43,7 +44,6 @@
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
- import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { preferences, user } from '$lib/stores/user.store';
@@ -95,7 +95,6 @@
let { data = $bindable() }: Props = $props();
- let { isViewing: showAssetViewer, setAssetId, gridScrollTarget } = assetViewingStore;
let { slideshowState, slideshowNavigation } = slideshowStore;
let oldAt: AssetGridRouteSearchParams | null | undefined = $state();
@@ -109,6 +108,15 @@
const assetInteraction = new AssetInteraction();
const timelineInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
afterNavigate(({ from }) => {
let url: string | undefined = from?.url?.pathname;
@@ -148,7 +156,8 @@
? await timelineManager.getRandomAsset()
: timelineManager.months[0]?.dayGroups[0]?.viewerAssets[0]?.asset;
if (asset) {
- handlePromiseError(setAssetId(asset.id).then(() => ($slideshowState = SlideshowState.PlaySlideshow)));
+ await navigate({ targetRoute: 'current', assetId: asset.id });
+ $slideshowState = SlideshowState.PlaySlideshow;
}
};
@@ -166,7 +175,7 @@
viewMode = AlbumPageViewMode.VIEW;
return;
}
- if ($showAssetViewer) {
+ if (assetManager.showAssetViewer) {
return;
}
if (assetInteraction.selectionActive) {
@@ -346,7 +355,7 @@
const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0);
$effect(() => {
- if ($showAssetViewer || !isShared) {
+ if (assetManager.showAssetViewer || !isShared) {
return;
}
@@ -361,7 +370,9 @@
let isOwned = $derived($user.id == album.ownerId);
let showActivityStatus = $derived(
- album.albumUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || activityManager.commentCount > 0),
+ album.albumUsers.length > 0 &&
+ !assetManager.showAssetViewer &&
+ (album.isActivityEnabled || activityManager.commentCount > 0),
);
let isEditor = $derived(
album.albumUsers.find(({ user: { id } }) => id === $user.id)?.role === AlbumUserRole.Editor ||
@@ -449,6 +460,7 @@
{album}
{timelineManager}
assetInteraction={currentAssetIntersection}
+ {assetManager}
{isShared}
{isSelectionMode}
{singleSelect}
@@ -626,7 +638,7 @@
onclick={async () => {
timelineManager.suspendTransitions = true;
viewMode = AlbumPageViewMode.SELECT_ASSETS;
- oldAt = { at: $gridScrollTarget?.at };
+ oldAt = { at: assetManager.gridScrollTarget?.at };
await navigate(
{ targetRoute: 'current', assetId: null, assetGridRouteSearchParams: { at: null } },
{ replaceState: true },
@@ -648,7 +660,7 @@
{/if}
{#if $featureFlags.loaded && $featureFlags.map}
-
+
{/if}
{#if album.assetCount > 0}
@@ -735,7 +747,7 @@
{/if}
{/if}
- {#if album.albumUsers.length > 0 && album && isShowActivity && $user && !$showAssetViewer}
+ {#if album.albumUsers.length > 0 && album && isShowActivity && $user && !assetManager.showAssetViewer}
{
await authenticate(url);
- const [album, asset] = await Promise.all([
- getAlbumInfo({ id: params.albumId, withoutAssets: true }),
- getAssetInfoFromParam(params),
- ]);
+ const album = await getAlbumInfo({ id: params.albumId, withoutAssets: true });
return {
album,
- asset,
+ assetId: params.assetId,
meta: {
title: album.albumName,
},
diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 5481224057..8d9a2fca3f 100644
--- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -14,6 +14,7 @@
import { AssetAction } from '$lib/constants';
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility } from '@immich/sdk';
@@ -33,6 +34,15 @@
const assetInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const handleEscape = () => {
if (assetInteraction.selectionActive) {
assetInteraction.clearMultiselect();
@@ -51,6 +61,7 @@
enableRouting={true}
{timelineManager}
{assetInteraction}
+ {assetManager}
removeAction={AssetAction.UNARCHIVE}
onEscape={handleEscape}
>
diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts
index f5d4560505..8a0a1d323f 100644
--- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,15 +1,13 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import type { PageLoad } from './$types';
export const load = (async ({ params, url }) => {
await authenticate(url);
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
meta: {
title: $t('archive'),
},
diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
index ae24a82da7..3684fa5599 100644
--- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -17,6 +17,7 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AssetAction } from '$lib/constants';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { preferences } from '$lib/stores/user.store';
@@ -37,6 +38,15 @@
const assetInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const handleEscape = () => {
if (assetInteraction.selectionActive) {
assetInteraction.clearMultiselect();
@@ -56,6 +66,7 @@
withStacked={true}
{timelineManager}
{assetInteraction}
+ {assetManager}
removeAction={AssetAction.UNFAVORITE}
onEscape={handleEscape}
>
diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts
index 0d9fe7a203..d9a606eb22 100644
--- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,15 +1,13 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import type { PageLoad } from './$types';
export const load = (async ({ params, url }) => {
await authenticate(url);
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
meta: {
title: $t('favorites'),
},
diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 84ae328ccc..3fdd7268b4 100644
--- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -21,8 +21,8 @@
import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
import { AppRoute, QueryParameter } from '$lib/constants';
- import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import type { Viewport } from '$lib/managers/timeline-manager/types';
+ import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { foldersStore } from '$lib/stores/folders.svelte';
import { preferences } from '$lib/stores/user.store';
import { cancelMultiselect } from '$lib/utils/asset-utils';
@@ -32,6 +32,8 @@
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
+ import { onDestroy } from 'svelte';
interface Props {
data: PageData;
@@ -43,6 +45,15 @@
const assetInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const handleNavigateToFolder = (folderName: string) => navigateToView(joinPaths(data.tree.path, folderName));
function getLinkForPath(path: string) {
@@ -106,6 +117,7 @@
{
await authenticate(url);
- const [, asset, $t] = await Promise.all([foldersStore.fetchTree(), getAssetInfoFromParam(params), getFormatter()]);
+ const [, $t] = await Promise.all([foldersStore.fetchTree(), getFormatter()]);
let tree = foldersStore.folders!;
const path = url.searchParams.get(QueryParameter.PATH);
@@ -23,7 +22,7 @@ export const load = (async ({ params, url }) => {
const pathAssets = tree.hasAssets ? await foldersStore.fetchAssetsByPath(tree.path) : null;
return {
- asset,
+ assetId: params.assetId,
tree,
pathAssets,
meta: {
diff --git a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 733e93db71..8a4346bf8d 100644
--- a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -12,6 +12,7 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AppRoute, AssetAction } from '$lib/constants';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility, lockAuthSession } from '@immich/sdk';
@@ -33,6 +34,15 @@
const assetInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const handleEscape = () => {
if (assetInteraction.selectionActive) {
assetInteraction.clearMultiselect();
@@ -62,6 +72,7 @@
enableRouting={true}
{timelineManager}
{assetInteraction}
+ {assetManager}
onEscape={handleEscape}
removeAction={AssetAction.SET_VISIBILITY_TIMELINE}
>
diff --git a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts
index cf2e415da0..55c19acce9 100644
--- a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,7 +1,6 @@
import { AppRoute } from '$lib/constants';
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import { getAuthStatus } from '@immich/sdk';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
@@ -14,11 +13,10 @@ export const load = (async ({ params, url }) => {
redirect(302, `${AppRoute.AUTH_PIN_PROMPT}?continue=${encodeURIComponent(url.pathname + url.search)}`);
}
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
meta: {
title: $t('locked_folder'),
},
diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte
index b1fff3a0cd..24ddd09665 100644
--- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -1,12 +1,10 @@
@@ -76,16 +78,16 @@
- {#if $showAssetViewer}
+ {#if assetManager.showAssetViewer}
{#await import('../../../../../lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
1}
onNext={navigateNext}
onPrevious={navigatePrevious}
onRandom={navigateRandom}
onClose={() => {
- assetViewingStore.showAssetViewer(false);
+ assetManager.showAssetViewer = false;
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
}}
isShared={false}
diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts
index add9882bcd..e46d4d3eb3 100644
--- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,15 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import type { PageLoad } from './$types';
-
export const load = (async ({ params, url }) => {
await authenticate(url);
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
meta: {
title: $t('map'),
},
diff --git a/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 7f34913224..1503bfa7fe 100644
--- a/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -1,5 +1,23 @@
-
-
+
diff --git a/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts
index 5c030da72f..333262384f 100644
--- a/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/memory/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,16 +1,14 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import type { PageLoad } from './$types';
export const load = (async ({ params, url }) => {
const user = await authenticate(url);
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
user,
- asset,
+ assetId: params.assetId,
meta: {
title: $t('memory'),
},
diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 83962c5a90..a65aab686e 100644
--- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -8,6 +8,7 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import { AppRoute } from '$lib/constants';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility } from '@immich/sdk';
@@ -34,6 +35,15 @@
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const handleEscape = () => {
if (assetInteraction.selectionActive) {
assetInteraction.clearMultiselect();
@@ -43,7 +53,7 @@
-
+
{#if assetInteraction.selectionActive}
diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.ts
index 1977d9a095..fb87856805 100644
--- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,6 +1,5 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import { getUser } from '@immich/sdk';
import type { PageLoad } from './$types';
@@ -8,11 +7,10 @@ export const load = (async ({ params, url }) => {
await authenticate(url);
const partner = await getUser({ id: params.userId });
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
partner,
meta: {
title: $t('partner'),
diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 70fe6a41d2..7c9fbf86be 100644
--- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -31,13 +31,13 @@
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
- import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { locale } from '$lib/stores/preferences.store';
import { preferences } from '$lib/stores/user.store';
import { websocketEvents } from '$lib/stores/websocket';
@@ -75,7 +75,6 @@
let { data }: Props = $props();
let numberOfAssets = $state(data.statistics.assets);
- let { isViewing: showAssetViewer } = assetViewingStore;
const timelineManager = new TimelineManager();
$effect(() => void timelineManager.updateOptions({ visibility: AssetVisibility.Timeline, personId: data.person.id }));
@@ -83,6 +82,15 @@
const assetInteraction = new AssetInteraction();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS);
let isEditingName = $state(false);
let previousRoute: string = $state(AppRoute.EXPLORE);
@@ -123,7 +131,7 @@
});
const handleEscape = async () => {
- if ($showAssetViewer) {
+ if (assetManager.showAssetViewer) {
return;
}
if (assetInteraction.selectionActive) {
@@ -388,6 +396,7 @@
{person}
{timelineManager}
{assetInteraction}
+ {assetManager}
isSelectionMode={viewMode === PersonPageViewMode.SELECT_PERSON}
singleSelect={viewMode === PersonPageViewMode.SELECT_PERSON}
onSelect={handleSelectFeaturePhoto}
diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts
index 92371bd34e..55b2d7b7a6 100644
--- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,23 +1,21 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import { getPerson, getPersonStatistics } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async ({ params, url }) => {
await authenticate(url);
- const [person, statistics, asset] = await Promise.all([
+ const [person, statistics] = await Promise.all([
getPerson({ id: params.personId }),
getPersonStatistics({ id: params.personId }),
- getAssetInfoFromParam(params),
]);
const $t = await getFormatter();
return {
person,
statistics,
- asset,
+ assetId: params.assetId,
meta: {
title: person.name || $t('person'),
},
diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte
index 0098667873..189d3558e2 100644
--- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte
@@ -22,9 +22,9 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AssetAction } from '$lib/constants';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
- import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { preferences, user } from '$lib/stores/user.store';
import {
@@ -39,12 +39,27 @@
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
+ import type { PageData } from './$types';
+
+ interface Props {
+ data: PageData;
+ }
+
+ let { data }: Props = $props();
- let { isViewing: showAssetViewer } = assetViewingStore;
const timelineManager = new TimelineManager();
void timelineManager.updateOptions({ visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true });
onDestroy(() => timelineManager.destroy());
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const assetInteraction = new AssetInteraction();
let selectedAssets = $derived(assetInteraction.selectedAssets);
@@ -59,7 +74,7 @@
return assetInteraction.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate);
});
const handleEscape = () => {
- if ($showAssetViewer) {
+ if (assetManager.showAssetViewer) {
return;
}
if (assetInteraction.selectionActive) {
@@ -93,6 +108,7 @@
enableRouting={true}
{timelineManager}
{assetInteraction}
+ {assetManager}
removeAction={AssetAction.ARCHIVE}
onEscape={handleEscape}
withStacked
diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.ts b/web/src/routes/(user)/photos/[[assetId=id]]/+page.ts
index 209b5483a8..b296386ceb 100644
--- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.ts
@@ -1,15 +1,13 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import type { PageLoad } from './$types';
export const load = (async ({ params, url }) => {
await authenticate(url);
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
meta: {
title: $t('photos'),
},
diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 092eb3b0d4..0ea0a383f5 100644
--- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -23,10 +23,10 @@
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
import { AppRoute, QueryParameter } from '$lib/constants';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
- import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { lang, locale } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { preferences } from '$lib/stores/user.store';
@@ -47,11 +47,17 @@
} from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
- import { tick } from 'svelte';
+ import { onDestroy, tick } from 'svelte';
import { t } from 'svelte-i18n';
+ import type { PageData } from './$types';
+
+ interface Props {
+ data: PageData;
+ }
+
+ let { data }: Props = $props();
const MAX_ASSET_COUNT = 5000;
- let { isViewing: showAssetViewer } = assetViewingStore;
const viewport: Viewport = $state({ width: 0, height: 0 });
// The GalleryViewer pushes it's own history state, which causes weird
@@ -83,8 +89,17 @@
let timelineManager = new TimelineManager();
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const onEscape = () => {
- if ($showAssetViewer) {
+ if (assetManager.showAssetViewer) {
return;
}
@@ -379,6 +394,7 @@
{
await authenticate(url);
- const asset = await getAssetInfoFromParam(params);
const $t = await getFormatter();
return {
- asset,
+ assetId: params.assetId,
meta: {
title: $t('search'),
},
diff --git a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index d16ba622e9..86b61dfbd1 100644
--- a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -5,14 +5,14 @@
import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte';
import PasswordField from '$lib/components/shared-components/password-field.svelte';
import ThemeButton from '$lib/components/shared-components/theme-button.svelte';
- import { assetViewingStore } from '$lib/stores/asset-viewing.store';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
import { user } from '$lib/stores/user.store';
import { setSharedLink } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { navigate } from '$lib/utils/navigation';
import { getMySharedLink, SharedLinkType } from '@immich/sdk';
import { Button } from '@immich/ui';
- import { tick } from 'svelte';
+ import { onDestroy, tick } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
@@ -22,12 +22,20 @@
let { data }: Props = $props();
- let { gridScrollTarget } = assetViewingStore;
let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = $state(data);
let { title, description } = $state(meta);
let isOwned = $derived($user ? $user.id === sharedLink?.userId : false);
let password = $state('');
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
const handlePasswordSubmit = async () => {
try {
sharedLink = await getMySharedLink({ password, key });
@@ -39,7 +47,7 @@
$t('shared_photos_and_videos_count', { values: { assetCount: sharedLink.assets.length } });
await tick();
await navigate(
- { targetRoute: 'current', assetId: null, assetGridRouteSearchParams: $gridScrollTarget },
+ { targetRoute: 'current', assetId: null, assetGridRouteSearchParams: assetManager.gridScrollTarget },
{ forceNavigate: true, replaceState: true },
);
} catch (error) {
@@ -88,10 +96,10 @@
{/if}
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Album}
-
+
{/if}
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Individual}
-
+
{/if}
diff --git a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.ts
index c0edb5e669..164b433312 100644
--- a/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/share/[key]/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -1,7 +1,6 @@
import { getAssetThumbnailUrl, setSharedLink } from '$lib/utils';
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getAssetInfoFromParam } from '$lib/utils/navigation';
import { getMySharedLink, isHttpError } from '@immich/sdk';
import type { PageLoad } from './$types';
@@ -12,7 +11,7 @@ export const load = (async ({ params, url }) => {
const $t = await getFormatter();
try {
- const [sharedLink, asset] = await Promise.all([getMySharedLink({ key }), getAssetInfoFromParam(params)]);
+ const sharedLink = await getMySharedLink({ key });
setSharedLink(sharedLink);
const assetCount = sharedLink.assets.length;
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
@@ -21,7 +20,7 @@ export const load = (async ({ params, url }) => {
return {
sharedLink,
sharedLinkKey: key,
- asset,
+ assetId: params.assetId,
meta: {
title: sharedLink.album ? sharedLink.album.albumName : $t('public_share'),
description: sharedLink.description || $t('shared_photos_and_videos_count', { values: { assetCount } }),
diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 503ea72d54..e3ea9bf815 100644
--- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -20,6 +20,7 @@
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
+ import { AssetManager } from '$lib/managers/asset-manager.svelte';
interface Props {
data: PageData;
@@ -33,6 +34,15 @@
$effect(() => void timelineManager.updateOptions({ deferInit: !tag, tagId: tag?.id }));
onDestroy(() => timelineManager.destroy());
+ const assetManager = new AssetManager();
+ $effect(() => {
+ if (data.assetId) {
+ assetManager.showAssetViewer = true;
+ void assetManager.updateOptions({ assetId: data.assetId });
+ }
+ });
+ onDestroy(() => assetManager.destroy());
+
let tags = $derived(data.tags);
const tree = $derived(TreeNode.fromTags(tags));
const tag = $derived(tree.traverse(data.path));
@@ -118,7 +128,13 @@