Files
immich/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
T
Min Idzelis 837b1e4929 feat(web): Scroll to asset in gridview; increase gridview perf; reduce memory; scrollbar ticks in fixed position (#10646)
* Squashed

* Change strategy - now pre-measure buckets offscreen, so don't need to worry about sub-bucket scroll preservation

* Reduce jank on scroll, delay DOM updates until after scroll

* css opt, log measure time

* Trickle out queue while scrolling, flush when stopped

* yay

* Cleanup cleanup...

* everybody...

* everywhere...

* Clean up cleanup!

* Everybody do their share

* CLEANUP!

* package-lock ?

* dynamic measure, todo

* Fix web test

* type lint

* fix e2e

* e2e test

* Better scrollbar

* Tuning, and more tunables

* Tunable tweaks, more tunables

* Scrollbar dots and viewport events

* lint

* Tweaked tunnables, use requestIdleCallback for garbage tasks, bug fixes

* New tunables, and don't update url by default

* Bug fixes

* Bug fix, with debug

* Fix flickr, fix graybox bug, reduced debug

* Refactor/cleanup

* Fix

* naming

* Final cleanup

* review comment

* Forgot to update this after naming change

* scrubber works, with debug

* cleanup

* Rename scrollbar to scrubber

* rename  to

* left over rename and change to previous album bar

* bugfix addassets, comments

* missing destroy(), cleanup

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-08-21 21:15:21 -05:00

127 lines
4.9 KiB
Svelte

<script lang="ts">
import { goto } from '$app/navigation';
import empty3Url from '$lib/assets/empty-3.svg';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
import RestoreAssets from '$lib/components/photos-page/actions/restore-assets.svelte';
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store';
import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error';
import { emptyTrash, restoreTrash } from '@immich/sdk';
import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js';
import type { PageData } from './$types';
import { handlePromiseError } from '$lib/utils';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
import { t } from 'svelte-i18n';
import { onDestroy } from 'svelte';
export let data: PageData;
if (!$featureFlags.trash) {
handlePromiseError(goto(AppRoute.PHOTOS));
}
const assetStore = new AssetStore({ isTrashed: true });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
const handleEmptyTrash = async () => {
const isConfirmed = await dialogController.show({
prompt: $t('empty_trash_confirmation'),
});
if (!isConfirmed) {
return;
}
try {
await emptyTrash();
const deletedAssetIds = assetStore.assets.map((a) => a.id);
const numberOfAssets = deletedAssetIds.length;
assetStore.removeAssets(deletedAssetIds);
notificationController.show({
message: $t('assets_permanently_deleted_count', { values: { count: numberOfAssets } }),
type: NotificationType.Info,
});
} catch (error) {
handleError(error, $t('errors.unable_to_empty_trash'));
}
};
const handleRestoreTrash = async () => {
const isConfirmed = await dialogController.show({
prompt: $t('assets_restore_confirmation'),
});
if (!isConfirmed) {
return;
}
try {
await restoreTrash();
const restoredAssetIds = assetStore.assets.map((a) => a.id);
const numberOfAssets = restoredAssetIds.length;
assetStore.removeAssets(restoredAssetIds);
notificationController.show({
message: $t('assets_restored_count', { values: { count: numberOfAssets } }),
type: NotificationType.Info,
});
} catch (error) {
handleError(error, $t('errors.unable_to_restore_trash'));
}
};
onDestroy(() => {
assetStore.destroy();
});
</script>
{#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<SelectAllAssets {assetStore} {assetInteractionStore} />
<DeleteAssets force onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
<RestoreAssets onRestore={(assetIds) => assetStore.removeAssets(assetIds)} />
</AssetSelectControlBar>
{/if}
{#if $featureFlags.loaded && $featureFlags.trash}
<UserPageLayout hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={false}>
<div class="flex place-items-center gap-2" slot="buttons">
<LinkButton on:click={handleRestoreTrash} disabled={$isMultiSelectState}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiHistory} size="18" />
{$t('restore_all')}
</div>
</LinkButton>
<LinkButton on:click={() => handleEmptyTrash()} disabled={$isMultiSelectState}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDeleteForeverOutline} size="18" />
{$t('empty_trash')}
</div>
</LinkButton>
</div>
<AssetGrid enableRouting={true} {assetStore} {assetInteractionStore}>
<p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4">
{$t('trashed_items_will_be_permanently_deleted_after', { values: { days: $serverConfig.trashDays } })}
</p>
<EmptyPlaceholder text={$t('trash_no_results_message')} src={empty3Url} slot="empty" />
</AssetGrid>
</UserPageLayout>
{/if}