fix: regression in select-all (#16969)

* bug: select-all

* set->[] in interaction store, clear select-all on cancel

* feedback

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Min Idzelis 2025-03-19 11:55:50 -04:00 committed by GitHub
parent 1a0a9ef36c
commit 9398b0d4b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 82 additions and 82 deletions

View file

@ -52,7 +52,7 @@
{#if isShowConfirmation}
<DeleteAssetDialog
size={getOwnedAssets().size}
size={getOwnedAssets().length}
onConfirm={handleDelete}
onCancel={() => (isShowConfirmation = false)}
/>

View file

@ -28,7 +28,7 @@
await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) });
};
let menuItemIcon = $derived(getAssets().size === 1 ? mdiFileDownloadOutline : mdiFolderDownloadOutline);
let menuItemIcon = $derived(getAssets().length === 1 ? mdiFileDownloadOutline : mdiFolderDownloadOutline);
</script>
<svelte:window use:shortcut={{ shortcut: { key: 'd', shift: true }, onShortcut: handleDownloadFiles }} />

View file

@ -23,7 +23,7 @@
const removeFromAlbum = async () => {
const isConfirmed = await dialogController.show({
prompt: $t('remove_assets_album_confirmation', { values: { count: getAssets().size } }),
prompt: $t('remove_assets_album_confirmation', { values: { count: getAssets().length } }),
});
if (!isConfirmed) {

View file

@ -20,7 +20,7 @@
const handleRemove = async () => {
const isConfirmed = await dialogController.show({
title: $t('remove_assets_title'),
prompt: $t('remove_assets_shared_link_confirmation', { values: { count: getAssets().size } }),
prompt: $t('remove_assets_shared_link_confirmation', { values: { count: getAssets().length } }),
confirmText: $t('remove'),
});

View file

@ -1,6 +1,6 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { AssetBucket } from '$lib/stores/assets-store.svelte';
import { AssetBucket, assetSnapshot, assetsSnapshot } from '$lib/stores/assets-store.svelte';
import { navigate } from '$lib/utils/navigation';
import { getDateLocaleString } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk';
@ -71,9 +71,7 @@
assetInteraction.removeGroupFromMultiselectGroup(groupTitle);
}
};
const snapshotAssetArray = (assets: AssetResponseDto[]) => {
return assets.map((a) => $state.snapshot(a));
};
const assetMouseEventHandler = (groupTitle: string, asset: AssetResponseDto | null) => {
// Show multi select icon on hover on date group
hoveredDateGroup = groupTitle;
@ -121,8 +119,8 @@
<div
transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}
class="inline-block px-2 hover:cursor-pointer"
onclick={() => handleSelectGroup(dateGroup.groupTitle, snapshotAssetArray(dateGroup.getAssets()))}
onkeydown={() => handleSelectGroup(dateGroup.groupTitle, snapshotAssetArray(dateGroup.getAssets()))}
onclick={() => handleSelectGroup(dateGroup.groupTitle, assetsSnapshot(dateGroup.getAssets()))}
onkeydown={() => handleSelectGroup(dateGroup.groupTitle, assetsSnapshot(dateGroup.getAssets()))}
>
{#if assetInteraction.selectedGroup.has(dateGroup.groupTitle)}
<Icon path={mdiCheckCircle} size="24" color="#4250af" />
@ -160,10 +158,10 @@
{showArchiveIcon}
{asset}
{groupIndex}
focussed={assetInteraction.isFocussedAsset(asset)}
focussed={assetInteraction.isFocussedAsset(asset.id)}
onClick={(asset) => onClick(dateGroup.getAssets(), dateGroup.groupTitle, asset)}
onSelect={(asset) => assetSelectHandler(asset, dateGroup.getAssets(), dateGroup.groupTitle)}
onMouseEvent={() => assetMouseEventHandler(dateGroup.groupTitle, $state.snapshot(asset))}
onMouseEvent={() => assetMouseEventHandler(dateGroup.groupTitle, assetSnapshot(asset))}
selected={assetInteraction.hasSelectedAsset(asset.id) || dateGroup.bucket.store.albumAssets.has(asset.id)}
selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)}
handleFocus={() => assetOnFocusHandler(asset)}

