mirror of
https://github.com/immich-app/immich.git
synced 2025-01-06 11:56:46 +01:00
feat(web): select a range of assets (#3086)
The shift key can be held to select a range of assets. Fixes: #2862
This commit is contained in:
parent
2099b04057
commit
8fd4edb206
5 changed files with 154 additions and 32 deletions
|
@ -3,14 +3,15 @@
|
||||||
import { timeToSeconds } from '$lib/utils/time-to-seconds';
|
import { timeToSeconds } from '$lib/utils/time-to-seconds';
|
||||||
import { api, AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
|
import { api, AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
|
||||||
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
||||||
|
import Heart from 'svelte-material-icons/Heart.svelte';
|
||||||
|
import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
|
||||||
import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
|
import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
|
||||||
import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
|
import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
|
||||||
import Heart from 'svelte-material-icons/Heart.svelte';
|
import { fade } from 'svelte/transition';
|
||||||
import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
|
|
||||||
import ImageThumbnail from './image-thumbnail.svelte';
|
import ImageThumbnail from './image-thumbnail.svelte';
|
||||||
import VideoThumbnail from './video-thumbnail.svelte';
|
import VideoThumbnail from './video-thumbnail.svelte';
|
||||||
import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
export let thumbnailHeight: number | undefined = undefined;
|
export let thumbnailHeight: number | undefined = undefined;
|
||||||
export let format: ThumbnailFormat = ThumbnailFormat.Webp;
|
export let format: ThumbnailFormat = ThumbnailFormat.Webp;
|
||||||
export let selected = false;
|
export let selected = false;
|
||||||
|
export let selectionCandidate = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let readonly = false;
|
export let readonly = false;
|
||||||
export let publicSharedKey: string | undefined = undefined;
|
export let publicSharedKey: string | undefined = undefined;
|
||||||
|
@ -30,7 +32,7 @@
|
||||||
|
|
||||||
$: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
|
$: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
|
||||||
|
|
||||||
$: [width, height] = (() => {
|
$: [width, height] = ((): [number, number] => {
|
||||||
if (thumbnailSize) {
|
if (thumbnailSize) {
|
||||||
return [thumbnailSize, thumbnailSize];
|
return [thumbnailSize, thumbnailSize];
|
||||||
}
|
}
|
||||||
|
@ -42,12 +44,19 @@
|
||||||
return [235, 235];
|
return [235, 235];
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const thumbnailClickedHandler = () => {
|
const thumbnailClickedHandler = (e: Event) => {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
|
e.preventDefault();
|
||||||
dispatch('click', { asset });
|
dispatch('click', { asset });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const thumbnailKeyDownHandler = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
thumbnailClickedHandler(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onIconClickedHandler = (e: MouseEvent) => {
|
const onIconClickedHandler = (e: MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
|
@ -68,21 +77,23 @@
|
||||||
on:mouseenter={() => (mouseOver = true)}
|
on:mouseenter={() => (mouseOver = true)}
|
||||||
on:mouseleave={() => (mouseOver = false)}
|
on:mouseleave={() => (mouseOver = false)}
|
||||||
on:click={thumbnailClickedHandler}
|
on:click={thumbnailClickedHandler}
|
||||||
on:keydown={thumbnailClickedHandler}
|
on:keydown={thumbnailKeyDownHandler}
|
||||||
>
|
>
|
||||||
{#if intersecting}
|
{#if intersecting}
|
||||||
<div class="absolute w-full h-full z-20">
|
<div class="absolute w-full h-full z-20">
|
||||||
<!-- Select asset button -->
|
<!-- Select asset button -->
|
||||||
{#if !readonly}
|
{#if !readonly && (mouseOver || selected || selectionCandidate)}
|
||||||
<button
|
<button
|
||||||
on:click={onIconClickedHandler}
|
on:click={onIconClickedHandler}
|
||||||
class="absolute p-2 group-hover:block"
|
on:keydown|preventDefault
|
||||||
class:group-hover:block={!disabled}
|
on:keyup|preventDefault
|
||||||
class:hidden={!selected}
|
class="absolute p-2"
|
||||||
class:cursor-not-allowed={disabled}
|
class:cursor-not-allowed={disabled}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={selected}
|
aria-checked={selected}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
in:fade={{ duration: 100 }}
|
||||||
|
out:fade={{ duration: 100 }}
|
||||||
>
|
>
|
||||||
{#if disabled}
|
{#if disabled}
|
||||||
<CheckCircle size="24" class="text-zinc-800" />
|
<CheckCircle size="24" class="text-zinc-800" />
|
||||||
|
@ -153,6 +164,13 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if selectionCandidate}
|
||||||
|
<div
|
||||||
|
class="absolute w-full h-full top-0 bg-immich-primary opacity-40"
|
||||||
|
in:fade={{ duration: 100 }}
|
||||||
|
out:fade={{ duration: 100 }}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</IntersectionObserver>
|
</IntersectionObserver>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
assetInteractionStore,
|
assetInteractionStore,
|
||||||
|
assetSelectionCandidates,
|
||||||
assetsInAlbumStoreState,
|
assetsInAlbumStoreState,
|
||||||
isMultiSelectStoreState,
|
isMultiSelectStoreState,
|
||||||
selectedAssets,
|
selectedAssets,
|
||||||
|
@ -8,15 +9,15 @@
|
||||||
} from '$lib/stores/asset-interaction.store';
|
} from '$lib/stores/asset-interaction.store';
|
||||||
import { assetStore } from '$lib/stores/assets.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 type { AssetResponseDto } from '@api';
|
import type { AssetResponseDto } from '@api';
|
||||||
import justifiedLayout from 'justified-layout';
|
import justifiedLayout from 'justified-layout';
|
||||||
import lodash from 'lodash-es';
|
import lodash from 'lodash-es';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
|
||||||
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 { getAssetRatio } from '$lib/utils/asset-utils';
|
|
||||||
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
export let assets: AssetResponseDto[];
|
export let assets: AssetResponseDto[];
|
||||||
export let bucketDate: string;
|
export let bucketDate: string;
|
||||||
|
@ -130,18 +131,19 @@
|
||||||
dateGroupTitle: string,
|
dateGroupTitle: string,
|
||||||
) => {
|
) => {
|
||||||
if ($selectedAssets.has(asset)) {
|
if ($selectedAssets.has(asset)) {
|
||||||
|
for (const candidate of $assetSelectionCandidates || []) {
|
||||||
|
assetInteractionStore.removeAssetFromMultiselectGroup(candidate);
|
||||||
|
}
|
||||||
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
|
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
|
||||||
} else {
|
} else {
|
||||||
|
for (const candidate of $assetSelectionCandidates || []) {
|
||||||
|
assetInteractionStore.addAssetToMultiselectGroup(candidate);
|
||||||
|
}
|
||||||
assetInteractionStore.addAssetToMultiselectGroup(asset);
|
assetInteractionStore.addAssetToMultiselectGroup(asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all assets are selected in a group to toggle the group selection's icon
|
// Check if all assets are selected in a group to toggle the group selection's icon
|
||||||
let selectedAssetsInGroupCount = 0;
|
let selectedAssetsInGroupCount = assetsInDateGroup.filter((asset) => $selectedAssets.has(asset)).length;
|
||||||
assetsInDateGroup.forEach((asset) => {
|
|
||||||
if ($selectedAssets.has(asset)) {
|
|
||||||
selectedAssetsInGroupCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if all assets are selected in a group, add the group to selected group
|
// if all assets are selected in a group, add the group to selected group
|
||||||
if (selectedAssetsInGroupCount == assetsInDateGroup.length) {
|
if (selectedAssetsInGroupCount == assetsInDateGroup.length) {
|
||||||
|
@ -151,9 +153,13 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const assetMouseEventHandler = (dateGroupTitle: string) => {
|
const assetMouseEventHandler = (dateGroupTitle: string, asset: AssetResponseDto | null) => {
|
||||||
// Show multi select icon on hover on date group
|
// Show multi select icon on hover on date group
|
||||||
hoveredDateGroup = dateGroupTitle;
|
hoveredDateGroup = dateGroupTitle;
|
||||||
|
|
||||||
|
if ($isMultiSelectStoreState) {
|
||||||
|
dispatch('selectAssetCandidates', { asset });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -171,9 +177,12 @@
|
||||||
class="flex flex-col mt-5"
|
class="flex flex-col mt-5"
|
||||||
on:mouseenter={() => {
|
on:mouseenter={() => {
|
||||||
isMouseOverGroup = true;
|
isMouseOverGroup = true;
|
||||||
assetMouseEventHandler(dateGroupTitle);
|
assetMouseEventHandler(dateGroupTitle, null);
|
||||||
|
}}
|
||||||
|
on:mouseleave={() => {
|
||||||
|
isMouseOverGroup = false;
|
||||||
|
assetMouseEventHandler(dateGroupTitle, null);
|
||||||
}}
|
}}
|
||||||
on:mouseleave={() => (isMouseOverGroup = false)}
|
|
||||||
>
|
>
|
||||||
<!-- Date group title -->
|
<!-- Date group title -->
|
||||||
<p
|
<p
|
||||||
|
@ -216,9 +225,10 @@
|
||||||
{groupIndex}
|
{groupIndex}
|
||||||
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)}
|
on:mouse-event={() => assetMouseEventHandler(dateGroupTitle, asset)}
|
||||||
selected={$selectedAssets.has(asset) || $assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
|
selected={$selectedAssets.has(asset) || $assetsInAlbumStoreState.some(({ id }) => id === asset.id)}
|
||||||
disabled={$assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
|
selectionCandidate={$assetSelectionCandidates.has(asset)}
|
||||||
|
disabled={$assetsInAlbumStoreState.some(({ id }) => id === asset.id)}
|
||||||
thumbnailWidth={box.width}
|
thumbnailWidth={box.width}
|
||||||
thumbnailHeight={box.height}
|
thumbnailHeight={box.height}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { BucketPosition } from '$lib/models/asset-grid-state';
|
||||||
import {
|
import {
|
||||||
assetInteractionStore,
|
assetInteractionStore,
|
||||||
|
isMultiSelectStoreState,
|
||||||
isViewingAssetStoreState,
|
isViewingAssetStoreState,
|
||||||
|
selectedAssets,
|
||||||
viewingAssetStoreState,
|
viewingAssetStoreState,
|
||||||
} from '$lib/stores/asset-interaction.store';
|
} from '$lib/stores/asset-interaction.store';
|
||||||
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
||||||
import type { UserResponseDto } from '@api';
|
import type { UserResponseDto } from '@api';
|
||||||
import { AssetCountByTimeBucketResponseDto, AssetResponseDto, TimeGroupEnum, api } from '@api';
|
import { api, AssetCountByTimeBucketResponseDto, AssetResponseDto, TimeGroupEnum } from '@api';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
||||||
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
||||||
|
@ -16,7 +19,6 @@
|
||||||
OnScrollbarDragDetail,
|
OnScrollbarDragDetail,
|
||||||
} from '../shared-components/scrollbar/scrollbar.svelte';
|
} from '../shared-components/scrollbar/scrollbar.svelte';
|
||||||
import AssetDateGroup from './asset-date-group.svelte';
|
import AssetDateGroup from './asset-date-group.svelte';
|
||||||
import { BucketPosition } from '$lib/models/asset-grid-state';
|
|
||||||
import MemoryLane from './memory-lane.svelte';
|
import MemoryLane from './memory-lane.svelte';
|
||||||
|
|
||||||
export let user: UserResponseDto | undefined = undefined;
|
export let user: UserResponseDto | undefined = undefined;
|
||||||
|
@ -111,8 +113,80 @@
|
||||||
navigateToNextAsset();
|
navigateToNextAsset();
|
||||||
assetStore.removeAsset(asset.id);
|
assetStore.removeAsset(asset.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lastAssetMouseEvent: AssetResponseDto | null = null;
|
||||||
|
|
||||||
|
$: if (!lastAssetMouseEvent) {
|
||||||
|
assetInteractionStore.clearAssetSelectionCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
let shiftKeyIsDown = false;
|
||||||
|
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Shift') {
|
||||||
|
e.preventDefault();
|
||||||
|
shiftKeyIsDown = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyUp = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Shift') {
|
||||||
|
e.preventDefault();
|
||||||
|
shiftKeyIsDown = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (!shiftKeyIsDown) {
|
||||||
|
assetInteractionStore.clearAssetSelectionCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (shiftKeyIsDown && lastAssetMouseEvent) {
|
||||||
|
selectAssetCandidates(lastAssetMouseEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLastSelectedAsset = () => {
|
||||||
|
let value;
|
||||||
|
for (value of $selectedAssets);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAssetCandidates = (e: CustomEvent) => {
|
||||||
|
const asset = e.detail.asset;
|
||||||
|
if (asset) {
|
||||||
|
selectAssetCandidates(asset);
|
||||||
|
}
|
||||||
|
lastAssetMouseEvent = asset;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAssetCandidates = (asset: AssetResponseDto) => {
|
||||||
|
if (!shiftKeyIsDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastSelectedAsset = getLastSelectedAsset();
|
||||||
|
if (!lastSelectedAsset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = $assetGridState.assets.indexOf(asset);
|
||||||
|
let end = $assetGridState.assets.indexOf(lastSelectedAsset);
|
||||||
|
|
||||||
|
if (start > end) {
|
||||||
|
[start, end] = [end, start];
|
||||||
|
}
|
||||||
|
|
||||||
|
assetInteractionStore.setAssetSelectionCandidates($assetGridState.assets.slice(start, end + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectStart = (e: Event) => {
|
||||||
|
if ($isMultiSelectStoreState && shiftKeyIsDown) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} on:selectstart={onSelectStart} />
|
||||||
|
|
||||||
{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
|
{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
|
||||||
<Scrollbar
|
<Scrollbar
|
||||||
scrollbarHeight={viewportHeight}
|
scrollbarHeight={viewportHeight}
|
||||||
|
@ -155,6 +229,7 @@
|
||||||
<AssetDateGroup
|
<AssetDateGroup
|
||||||
{isAlbumSelectionMode}
|
{isAlbumSelectionMode}
|
||||||
on:shift={handleScrollTimeline}
|
on:shift={handleScrollTimeline}
|
||||||
|
on:selectAssetCandidates={handleSelectAssetCandidates}
|
||||||
assets={bucket.assets}
|
assets={bucket.assets}
|
||||||
bucketDate={bucket.bucketDate}
|
bucketDate={bucket.bucketDate}
|
||||||
bucketHeight={bucket.bucketHeight}
|
bucketHeight={bucket.bucketHeight}
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const assetsInAlbumStoreState = writable<AssetResponseDto[]>([]);
|
||||||
export const selectedAssets = writable<Set<AssetResponseDto>>(new Set());
|
export const selectedAssets = writable<Set<AssetResponseDto>>(new Set());
|
||||||
export const selectedGroup = writable<Set<string>>(new Set());
|
export const selectedGroup = writable<Set<string>>(new Set());
|
||||||
export const isMultiSelectStoreState = derived(selectedAssets, ($selectedAssets) => $selectedAssets.size > 0);
|
export const isMultiSelectStoreState = derived(selectedAssets, ($selectedAssets) => $selectedAssets.size > 0);
|
||||||
|
export const assetSelectionCandidates = writable<Set<AssetResponseDto>>(new Set());
|
||||||
|
|
||||||
function createAssetInteractionStore() {
|
function createAssetInteractionStore() {
|
||||||
let _assetGridState = new AssetGridState();
|
let _assetGridState = new AssetGridState();
|
||||||
|
@ -19,6 +20,7 @@ function createAssetInteractionStore() {
|
||||||
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>;
|
||||||
|
|
||||||
// Subscriber
|
// Subscriber
|
||||||
assetGridState.subscribe((state) => {
|
assetGridState.subscribe((state) => {
|
||||||
|
@ -41,6 +43,10 @@ function createAssetInteractionStore() {
|
||||||
_assetsInAlbums = assets;
|
_assetsInAlbums = assets;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assetSelectionCandidates.subscribe((assets) => {
|
||||||
|
_assetSelectionCandidates = assets;
|
||||||
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,14 +123,26 @@ function createAssetInteractionStore() {
|
||||||
selectedGroup.set(_selectedGroup);
|
selectedGroup.set(_selectedGroup);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setAssetSelectionCandidates = (assets: AssetResponseDto[]) => {
|
||||||
|
_assetSelectionCandidates = new Set(assets);
|
||||||
|
assetSelectionCandidates.set(_assetSelectionCandidates);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearAssetSelectionCandidates = () => {
|
||||||
|
_assetSelectionCandidates.clear();
|
||||||
|
assetSelectionCandidates.set(_assetSelectionCandidates);
|
||||||
|
};
|
||||||
|
|
||||||
const clearMultiselect = () => {
|
const clearMultiselect = () => {
|
||||||
_selectedAssets.clear();
|
_selectedAssets.clear();
|
||||||
_selectedGroup.clear();
|
_selectedGroup.clear();
|
||||||
|
_assetSelectionCandidates.clear();
|
||||||
_assetsInAlbums = [];
|
_assetsInAlbums = [];
|
||||||
|
|
||||||
selectedAssets.set(_selectedAssets);
|
selectedAssets.set(_selectedAssets);
|
||||||
selectedGroup.set(_selectedGroup);
|
selectedGroup.set(_selectedGroup);
|
||||||
assetsInAlbumStoreState.set(_assetsInAlbums);
|
assetsInAlbumStoreState.set(_assetsInAlbums);
|
||||||
|
assetSelectionCandidates.set(_assetSelectionCandidates);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -136,6 +154,8 @@ function createAssetInteractionStore() {
|
||||||
removeAssetFromMultiselectGroup,
|
removeAssetFromMultiselectGroup,
|
||||||
addGroupToMultiselectGroup,
|
addGroupToMultiselectGroup,
|
||||||
removeGroupFromMultiselectGroup,
|
removeGroupFromMultiselectGroup,
|
||||||
|
setAssetSelectionCandidates,
|
||||||
|
clearAssetSelectionCandidates,
|
||||||
clearMultiselect,
|
clearMultiselect,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
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 } from '@api';
|
||||||
import { flatMap, sumBy } from 'lodash-es';
|
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +59,7 @@ function createAssetStore() {
|
||||||
|
|
||||||
// Update timeline height based on calculated bucket height
|
// Update timeline height based on calculated bucket height
|
||||||
assetGridState.update((state) => {
|
assetGridState.update((state) => {
|
||||||
state.timelineHeight = sumBy(state.buckets, (d) => d.bucketHeight);
|
state.timelineHeight = state.buckets.reduce((acc, b) => acc + b.bucketHeight, 0);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -101,7 +100,7 @@ function createAssetStore() {
|
||||||
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;
|
||||||
state.assets = flatMap(state.buckets, (b) => b.assets);
|
state.assets = state.buckets.flatMap((b) => b.assets);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -123,7 +122,7 @@ function createAssetStore() {
|
||||||
if (state.buckets[bucketIndex].assets.length === 0) {
|
if (state.buckets[bucketIndex].assets.length === 0) {
|
||||||
_removeBucket(state.buckets[bucketIndex].bucketDate);
|
_removeBucket(state.buckets[bucketIndex].bucketDate);
|
||||||
}
|
}
|
||||||
state.assets = flatMap(state.buckets, (b) => b.assets);
|
state.assets = state.buckets.flatMap((b) => b.assets);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -132,7 +131,7 @@ function createAssetStore() {
|
||||||
assetGridState.update((state) => {
|
assetGridState.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 = flatMap(state.buckets, (b) => b.assets);
|
state.assets = state.buckets.flatMap((b) => b.assets);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -180,7 +179,7 @@ function createAssetStore() {
|
||||||
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;
|
||||||
|
|
||||||
state.assets = flatMap(state.buckets, (b) => b.assets);
|
state.assets = state.buckets.flatMap((b) => b.assets);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue