refactor(web): asset grid stores (#3464)

* Refactor asset grid stores

* Iterate over buckets with for..of loop

* Rebase on top of main branch changes
This commit is contained in:
Sergey Kondrikov 2023-08-01 04:27:56 +03:00 committed by GitHub
parent 13051c1e5a
commit 5f9dfa9493
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 330 additions and 265 deletions

View file

@ -43,13 +43,15 @@
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { handleError } from '../../utils/handle-error';
import { downloadArchive } from '../../utils/asset-utils';
import { isViewingAssetStoreState } from '$lib/stores/asset-interaction.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
export let album: AlbumResponseDto;
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
let { isViewing: showAssetViewer } = assetViewingStore;
let isShowAssetSelection = false;
let isShowShareLinkModal = false;
@ -141,7 +143,7 @@
});
const handleKeyboardPress = (event: KeyboardEvent) => {
if (!$isViewingAssetStoreState) {
if (!$showAssetViewer) {
switch (event.key) {
case 'Escape':
if (isMultiSelectionMode) {

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { assetInteractionStore, assetsInAlbumStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
import { locale } from '$lib/stores/preferences.store';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import type { AssetResponseDto } from '@api';
@ -9,14 +8,20 @@
import Button from '../elements/buttons/button.svelte';
import AssetGrid from '../photos-page/asset-grid.svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import { createAssetStore } from '$lib/stores/assets.store';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
const dispatch = createEventDispatcher();
const assetStore = createAssetStore();
const assetInteractionStore = createAssetInteractionStore();
const { selectedAssets, assetsInAlbumState } = assetInteractionStore;
export let albumId: string;
export let assetsInAlbum: AssetResponseDto[];
onMount(() => {
$assetsInAlbumStoreState = assetsInAlbum;
$assetsInAlbumState = assetsInAlbum;
});
const addSelectedAssets = async () => {
@ -64,6 +69,6 @@
</svelte:fragment>
</ControlAppBar>
<section class="grid h-screen bg-immich-bg pl-[70px] pt-[100px] dark:bg-immich-dark-bg">
<AssetGrid isAlbumSelectionMode={true} />
<AssetGrid {assetStore} {assetInteractionStore} isAlbumSelectionMode={true} />
</section>
</section>

View file

@ -17,13 +17,14 @@
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte';
import { assetStore } from '$lib/stores/assets.store';
import { isShowDetail } from '$lib/stores/preferences.store';
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
import NavigationArea from './navigation-area.svelte';
import { browser } from '$app/environment';
import { handleError } from '$lib/utils/handle-error';
import type { AssetStore } from '$lib/stores/assets.store';
export let assetStore: AssetStore | null = null;
export let asset: AssetResponseDto;
export let publicSharedKey = '';
export let showNavigation = true;
@ -134,7 +135,7 @@
for (const asset of deletedAssets) {
if (asset.status == 'SUCCESS') {
assetStore.removeAsset(asset.id);
assetStore?.removeAsset(asset.id);
}
}
} catch (e) {
@ -158,7 +159,7 @@
});
asset.isFavorite = data.isFavorite;
assetStore.updateAsset(asset.id, data.isFavorite);
assetStore?.updateAsset(asset.id, data.isFavorite);
notificationController.show({
type: NotificationType.Info,

View file

@ -1,28 +1,30 @@
<script lang="ts">
import { get } from 'svelte/store';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import SelectAll from 'svelte-material-icons/SelectAll.svelte';
import TimerSand from 'svelte-material-icons/TimerSand.svelte';
import { assetInteractionStore } from '$lib/stores/asset-interaction.store';
import { assetGridState, assetStore } from '$lib/stores/assets.store';
import { handleError } from '../../../utils/handle-error';
import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
import { BucketPosition } from '$lib/models/asset-grid-state';
import type { AssetStore } from '$lib/stores/assets.store';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
export let assetStore: AssetStore;
export let assetInteractionStore: AssetInteractionStore;
let selecting = false;
const handleSelectAll = async () => {
try {
selecting = true;
let _assetGridState = new AssetGridState();
assetGridState.subscribe((state) => {
_assetGridState = state;
});
for (let i = 0; i < _assetGridState.buckets.length; i++) {
await assetStore.getAssetsByBucket(_assetGridState.buckets[i].bucketDate, BucketPosition.Unknown);
for (const asset of _assetGridState.buckets[i].assets) {
const assetGridState = get(assetStore);
for (const bucket of assetGridState.buckets) {
await assetStore.getAssetsByBucket(bucket.bucketDate, BucketPosition.Unknown);
for (const asset of bucket.assets) {
assetInteractionStore.addAssetToMultiselectGroup(asset);
}
}
selecting = false;
} catch (e) {
handleError(e, 'Error selecting all assets');

View file

@ -1,13 +1,4 @@
<script lang="ts">
import {
assetInteractionStore,
assetSelectionCandidates,
assetsInAlbumStoreState,
isMultiSelectStoreState,
selectedAssets,
selectedGroup,
} from '$lib/stores/asset-interaction.store';
import { assetStore } from '$lib/stores/assets.store';
import { locale } from '$lib/stores/preferences.store';
import { getAssetRatio } from '$lib/utils/asset-utils';
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
@ -19,6 +10,9 @@
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
import { fly } from 'svelte/transition';
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import type { AssetStore } from '$lib/stores/assets.store';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
export let assets: AssetResponseDto[];
export let bucketDate: string;
@ -26,6 +20,12 @@
export let isAlbumSelectionMode = false;
export let viewportWidth: number;
export let assetStore: AssetStore;
export let assetInteractionStore: AssetInteractionStore;
const { selectedGroup, selectedAssets, assetsInAlbumState, assetSelectionCandidates, isMultiSelectState } =
assetInteractionStore;
const dispatch = createEventDispatcher();
let isMouseOverGroup = false;
@ -94,10 +94,10 @@
return;
}
if ($isMultiSelectStoreState) {
if ($isMultiSelectState) {
assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
} else {
assetInteractionStore.setViewingAsset(asset);
assetViewingStore.setAssetId(asset.id);
}
};
@ -137,7 +137,7 @@
// Show multi select icon on hover on date group
hoveredDateGroup = dateGroupTitle;
if ($isMultiSelectStoreState) {
if ($isMultiSelectState) {
dispatch('selectAssetCandidates', { asset });
}
};
@ -207,9 +207,9 @@
on:click={() => assetClickHandler(asset, assetsInDateGroup, dateGroupTitle)}
on:select={() => assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle)}
on:mouse-event={() => assetMouseEventHandler(dateGroupTitle, asset)}
selected={$selectedAssets.has(asset) || $assetsInAlbumStoreState.some(({ id }) => id === asset.id)}
selected={$selectedAssets.has(asset) || $assetsInAlbumState.some(({ id }) => id === asset.id)}
selectionCandidate={$assetSelectionCandidates.has(asset)}
disabled={$assetsInAlbumStoreState.some(({ id }) => id === asset.id)}
disabled={$assetsInAlbumState.some(({ id }) => id === asset.id)}
thumbnailWidth={box.width}
thumbnailHeight={box.height}
/>

View file

@ -1,15 +1,6 @@
<script lang="ts">
import { BucketPosition } from '$lib/models/asset-grid-state';
import {
assetInteractionStore,
assetSelectionCandidates,
assetSelectionStart,
isMultiSelectStoreState,
isViewingAssetStoreState,
selectedAssets,
viewingAssetStoreState,
} from '$lib/stores/asset-interaction.store';
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { locale } from '$lib/stores/preferences.store';
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
import type { UserResponseDto } from '@api';
@ -31,11 +22,20 @@
import { browser } from '$app/environment';
import { isSearchEnabled } from '$lib/stores/search.store';
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
import type { AssetStore } from '$lib/stores/assets.store';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
export let user: UserResponseDto | undefined = undefined;
export let isAlbumSelectionMode = false;
export let showMemoryLane = false;
export let assetStore: AssetStore;
export let assetInteractionStore: AssetInteractionStore;
const { assetSelectionCandidates, assetSelectionStart, selectedAssets, isMultiSelectState } = assetInteractionStore;
let { isViewing: showAssetViewer, asset: viewingAsset } = assetViewingStore;
let viewportHeight = 0;
let viewportWidth = 0;
let assetGridElement: HTMLElement;
@ -61,7 +61,7 @@
// Get asset bucket if bucket height is smaller than viewport height
let bucketsToFetchInitially: string[] = [];
let initialBucketsHeight = 0;
$assetGridState.buckets.every((bucket) => {
$assetStore.buckets.every((bucket) => {
if (initialBucketsHeight < viewportHeight) {
initialBucketsHeight += bucket.bucketHeight;
bucketsToFetchInitially.push(bucket.bucketDate);
@ -89,7 +89,7 @@
return;
}
if (!$isViewingAssetStoreState) {
if (!$showAssetViewer) {
switch (event.key) {
case 'Escape':
assetInteractionStore.clearMultiselect();
@ -121,12 +121,18 @@
assetGridElement.scrollBy(0, event.detail.heightDelta);
}
const navigateToPreviousAsset = () => {
assetInteractionStore.navigateAsset('previous');
const navigateToPreviousAsset = async () => {
const prevAsset = await assetStore.getAdjacentAsset($viewingAsset.id, 'previous');
if (prevAsset) {
assetViewingStore.setAssetId(prevAsset);
}
};
const navigateToNextAsset = () => {
assetInteractionStore.navigateAsset('next');
const navigateToNextAsset = async () => {
const nextAsset = await assetStore.getAdjacentAsset($viewingAsset.id, 'next');
if (nextAsset) {
assetViewingStore.setAssetId(nextAsset);
}
};
let lastScrollPosition = 0;
@ -228,8 +234,8 @@
assetInteractionStore.clearAssetSelectionCandidates();
if ($assetSelectionStart && rangeSelection) {
let startBucketIndex = $assetGridState.loadedAssets[$assetSelectionStart.id];
let endBucketIndex = $assetGridState.loadedAssets[asset.id];
let startBucketIndex = $assetStore.loadedAssets[$assetSelectionStart.id];
let endBucketIndex = $assetStore.loadedAssets[asset.id];
if (endBucketIndex < startBucketIndex) {
[startBucketIndex, endBucketIndex] = [endBucketIndex, startBucketIndex];
@ -237,7 +243,7 @@
// Select/deselect assets in all intermediate buckets
for (let bucketIndex = startBucketIndex + 1; bucketIndex < endBucketIndex; bucketIndex++) {
const bucket = $assetGridState.buckets[bucketIndex];
const bucket = $assetStore.buckets[bucketIndex];
await assetStore.getAssetsByBucket(bucket.bucketDate, BucketPosition.Unknown);
for (const asset of bucket.assets) {
if (deselect) {
@ -250,7 +256,7 @@
// Update date group selection
for (let bucketIndex = startBucketIndex; bucketIndex <= endBucketIndex; bucketIndex++) {
const bucket = $assetGridState.buckets[bucketIndex];
const bucket = $assetStore.buckets[bucketIndex];
// Split bucket into date groups and check each group
const assetsGroupByDate = splitBucketIntoDateGroups(bucket.assets, $locale);
@ -279,18 +285,18 @@
return;
}
let start = $assetGridState.assets.indexOf(rangeStart);
let end = $assetGridState.assets.indexOf(asset);
let start = $assetStore.assets.indexOf(rangeStart);
let end = $assetStore.assets.indexOf(asset);
if (start > end) {
[start, end] = [end, start];
}
assetInteractionStore.setAssetSelectionCandidates($assetGridState.assets.slice(start, end + 1));
assetInteractionStore.setAssetSelectionCandidates($assetStore.assets.slice(start, end + 1));
};
const onSelectStart = (e: Event) => {
if ($isMultiSelectStoreState && shiftKeyIsDown) {
if ($isMultiSelectState && shiftKeyIsDown) {
e.preventDefault();
}
};
@ -302,8 +308,9 @@
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
{/if}
{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
{#if bucketInfo && viewportHeight && $assetStore.timelineHeight > viewportHeight}
<Scrollbar
{assetStore}
scrollbarHeight={viewportHeight}
scrollTop={lastScrollPosition}
on:onscrollbarclick={(e) => handleScrollbarClick(e.detail)}
@ -324,15 +331,12 @@
{#if showMemoryLane}
<MemoryLane />
{/if}
<section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
{#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)}
<section id="virtual-timeline" style:height={$assetStore.timelineHeight + 'px'}>
{#each $assetStore.buckets as bucket, bucketIndex (bucketIndex)}
<IntersectionObserver
on:intersected={intersectedHandler}
on:hidden={async () => {
// If bucket is hidden and in loading state, cancel the request
if ($loadingBucketState[bucket.bucketDate]) {
await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
}
await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
}}
let:intersecting
top={750}
@ -342,6 +346,8 @@
<div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
{#if intersecting}
<AssetDateGroup
{assetStore}
{assetInteractionStore}
{isAlbumSelectionMode}
on:shift={handleScrollTimeline}
on:selectAssetCandidates={handleSelectAssetCandidates}
@ -360,13 +366,14 @@
</section>
<Portal target="body">
{#if $isViewingAssetStoreState}
{#if $showAssetViewer}
<AssetViewer
asset={$viewingAssetStoreState}
{assetStore}
asset={$viewingAsset}
on:navigate-previous={navigateToPreviousAsset}
on:navigate-next={navigateToNextAsset}
on:close={() => {
assetInteractionStore.setIsViewingAsset(false);
assetViewingStore.showAssetViewer(false);
}}
on:archived={handleArchiveSuccess}
/>

View file

@ -11,7 +11,7 @@
import { flip } from 'svelte/animate';
import { archivedAsset } from '$lib/stores/archived-asset.store';
import { getThumbnailSize } from '$lib/utils/thumbnail-util';
import { isViewingAssetStoreState } from '$lib/stores/asset-interaction.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
export let assets: AssetResponseDto[];
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
@ -20,6 +20,8 @@
export let viewFrom: ViewFrom;
export let showArchiveIcon = false;
let { isViewing: showAssetViewer } = assetViewingStore;
let selectedAsset: AssetResponseDto;
let currentViewAssetIndex = 0;
@ -33,7 +35,7 @@
currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
selectedAsset = assets[currentViewAssetIndex];
$isViewingAssetStoreState = true;
$showAssetViewer = true;
pushState(selectedAsset.id);
};
@ -81,7 +83,7 @@
};
const closeViewer = () => {
$isViewingAssetStoreState = false;
$showAssetViewer = false;
history.pushState(null, '', `${$page.url.pathname}`);
};
@ -117,7 +119,7 @@
{/if}
<!-- Overlay Asset Viewer -->
{#if $isViewingAssetStoreState}
{#if $showAssetViewer}
<AssetViewer
asset={selectedAsset}
publicSharedKey={sharedLink?.key}

View file

@ -19,15 +19,15 @@
<script lang="ts">
import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
import { assetGridState } from '$lib/stores/assets.store';
import { createEventDispatcher } from 'svelte';
import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
import type { AssetStore } from '$lib/stores/assets.store';
export let scrollTop = 0;
export let scrollbarHeight = 0;
export let assetStore: AssetStore;
$: timelineHeight = $assetGridState.timelineHeight;
$: timelineHeight = $assetStore.timelineHeight;
$: timelineScrolltop = (scrollbarPosition / scrollbarHeight) * timelineHeight;
let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
@ -48,7 +48,7 @@
$: {
let result: SegmentScrollbarLayout[] = [];
for (const bucket of $assetGridState.buckets) {
for (const bucket of $assetStore.buckets) {
let segmentLayout = new SegmentScrollbarLayout();
segmentLayout.count = bucket.assets.length;
segmentLayout.height = (bucket.bucketHeight / timelineHeight) * scrollbarHeight;