mirror of
https://github.com/immich-app/immich.git
synced 2025-01-12 15:32:36 +02:00
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:
parent
13051c1e5a
commit
5f9dfa9493
@ -43,13 +43,15 @@
|
|||||||
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import { downloadArchive } from '../../utils/asset-utils';
|
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 album: AlbumResponseDto;
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
|
||||||
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
|
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
|
||||||
let isShowAssetSelection = false;
|
let isShowAssetSelection = false;
|
||||||
|
|
||||||
let isShowShareLinkModal = false;
|
let isShowShareLinkModal = false;
|
||||||
@ -141,7 +143,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleKeyboardPress = (event: KeyboardEvent) => {
|
const handleKeyboardPress = (event: KeyboardEvent) => {
|
||||||
if (!$isViewingAssetStoreState) {
|
if (!$showAssetViewer) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
if (isMultiSelectionMode) {
|
if (isMultiSelectionMode) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { assetInteractionStore, assetsInAlbumStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
|
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import type { AssetResponseDto } from '@api';
|
import type { AssetResponseDto } from '@api';
|
||||||
@ -9,14 +8,20 @@
|
|||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import AssetGrid from '../photos-page/asset-grid.svelte';
|
import AssetGrid from '../photos-page/asset-grid.svelte';
|
||||||
import ControlAppBar from '../shared-components/control-app-bar.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 dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const assetStore = createAssetStore();
|
||||||
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
|
const { selectedAssets, assetsInAlbumState } = assetInteractionStore;
|
||||||
|
|
||||||
export let albumId: string;
|
export let albumId: string;
|
||||||
export let assetsInAlbum: AssetResponseDto[];
|
export let assetsInAlbum: AssetResponseDto[];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
$assetsInAlbumStoreState = assetsInAlbum;
|
$assetsInAlbumState = assetsInAlbum;
|
||||||
});
|
});
|
||||||
|
|
||||||
const addSelectedAssets = async () => {
|
const addSelectedAssets = async () => {
|
||||||
@ -64,6 +69,6 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
<section class="grid h-screen bg-immich-bg pl-[70px] pt-[100px] dark:bg-immich-dark-bg">
|
<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>
|
||||||
</section>
|
</section>
|
||||||
|
@ -17,13 +17,14 @@
|
|||||||
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
||||||
import ProfileImageCropper from '../shared-components/profile-image-cropper.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 { isShowDetail } from '$lib/stores/preferences.store';
|
||||||
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
||||||
import NavigationArea from './navigation-area.svelte';
|
import NavigationArea from './navigation-area.svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
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 asset: AssetResponseDto;
|
||||||
export let publicSharedKey = '';
|
export let publicSharedKey = '';
|
||||||
export let showNavigation = true;
|
export let showNavigation = true;
|
||||||
@ -134,7 +135,7 @@
|
|||||||
|
|
||||||
for (const asset of deletedAssets) {
|
for (const asset of deletedAssets) {
|
||||||
if (asset.status == 'SUCCESS') {
|
if (asset.status == 'SUCCESS') {
|
||||||
assetStore.removeAsset(asset.id);
|
assetStore?.removeAsset(asset.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -158,7 +159,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
asset.isFavorite = data.isFavorite;
|
asset.isFavorite = data.isFavorite;
|
||||||
assetStore.updateAsset(asset.id, data.isFavorite);
|
assetStore?.updateAsset(asset.id, data.isFavorite);
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
|
@ -1,28 +1,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { get } from 'svelte/store';
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import SelectAll from 'svelte-material-icons/SelectAll.svelte';
|
import SelectAll from 'svelte-material-icons/SelectAll.svelte';
|
||||||
import TimerSand from 'svelte-material-icons/TimerSand.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 { 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;
|
let selecting = false;
|
||||||
|
|
||||||
const handleSelectAll = async () => {
|
const handleSelectAll = async () => {
|
||||||
try {
|
try {
|
||||||
selecting = true;
|
selecting = true;
|
||||||
let _assetGridState = new AssetGridState();
|
|
||||||
assetGridState.subscribe((state) => {
|
|
||||||
_assetGridState = state;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < _assetGridState.buckets.length; i++) {
|
const assetGridState = get(assetStore);
|
||||||
await assetStore.getAssetsByBucket(_assetGridState.buckets[i].bucketDate, BucketPosition.Unknown);
|
for (const bucket of assetGridState.buckets) {
|
||||||
for (const asset of _assetGridState.buckets[i].assets) {
|
await assetStore.getAssetsByBucket(bucket.bucketDate, BucketPosition.Unknown);
|
||||||
|
for (const asset of bucket.assets) {
|
||||||
assetInteractionStore.addAssetToMultiselectGroup(asset);
|
assetInteractionStore.addAssetToMultiselectGroup(asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selecting = false;
|
selecting = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e, 'Error selecting all assets');
|
handleError(e, 'Error selecting all assets');
|
||||||
|
@ -1,13 +1,4 @@
|
|||||||
<script lang="ts">
|
<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 { locale } from '$lib/stores/preferences.store';
|
||||||
import { getAssetRatio } from '$lib/utils/asset-utils';
|
import { getAssetRatio } from '$lib/utils/asset-utils';
|
||||||
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
|
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
|
||||||
@ -19,6 +10,9 @@
|
|||||||
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
|
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
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 assets: AssetResponseDto[];
|
||||||
export let bucketDate: string;
|
export let bucketDate: string;
|
||||||
@ -26,6 +20,12 @@
|
|||||||
export let isAlbumSelectionMode = false;
|
export let isAlbumSelectionMode = false;
|
||||||
export let viewportWidth: number;
|
export let viewportWidth: number;
|
||||||
|
|
||||||
|
export let assetStore: AssetStore;
|
||||||
|
export let assetInteractionStore: AssetInteractionStore;
|
||||||
|
|
||||||
|
const { selectedGroup, selectedAssets, assetsInAlbumState, assetSelectionCandidates, isMultiSelectState } =
|
||||||
|
assetInteractionStore;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let isMouseOverGroup = false;
|
let isMouseOverGroup = false;
|
||||||
@ -94,10 +94,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isMultiSelectStoreState) {
|
if ($isMultiSelectState) {
|
||||||
assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
|
assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
|
||||||
} else {
|
} else {
|
||||||
assetInteractionStore.setViewingAsset(asset);
|
assetViewingStore.setAssetId(asset.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -137,7 +137,7 @@
|
|||||||
// Show multi select icon on hover on date group
|
// Show multi select icon on hover on date group
|
||||||
hoveredDateGroup = dateGroupTitle;
|
hoveredDateGroup = dateGroupTitle;
|
||||||
|
|
||||||
if ($isMultiSelectStoreState) {
|
if ($isMultiSelectState) {
|
||||||
dispatch('selectAssetCandidates', { asset });
|
dispatch('selectAssetCandidates', { asset });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -207,9 +207,9 @@
|
|||||||
on:click={() => assetClickHandler(asset, assetsInDateGroup, dateGroupTitle)}
|
on:click={() => assetClickHandler(asset, assetsInDateGroup, dateGroupTitle)}
|
||||||
on:select={() => assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle)}
|
on:select={() => assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle)}
|
||||||
on:mouse-event={() => assetMouseEventHandler(dateGroupTitle, asset)}
|
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)}
|
selectionCandidate={$assetSelectionCandidates.has(asset)}
|
||||||
disabled={$assetsInAlbumStoreState.some(({ id }) => id === asset.id)}
|
disabled={$assetsInAlbumState.some(({ id }) => id === asset.id)}
|
||||||
thumbnailWidth={box.width}
|
thumbnailWidth={box.width}
|
||||||
thumbnailHeight={box.height}
|
thumbnailHeight={box.height}
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BucketPosition } from '$lib/models/asset-grid-state';
|
import { BucketPosition } from '$lib/models/asset-grid-state';
|
||||||
import {
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
assetInteractionStore,
|
|
||||||
assetSelectionCandidates,
|
|
||||||
assetSelectionStart,
|
|
||||||
isMultiSelectStoreState,
|
|
||||||
isViewingAssetStoreState,
|
|
||||||
selectedAssets,
|
|
||||||
viewingAssetStoreState,
|
|
||||||
} from '$lib/stores/asset-interaction.store';
|
|
||||||
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
|
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
|
||||||
import type { UserResponseDto } from '@api';
|
import type { UserResponseDto } from '@api';
|
||||||
@ -31,11 +22,20 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { isSearchEnabled } from '$lib/stores/search.store';
|
import { isSearchEnabled } from '$lib/stores/search.store';
|
||||||
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
|
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 user: UserResponseDto | undefined = undefined;
|
||||||
export let isAlbumSelectionMode = false;
|
export let isAlbumSelectionMode = false;
|
||||||
export let showMemoryLane = 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 viewportHeight = 0;
|
||||||
let viewportWidth = 0;
|
let viewportWidth = 0;
|
||||||
let assetGridElement: HTMLElement;
|
let assetGridElement: HTMLElement;
|
||||||
@ -61,7 +61,7 @@
|
|||||||
// Get asset bucket if bucket height is smaller than viewport height
|
// Get asset bucket if bucket height is smaller than viewport height
|
||||||
let bucketsToFetchInitially: string[] = [];
|
let bucketsToFetchInitially: string[] = [];
|
||||||
let initialBucketsHeight = 0;
|
let initialBucketsHeight = 0;
|
||||||
$assetGridState.buckets.every((bucket) => {
|
$assetStore.buckets.every((bucket) => {
|
||||||
if (initialBucketsHeight < viewportHeight) {
|
if (initialBucketsHeight < viewportHeight) {
|
||||||
initialBucketsHeight += bucket.bucketHeight;
|
initialBucketsHeight += bucket.bucketHeight;
|
||||||
bucketsToFetchInitially.push(bucket.bucketDate);
|
bucketsToFetchInitially.push(bucket.bucketDate);
|
||||||
@ -89,7 +89,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$isViewingAssetStoreState) {
|
if (!$showAssetViewer) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
@ -121,12 +121,18 @@
|
|||||||
assetGridElement.scrollBy(0, event.detail.heightDelta);
|
assetGridElement.scrollBy(0, event.detail.heightDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateToPreviousAsset = () => {
|
const navigateToPreviousAsset = async () => {
|
||||||
assetInteractionStore.navigateAsset('previous');
|
const prevAsset = await assetStore.getAdjacentAsset($viewingAsset.id, 'previous');
|
||||||
|
if (prevAsset) {
|
||||||
|
assetViewingStore.setAssetId(prevAsset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToNextAsset = () => {
|
const navigateToNextAsset = async () => {
|
||||||
assetInteractionStore.navigateAsset('next');
|
const nextAsset = await assetStore.getAdjacentAsset($viewingAsset.id, 'next');
|
||||||
|
if (nextAsset) {
|
||||||
|
assetViewingStore.setAssetId(nextAsset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastScrollPosition = 0;
|
let lastScrollPosition = 0;
|
||||||
@ -228,8 +234,8 @@
|
|||||||
assetInteractionStore.clearAssetSelectionCandidates();
|
assetInteractionStore.clearAssetSelectionCandidates();
|
||||||
|
|
||||||
if ($assetSelectionStart && rangeSelection) {
|
if ($assetSelectionStart && rangeSelection) {
|
||||||
let startBucketIndex = $assetGridState.loadedAssets[$assetSelectionStart.id];
|
let startBucketIndex = $assetStore.loadedAssets[$assetSelectionStart.id];
|
||||||
let endBucketIndex = $assetGridState.loadedAssets[asset.id];
|
let endBucketIndex = $assetStore.loadedAssets[asset.id];
|
||||||
|
|
||||||
if (endBucketIndex < startBucketIndex) {
|
if (endBucketIndex < startBucketIndex) {
|
||||||
[startBucketIndex, endBucketIndex] = [endBucketIndex, startBucketIndex];
|
[startBucketIndex, endBucketIndex] = [endBucketIndex, startBucketIndex];
|
||||||
@ -237,7 +243,7 @@
|
|||||||
|
|
||||||
// Select/deselect assets in all intermediate buckets
|
// Select/deselect assets in all intermediate buckets
|
||||||
for (let bucketIndex = startBucketIndex + 1; bucketIndex < endBucketIndex; bucketIndex++) {
|
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);
|
await assetStore.getAssetsByBucket(bucket.bucketDate, BucketPosition.Unknown);
|
||||||
for (const asset of bucket.assets) {
|
for (const asset of bucket.assets) {
|
||||||
if (deselect) {
|
if (deselect) {
|
||||||
@ -250,7 +256,7 @@
|
|||||||
|
|
||||||
// Update date group selection
|
// Update date group selection
|
||||||
for (let bucketIndex = startBucketIndex; bucketIndex <= endBucketIndex; bucketIndex++) {
|
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
|
// Split bucket into date groups and check each group
|
||||||
const assetsGroupByDate = splitBucketIntoDateGroups(bucket.assets, $locale);
|
const assetsGroupByDate = splitBucketIntoDateGroups(bucket.assets, $locale);
|
||||||
@ -279,18 +285,18 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = $assetGridState.assets.indexOf(rangeStart);
|
let start = $assetStore.assets.indexOf(rangeStart);
|
||||||
let end = $assetGridState.assets.indexOf(asset);
|
let end = $assetStore.assets.indexOf(asset);
|
||||||
|
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
[start, end] = [end, start];
|
[start, end] = [end, start];
|
||||||
}
|
}
|
||||||
|
|
||||||
assetInteractionStore.setAssetSelectionCandidates($assetGridState.assets.slice(start, end + 1));
|
assetInteractionStore.setAssetSelectionCandidates($assetStore.assets.slice(start, end + 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectStart = (e: Event) => {
|
const onSelectStart = (e: Event) => {
|
||||||
if ($isMultiSelectStoreState && shiftKeyIsDown) {
|
if ($isMultiSelectState && shiftKeyIsDown) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -302,8 +308,9 @@
|
|||||||
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
|
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
|
{#if bucketInfo && viewportHeight && $assetStore.timelineHeight > viewportHeight}
|
||||||
<Scrollbar
|
<Scrollbar
|
||||||
|
{assetStore}
|
||||||
scrollbarHeight={viewportHeight}
|
scrollbarHeight={viewportHeight}
|
||||||
scrollTop={lastScrollPosition}
|
scrollTop={lastScrollPosition}
|
||||||
on:onscrollbarclick={(e) => handleScrollbarClick(e.detail)}
|
on:onscrollbarclick={(e) => handleScrollbarClick(e.detail)}
|
||||||
@ -324,15 +331,12 @@
|
|||||||
{#if showMemoryLane}
|
{#if showMemoryLane}
|
||||||
<MemoryLane />
|
<MemoryLane />
|
||||||
{/if}
|
{/if}
|
||||||
<section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
|
<section id="virtual-timeline" style:height={$assetStore.timelineHeight + 'px'}>
|
||||||
{#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)}
|
{#each $assetStore.buckets as bucket, bucketIndex (bucketIndex)}
|
||||||
<IntersectionObserver
|
<IntersectionObserver
|
||||||
on:intersected={intersectedHandler}
|
on:intersected={intersectedHandler}
|
||||||
on:hidden={async () => {
|
on:hidden={async () => {
|
||||||
// If bucket is hidden and in loading state, cancel the request
|
await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
|
||||||
if ($loadingBucketState[bucket.bucketDate]) {
|
|
||||||
await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
let:intersecting
|
let:intersecting
|
||||||
top={750}
|
top={750}
|
||||||
@ -342,6 +346,8 @@
|
|||||||
<div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
|
<div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
|
||||||
{#if intersecting}
|
{#if intersecting}
|
||||||
<AssetDateGroup
|
<AssetDateGroup
|
||||||
|
{assetStore}
|
||||||
|
{assetInteractionStore}
|
||||||
{isAlbumSelectionMode}
|
{isAlbumSelectionMode}
|
||||||
on:shift={handleScrollTimeline}
|
on:shift={handleScrollTimeline}
|
||||||
on:selectAssetCandidates={handleSelectAssetCandidates}
|
on:selectAssetCandidates={handleSelectAssetCandidates}
|
||||||
@ -360,13 +366,14 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
{#if $isViewingAssetStoreState}
|
{#if $showAssetViewer}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAssetStoreState}
|
{assetStore}
|
||||||
|
asset={$viewingAsset}
|
||||||
on:navigate-previous={navigateToPreviousAsset}
|
on:navigate-previous={navigateToPreviousAsset}
|
||||||
on:navigate-next={navigateToNextAsset}
|
on:navigate-next={navigateToNextAsset}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
assetInteractionStore.setIsViewingAsset(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
}}
|
}}
|
||||||
on:archived={handleArchiveSuccess}
|
on:archived={handleArchiveSuccess}
|
||||||
/>
|
/>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { archivedAsset } from '$lib/stores/archived-asset.store';
|
import { archivedAsset } from '$lib/stores/archived-asset.store';
|
||||||
import { getThumbnailSize } from '$lib/utils/thumbnail-util';
|
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 assets: AssetResponseDto[];
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
@ -20,6 +20,8 @@
|
|||||||
export let viewFrom: ViewFrom;
|
export let viewFrom: ViewFrom;
|
||||||
export let showArchiveIcon = false;
|
export let showArchiveIcon = false;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
|
||||||
let selectedAsset: AssetResponseDto;
|
let selectedAsset: AssetResponseDto;
|
||||||
let currentViewAssetIndex = 0;
|
let currentViewAssetIndex = 0;
|
||||||
|
|
||||||
@ -33,7 +35,7 @@
|
|||||||
|
|
||||||
currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
|
currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
|
||||||
selectedAsset = assets[currentViewAssetIndex];
|
selectedAsset = assets[currentViewAssetIndex];
|
||||||
$isViewingAssetStoreState = true;
|
$showAssetViewer = true;
|
||||||
pushState(selectedAsset.id);
|
pushState(selectedAsset.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,7 +83,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const closeViewer = () => {
|
const closeViewer = () => {
|
||||||
$isViewingAssetStoreState = false;
|
$showAssetViewer = false;
|
||||||
history.pushState(null, '', `${$page.url.pathname}`);
|
history.pushState(null, '', `${$page.url.pathname}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +119,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Overlay Asset Viewer -->
|
<!-- Overlay Asset Viewer -->
|
||||||
{#if $isViewingAssetStoreState}
|
{#if $showAssetViewer}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={selectedAsset}
|
asset={selectedAsset}
|
||||||
publicSharedKey={sharedLink?.key}
|
publicSharedKey={sharedLink?.key}
|
||||||
|
@ -19,15 +19,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
|
import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
|
||||||
|
|
||||||
import { assetGridState } from '$lib/stores/assets.store';
|
|
||||||
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
|
import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
|
||||||
|
import type { AssetStore } from '$lib/stores/assets.store';
|
||||||
|
|
||||||
export let scrollTop = 0;
|
export let scrollTop = 0;
|
||||||
export let scrollbarHeight = 0;
|
export let scrollbarHeight = 0;
|
||||||
|
export let assetStore: AssetStore;
|
||||||
|
|
||||||
$: timelineHeight = $assetGridState.timelineHeight;
|
$: timelineHeight = $assetStore.timelineHeight;
|
||||||
$: timelineScrolltop = (scrollbarPosition / scrollbarHeight) * timelineHeight;
|
$: timelineScrolltop = (scrollbarPosition / scrollbarHeight) * timelineHeight;
|
||||||
|
|
||||||
let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
|
let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
|
||||||
@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
let result: SegmentScrollbarLayout[] = [];
|
let result: SegmentScrollbarLayout[] = [];
|
||||||
for (const bucket of $assetGridState.buckets) {
|
for (const bucket of $assetStore.buckets) {
|
||||||
let segmentLayout = new SegmentScrollbarLayout();
|
let segmentLayout = new SegmentScrollbarLayout();
|
||||||
segmentLayout.count = bucket.assets.length;
|
segmentLayout.count = bucket.assets.length;
|
||||||
segmentLayout.height = (bucket.bucketHeight / timelineHeight) * scrollbarHeight;
|
segmentLayout.height = (bucket.bucketHeight / timelineHeight) * scrollbarHeight;
|
||||||
|
@ -1,49 +1,68 @@
|
|||||||
import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
|
|
||||||
import { api, AssetResponseDto } from '@api';
|
|
||||||
import { derived, writable } from 'svelte/store';
|
import { derived, writable } from 'svelte/store';
|
||||||
import { assetGridState, assetStore } from './assets.store';
|
import type { AssetResponseDto } from '../../api/open-api';
|
||||||
|
|
||||||
// Asset Viewer
|
export interface AssetInteractionStore {
|
||||||
export const viewingAssetStoreState = writable<AssetResponseDto>();
|
addAssetToMultiselectGroup: (asset: AssetResponseDto) => void;
|
||||||
export const isViewingAssetStoreState = writable<boolean>(false);
|
removeAssetFromMultiselectGroup: (asset: AssetResponseDto) => void;
|
||||||
|
addGroupToMultiselectGroup: (group: string) => void;
|
||||||
|
removeGroupFromMultiselectGroup: (group: string) => void;
|
||||||
|
setAssetSelectionCandidates: (assets: AssetResponseDto[]) => void;
|
||||||
|
clearAssetSelectionCandidates: () => void;
|
||||||
|
setAssetSelectionStart: (asset: AssetResponseDto | null) => void;
|
||||||
|
clearMultiselect: () => void;
|
||||||
|
isMultiSelectState: {
|
||||||
|
subscribe: (run: (value: boolean) => void, invalidate?: (value?: boolean) => void) => () => void;
|
||||||
|
};
|
||||||
|
assetsInAlbumState: {
|
||||||
|
subscribe: (
|
||||||
|
run: (value: AssetResponseDto[]) => void,
|
||||||
|
invalidate?: (value?: AssetResponseDto[]) => void,
|
||||||
|
) => () => void;
|
||||||
|
set: (value: AssetResponseDto[]) => void;
|
||||||
|
};
|
||||||
|
selectedAssets: {
|
||||||
|
subscribe: (
|
||||||
|
run: (value: Set<AssetResponseDto>) => void,
|
||||||
|
invalidate?: (value?: Set<AssetResponseDto>) => void,
|
||||||
|
) => () => void;
|
||||||
|
};
|
||||||
|
selectedGroup: {
|
||||||
|
subscribe: (run: (value: Set<string>) => void, invalidate?: (value?: Set<string>) => void) => () => void;
|
||||||
|
};
|
||||||
|
assetSelectionCandidates: {
|
||||||
|
subscribe: (
|
||||||
|
run: (value: Set<AssetResponseDto>) => void,
|
||||||
|
invalidate?: (value?: Set<AssetResponseDto>) => void,
|
||||||
|
) => () => void;
|
||||||
|
};
|
||||||
|
assetSelectionStart: {
|
||||||
|
subscribe: (
|
||||||
|
run: (value: AssetResponseDto | null) => void,
|
||||||
|
invalidate?: (value?: AssetResponseDto | null) => void,
|
||||||
|
) => () => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
export function createAssetInteractionStore(): AssetInteractionStore {
|
||||||
* Multi-selection mode
|
|
||||||
*/
|
|
||||||
export const assetsInAlbumStoreState = writable<AssetResponseDto[]>([]);
|
|
||||||
// Selected assets
|
|
||||||
export const selectedAssets = writable<Set<AssetResponseDto>>(new Set());
|
|
||||||
// Selected date groups
|
|
||||||
export const selectedGroup = writable<Set<string>>(new Set());
|
|
||||||
// If any asset selected
|
|
||||||
export const isMultiSelectStoreState = derived(selectedAssets, ($selectedAssets) => $selectedAssets.size > 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Range selection
|
|
||||||
*/
|
|
||||||
// Candidates for the range selection. This set includes only loaded assets, so it improves highlight
|
|
||||||
// performance. From the user's perspective, range is highlighted almost immediately
|
|
||||||
export const assetSelectionCandidates = writable<Set<AssetResponseDto>>(new Set());
|
|
||||||
// The beginning of the selection range
|
|
||||||
export const assetSelectionStart = writable<AssetResponseDto | null>(null);
|
|
||||||
|
|
||||||
function createAssetInteractionStore() {
|
|
||||||
let _assetGridState = new AssetGridState();
|
|
||||||
let _viewingAssetStoreState: AssetResponseDto;
|
|
||||||
let _selectedAssets: Set<AssetResponseDto>;
|
let _selectedAssets: Set<AssetResponseDto>;
|
||||||
let _selectedGroup: Set<string>;
|
let _selectedGroup: Set<string>;
|
||||||
let _assetsInAlbums: AssetResponseDto[];
|
let _assetsInAlbums: AssetResponseDto[];
|
||||||
let _assetSelectionCandidates: Set<AssetResponseDto>;
|
let _assetSelectionCandidates: Set<AssetResponseDto>;
|
||||||
let _assetSelectionStart: AssetResponseDto | null;
|
let _assetSelectionStart: AssetResponseDto | null;
|
||||||
|
|
||||||
// Subscriber
|
const assetsInAlbumStoreState = writable<AssetResponseDto[]>([]);
|
||||||
assetGridState.subscribe((state) => {
|
// Selected assets
|
||||||
_assetGridState = state;
|
const selectedAssets = writable<Set<AssetResponseDto>>(new Set());
|
||||||
});
|
// Selected date groups
|
||||||
|
const selectedGroup = writable<Set<string>>(new Set());
|
||||||
|
// If any asset selected
|
||||||
|
const isMultiSelectStoreState = derived(selectedAssets, ($selectedAssets) => $selectedAssets.size > 0);
|
||||||
|
|
||||||
viewingAssetStoreState.subscribe((asset) => {
|
// Candidates for the range selection. This set includes only loaded assets, so it improves highlight
|
||||||
_viewingAssetStoreState = asset;
|
// performance. From the user's perspective, range is highlighted almost immediately
|
||||||
});
|
const assetSelectionCandidates = writable<Set<AssetResponseDto>>(new Set());
|
||||||
|
// The beginning of the selection range
|
||||||
|
const assetSelectionStart = writable<AssetResponseDto | null>(null);
|
||||||
|
|
||||||
selectedAssets.subscribe((assets) => {
|
selectedAssets.subscribe((assets) => {
|
||||||
_selectedAssets = assets;
|
_selectedAssets = assets;
|
||||||
@ -64,89 +83,7 @@ function createAssetInteractionStore() {
|
|||||||
assetSelectionStart.subscribe((asset) => {
|
assetSelectionStart.subscribe((asset) => {
|
||||||
_assetSelectionStart = asset;
|
_assetSelectionStart = asset;
|
||||||
});
|
});
|
||||||
// Methods
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asset Viewer
|
|
||||||
*/
|
|
||||||
const setViewingAsset = async (asset: AssetResponseDto) => {
|
|
||||||
setViewingAssetId(asset.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setViewingAssetId = async (id: string) => {
|
|
||||||
const { data } = await api.assetApi.getAssetById({ id });
|
|
||||||
viewingAssetStoreState.set(data);
|
|
||||||
isViewingAssetStoreState.set(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setIsViewingAsset = (isViewing: boolean) => {
|
|
||||||
isViewingAssetStoreState.set(isViewing);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNextAsset = async (currentBucketIndex: number, assetId: string): Promise<AssetResponseDto | null> => {
|
|
||||||
const currentBucket = _assetGridState.buckets[currentBucketIndex];
|
|
||||||
const assetIndex = currentBucket.assets.findIndex(({ id }) => id == assetId);
|
|
||||||
if (assetIndex === -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assetIndex + 1 < currentBucket.assets.length) {
|
|
||||||
return currentBucket.assets[assetIndex + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextBucketIndex = currentBucketIndex + 1;
|
|
||||||
if (nextBucketIndex >= _assetGridState.buckets.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextBucket = _assetGridState.buckets[nextBucketIndex];
|
|
||||||
await assetStore.getAssetsByBucket(nextBucket.bucketDate, BucketPosition.Unknown);
|
|
||||||
|
|
||||||
return nextBucket.assets[0] ?? null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPrevAsset = async (currentBucketIndex: number, assetId: string): Promise<AssetResponseDto | null> => {
|
|
||||||
const currentBucket = _assetGridState.buckets[currentBucketIndex];
|
|
||||||
const assetIndex = currentBucket.assets.findIndex(({ id }) => id == assetId);
|
|
||||||
if (assetIndex === -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assetIndex > 0) {
|
|
||||||
return currentBucket.assets[assetIndex - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevBucketIndex = currentBucketIndex - 1;
|
|
||||||
if (prevBucketIndex < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevBucket = _assetGridState.buckets[prevBucketIndex];
|
|
||||||
await assetStore.getAssetsByBucket(prevBucket.bucketDate, BucketPosition.Unknown);
|
|
||||||
|
|
||||||
return prevBucket.assets[prevBucket.assets.length - 1] ?? null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateAsset = async (direction: 'next' | 'previous') => {
|
|
||||||
const currentAssetId = _viewingAssetStoreState.id;
|
|
||||||
const currentBucketIndex = _assetGridState.loadedAssets[currentAssetId];
|
|
||||||
if (currentBucketIndex < 0 || currentBucketIndex >= _assetGridState.buckets.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const asset =
|
|
||||||
direction === 'next'
|
|
||||||
? await getNextAsset(currentBucketIndex, currentAssetId)
|
|
||||||
: await getPrevAsset(currentBucketIndex, currentAssetId);
|
|
||||||
|
|
||||||
if (asset) {
|
|
||||||
setViewingAsset(asset);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multiselect
|
|
||||||
*/
|
|
||||||
const addAssetToMultiselectGroup = (asset: AssetResponseDto) => {
|
const addAssetToMultiselectGroup = (asset: AssetResponseDto) => {
|
||||||
// Not select if in album already
|
// Not select if in album already
|
||||||
if (_assetsInAlbums.find((a) => a.id === asset.id)) {
|
if (_assetsInAlbums.find((a) => a.id === asset.id)) {
|
||||||
@ -205,10 +142,6 @@ function createAssetInteractionStore() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setViewingAsset,
|
|
||||||
setViewingAssetId,
|
|
||||||
setIsViewingAsset,
|
|
||||||
navigateAsset,
|
|
||||||
addAssetToMultiselectGroup,
|
addAssetToMultiselectGroup,
|
||||||
removeAssetFromMultiselectGroup,
|
removeAssetFromMultiselectGroup,
|
||||||
addGroupToMultiselectGroup,
|
addGroupToMultiselectGroup,
|
||||||
@ -217,7 +150,24 @@ function createAssetInteractionStore() {
|
|||||||
clearAssetSelectionCandidates,
|
clearAssetSelectionCandidates,
|
||||||
setAssetSelectionStart,
|
setAssetSelectionStart,
|
||||||
clearMultiselect,
|
clearMultiselect,
|
||||||
|
isMultiSelectState: {
|
||||||
|
subscribe: isMultiSelectStoreState.subscribe,
|
||||||
|
},
|
||||||
|
assetsInAlbumState: {
|
||||||
|
subscribe: assetsInAlbumStoreState.subscribe,
|
||||||
|
set: assetsInAlbumStoreState.set,
|
||||||
|
},
|
||||||
|
selectedAssets: {
|
||||||
|
subscribe: selectedAssets.subscribe,
|
||||||
|
},
|
||||||
|
selectedGroup: {
|
||||||
|
subscribe: selectedGroup.subscribe,
|
||||||
|
},
|
||||||
|
assetSelectionCandidates: {
|
||||||
|
subscribe: assetSelectionCandidates.subscribe,
|
||||||
|
},
|
||||||
|
assetSelectionStart: {
|
||||||
|
subscribe: assetSelectionStart.subscribe,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const assetInteractionStore = createAssetInteractionStore();
|
|
||||||
|
31
web/src/lib/stores/asset-viewing.store.ts
Normal file
31
web/src/lib/stores/asset-viewing.store.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import { api, type AssetResponseDto } from '@api';
|
||||||
|
|
||||||
|
function createAssetViewingStore() {
|
||||||
|
const viewingAssetStoreState = writable<AssetResponseDto>();
|
||||||
|
const viewState = writable<boolean>(false);
|
||||||
|
|
||||||
|
const setAssetId = async (id: string) => {
|
||||||
|
const { data } = await api.assetApi.getAssetById({ id });
|
||||||
|
viewingAssetStoreState.set(data);
|
||||||
|
viewState.set(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAssetViewer = (show: boolean) => {
|
||||||
|
viewState.set(show);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
asset: {
|
||||||
|
subscribe: viewingAssetStoreState.subscribe,
|
||||||
|
},
|
||||||
|
isViewing: {
|
||||||
|
subscribe: viewState.subscribe,
|
||||||
|
set: viewState.set,
|
||||||
|
},
|
||||||
|
setAssetId,
|
||||||
|
showAssetViewer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const assetViewingStore = createAssetViewingStore();
|
@ -1,25 +1,34 @@
|
|||||||
import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
|
import { AssetGridState, BucketPosition } from '$lib/models/asset-grid-state';
|
||||||
import { api, AssetCountByTimeBucketResponseDto } from '@api';
|
import { api, AssetCountByTimeBucketResponseDto, AssetResponseDto } from '@api';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
/**
|
export interface AssetStore {
|
||||||
* The state that holds information about the asset grid
|
setInitialState: (
|
||||||
*/
|
viewportHeight: number,
|
||||||
export const assetGridState = writable<AssetGridState>(new AssetGridState());
|
viewportWidth: number,
|
||||||
export const loadingBucketState = writable<{ [key: string]: boolean }>({});
|
data: AssetCountByTimeBucketResponseDto,
|
||||||
|
userId: string | undefined,
|
||||||
|
) => void;
|
||||||
|
getAssetsByBucket: (bucket: string, position: BucketPosition) => Promise<void>;
|
||||||
|
updateBucketHeight: (bucket: string, actualBucketHeight: number) => number;
|
||||||
|
cancelBucketRequest: (token: AbortController, bucketDate: string) => Promise<void>;
|
||||||
|
getAdjacentAsset: (assetId: string, direction: 'next' | 'previous') => Promise<string | null>;
|
||||||
|
removeAsset: (assetId: string) => void;
|
||||||
|
updateAsset: (assetId: string, isFavorite: boolean) => void;
|
||||||
|
subscribe: (run: (value: AssetGridState) => void, invalidate?: (value?: AssetGridState) => void) => () => void;
|
||||||
|
}
|
||||||
|
|
||||||
function createAssetStore() {
|
export function createAssetStore(): AssetStore {
|
||||||
|
let _loadingBuckets: { [key: string]: boolean } = {};
|
||||||
let _assetGridState = new AssetGridState();
|
let _assetGridState = new AssetGridState();
|
||||||
assetGridState.subscribe((state) => {
|
|
||||||
|
const { subscribe, set, update } = writable(new AssetGridState());
|
||||||
|
|
||||||
|
subscribe((state) => {
|
||||||
_assetGridState = state;
|
_assetGridState = state;
|
||||||
});
|
});
|
||||||
|
|
||||||
let _loadingBucketState: { [key: string]: boolean } = {};
|
const _estimateViewportHeight = (assetCount: number, viewportWidth: number): number => {
|
||||||
loadingBucketState.subscribe((state) => {
|
|
||||||
_loadingBucketState = state;
|
|
||||||
});
|
|
||||||
|
|
||||||
const estimateViewportHeight = (assetCount: number, viewportWidth: number): number => {
|
|
||||||
// Ideally we would use the average aspect ratio for the photoset, however assume
|
// Ideally we would use the average aspect ratio for the photoset, however assume
|
||||||
// a normal landscape aspect ratio of 3:2, then discount for the likelihood we
|
// a normal landscape aspect ratio of 3:2, then discount for the likelihood we
|
||||||
// will be scaling down and coalescing.
|
// will be scaling down and coalescing.
|
||||||
@ -39,25 +48,19 @@ function createAssetStore() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Set initial state
|
|
||||||
* @param viewportHeight
|
|
||||||
* @param viewportWidth
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
const setInitialState = (
|
const setInitialState = (
|
||||||
viewportHeight: number,
|
viewportHeight: number,
|
||||||
viewportWidth: number,
|
viewportWidth: number,
|
||||||
data: AssetCountByTimeBucketResponseDto,
|
data: AssetCountByTimeBucketResponseDto,
|
||||||
userId: string | undefined,
|
userId: string | undefined,
|
||||||
) => {
|
) => {
|
||||||
assetGridState.set({
|
set({
|
||||||
viewportHeight,
|
viewportHeight,
|
||||||
viewportWidth,
|
viewportWidth,
|
||||||
timelineHeight: 0,
|
timelineHeight: 0,
|
||||||
buckets: data.buckets.map((bucket) => ({
|
buckets: data.buckets.map((bucket) => ({
|
||||||
bucketDate: bucket.timeBucket,
|
bucketDate: bucket.timeBucket,
|
||||||
bucketHeight: estimateViewportHeight(bucket.count, viewportWidth),
|
bucketHeight: _estimateViewportHeight(bucket.count, viewportWidth),
|
||||||
assets: [],
|
assets: [],
|
||||||
cancelToken: new AbortController(),
|
cancelToken: new AbortController(),
|
||||||
position: BucketPosition.Unknown,
|
position: BucketPosition.Unknown,
|
||||||
@ -67,8 +70,7 @@ function createAssetStore() {
|
|||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update timeline height based on calculated bucket height
|
update((state) => {
|
||||||
assetGridState.update((state) => {
|
|
||||||
state.timelineHeight = state.buckets.reduce((acc, b) => acc + b.bucketHeight, 0);
|
state.timelineHeight = state.buckets.reduce((acc, b) => acc + b.bucketHeight, 0);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
@ -78,7 +80,7 @@ function createAssetStore() {
|
|||||||
try {
|
try {
|
||||||
const currentBucketData = _assetGridState.buckets.find((b) => b.bucketDate === bucket);
|
const currentBucketData = _assetGridState.buckets.find((b) => b.bucketDate === bucket);
|
||||||
if (currentBucketData?.assets && currentBucketData.assets.length > 0) {
|
if (currentBucketData?.assets && currentBucketData.assets.length > 0) {
|
||||||
assetGridState.update((state) => {
|
update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
||||||
state.buckets[bucketIndex].position = position;
|
state.buckets[bucketIndex].position = position;
|
||||||
return state;
|
return state;
|
||||||
@ -86,10 +88,7 @@ function createAssetStore() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingBucketState.set({
|
_loadingBuckets = { ..._loadingBuckets, [bucket]: true };
|
||||||
..._loadingBucketState,
|
|
||||||
[bucket]: true,
|
|
||||||
});
|
|
||||||
const { data: assets } = await api.assetApi.getAssetByTimeBucket(
|
const { data: assets } = await api.assetApi.getAssetByTimeBucket(
|
||||||
{
|
{
|
||||||
getAssetByTimeBucketDto: {
|
getAssetByTimeBucketDto: {
|
||||||
@ -100,13 +99,9 @@ function createAssetStore() {
|
|||||||
},
|
},
|
||||||
{ signal: currentBucketData?.cancelToken.signal },
|
{ signal: currentBucketData?.cancelToken.signal },
|
||||||
);
|
);
|
||||||
loadingBucketState.set({
|
_loadingBuckets = { ..._loadingBuckets, [bucket]: false };
|
||||||
..._loadingBucketState,
|
|
||||||
[bucket]: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update assetGridState with assets by time bucket
|
update((state) => {
|
||||||
assetGridState.update((state) => {
|
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
||||||
state.buckets[bucketIndex].assets = assets;
|
state.buckets[bucketIndex].assets = assets;
|
||||||
state.buckets[bucketIndex].position = position;
|
state.buckets[bucketIndex].position = position;
|
||||||
@ -125,7 +120,7 @@ function createAssetStore() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeAsset = (assetId: string) => {
|
const removeAsset = (assetId: string) => {
|
||||||
assetGridState.update((state) => {
|
update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
|
const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
|
||||||
const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
|
const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
|
||||||
state.buckets[bucketIndex].assets.splice(assetIndex, 1);
|
state.buckets[bucketIndex].assets.splice(assetIndex, 1);
|
||||||
@ -140,7 +135,7 @@ function createAssetStore() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _removeBucket = (bucketDate: string) => {
|
const _removeBucket = (bucketDate: string) => {
|
||||||
assetGridState.update((state) => {
|
update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
|
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
|
||||||
state.buckets.splice(bucketIndex, 1);
|
state.buckets.splice(bucketIndex, 1);
|
||||||
state.assets = state.buckets.flatMap((b) => b.assets);
|
state.assets = state.buckets.flatMap((b) => b.assets);
|
||||||
@ -153,7 +148,7 @@ function createAssetStore() {
|
|||||||
let scrollTimeline = false;
|
let scrollTimeline = false;
|
||||||
let heightDelta = 0;
|
let heightDelta = 0;
|
||||||
|
|
||||||
assetGridState.update((state) => {
|
update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
||||||
// Update timeline height based on the new bucket height
|
// Update timeline height based on the new bucket height
|
||||||
const estimateBucketHeight = state.buckets[bucketIndex].bucketHeight;
|
const estimateBucketHeight = state.buckets[bucketIndex].bucketHeight;
|
||||||
@ -177,9 +172,13 @@ function createAssetStore() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cancelBucketRequest = async (token: AbortController, bucketDate: string) => {
|
const cancelBucketRequest = async (token: AbortController, bucketDate: string) => {
|
||||||
|
if (!_loadingBuckets[bucketDate]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
token.abort();
|
token.abort();
|
||||||
// set new abort controller for bucket
|
|
||||||
assetGridState.update((state) => {
|
update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
|
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
|
||||||
state.buckets[bucketIndex].cancelToken = new AbortController();
|
state.buckets[bucketIndex].cancelToken = new AbortController();
|
||||||
return state;
|
return state;
|
||||||
@ -187,7 +186,7 @@ function createAssetStore() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateAsset = (assetId: string, isFavorite: boolean) => {
|
const updateAsset = (assetId: string, isFavorite: boolean) => {
|
||||||
assetGridState.update((state) => {
|
update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
|
const bucketIndex = state.buckets.findIndex((b) => b.assets.some((a) => a.id === assetId));
|
||||||
const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
|
const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
|
||||||
state.buckets[bucketIndex].assets[assetIndex].isFavorite = isFavorite;
|
state.buckets[bucketIndex].assets[assetIndex].isFavorite = isFavorite;
|
||||||
@ -198,14 +197,72 @@ function createAssetStore() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _getNextAsset = async (currentBucketIndex: number, assetId: string): Promise<AssetResponseDto | null> => {
|
||||||
|
const currentBucket = _assetGridState.buckets[currentBucketIndex];
|
||||||
|
const assetIndex = currentBucket.assets.findIndex(({ id }) => id == assetId);
|
||||||
|
if (assetIndex === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetIndex + 1 < currentBucket.assets.length) {
|
||||||
|
return currentBucket.assets[assetIndex + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextBucketIndex = currentBucketIndex + 1;
|
||||||
|
if (nextBucketIndex >= _assetGridState.buckets.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextBucket = _assetGridState.buckets[nextBucketIndex];
|
||||||
|
await getAssetsByBucket(nextBucket.bucketDate, BucketPosition.Unknown);
|
||||||
|
|
||||||
|
return nextBucket.assets[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _getPrevAsset = async (currentBucketIndex: number, assetId: string): Promise<AssetResponseDto | null> => {
|
||||||
|
const currentBucket = _assetGridState.buckets[currentBucketIndex];
|
||||||
|
const assetIndex = currentBucket.assets.findIndex(({ id }) => id == assetId);
|
||||||
|
if (assetIndex === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetIndex > 0) {
|
||||||
|
return currentBucket.assets[assetIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevBucketIndex = currentBucketIndex - 1;
|
||||||
|
if (prevBucketIndex < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevBucket = _assetGridState.buckets[prevBucketIndex];
|
||||||
|
await getAssetsByBucket(prevBucket.bucketDate, BucketPosition.Unknown);
|
||||||
|
|
||||||
|
return prevBucket.assets[prevBucket.assets.length - 1] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAdjacentAsset = async (assetId: string, direction: 'next' | 'previous'): Promise<string | null> => {
|
||||||
|
const currentBucketIndex = _assetGridState.loadedAssets[assetId];
|
||||||
|
if (currentBucketIndex < 0 || currentBucketIndex >= _assetGridState.buckets.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset =
|
||||||
|
direction === 'next'
|
||||||
|
? await _getNextAsset(currentBucketIndex, assetId)
|
||||||
|
: await _getPrevAsset(currentBucketIndex, assetId);
|
||||||
|
|
||||||
|
return asset?.id ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setInitialState,
|
setInitialState,
|
||||||
getAssetsByBucket,
|
getAssetsByBucket,
|
||||||
removeAsset,
|
removeAsset,
|
||||||
updateBucketHeight,
|
updateBucketHeight,
|
||||||
cancelBucketRequest,
|
cancelBucketRequest,
|
||||||
|
getAdjacentAsset,
|
||||||
updateAsset,
|
updateAsset,
|
||||||
|
subscribe,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const assetStore = createAssetStore();
|
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
|
import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
import {
|
|
||||||
assetInteractionStore,
|
|
||||||
isViewingAssetStoreState,
|
|
||||||
viewingAssetStoreState,
|
|
||||||
} from '$lib/stores/asset-interaction.store';
|
|
||||||
import { mapSettings } from '$lib/stores/preferences.store';
|
import { mapSettings } from '$lib/stores/preferences.store';
|
||||||
import { MapMarkerResponseDto, api } from '@api';
|
import { MapMarkerResponseDto, api } from '@api';
|
||||||
import { isEqual, omit } from 'lodash-es';
|
import { isEqual, omit } from 'lodash-es';
|
||||||
@ -15,9 +10,12 @@
|
|||||||
import Cog from 'svelte-material-icons/Cog.svelte';
|
import Cog from 'svelte-material-icons/Cog.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer, asset: viewingAsset } = assetViewingStore;
|
||||||
|
|
||||||
let leaflet: typeof import('$lib/components/shared-components/leaflet');
|
let leaflet: typeof import('$lib/components/shared-components/leaflet');
|
||||||
let mapMarkers: MapMarkerResponseDto[] = [];
|
let mapMarkers: MapMarkerResponseDto[] = [];
|
||||||
let abortController: AbortController;
|
let abortController: AbortController;
|
||||||
@ -34,8 +32,7 @@
|
|||||||
if (abortController) {
|
if (abortController) {
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
}
|
}
|
||||||
assetInteractionStore.clearMultiselect();
|
assetViewingStore.showAssetViewer(false);
|
||||||
assetInteractionStore.setIsViewingAsset(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadMapMarkers() {
|
async function loadMapMarkers() {
|
||||||
@ -83,20 +80,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onViewAssets(assetIds: string[], activeAssetIndex: number) {
|
function onViewAssets(assetIds: string[], activeAssetIndex: number) {
|
||||||
assetInteractionStore.setViewingAssetId(assetIds[activeAssetIndex]);
|
assetViewingStore.setAssetId(assetIds[activeAssetIndex]);
|
||||||
viewingAssets = assetIds;
|
viewingAssets = assetIds;
|
||||||
viewingAssetCursor = activeAssetIndex;
|
viewingAssetCursor = activeAssetIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateNext() {
|
function navigateNext() {
|
||||||
if (viewingAssetCursor < viewingAssets.length - 1) {
|
if (viewingAssetCursor < viewingAssets.length - 1) {
|
||||||
assetInteractionStore.setViewingAssetId(viewingAssets[++viewingAssetCursor]);
|
assetViewingStore.setAssetId(viewingAssets[++viewingAssetCursor]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigatePrevious() {
|
function navigatePrevious() {
|
||||||
if (viewingAssetCursor > 0) {
|
if (viewingAssetCursor > 0) {
|
||||||
assetInteractionStore.setViewingAssetId(viewingAssets[--viewingAssetCursor]);
|
assetViewingStore.setAssetId(viewingAssets[--viewingAssetCursor]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -142,14 +139,14 @@
|
|||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
|
||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
{#if $isViewingAssetStoreState}
|
{#if $showAssetViewer}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAssetStoreState}
|
asset={$viewingAsset}
|
||||||
showNavigation={viewingAssets.length > 1}
|
showNavigation={viewingAssets.length > 1}
|
||||||
on:navigate-next={navigateNext}
|
on:navigate-next={navigateNext}
|
||||||
on:navigate-previous={navigatePrevious}
|
on:navigate-previous={navigatePrevious}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
assetInteractionStore.setIsViewingAsset(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -8,21 +8,26 @@
|
|||||||
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 ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { assetInteractionStore, isMultiSelectStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
|
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
||||||
import Plus from 'svelte-material-icons/Plus.svelte';
|
import Plus from 'svelte-material-icons/Plus.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import { createAssetStore } from '$lib/stores/assets.store';
|
||||||
|
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
const assetStore = createAssetStore();
|
||||||
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
|
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="grid h-screen bg-immich-bg pt-18 dark:bg-immich-dark-bg">
|
<main class="grid h-screen bg-immich-bg pt-18 dark:bg-immich-dark-bg">
|
||||||
{#if $isMultiSelectStoreState}
|
{#if $isMultiSelectState}
|
||||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
|
<AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
|
||||||
<DownloadAction />
|
<DownloadAction />
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
@ -44,5 +49,5 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
{/if}
|
{/if}
|
||||||
<AssetGrid user={data.partner} />
|
<AssetGrid {assetStore} {assetInteractionStore} user={data.partner} />
|
||||||
</main>
|
</main>
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
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 { assetInteractionStore, isMultiSelectStoreState, selectedAssets } from '$lib/stores/asset-interaction.store';
|
import { createAssetStore } from '$lib/stores/assets.store';
|
||||||
import { assetStore } from '$lib/stores/assets.store';
|
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
@ -23,6 +23,10 @@
|
|||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
let assetCount = 1;
|
let assetCount = 1;
|
||||||
|
|
||||||
|
const assetStore = createAssetStore();
|
||||||
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
|
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const { data: stats } = await api.assetApi.getAssetStats();
|
const { data: stats } = await api.assetApi.getAssetStats();
|
||||||
assetCount = stats.total;
|
assetCount = stats.total;
|
||||||
@ -39,12 +43,12 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectStoreState} showUploadButton>
|
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} showUploadButton>
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
{#if $isMultiSelectStoreState}
|
{#if $isMultiSelectState}
|
||||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
|
<AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
|
||||||
<CreateSharedLink />
|
<CreateSharedLink />
|
||||||
<SelectAllAssets />
|
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||||
<AssetSelectContextMenu icon={Plus} title="Add">
|
<AssetSelectContextMenu icon={Plus} title="Add">
|
||||||
<AddToAlbum />
|
<AddToAlbum />
|
||||||
<AddToAlbum shared />
|
<AddToAlbum shared />
|
||||||
@ -60,7 +64,7 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
{#if assetCount}
|
{#if assetCount}
|
||||||
<AssetGrid showMemoryLane />
|
<AssetGrid {assetStore} {assetInteractionStore} showMemoryLane />
|
||||||
{:else}
|
{:else}
|
||||||
<EmptyPlaceholder text="CLICK TO UPLOAD YOUR FIRST PHOTO" actionHandler={handleUpload} />
|
<EmptyPlaceholder text="CLICK TO UPLOAD YOUR FIRST PHOTO" actionHandler={handleUpload} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -25,10 +25,12 @@
|
|||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { isViewingAssetStoreState } from '$lib/stores/asset-interaction.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
|
||||||
// The GalleryViewer pushes it's own history state, which causes weird
|
// The GalleryViewer pushes it's own history state, which causes weird
|
||||||
// behavior for history.back(). To prevent that we store the previous page
|
// behavior for history.back(). To prevent that we store the previous page
|
||||||
// manually and navigate back to that.
|
// manually and navigate back to that.
|
||||||
@ -48,7 +50,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleKeyboardPress = (event: KeyboardEvent) => {
|
const handleKeyboardPress = (event: KeyboardEvent) => {
|
||||||
if (!$isViewingAssetStoreState) {
|
if (!$showAssetViewer) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
goto(previousRoute);
|
goto(previousRoute);
|
||||||
|
Loading…
Reference in New Issue
Block a user