mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
feat: extract actions from asset-date-group
This commit is contained in:
parent
5ea66e88f1
commit
ef304fa2f6
@ -0,0 +1,218 @@
|
||||
<script lang="ts">
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
||||
import { searchStore } from '$lib/stores/search.svelte';
|
||||
|
||||
interface Props {
|
||||
timelineManager: TimelineManager;
|
||||
assetInteraction: AssetInteraction;
|
||||
singleSelect?: boolean;
|
||||
|
||||
handleScrollTop: (scrollTop: number) => void;
|
||||
onSelect?: (asset: TimelineAsset) => void;
|
||||
|
||||
onDateGroupSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
|
||||
onSelectAssets: (asset: TimelineAsset) => Promise<void>;
|
||||
onSelectAssetCandidates: (asset: TimelineAsset | null) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
singleSelect = false,
|
||||
timelineManager,
|
||||
assetInteraction,
|
||||
handleScrollTop = () => {},
|
||||
onSelect = () => {},
|
||||
onDateGroupSelect = $bindable(),
|
||||
onSelectAssets = $bindable(),
|
||||
onSelectAssetCandidates = $bindable(),
|
||||
}: Props = $props();
|
||||
|
||||
const handleSelectAsset = (asset: TimelineAsset) => {
|
||||
if (!timelineManager.albumAssets.has(asset.id)) {
|
||||
assetInteraction.selectAsset(asset);
|
||||
}
|
||||
};
|
||||
|
||||
let lastAssetMouseEvent: TimelineAsset | null = $state(null);
|
||||
|
||||
let shiftKeyIsDown = $state(false);
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (searchStore.isSearchEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Shift') {
|
||||
event.preventDefault();
|
||||
shiftKeyIsDown = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyUp = (event: KeyboardEvent) => {
|
||||
if (searchStore.isSearchEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Shift') {
|
||||
event.preventDefault();
|
||||
shiftKeyIsDown = false;
|
||||
}
|
||||
};
|
||||
|
||||
onSelectAssetCandidates = (asset: TimelineAsset | null) => {
|
||||
if (asset) {
|
||||
void selectAssetCandidates(asset);
|
||||
}
|
||||
lastAssetMouseEvent = asset;
|
||||
};
|
||||
|
||||
onDateGroupSelect = ({ title: group, assets }: { title: string; assets: TimelineAsset[] }) => {
|
||||
if (assetInteraction.selectedGroup.has(group)) {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(group);
|
||||
for (const asset of assets) {
|
||||
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||
}
|
||||
} else {
|
||||
assetInteraction.addGroupToMultiselectGroup(group);
|
||||
for (const asset of assets) {
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
}
|
||||
|
||||
if (timelineManager.assetCount == assetInteraction.selectedAssets.length) {
|
||||
isSelectingAllAssets.set(true);
|
||||
} else {
|
||||
isSelectingAllAssets.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
onSelectAssets = async (asset: TimelineAsset) => {
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
onSelect(asset);
|
||||
|
||||
if (singleSelect) {
|
||||
handleScrollTop(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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.id);
|
||||
}
|
||||
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||
} else {
|
||||
for (const candidate of assetInteraction.assetSelectionCandidates) {
|
||||
handleSelectAsset(candidate);
|
||||
}
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
|
||||
assetInteraction.clearAssetSelectionCandidates();
|
||||
|
||||
if (assetInteraction.assetSelectionStart && rangeSelection) {
|
||||
let startBucket = timelineManager.getMonthGroupByAssetId(assetInteraction.assetSelectionStart.id);
|
||||
let endBucket = timelineManager.getMonthGroupByAssetId(asset.id);
|
||||
|
||||
if (startBucket === null || endBucket === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select/deselect assets in range (start,end)
|
||||
let started = false;
|
||||
for (const monthGroup of timelineManager.months) {
|
||||
if (monthGroup === endBucket) {
|
||||
break;
|
||||
}
|
||||
if (started) {
|
||||
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
|
||||
for (const asset of monthGroup.assetsIterator()) {
|
||||
if (deselect) {
|
||||
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||
} else {
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monthGroup === startBucket) {
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update date group selection in range [start,end]
|
||||
started = false;
|
||||
for (const monthGroup of timelineManager.months) {
|
||||
if (monthGroup === startBucket) {
|
||||
started = true;
|
||||
}
|
||||
if (started) {
|
||||
// Split month group into day groups and check each group
|
||||
for (const dayGroup of monthGroup.dayGroups) {
|
||||
const dayGroupTitle = dayGroup.groupTitle;
|
||||
if (dayGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
||||
assetInteraction.addGroupToMultiselectGroup(dayGroupTitle);
|
||||
} else {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(dayGroupTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monthGroup === endBucket) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assetInteraction.setAssetSelectionStart(deselect ? null : asset);
|
||||
};
|
||||
|
||||
const selectAssetCandidates = async (endAsset: TimelineAsset) => {
|
||||
if (!shiftKeyIsDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startAsset = assetInteraction.assetSelectionStart;
|
||||
if (!startAsset) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assets = assetsSnapshot(await timelineManager.retrieveRange(startAsset, endAsset));
|
||||
assetInteraction.setAssetSelectionCandidates(assets);
|
||||
};
|
||||
|
||||
let isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
|
||||
|
||||
$effect(() => {
|
||||
if (isEmpty) {
|
||||
assetInteraction.clearMultiselect();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!lastAssetMouseEvent) {
|
||||
assetInteraction.clearAssetSelectionCandidates();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!shiftKeyIsDown) {
|
||||
assetInteraction.clearAssetSelectionCandidates();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (shiftKeyIsDown && lastAssetMouseEvent) {
|
||||
void selectAssetCandidates(lastAssetMouseEvent);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} />
|
@ -7,6 +7,7 @@
|
||||
setFocusToAsset as setFocusAssetInit,
|
||||
setFocusTo as setFocusToInit,
|
||||
} from '$lib/components/photos-page/actions/focus-actions';
|
||||
import AssetDateGroupActions from '$lib/components/photos-page/asset-date-group-actions.svelte';
|
||||
import AssetViewerAndActions from '$lib/components/photos-page/asset-viewer-and-actions.svelte';
|
||||
import Skeleton from '$lib/components/photos-page/skeleton.svelte';
|
||||
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||
@ -17,11 +18,9 @@
|
||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||
import { showDeleteModal } from '$lib/stores/preferences.store';
|
||||
import { searchStore } from '$lib/stores/search.svelte';
|
||||
@ -425,14 +424,6 @@
|
||||
deselectAllAssets();
|
||||
};
|
||||
|
||||
const handleSelectAsset = (asset: TimelineAsset) => {
|
||||
if (!timelineManager.albumAssets.has(asset.id)) {
|
||||
assetInteraction.selectAsset(asset);
|
||||
}
|
||||
};
|
||||
|
||||
let lastAssetMouseEvent: TimelineAsset | null = $state(null);
|
||||
|
||||
let shiftKeyIsDown = $state(false);
|
||||
|
||||
const deselectAllAssets = () => {
|
||||
@ -461,131 +452,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAssetCandidates = (asset: TimelineAsset | null) => {
|
||||
if (asset) {
|
||||
void selectAssetCandidates(asset);
|
||||
}
|
||||
lastAssetMouseEvent = asset;
|
||||
};
|
||||
|
||||
const handleGroupSelect = (timelineManager: TimelineManager, group: string, assets: TimelineAsset[]) => {
|
||||
if (assetInteraction.selectedGroup.has(group)) {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(group);
|
||||
for (const asset of assets) {
|
||||
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||
}
|
||||
} else {
|
||||
assetInteraction.addGroupToMultiselectGroup(group);
|
||||
for (const asset of assets) {
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
}
|
||||
|
||||
if (timelineManager.assetCount == assetInteraction.selectedAssets.length) {
|
||||
isSelectingAllAssets.set(true);
|
||||
} else {
|
||||
isSelectingAllAssets.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAssets = async (asset: TimelineAsset) => {
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
onSelect(asset);
|
||||
|
||||
if (singleSelect) {
|
||||
scrollTop(0);
|
||||
return;
|
||||
}
|
||||
|
||||
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.id);
|
||||
}
|
||||
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||
} else {
|
||||
for (const candidate of assetInteraction.assetSelectionCandidates) {
|
||||
handleSelectAsset(candidate);
|
||||
}
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
|
||||
assetInteraction.clearAssetSelectionCandidates();
|
||||
|
||||
if (assetInteraction.assetSelectionStart && rangeSelection) {
|
||||
let startBucket = timelineManager.getMonthGroupByAssetId(assetInteraction.assetSelectionStart.id);
|
||||
let endBucket = timelineManager.getMonthGroupByAssetId(asset.id);
|
||||
|
||||
if (startBucket === null || endBucket === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select/deselect assets in range (start,end)
|
||||
let started = false;
|
||||
for (const monthGroup of timelineManager.months) {
|
||||
if (monthGroup === endBucket) {
|
||||
break;
|
||||
}
|
||||
if (started) {
|
||||
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
|
||||
for (const asset of monthGroup.assetsIterator()) {
|
||||
if (deselect) {
|
||||
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||
} else {
|
||||
handleSelectAsset(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monthGroup === startBucket) {
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update date group selection in range [start,end]
|
||||
started = false;
|
||||
for (const monthGroup of timelineManager.months) {
|
||||
if (monthGroup === startBucket) {
|
||||
started = true;
|
||||
}
|
||||
if (started) {
|
||||
// Split month group into day groups and check each group
|
||||
for (const dayGroup of monthGroup.dayGroups) {
|
||||
const dayGroupTitle = dayGroup.groupTitle;
|
||||
if (dayGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
||||
assetInteraction.addGroupToMultiselectGroup(dayGroupTitle);
|
||||
} else {
|
||||
assetInteraction.removeGroupFromMultiselectGroup(dayGroupTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monthGroup === endBucket) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assetInteraction.setAssetSelectionStart(deselect ? null : asset);
|
||||
};
|
||||
|
||||
const selectAssetCandidates = async (endAsset: TimelineAsset) => {
|
||||
if (!shiftKeyIsDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startAsset = assetInteraction.assetSelectionStart;
|
||||
if (!startAsset) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assets = assetsSnapshot(await timelineManager.retrieveRange(startAsset, endAsset));
|
||||
assetInteraction.setAssetSelectionCandidates(assets);
|
||||
};
|
||||
|
||||
const onSelectStart = (e: Event) => {
|
||||
if (assetInteraction.selectionActive && shiftKeyIsDown) {
|
||||
e.preventDefault();
|
||||
@ -652,27 +518,23 @@
|
||||
})(),
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (!lastAssetMouseEvent) {
|
||||
assetInteraction.clearAssetSelectionCandidates();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!shiftKeyIsDown) {
|
||||
assetInteraction.clearAssetSelectionCandidates();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (shiftKeyIsDown && lastAssetMouseEvent) {
|
||||
void selectAssetCandidates(lastAssetMouseEvent);
|
||||
}
|
||||
});
|
||||
let onDateGroupSelect = <({ title, assets }: { title: string; assets: TimelineAsset[] }) => void>$state();
|
||||
let onSelectAssets = <(asset: TimelineAsset) => Promise<void>>$state();
|
||||
let onSelectAssetCandidates = <(asset: TimelineAsset | null) => void>$state();
|
||||
</script>
|
||||
|
||||
<svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} onselectstart={onSelectStart} use:shortcuts={shortcutList} />
|
||||
|
||||
<AssetDateGroupActions
|
||||
{timelineManager}
|
||||
{assetInteraction}
|
||||
handleScrollTop={scrollTop}
|
||||
{onSelect}
|
||||
bind:onDateGroupSelect
|
||||
bind:onSelectAssets
|
||||
bind:onSelectAssetCandidates
|
||||
></AssetDateGroupActions>
|
||||
|
||||
{#if isShowDeleteConfirmation}
|
||||
<DeleteAssetDialog
|
||||
size={idsSelectedAssets.length}
|
||||
@ -792,9 +654,9 @@
|
||||
{isSelectionMode}
|
||||
{singleSelect}
|
||||
{monthGroup}
|
||||
onSelect={({ title, assets }) => handleGroupSelect(timelineManager, title, assets)}
|
||||
onSelectAssetCandidates={handleSelectAssetCandidates}
|
||||
onSelectAssets={handleSelectAssets}
|
||||
onSelect={onDateGroupSelect}
|
||||
{onSelectAssetCandidates}
|
||||
{onSelectAssets}
|
||||
onScrollCompensation={handleScrollCompensation}
|
||||
/>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user