mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
refactor: auto advance asset viewer (#3554)
This commit is contained in:
parent
c6abef186c
commit
68b5202730
@ -449,7 +449,7 @@
|
||||
{/if}
|
||||
|
||||
{#if album.assetCount > 0}
|
||||
<GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} viewFrom="album-page" />
|
||||
<GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} />
|
||||
{:else}
|
||||
<!-- Album is empty - Show asset selectection buttons -->
|
||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||
|
@ -30,7 +30,16 @@
|
||||
export let showNavigation = true;
|
||||
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 isShowAlbumPicker = false;
|
||||
let isShowDeleteConfirmation = false;
|
||||
@ -105,18 +114,16 @@
|
||||
closeViewer();
|
||||
};
|
||||
|
||||
const closeViewer = () => {
|
||||
dispatch('close');
|
||||
};
|
||||
const closeViewer = () => dispatch('close');
|
||||
|
||||
const navigateAssetForward = (e?: Event) => {
|
||||
e?.stopPropagation();
|
||||
dispatch('navigate-next');
|
||||
dispatch('next');
|
||||
};
|
||||
|
||||
const navigateAssetBackward = (e?: Event) => {
|
||||
e?.stopPropagation();
|
||||
dispatch('navigate-previous');
|
||||
dispatch('previous');
|
||||
};
|
||||
|
||||
const showDetailInfoHandler = () => {
|
||||
@ -159,7 +166,8 @@
|
||||
});
|
||||
|
||||
asset.isFavorite = data.isFavorite;
|
||||
assetStore?.updateAsset(asset.id, data.isFavorite);
|
||||
assetStore?.updateAsset(data);
|
||||
dispatch(data.isFavorite ? 'favorite' : 'unfavorite', data);
|
||||
|
||||
notificationController.show({
|
||||
type: NotificationType.Info,
|
||||
@ -215,12 +223,8 @@
|
||||
});
|
||||
|
||||
asset.isArchived = data.isArchived;
|
||||
|
||||
if (data.isArchived) {
|
||||
dispatch('archived', data);
|
||||
} else {
|
||||
dispatch('unarchived', data);
|
||||
}
|
||||
assetStore?.updateAsset(data);
|
||||
dispatch(data.isArchived ? 'archived' : 'unarchived', data);
|
||||
|
||||
notificationController.show({
|
||||
type: NotificationType.Info,
|
||||
|
@ -268,7 +268,7 @@
|
||||
bottom={-200}
|
||||
>
|
||||
<div id="gallery-memory" bind:this={memoryGallery}>
|
||||
<GalleryViewer assets={currentMemory.assets} viewFrom="album-page" />
|
||||
<GalleryViewer assets={currentMemory.assets} />
|
||||
</div>
|
||||
</IntersectionObserver>
|
||||
</section>
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
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 { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
|
||||
import { isSearchEnabled } from '$lib/stores/search.store';
|
||||
@ -22,6 +22,7 @@
|
||||
export let isAlbumSelectionMode = false;
|
||||
export let assetStore: AssetStore;
|
||||
export let assetInteractionStore: AssetInteractionStore;
|
||||
export let removeAction: AssetAction | null = null;
|
||||
|
||||
const { assetSelectionCandidates, assetSelectionStart, selectedAssets, isMultiSelectState } = assetInteractionStore;
|
||||
const viewport: Viewport = { width: 0, height: 0 };
|
||||
@ -81,18 +82,34 @@
|
||||
element.scrollBy(0, event.detail.heightDelta);
|
||||
}
|
||||
|
||||
const navigateToPreviousAsset = async () => {
|
||||
const prevAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
||||
if (prevAsset) {
|
||||
assetViewingStore.setAssetId(prevAsset);
|
||||
const handlePrevious = async () => {
|
||||
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
||||
if (previousAsset) {
|
||||
assetViewingStore.setAssetId(previousAsset);
|
||||
}
|
||||
|
||||
return !!previousAsset;
|
||||
};
|
||||
|
||||
const navigateToNextAsset = async () => {
|
||||
const handleNext = async () => {
|
||||
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
|
||||
if (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;
|
||||
@ -109,12 +126,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleArchiveSuccess = (e: CustomEvent) => {
|
||||
const asset = e.detail as AssetResponseDto;
|
||||
navigateToNextAsset();
|
||||
assetStore.removeAsset(asset.id);
|
||||
};
|
||||
|
||||
let lastAssetMouseEvent: AssetResponseDto | null = null;
|
||||
|
||||
$: if (!lastAssetMouseEvent) {
|
||||
@ -319,12 +330,13 @@
|
||||
<AssetViewer
|
||||
{assetStore}
|
||||
asset={$viewingAsset}
|
||||
on:navigate-previous={navigateToPreviousAsset}
|
||||
on:navigate-next={navigateToNextAsset}
|
||||
on:close={() => {
|
||||
assetViewingStore.showAssetViewer(false);
|
||||
}}
|
||||
on:archived={handleArchiveSuccess}
|
||||
on:previous={() => handlePrevious()}
|
||||
on:next={() => handleNext()}
|
||||
on:close={() => handleClose()}
|
||||
on:archived={({ detail: asset }) => handleAction(asset, AssetAction.ARCHIVE)}
|
||||
on:unarchived={({ detail: asset }) => handleAction(asset, AssetAction.UNARCHIVE)}
|
||||
on:favorite={({ detail: asset }) => handleAction(asset, AssetAction.FAVORITE)}
|
||||
on:unfavorite={({ detail: asset }) => handleAction(asset, AssetAction.UNFAVORITE)}
|
||||
/>
|
||||
{/if}
|
||||
</Portal>
|
||||
|
@ -106,6 +106,6 @@
|
||||
</ControlAppBar>
|
||||
{/if}
|
||||
<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>
|
||||
|
@ -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">
|
||||
import { page } from '$app/stores';
|
||||
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
||||
@ -9,7 +5,6 @@
|
||||
import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
|
||||
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { archivedAsset } from '$lib/stores/archived-asset.store';
|
||||
import { getThumbnailSize } from '$lib/utils/thumbnail-util';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
|
||||
@ -17,7 +12,6 @@
|
||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||
export let disableAssetSelect = false;
|
||||
export let viewFrom: ViewFrom;
|
||||
export let showArchiveIcon = false;
|
||||
|
||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
@ -86,16 +80,6 @@
|
||||
$showAssetViewer = false;
|
||||
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>
|
||||
|
||||
{#if assets.length > 0}
|
||||
@ -124,9 +108,8 @@
|
||||
asset={selectedAsset}
|
||||
publicSharedKey={sharedLink?.key}
|
||||
{sharedLink}
|
||||
on:navigate-previous={navigateAssetBackward}
|
||||
on:navigate-next={navigateAssetForward}
|
||||
on:previous={navigateAssetBackward}
|
||||
on:next={navigateAssetForward}
|
||||
on:close={closeViewer}
|
||||
on:unarchived={handleUnarchivedSuccess}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
export const loginPageMessage: string | undefined = env.PUBLIC_LOGIN_PAGE_MESSAGE;
|
||||
|
||||
export enum AssetAction {
|
||||
ARCHIVE = 'archive',
|
||||
UNARCHIVE = 'unarchive',
|
||||
FAVORITE = 'favorite',
|
||||
UNFAVORITE = 'unfavorite',
|
||||
}
|
||||
|
||||
export enum AppRoute {
|
||||
ADMIN_USER_MANAGEMENT = '/admin/user-management',
|
||||
ADMIN_SETTINGS = '/admin/system-settings',
|
||||
|
@ -1,4 +0,0 @@
|
||||
import type { AssetResponseDto } from '@api';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const archivedAsset = writable<AssetResponseDto[]>([]);
|
@ -146,13 +146,14 @@ export class AssetStore {
|
||||
return this.assetToBucket[assetId]?.bucketIndex ?? null;
|
||||
}
|
||||
|
||||
updateAsset(assetId: string, isFavorite: boolean) {
|
||||
const asset = this.assets.find((asset) => asset.id === assetId);
|
||||
updateAsset(_asset: AssetResponseDto) {
|
||||
const asset = this.assets.find((asset) => asset.id === _asset.id);
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
|
||||
asset.isFavorite = isFavorite;
|
||||
Object.assign(asset, _asset);
|
||||
|
||||
this.emit(false);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
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 EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||
import { AssetStore } from '$lib/stores/assets.store';
|
||||
import { api, TimeBucketSize } from '@api';
|
||||
@ -53,7 +54,7 @@
|
||||
|
||||
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
|
||||
{#if assetCount}
|
||||
<AssetGrid {assetStore} {assetInteractionStore} />
|
||||
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNARCHIVE} />
|
||||
{:else}
|
||||
<EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" />
|
||||
{/if}
|
||||
|
@ -11,6 +11,7 @@
|
||||
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 EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||
import { AssetStore } from '$lib/stores/assets.store';
|
||||
import { api, TimeBucketSize } from '@api';
|
||||
@ -54,7 +55,7 @@
|
||||
|
||||
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
|
||||
{#if assetCount}
|
||||
<AssetGrid {assetStore} {assetInteractionStore} />
|
||||
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.UNFAVORITE} />
|
||||
{:else}
|
||||
<EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" alt="Empty favorites" />
|
||||
{/if}
|
||||
|
@ -143,11 +143,9 @@
|
||||
<AssetViewer
|
||||
asset={$viewingAsset}
|
||||
showNavigation={viewingAssets.length > 1}
|
||||
on:navigate-next={navigateNext}
|
||||
on:navigate-previous={navigatePrevious}
|
||||
on:close={() => {
|
||||
assetViewingStore.showAssetViewer(false);
|
||||
}}
|
||||
on:next={navigateNext}
|
||||
on:previous={navigatePrevious}
|
||||
on:close={() => assetViewingStore.showAssetViewer(false)}
|
||||
/>
|
||||
{/if}
|
||||
</Portal>
|
||||
|
@ -242,7 +242,7 @@
|
||||
<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 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>
|
||||
|
@ -12,6 +12,7 @@
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import MemoryLane from '$lib/components/photos-page/memory-lane.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 { AssetStore } from '$lib/stores/assets.store';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
@ -31,7 +32,7 @@
|
||||
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
|
||||
|
||||
onMount(async () => {
|
||||
const { data: stats } = await api.assetApi.getAssetStats();
|
||||
const { data: stats } = await api.assetApi.getAssetStats({ isArchived: false });
|
||||
assetCount = stats.total;
|
||||
});
|
||||
</script>
|
||||
@ -57,7 +58,7 @@
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{#if assetCount}
|
||||
<AssetGrid {assetStore} {assetInteractionStore}>
|
||||
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE}>
|
||||
<MemoryLane />
|
||||
</AssetGrid>
|
||||
{:else}
|
||||
|
@ -138,12 +138,7 @@
|
||||
<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
|
||||
{#if data.results?.assets?.items.length > 0}
|
||||
<div class="pl-4">
|
||||
<GalleryViewer
|
||||
assets={searchResultAssets}
|
||||
bind:selectedAssets
|
||||
viewFrom="search-page"
|
||||
showArchiveIcon={true}
|
||||
/>
|
||||
<GalleryViewer assets={searchResultAssets} bind:selectedAssets showArchiveIcon={true} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">
|
||||
|
@ -9,9 +9,9 @@
|
||||
<AssetViewer
|
||||
asset={data.asset}
|
||||
publicSharedKey={data.key}
|
||||
on:navigate-previous={() => null}
|
||||
on:navigate-next={() => null}
|
||||
showNavigation={false}
|
||||
on:previous={() => null}
|
||||
on:next={() => null}
|
||||
on:close={() => goto(`/share/${data.key}`)}
|
||||
/>
|
||||
{/if}
|
||||
|
Loading…
Reference in New Issue
Block a user