Skip to content

Commit 2180a22

Browse files
committed
feat: extract actions from asset-date-group
1 parent e789fb3 commit 2180a22

File tree

2 files changed

+236
-173
lines changed

2 files changed

+236
-173
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<script lang="ts">
2+
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
3+
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
4+
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
5+
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
6+
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
7+
import { searchStore } from '$lib/stores/search.svelte';
8+
9+
interface Props {
10+
timelineManager: TimelineManager;
11+
assetInteraction: AssetInteraction;
12+
singleSelect?: boolean;
13+
14+
handleScrollTop: (scrollTop: number) => void;
15+
onSelect?: (asset: TimelineAsset) => void;
16+
17+
onDateGroupSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
18+
onSelectAssets: (asset: TimelineAsset) => Promise<void>;
19+
onSelectAssetCandidates: (asset: TimelineAsset | null) => void;
20+
}
21+
22+
let {
23+
singleSelect = false,
24+
timelineManager,
25+
assetInteraction,
26+
handleScrollTop = () => {},
27+
onSelect = () => {},
28+
onDateGroupSelect = $bindable(),
29+
onSelectAssets = $bindable(),
30+
onSelectAssetCandidates = $bindable(),
31+
}: Props = $props();
32+
33+
const handleSelectAsset = (asset: TimelineAsset) => {
34+
if (!timelineManager.albumAssets.has(asset.id)) {
35+
assetInteraction.selectAsset(asset);
36+
}
37+
};
38+
39+
let lastAssetMouseEvent: TimelineAsset | null = $state(null);
40+
41+
let shiftKeyIsDown = $state(false);
42+
43+
const onKeyDown = (event: KeyboardEvent) => {
44+
if (searchStore.isSearchEnabled) {
45+
return;
46+
}
47+
48+
if (event.key === 'Shift') {
49+
event.preventDefault();
50+
shiftKeyIsDown = true;
51+
}
52+
};
53+
54+
const onKeyUp = (event: KeyboardEvent) => {
55+
if (searchStore.isSearchEnabled) {
56+
return;
57+
}
58+
59+
if (event.key === 'Shift') {
60+
event.preventDefault();
61+
shiftKeyIsDown = false;
62+
}
63+
};
64+
65+
onSelectAssetCandidates = (asset: TimelineAsset | null) => {
66+
if (asset) {
67+
void selectAssetCandidates(asset);
68+
}
69+
lastAssetMouseEvent = asset;
70+
};
71+
72+
onDateGroupSelect = ({ title: group, assets }: { title: string; assets: TimelineAsset[] }) => {
73+
if (assetInteraction.selectedGroup.has(group)) {
74+
assetInteraction.removeGroupFromMultiselectGroup(group);
75+
for (const asset of assets) {
76+
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
77+
}
78+
} else {
79+
assetInteraction.addGroupToMultiselectGroup(group);
80+
for (const asset of assets) {
81+
handleSelectAsset(asset);
82+
}
83+
}
84+
85+
if (timelineManager.assetCount == assetInteraction.selectedAssets.length) {
86+
isSelectingAllAssets.set(true);
87+
} else {
88+
isSelectingAllAssets.set(false);
89+
}
90+
};
91+
92+
onSelectAssets = async (asset: TimelineAsset) => {
93+
if (!asset) {
94+
return;
95+
}
96+
onSelect(asset);
97+
98+
if (singleSelect) {
99+
handleScrollTop(0);
100+
101+
return;
102+
}
103+
104+
const rangeSelection = assetInteraction.assetSelectionCandidates.length > 0;
105+
const deselect = assetInteraction.hasSelectedAsset(asset.id);
106+
107+
// Select/deselect already loaded assets
108+
if (deselect) {
109+
for (const candidate of assetInteraction.assetSelectionCandidates) {
110+
assetInteraction.removeAssetFromMultiselectGroup(candidate.id);
111+
}
112+
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
113+
} else {
114+
for (const candidate of assetInteraction.assetSelectionCandidates) {
115+
handleSelectAsset(candidate);
116+
}
117+
handleSelectAsset(asset);
118+
}
119+
120+
assetInteraction.clearAssetSelectionCandidates();
121+
122+
if (assetInteraction.assetSelectionStart && rangeSelection) {
123+
let startBucket = timelineManager.getMonthGroupByAssetId(assetInteraction.assetSelectionStart.id);
124+
let endBucket = timelineManager.getMonthGroupByAssetId(asset.id);
125+
126+
if (startBucket === null || endBucket === null) {
127+
return;
128+
}
129+
130+
// Select/deselect assets in range (start,end)
131+
let started = false;
132+
for (const monthGroup of timelineManager.months) {
133+
if (monthGroup === endBucket) {
134+
break;
135+
}
136+
if (started) {
137+
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
138+
for (const asset of monthGroup.assetsIterator()) {
139+
if (deselect) {
140+
assetInteraction.removeAssetFromMultiselectGroup(asset.id);
141+
} else {
142+
handleSelectAsset(asset);
143+
}
144+
}
145+
}
146+
if (monthGroup === startBucket) {
147+
started = true;
148+
}
149+
}
150+
151+
// Update date group selection in range [start,end]
152+
started = false;
153+
for (const monthGroup of timelineManager.months) {
154+
if (monthGroup === startBucket) {
155+
started = true;
156+
}
157+
if (started) {
158+
// Split month group into day groups and check each group
159+
for (const dayGroup of monthGroup.dayGroups) {
160+
const dayGroupTitle = dayGroup.groupTitle;
161+
if (dayGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
162+
assetInteraction.addGroupToMultiselectGroup(dayGroupTitle);
163+
} else {
164+
assetInteraction.removeGroupFromMultiselectGroup(dayGroupTitle);
165+
}
166+
}
167+
}
168+
if (monthGroup === endBucket) {
169+
break;
170+
}
171+
}
172+
}
173+
174+
assetInteraction.setAssetSelectionStart(deselect ? null : asset);
175+
};
176+
177+
const selectAssetCandidates = async (endAsset: TimelineAsset) => {
178+
if (!shiftKeyIsDown) {
179+
return;
180+
}
181+
182+
const startAsset = assetInteraction.assetSelectionStart;
183+
if (!startAsset) {
184+
return;
185+
}
186+
187+
const assets = assetsSnapshot(await timelineManager.retrieveRange(startAsset, endAsset));
188+
assetInteraction.setAssetSelectionCandidates(assets);
189+
};
190+
191+
let isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
192+
193+
$effect(() => {
194+
if (isEmpty) {
195+
assetInteraction.clearMultiselect();
196+
}
197+
});
198+
199+
$effect(() => {
200+
if (!lastAssetMouseEvent) {
201+
assetInteraction.clearAssetSelectionCandidates();
202+
}
203+
});
204+
205+
$effect(() => {
206+
if (!shiftKeyIsDown) {
207+
assetInteraction.clearAssetSelectionCandidates();
208+
}
209+
});
210+
211+
$effect(() => {
212+
if (shiftKeyIsDown && lastAssetMouseEvent) {
213+
void selectAssetCandidates(lastAssetMouseEvent);
214+
}
215+
});
216+
</script>
217+
218+
<svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} />

0 commit comments

Comments
 (0)