View file

@ -4,7 +4,7 @@
import type { Action } from '$lib/components/asset-viewer/actions/action';
import { AppRoute, AssetAction } from '$lib/constants';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetBucket, AssetStore } from '$lib/stores/assets-store.svelte';
import { AssetBucket, assetsSnapshot, AssetStore } from '$lib/stores/assets-store.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { isSearchEnabled } from '$lib/stores/search.store';
import { featureFlags } from '$lib/stores/server-config.store';
@ -286,7 +286,7 @@
};
const onDelete = () => {
const hasTrashedAsset = assetInteraction.selectedAssetsArray.some((asset) => asset.isTrashed);
const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed);
if ($showDeleteModal && (!isTrashEnabled || hasTrashedAsset)) {
isShowDeleteConfirmation = true;
@ -304,7 +304,7 @@
};
const onStackAssets = async () => {
const ids = await stackAssets(assetInteraction.selectedAssetsArray);
const ids = await stackAssets(assetInteraction.selectedAssets);
if (ids) {
assetStore.removeAssets(ids);
onEscape();
@ -312,8 +312,8 @@
};
const toggleArchive = async () => {
await archiveAssets(assetInteraction.selectedAssetsArray, !assetInteraction.isAllArchived);
assetStore.updateAssets(assetInteraction.selectedAssetsArray);
await archiveAssets(assetInteraction.selectedAssets, !assetInteraction.isAllArchived);
assetStore.updateAssets(assetInteraction.selectedAssets);
deselectAllAssets();
};
@ -450,7 +450,7 @@
if (assetInteraction.selectedGroup.has(group)) {
assetInteraction.removeGroupFromMultiselectGroup(group);
for (const asset of assets) {
assetInteraction.removeAssetFromMultiselectGroup(asset);
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
}
} else {
assetInteraction.addGroupToMultiselectGroup(group);
@ -471,15 +471,15 @@
return;
}
const rangeSelection = assetInteraction.assetSelectionCandidates.size > 0;
const rangeSelection = assetInteraction.assetSelectionCandidates.length > 0;
const deselect = assetInteraction.hasSelectedAsset(asset.id);
// Select/deselect already loaded assets
if (deselect) {
for (const candidate of assetInteraction.assetSelectionCandidates) {
assetInteraction.removeAssetFromMultiselectGroup(candidate);
assetInteraction.removeAssetFromMultiselectGroup(candidate.id);
}
assetInteraction.removeAssetFromMultiselectGroup(asset);
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
} else {
for (const candidate of assetInteraction.assetSelectionCandidates) {
handleSelectAsset(candidate);
@ -510,7 +510,7 @@
await assetStore.loadBucket(bucket.bucketDate);
for (const asset of bucket.getAssets()) {
if (deselect) {
assetInteraction.removeAssetFromMultiselectGroup(asset);
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
} else {
handleSelectAsset(asset);
}
@ -553,7 +553,7 @@
return;
}
const assets = assetStore.getAssets();
const assets = assetsSnapshot(assetStore.getAssets());
let start = assets.findIndex((a) => a.id === startAsset.id);
let end = assets.findIndex((a) => a.id === endAsset.id);
@ -602,7 +602,7 @@
let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash);
let isEmpty = $derived(assetStore.isInitialized && assetStore.buckets.length === 0);
let idsSelectedAssets = $derived(assetInteraction.selectedAssetsArray.map(({ id }) => id));
let idsSelectedAssets = $derived(assetInteraction.selectedAssets.map(({ id }) => id));
$effect(() => {
if (isEmpty) {

View file

@ -4,8 +4,8 @@
export interface AssetControlContext {
// Wrap assets in a function, because context isn't reactive.
getAssets: () => Set<AssetResponseDto>; // All assets includes partners' assets
getOwnedAssets: () => Set<AssetResponseDto>; // Only assets owned by the user
getAssets: () => AssetResponseDto[]; // All assets includes partners' assets
getOwnedAssets: () => AssetResponseDto[]; // Only assets owned by the user
clearSelect: () => void;
}
@ -20,7 +20,7 @@
import type { Snippet } from 'svelte';
interface Props {
assets: Set<AssetResponseDto>;
assets: AssetResponseDto[];
clearSelect: () => void;
ownerId?: string | undefined;
children?: Snippet;
@ -30,8 +30,7 @@
setContext({
getAssets: () => assets,
getOwnedAssets: () =>
ownerId === undefined ? assets : new Set([...assets].filter((asset) => asset.ownerId === ownerId)),
getOwnedAssets: () => (ownerId === undefined ? assets : assets.filter((asset) => asset.ownerId === ownerId)),
clearSelect,
});
</script>
@ -39,8 +38,8 @@
<ControlAppBar onClose={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
{#snippet leading()}
<div class="font-medium text-immich-primary dark:text-immich-dark-primary">
<p class="block sm:hidden">{assets.size}</p>
<p class="hidden sm:block">{$t('selected_count', { values: { count: assets.size } })}</p>
<p class="block sm:hidden">{assets.length}</p>
<p class="hidden sm:block">{$t('selected_count', { values: { count: assets.length } })}</p>
</div>
{/snippet}
{#snippet trailing()}

View file

@ -15,7 +15,6 @@
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
import Map from '$lib/components/shared-components/map/map.svelte';
import { get } from 'svelte/store';
interface Point {
lng: number;
lat: number;

View file

@ -169,9 +169,9 @@
// Select/deselect already loaded assets
if (deselect) {
for (const candidate of assetInteraction.assetSelectionCandidates) {
assetInteraction.removeAssetFromMultiselectGroup(candidate);
assetInteraction.removeAssetFromMultiselectGroup(candidate.id);
}
assetInteraction.removeAssetFromMultiselectGroup(asset);
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
} else {
for (const candidate of assetInteraction.assetSelectionCandidates) {
assetInteraction.selectAsset(candidate);
@ -217,7 +217,7 @@
};
const onDelete = () => {
const hasTrashedAsset = assetInteraction.selectedAssetsArray.some((asset) => asset.isTrashed);
const hasTrashedAsset = assetInteraction.selectedAssets.some((asset) => asset.isTrashed);
if ($showDeleteModal && (!isTrashEnabled || hasTrashedAsset)) {
isShowDeleteConfirmation = true;
@ -245,7 +245,7 @@
};
const toggleArchive = async () => {
const ids = await archiveAssets(assetInteraction.selectedAssetsArray, !assetInteraction.isAllArchived);
const ids = await archiveAssets(assetInteraction.selectedAssets, !assetInteraction.isAllArchived);
if (ids) {
assets = assets.filter((asset) => !ids.includes(asset.id));
deselectAllAssets();
@ -407,7 +407,7 @@
};
let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash);
let idsSelectedAssets = $derived(assetInteraction.selectedAssetsArray.map(({ id }) => id));
let idsSelectedAssets = $derived(assetInteraction.selectedAssets.map(({ id }) => id));
$effect(() => {
if (!lastAssetMouseEvent) {
@ -438,7 +438,7 @@
{#if isShowDeleteConfirmation}
<DeleteAssetDialog
size={assetInteraction.selectedAssets.size}
size={assetInteraction.selectedAssets.length}
onCancel={() => (isShowDeleteConfirmation = false)}
onConfirm={() => handlePromiseError(trashOrDelete(true))}
/>
@ -480,7 +480,7 @@
{asset}
selected={assetInteraction.hasSelectedAsset(asset.id)}
selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)}
focussed={assetInteraction.isFocussedAsset(asset)}
focussed={assetInteraction.isFocussedAsset(asset.id)}
thumbnailWidth={layout.width}
thumbnailHeight={layout.height}
/>