1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-01 09:51:16 +02:00

refactor: auto advance asset viewer (#3554)

This commit is contained in:
Jason Rasmussen 2023-08-04 23:26:28 -04:00 committed by GitHub
parent c6abef186c
commit 68b5202730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 77 additions and 78 deletions

View File

@ -449,7 +449,7 @@
{/if} {/if}
{#if album.assetCount > 0} {#if album.assetCount > 0}
<GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} viewFrom="album-page" /> <GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} />
{:else} {:else}
<!-- Album is empty - Show asset selectection buttons --> <!-- Album is empty - Show asset selectection buttons -->
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center"> <section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">

View File

@ -30,7 +30,16 @@
export let showNavigation = true; export let showNavigation = true;
export let sharedLink: SharedLinkResponseDto | undefined = undefined; export let sharedLink: SharedLinkResponseDto | undefined = undefined;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
archived: AssetResponseDto;
unarchived: AssetResponseDto;
favorite: AssetResponseDto;
unfavorite: AssetResponseDto;
close: void;
next: void;
previous: void;
}>();
let appearsInAlbums: AlbumResponseDto[] = []; let appearsInAlbums: AlbumResponseDto[] = [];
let isShowAlbumPicker = false; let isShowAlbumPicker = false;
let isShowDeleteConfirmation = false; let isShowDeleteConfirmation = false;
@ -105,18 +114,16 @@
closeViewer(); closeViewer();
}; };
const closeViewer = () => { const closeViewer = () => dispatch('close');
dispatch('close');
};
const navigateAssetForward = (e?: Event) => { const navigateAssetForward = (e?: Event) => {
e?.stopPropagation(); e?.stopPropagation();
dispatch('navigate-next'); dispatch('next');
}; };
const navigateAssetBackward = (e?: Event) => { const navigateAssetBackward = (e?: Event) => {
e?.stopPropagation(); e?.stopPropagation();
dispatch('navigate-previous'); dispatch('previous');
}; };
const showDetailInfoHandler = () => { const showDetailInfoHandler = () => {
@ -159,7 +166,8 @@
}); });
asset.isFavorite = data.isFavorite; asset.isFavorite = data.isFavorite;
assetStore?.updateAsset(asset.id, data.isFavorite); assetStore?.updateAsset(data);
dispatch(data.isFavorite ? 'favorite' : 'unfavorite', data);
notificationController.show({ notificationController.show({
type: NotificationType.Info, type: NotificationType.Info,
@ -215,12 +223,8 @@
}); });
asset.isArchived = data.isArchived; asset.isArchived = data.isArchived;
assetStore?.updateAsset(data);
if (data.isArchived) { dispatch(data.isArchived ? 'archived' : 'unarchived', data);
dispatch('archived', data);
} else {
dispatch('unarchived', data);
}
notificationController.show({ notificationController.show({
type: NotificationType.Info, type: NotificationType.Info,

View File

@ -268,7 +268,7 @@
bottom={-200} bottom={-200}
> >
<div id="gallery-memory" bind:this={memoryGallery}> <div id="gallery-memory" bind:this={memoryGallery}>
<GalleryViewer assets={currentMemory.assets} viewFrom="album-page" /> <GalleryViewer assets={currentMemory.assets} />
</div> </div>
</IntersectionObserver> </IntersectionObserver>
</section> </section>

View File

@ -13,7 +13,7 @@
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants'; import { AppRoute, AssetAction } from '$lib/constants';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store'; import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
import { isSearchEnabled } from '$lib/stores/search.store'; import { isSearchEnabled } from '$lib/stores/search.store';
@ -22,6 +22,7 @@
export let isAlbumSelectionMode = false; export let isAlbumSelectionMode = false;
export let assetStore: AssetStore; export let assetStore: AssetStore;
export let assetInteractionStore: AssetInteractionStore; export let assetInteractionStore: AssetInteractionStore;
export let removeAction: AssetAction | null = null;
const { assetSelectionCandidates, assetSelectionStart, selectedAssets, isMultiSelectState } = assetInteractionStore; const { assetSelectionCandidates, assetSelectionStart, selectedAssets, isMultiSelectState } = assetInteractionStore;
const viewport: Viewport = { width: 0, height: 0 }; const viewport: Viewport = { width: 0, height: 0 };
@ -81,18 +82,34 @@
element.scrollBy(0, event.detail.heightDelta); element.scrollBy(0, event.detail.heightDelta);
} }
const navigateToPreviousAsset = async () => { const handlePrevious = async () => {
const prevAsset = await assetStore.getPreviousAssetId($viewingAsset.id); const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
if (prevAsset) { if (previousAsset) {
assetViewingStore.setAssetId(prevAsset); assetViewingStore.setAssetId(previousAsset);
} }
return !!previousAsset;
}; };
const navigateToNextAsset = async () => { const handleNext = async () => {
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id); const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
if (nextAsset) { if (nextAsset) {
assetViewingStore.setAssetId(nextAsset); assetViewingStore.setAssetId(nextAsset);
} }
return !!nextAsset;
};
const handleClose = () => assetViewingStore.showAssetViewer(false);
const handleAction = async (asset: AssetResponseDto, action: AssetAction) => {
if (removeAction === action) {
// find the next asset to show or close the viewer
(await handleNext()) || (await handlePrevious()) || handleClose();
// delete after find the next one
assetStore.removeAsset(asset.id);
}
}; };
let animationTick = false; let animationTick = false;
@ -109,12 +126,6 @@
}); });
}; };
const handleArchiveSuccess = (e: CustomEvent) => {
const asset = e.detail as AssetResponseDto;
navigateToNextAsset();
assetStore.removeAsset(asset.id);
};
let lastAssetMouseEvent: AssetResponseDto | null = null; let lastAssetMouseEvent: AssetResponseDto | null = null;
$: if (!lastAssetMouseEvent) { $: if (!lastAssetMouseEvent) {
@ -319,12 +330,13 @@
<AssetViewer <AssetViewer
{assetStore} {assetStore}
asset={$viewingAsset} asset={$viewingAsset}
on:navigate-previous={navigateToPreviousAsset} on:previous={() => handlePrevious()}
on:navigate-next={navigateToNextAsset} on:next={() => handleNext()}
on:close={() => { on:close={() => handleClose()}
assetViewingStore.showAssetViewer(false); on:archived={({ detail: asset }) => handleAction(asset, AssetAction.ARCHIVE)}
}} on:unarchived={({ detail: asset }) => handleAction(asset, AssetAction.UNARCHIVE)}
on:archived={handleArchiveSuccess} on:favorite={({ detail: asset }) => handleAction(asset, AssetAction.FAVORITE)}
on:unfavorite={({ detail: asset }) => handleAction(asset, AssetAction.UNFAVORITE)}
/> />
{/if} {/if}
</Portal> </Portal>

View File

@ -106,6 +106,6 @@
</ControlAppBar> </ControlAppBar>
{/if} {/if}
<section class="my-[160px] flex flex-col px-6 sm:px-12 md:px-24 lg:px-40"> <section class="my-[160px] flex flex-col px-6 sm:px-12 md:px-24 lg:px-40">
<GalleryViewer {assets} {sharedLink} bind:selectedAssets viewFrom="shared-link-page" /> <GalleryViewer {assets} {sharedLink} bind:selectedAssets />
</section> </section>
</section> </section>

View File

@ -1,7 +1,3 @@
<script lang="ts" context="module">
export type ViewFrom = 'archive-page' | 'album-page' | 'favorites-page' | 'search-page' | 'shared-link-page';
</script>
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
@ -9,7 +5,6 @@
import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api'; import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
import AssetViewer from '../../asset-viewer/asset-viewer.svelte'; import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { archivedAsset } from '$lib/stores/archived-asset.store';
import { getThumbnailSize } from '$lib/utils/thumbnail-util'; import { getThumbnailSize } from '$lib/utils/thumbnail-util';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@ -17,7 +12,6 @@
export let sharedLink: SharedLinkResponseDto | undefined = undefined; export let sharedLink: SharedLinkResponseDto | undefined = undefined;
export let selectedAssets: Set<AssetResponseDto> = new Set(); export let selectedAssets: Set<AssetResponseDto> = new Set();
export let disableAssetSelect = false; export let disableAssetSelect = false;
export let viewFrom: ViewFrom;
export let showArchiveIcon = false; export let showArchiveIcon = false;
let { isViewing: showAssetViewer } = assetViewingStore; let { isViewing: showAssetViewer } = assetViewingStore;
@ -86,16 +80,6 @@
$showAssetViewer = false; $showAssetViewer = false;
history.pushState(null, '', `${$page.url.pathname}`); history.pushState(null, '', `${$page.url.pathname}`);
}; };
const handleUnarchivedSuccess = (event: CustomEvent) => {
const asset = event.detail as AssetResponseDto;
switch (viewFrom) {
case 'archive-page':
$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
navigateAssetForward();
break;
}
};
</script> </script>
{#if assets.length > 0} {#if assets.length > 0}
@ -124,9 +108,8 @@
asset={selectedAsset} asset={selectedAsset}
publicSharedKey={sharedLink?.key} publicSharedKey={sharedLink?.key}
{sharedLink} {sharedLink}
on:navigate-previous={navigateAssetBackward} on:previous={navigateAssetBackward}
on:navigate-next={navigateAssetForward} on:next={navigateAssetForward}
on:close={closeViewer} on:close={closeViewer}
on:unarchived={handleUnarchivedSuccess}
/> />
{/if} {/if}

View File

@ -1,6 +1,13 @@
import { env } from '$env/dynamic/public'; import { env } from '$env/dynamic/public';
export const loginPageMessage: string | undefined = env.PUBLIC_LOGIN_PAGE_MESSAGE; export const loginPageMessage: string | undefined = env.PUBLIC_LOGIN_PAGE_MESSAGE;
export enum AssetAction {
ARCHIVE = 'archive',
UNARCHIVE = 'unarchive',
FAVORITE = 'favorite',
UNFAVORITE = 'unfavorite',
}
export enum AppRoute { export enum AppRoute {
ADMIN_USER_MANAGEMENT = '/admin/user-management', ADMIN_USER_MANAGEMENT = '/admin/user-management',
ADMIN_SETTINGS = '/admin/system-settings', ADMIN_SETTINGS = '/admin/system-settings',

View File

@ -1,4 +0,0 @@
import type { AssetResponseDto } from '@api';
import { writable } from 'svelte/store';
export const archivedAsset = writable<AssetResponseDto[]>([]);

View File

@ -146,13 +146,14 @@ export class AssetStore {
return this.assetToBucket[assetId]?.bucketIndex ?? null; return this.assetToBucket[assetId]?.bucketIndex ?? null;
} }
updateAsset(assetId: string, isFavorite: boolean) { updateAsset(_asset: AssetResponseDto) {
const asset = this.assets.find((asset) => asset.id === assetId); const asset = this.assets.find((asset) => asset.id === _asset.id);
if (!asset) { if (!asset) {
return; return;
} }
asset.isFavorite = isFavorite; Object.assign(asset, _asset);
this.emit(false); this.emit(false);
} }

View File

@ -11,6 +11,7 @@
import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte'; import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store'; import { AssetStore } from '$lib/stores/assets.store';
import { api, TimeBucketSize } from '@api'; import { api, TimeBucketSize } from '@api';
@ -53,7 +54,7 @@
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}> <UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
{#if assetCount} {#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} /> <AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNARCHIVE} />
{:else} {:else}
<EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" /> <EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" />
{/if} {/if}

View File

@ -11,6 +11,7 @@
import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte'; import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store'; import { AssetStore } from '$lib/stores/assets.store';
import { api, TimeBucketSize } from '@api'; import { api, TimeBucketSize } from '@api';
@ -54,7 +55,7 @@
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}> <UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
{#if assetCount} {#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} /> <AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNFAVORITE} />
{:else} {:else}
<EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" alt="Empty favorites" /> <EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" alt="Empty favorites" />
{/if} {/if}

View File

@ -143,11 +143,9 @@
<AssetViewer <AssetViewer
asset={$viewingAsset} asset={$viewingAsset}
showNavigation={viewingAssets.length > 1} showNavigation={viewingAssets.length > 1}
on:navigate-next={navigateNext} on:next={navigateNext}
on:navigate-previous={navigatePrevious} on:previous={navigatePrevious}
on:close={() => { on:close={() => assetViewingStore.showAssetViewer(false)}
assetViewingStore.showAssetViewer(false);
}}
/> />
{/if} {/if}
</Portal> </Portal>

View File

@ -242,7 +242,7 @@
<section class="relative mb-12 bg-immich-bg pt-8 dark:bg-immich-dark-bg sm:px-4"> <section class="relative mb-12 bg-immich-bg pt-8 dark:bg-immich-dark-bg sm:px-4">
<section class="immich-scrollbar relative overflow-y-scroll"> <section class="immich-scrollbar relative overflow-y-scroll">
<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg"> <section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
<GalleryViewer assets={data.assets} viewFrom="search-page" showArchiveIcon={true} bind:selectedAssets /> <GalleryViewer assets={data.assets} showArchiveIcon={true} bind:selectedAssets />
</section> </section>
</section> </section>
</section> </section>

View File

@ -12,6 +12,7 @@
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import MemoryLane from '$lib/components/photos-page/memory-lane.svelte'; import MemoryLane from '$lib/components/photos-page/memory-lane.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store'; import { AssetStore } from '$lib/stores/assets.store';
import { openFileUploadDialog } from '$lib/utils/file-uploader'; import { openFileUploadDialog } from '$lib/utils/file-uploader';
@ -31,7 +32,7 @@
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite); $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
onMount(async () => { onMount(async () => {
const { data: stats } = await api.assetApi.getAssetStats(); const { data: stats } = await api.assetApi.getAssetStats({ isArchived: false });
assetCount = stats.total; assetCount = stats.total;
}); });
</script> </script>
@ -57,7 +58,7 @@
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
{#if assetCount} {#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore}> <AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE}>
<MemoryLane /> <MemoryLane />
</AssetGrid> </AssetGrid>
{:else} {:else}

View File

@ -138,12 +138,7 @@
<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg"> <section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
{#if data.results?.assets?.items.length > 0} {#if data.results?.assets?.items.length > 0}
<div class="pl-4"> <div class="pl-4">
<GalleryViewer <GalleryViewer assets={searchResultAssets} bind:selectedAssets showArchiveIcon={true} />
assets={searchResultAssets}
bind:selectedAssets
viewFrom="search-page"
showArchiveIcon={true}
/>
</div> </div>
{:else} {:else}
<div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white"> <div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">

View File

@ -9,9 +9,9 @@
<AssetViewer <AssetViewer
asset={data.asset} asset={data.asset}
publicSharedKey={data.key} publicSharedKey={data.key}
on:navigate-previous={() => null}
on:navigate-next={() => null}
showNavigation={false} showNavigation={false}
on:previous={() => null}
on:next={() => null}
on:close={() => goto(`/share/${data.key}`)} on:close={() => goto(`/share/${data.key}`)}
/> />
{/if} {/if}