mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 14:35:25 -04:00
feat(web): persist people page scroll position when clicking back on person page
This commit is contained in:
parent
a3799b3053
commit
1eed0e14f5
@ -11,6 +11,8 @@
|
|||||||
export let scrollbar = true;
|
export let scrollbar = true;
|
||||||
export let admin = false;
|
export let admin = false;
|
||||||
|
|
||||||
|
export let scrollSlot: HTMLDivElement;
|
||||||
|
|
||||||
$: scrollbarClass = scrollbar ? 'immich-scrollbar p-2 pb-8' : 'scrollbar-hidden';
|
$: scrollbarClass = scrollbar ? 'immich-scrollbar p-2 pb-8' : 'scrollbar-hidden';
|
||||||
$: hasTitleClass = title ? 'top-16 h-[calc(100%-theme(spacing.16))]' : 'top-0 h-full';
|
$: hasTitleClass = title ? 'top-16 h-[calc(100%-theme(spacing.16))]' : 'top-0 h-full';
|
||||||
</script>
|
</script>
|
||||||
@ -49,7 +51,10 @@
|
|||||||
</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"
|
||||||
|
bind:this={scrollSlot}
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -78,6 +78,11 @@ export enum QueryParameter {
|
|||||||
PAGE = 'page',
|
PAGE = 'page',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { beforeNavigate, 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 Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
@ -17,7 +17,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';
|
||||||
@ -61,12 +61,61 @@
|
|||||||
let changeNameInputEl: HTMLInputElement | null;
|
let changeNameInputEl: HTMLInputElement | null;
|
||||||
let innerHeight: number;
|
let innerHeight: number;
|
||||||
|
|
||||||
|
let scrollSlot: HTMLDivElement;
|
||||||
|
beforeNavigate(({ to }) => {
|
||||||
|
// Save current scroll information when going into a person page.
|
||||||
|
if (to && to.url.pathname.startsWith(AppRoute.PEOPLE)) {
|
||||||
|
if (nextPage) sessionStorage.setItem(SessionStorageKey.INFINITE_SCROLL_PAGE, nextPage.toString());
|
||||||
|
sessionStorage.setItem(SessionStorageKey.SCROLL_POSITION, scrollSlot.scrollTop.toString());
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoreScrollPosition = () => {
|
||||||
|
let newScroll = sessionStorage.getItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
if (newScroll)
|
||||||
|
scrollSlot.scroll({
|
||||||
|
top: parseFloat(newScroll),
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
|
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
|
||||||
if (getSearchedPeople) {
|
if (getSearchedPeople) {
|
||||||
searchName = getSearchedPeople;
|
searchName = getSearchedPeople;
|
||||||
handlePromiseError(handleSearchPeople(true, searchName));
|
handlePromiseError(handleSearchPeople(true, searchName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load up to previously loaded page when returning.
|
||||||
|
let newNextPage = sessionStorage.getItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
if (newNextPage && nextPage) {
|
||||||
|
let startingPage = nextPage,
|
||||||
|
pagesToLoad = parseInt(newNextPage) - nextPage;
|
||||||
|
|
||||||
|
if (pagesToLoad) {
|
||||||
|
handlePromiseError(
|
||||||
|
Promise.all(
|
||||||
|
Array(pagesToLoad).map((_, i) => {
|
||||||
|
return getAllPeople({ withHidden: true, page: startingPage + i });
|
||||||
|
}),
|
||||||
|
).then((pages) => {
|
||||||
|
pages.forEach((page) => {
|
||||||
|
people = people.concat(page.people);
|
||||||
|
});
|
||||||
|
nextPage = pages[pages.length - 1].hasNextPage ? startingPage + pagesToLoad : null;
|
||||||
|
restoreScrollPosition(); // wait until extra pages are loaded
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
restoreScrollPosition();
|
||||||
|
}
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -311,6 +360,7 @@
|
|||||||
<UserPageLayout
|
<UserPageLayout
|
||||||
title={$t('people')}
|
title={$t('people')}
|
||||||
description={countVisiblePeople === 0 && !searchName ? undefined : `(${countVisiblePeople.toLocaleString($locale)})`}
|
description={countVisiblePeople === 0 && !searchName ? undefined : `(${countVisiblePeople.toLocaleString($locale)})`}
|
||||||
|
bind:scrollSlot
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="buttons">
|
<svelte:fragment slot="buttons">
|
||||||
{#if people.length > 0}
|
{#if people.length > 0}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterNavigate, goto } from '$app/navigation';
|
import { afterNavigate, beforeNavigate, goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
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';
|
||||||
@ -25,7 +25,7 @@
|
|||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { AppRoute, 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';
|
||||||
@ -126,6 +126,14 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeNavigate(({ to }) => {
|
||||||
|
// Forget scroll position from people page if going somewhere else.
|
||||||
|
if (to && !to.url.pathname.startsWith(AppRoute.PEOPLE)) {
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.INFINITE_SCROLL_PAGE);
|
||||||
|
sessionStorage.removeItem(SessionStorageKey.SCROLL_POSITION);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleEscape = async () => {
|
const handleEscape = async () => {
|
||||||
if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) {
|
if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) {
|
||||||
return;
|
return;
|
||||||
@ -187,7 +195,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'));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user