mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
feat(web): persist scroll position on navigation back to album (#11388)
Co-authored-by: Calum Dingwall <caburum@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
51de108d43
commit
d277096d58
8 changed files with 234 additions and 7 deletions
87
web/src/lib/actions/scroll-memory.ts
Normal file
87
web/src/lib/actions/scroll-memory.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import { navigating } from '$app/stores';
|
||||||
|
import { AppRoute, SessionStorageKey } from '$lib/constants';
|
||||||
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
routeStartsWith: AppRoute;
|
||||||
|
/**
|
||||||
|
* Function to clear additional data/state before scrolling (ex infinite scroll).
|
||||||
|
*/
|
||||||
|
beforeClear?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageOptions extends Options {
|
||||||
|
/**
|
||||||
|
* Function to save additional data/state before scrolling (ex infinite scroll).
|
||||||
|
*/
|
||||||
|
beforeSave?: () => void;
|
||||||
|
/**
|
||||||
|
* Function to load additional data/state before scrolling (ex infinite scroll).
|
||||||
|
*/
|
||||||
|
beforeScroll?: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param node The scroll slot element, typically from {@link UserPageLayout}
|
||||||
|
*/
|
||||||
|
export function scrollMemory(
|
||||||
|
node: HTMLElement,
|
||||||
|
{ routeStartsWith, beforeSave, beforeClear, beforeScroll }: PageOptions,
|
||||||
|
) {
|
||||||
|
const unsubscribeNavigating = navigating.subscribe((navigation) => {
|
||||||
|
const existingScroll = sessionStorage.getItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
if (navigation?.to && !existingScroll) {
|
||||||
|
// Save current scroll information when going into a subpage.
|
||||||
|
if (navigation.to.url.pathname.startsWith(routeStartsWith)) {
|
||||||
|
beforeSave?.();
|
||||||
|
sessionStorage.setItem(SessionStorageKey.SCROLL_POSITION, node.scrollTop.toString());
|
||||||
|
} else {
|
||||||
|
beforeClear?.();
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePromiseError(
|
||||||
|
(async () => {
|
||||||
|
await beforeScroll?.();
|
||||||
|
|
||||||
|
const newScroll = sessionStorage.getItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
if (newScroll) {
|
||||||
|
node.scroll({
|
||||||
|
top: Number.parseFloat(newScroll),
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
beforeClear?.();
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
unsubscribeNavigating();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scrollMemoryClearer(_node: HTMLElement, { routeStartsWith, beforeClear }: Options) {
|
||||||
|
const unsubscribeNavigating = navigating.subscribe((navigation) => {
|
||||||
|
// Forget scroll position from main page if going somewhere else.
|
||||||
|
if (navigation?.to && !navigation?.to.url.pathname.startsWith(routeStartsWith)) {
|
||||||
|
beforeClear?.();
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
unsubscribeNavigating();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
67
web/src/lib/actions/use-actions.ts
Normal file
67
web/src/lib/actions/use-actions.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* @license Apache-2.0
|
||||||
|
* https://github.com/hperrin/svelte-material-ui/blob/master/packages/common/src/internal/useActions.ts
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type SvelteActionReturnType<P> = {
|
||||||
|
update?: (newParams?: P) => void;
|
||||||
|
destroy?: () => void;
|
||||||
|
} | void;
|
||||||
|
|
||||||
|
export type SvelteHTMLActionType<P> = (node: HTMLElement, params?: P) => SvelteActionReturnType<P>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export type HTMLActionEntry<P = any> = SvelteHTMLActionType<P> | [SvelteHTMLActionType<P>, P];
|
||||||
|
|
||||||
|
export type HTMLActionArray = HTMLActionEntry[];
|
||||||
|
|
||||||
|
export type SvelteSVGActionType<P> = (node: SVGElement, params?: P) => SvelteActionReturnType<P>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export type SVGActionEntry<P = any> = SvelteSVGActionType<P> | [SvelteSVGActionType<P>, P];
|
||||||
|
|
||||||
|
export type SVGActionArray = SVGActionEntry[];
|
||||||
|
|
||||||
|
export type ActionArray = HTMLActionArray | SVGActionArray;
|
||||||
|
|
||||||
|
export function useActions(node: HTMLElement | SVGElement, actions: ActionArray) {
|
||||||
|
const actionReturns: SvelteActionReturnType<unknown>[] = [];
|
||||||
|
|
||||||
|
if (actions) {
|
||||||
|
for (const actionEntry of actions) {
|
||||||
|
const action = Array.isArray(actionEntry) ? actionEntry[0] : actionEntry;
|
||||||
|
if (Array.isArray(actionEntry) && actionEntry.length > 1) {
|
||||||
|
actionReturns.push(action(node as HTMLElement & SVGElement, actionEntry[1]));
|
||||||
|
} else {
|
||||||
|
actionReturns.push(action(node as HTMLElement & SVGElement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(actions: ActionArray) {
|
||||||
|
if ((actions?.length || 0) != actionReturns.length) {
|
||||||
|
throw new Error('You must not change the length of an actions array.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions) {
|
||||||
|
for (const [i, returnEntry] of actionReturns.entries()) {
|
||||||
|
if (returnEntry && returnEntry.update) {
|
||||||
|
const actionEntry = actions[i];
|
||||||
|
if (Array.isArray(actionEntry) && actionEntry.length > 1) {
|
||||||
|
returnEntry.update(actionEntry[1]);
|
||||||
|
} else {
|
||||||
|
returnEntry.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
for (const returnEntry of actionReturns) {
|
||||||
|
returnEntry?.destroy?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte';
|
import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte';
|
||||||
import SideBar from '../shared-components/side-bar/side-bar.svelte';
|
import SideBar from '../shared-components/side-bar/side-bar.svelte';
|
||||||
import AdminSideBar from '../shared-components/side-bar/admin-side-bar.svelte';
|
import AdminSideBar from '../shared-components/side-bar/admin-side-bar.svelte';
|
||||||
|
import { useActions, type ActionArray } from '$lib/actions/use-actions';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
description?: string | undefined;
|
description?: string | undefined;
|
||||||
scrollbar?: boolean;
|
scrollbar?: boolean;
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
|
use?: ActionArray;
|
||||||
header?: Snippet;
|
header?: Snippet;
|
||||||
sidebar?: Snippet;
|
sidebar?: Snippet;
|
||||||
buttons?: Snippet;
|
buttons?: Snippet;
|
||||||
|
@ -29,6 +31,7 @@
|
||||||
description = undefined,
|
description = undefined,
|
||||||
scrollbar = true,
|
scrollbar = true,
|
||||||
admin = false,
|
admin = false,
|
||||||
|
use = [],
|
||||||
header,
|
header,
|
||||||
sidebar,
|
sidebar,
|
||||||
buttons,
|
buttons,
|
||||||
|
@ -73,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="{scrollbarClass} scrollbar-stable absolute {hasTitleClass} w-full overflow-y-auto">
|
<div class="{scrollbarClass} scrollbar-stable absolute {hasTitleClass} w-full overflow-y-auto" use:useActions={use}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -84,6 +84,11 @@ export enum QueryParameter {
|
||||||
PATH = 'path',
|
PATH = 'path',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SessionStorageKey {
|
||||||
|
INFINITE_SCROLL_PAGE = 'infiniteScrollPage',
|
||||||
|
SCROLL_POSITION = 'scrollPosition',
|
||||||
|
}
|
||||||
|
|
||||||
export enum OpenSettingQueryParameterValue {
|
export enum OpenSettingQueryParameterValue {
|
||||||
OAUTH = 'oauth',
|
OAUTH = 'oauth',
|
||||||
JOB = 'job',
|
JOB = 'job',
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import { scrollMemory } from '$lib/actions/scroll-memory';
|
||||||
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 UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import GroupTab from '$lib/components/elements/group-tab.svelte';
|
import GroupTab from '$lib/components/elements/group-tab.svelte';
|
||||||
import SearchBar from '$lib/components/elements/search-bar.svelte';
|
import SearchBar from '$lib/components/elements/search-bar.svelte';
|
||||||
|
import { AppRoute } from '$lib/constants';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -20,7 +22,7 @@
|
||||||
let albumGroups: string[] = $state([]);
|
let albumGroups: string[] = $state([]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout title={data.meta.title}>
|
<UserPageLayout title={data.meta.title} use={[[scrollMemory, { routeStartsWith: AppRoute.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 />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterNavigate, goto, onNavigate } from '$app/navigation';
|
import { afterNavigate, goto, onNavigate } from '$app/navigation';
|
||||||
|
import { scrollMemoryClearer } from '$lib/actions/scroll-memory';
|
||||||
import AlbumDescription from '$lib/components/album-page/album-description.svelte';
|
import AlbumDescription from '$lib/components/album-page/album-description.svelte';
|
||||||
import AlbumOptions from '$lib/components/album-page/album-options.svelte';
|
import AlbumOptions from '$lib/components/album-page/album-options.svelte';
|
||||||
import AlbumSummary from '$lib/components/album-page/album-summary.svelte';
|
import AlbumSummary from '$lib/components/album-page/album-summary.svelte';
|
||||||
|
@ -430,7 +431,7 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex overflow-hidden">
|
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
|
||||||
<div class="relative w-full shrink">
|
<div class="relative w-full shrink">
|
||||||
{#if $isMultiSelectState}
|
{#if $isMultiSelectState}
|
||||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { focusTrap } from '$lib/actions/focus-trap';
|
import { focusTrap } from '$lib/actions/focus-trap';
|
||||||
|
import { scrollMemory } from '$lib/actions/scroll-memory';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { ActionQueryParameterValue, AppRoute, QueryParameter } from '$lib/constants';
|
import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { websocketEvents } from '$lib/stores/websocket';
|
import { websocketEvents } from '$lib/stores/websocket';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
let showSetBirthDateModal = $state(false);
|
let showSetBirthDateModal = $state(false);
|
||||||
let showMergeModal = $state(false);
|
let showMergeModal = $state(false);
|
||||||
let personName = $state('');
|
let personName = $state('');
|
||||||
|
let currentPage = $state(1);
|
||||||
let nextPage = $state(data.people.hasNextPage ? 2 : null);
|
let nextPage = $state(data.people.hasNextPage ? 2 : null);
|
||||||
let personMerge1 = $state<PersonResponseDto>();
|
let personMerge1 = $state<PersonResponseDto>();
|
||||||
let personMerge2 = $state<PersonResponseDto>();
|
let personMerge2 = $state<PersonResponseDto>();
|
||||||
|
@ -68,6 +70,7 @@
|
||||||
handlePromiseError(searchPeopleElement.searchPeople(true, searchName));
|
handlePromiseError(searchPeopleElement.searchPeople(true, searchName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return websocketEvents.on('on_person_thumbnail', (personId: string) => {
|
return websocketEvents.on('on_person_thumbnail', (personId: string) => {
|
||||||
for (const person of people) {
|
for (const person of people) {
|
||||||
if (person.id === personId) {
|
if (person.id === personId) {
|
||||||
|
@ -77,6 +80,36 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loadInitialScroll = () =>
|
||||||
|
new Promise<void>((resolve) => {
|
||||||
|
// Load up to previously loaded page when returning.
|
||||||
|
let newNextPage = sessionStorage.getItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
if (newNextPage && nextPage) {
|
||||||
|
let startingPage = nextPage,
|
||||||
|
pagesToLoad = Number.parseInt(newNextPage) - nextPage;
|
||||||
|
|
||||||
|
if (pagesToLoad) {
|
||||||
|
handlePromiseError(
|
||||||
|
Promise.all(
|
||||||
|
Array.from({ length: pagesToLoad }).map((_, i) => {
|
||||||
|
return getAllPeople({ withHidden: true, page: startingPage + i });
|
||||||
|
}),
|
||||||
|
).then((pages) => {
|
||||||
|
for (const page of pages) {
|
||||||
|
people = people.concat(page.people);
|
||||||
|
}
|
||||||
|
currentPage = startingPage + pagesToLoad - 1;
|
||||||
|
nextPage = pages.at(-1)?.hasNextPage ? startingPage + pagesToLoad : null;
|
||||||
|
resolve(); // wait until extra pages are loaded
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const loadNextPage = async () => {
|
const loadNextPage = async () => {
|
||||||
if (!nextPage) {
|
if (!nextPage) {
|
||||||
return;
|
return;
|
||||||
|
@ -85,6 +118,9 @@
|
||||||
try {
|
try {
|
||||||
const { people: newPeople, hasNextPage } = await getAllPeople({ withHidden: true, page: nextPage });
|
const { people: newPeople, hasNextPage } = await getAllPeople({ withHidden: true, page: nextPage });
|
||||||
people = people.concat(newPeople);
|
people = people.concat(newPeople);
|
||||||
|
if (nextPage !== null) {
|
||||||
|
currentPage = nextPage;
|
||||||
|
}
|
||||||
nextPage = hasNextPage ? nextPage + 1 : null;
|
nextPage = hasNextPage ? nextPage + 1 : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.failed_to_load_people'));
|
handleError(error, $t('errors.failed_to_load_people'));
|
||||||
|
@ -323,6 +359,23 @@
|
||||||
<UserPageLayout
|
<UserPageLayout
|
||||||
title={$t('people')}
|
title={$t('people')}
|
||||||
description={countVisiblePeople === 0 && !searchName ? undefined : `(${countVisiblePeople.toLocaleString($locale)})`}
|
description={countVisiblePeople === 0 && !searchName ? undefined : `(${countVisiblePeople.toLocaleString($locale)})`}
|
||||||
|
use={[
|
||||||
|
[
|
||||||
|
scrollMemory,
|
||||||
|
{
|
||||||
|
routeStartsWith: AppRoute.PEOPLE,
|
||||||
|
beforeSave: () => {
|
||||||
|
if (currentPage) {
|
||||||
|
sessionStorage.setItem(SessionStorageKey.INFINITE_SCROLL_PAGE, currentPage.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeClear: () => {
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
},
|
||||||
|
beforeLoad: loadInitialScroll,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{#snippet buttons()}
|
{#snippet buttons()}
|
||||||
{#if people.length > 0}
|
{#if people.length > 0}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterNavigate, goto } from '$app/navigation';
|
import { afterNavigate, goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import { scrollMemoryClearer } from '$lib/actions/scroll-memory';
|
||||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||||
import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
|
import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
|
||||||
import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte';
|
import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte';
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute, PersonPageViewMode, QueryParameter } from '$lib/constants';
|
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { AssetStore } from '$lib/stores/assets.store';
|
import { AssetStore } from '$lib/stores/assets.store';
|
||||||
|
@ -164,7 +165,7 @@
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
});
|
});
|
||||||
|
|
||||||
await goto(previousRoute, { replaceState: true });
|
await goto(previousRoute);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_hide_person'));
|
handleError(error, $t('errors.unable_to_hide_person'));
|
||||||
}
|
}
|
||||||
|
@ -431,7 +432,15 @@
|
||||||
{/if}
|
{/if}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="relative h-screen overflow-hidden bg-immich-bg tall:ml-4 pt-[var(--navbar-height)] dark:bg-immich-dark-bg">
|
<main
|
||||||
|
class="relative h-screen overflow-hidden bg-immich-bg tall:ml-4 pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
||||||
|
use:scrollMemoryClearer={{
|
||||||
|
routeStartsWith: AppRoute.PEOPLE,
|
||||||
|
beforeClear: () => {
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
{#key person.id}
|
{#key person.id}
|
||||||
<AssetGrid
|
<AssetGrid
|
||||||
enableRouting={true}
|
enableRouting={true}
|
||||||
|
|
Loading…
Reference in a new issue