diff --git a/web/src/lib/components/photos-page/asset-date-group-selection-aware.svelte b/web/src/lib/components/photos-page/asset-date-group-selection-aware.svelte index fe6638d754..241ceeb535 100644 --- a/web/src/lib/components/photos-page/asset-date-group-selection-aware.svelte +++ b/web/src/lib/components/photos-page/asset-date-group-selection-aware.svelte @@ -9,8 +9,8 @@ import AssetDateGroupComp from '$lib/components/photos-page/asset-date-group-comp.svelte'; import { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; - import { onMount } from 'svelte'; - import { DateGroupActionLib } from './date-group-actions-lib.svelte'; + import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; + import { searchStore } from '$lib/stores/search.svelte'; let { isUploading } = uploadAssetsStore; @@ -40,14 +40,20 @@ onScrollToTop, }: Props = $props(); - const actionLib = new DateGroupActionLib(); + let lastAssetMouseEvent: TimelineAsset | null = $state(null); + let shiftKeyIsDown = $state(false); + let isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0); - onMount(() => { - actionLib.assetInteraction = assetInteraction; - actionLib.timelineManager = timelineManager; - actionLib.singleSelect = singleSelect; - actionLib.onSelect = onSelect; - actionLib.onScrollToTop = onScrollToTop; + $effect(() => { + if (!lastAssetMouseEvent || !lastAssetMouseEvent) { + assetInteraction.clearAssetSelectionCandidates(); + } + if (shiftKeyIsDown && lastAssetMouseEvent) { + void selectAssetCandidates(lastAssetMouseEvent); + } + if (isEmpty) { + assetInteraction.clearMultiselect(); + } }); const handleOnAssetOpen = (dayGroup: DayGroup, asset: TimelineAsset) => { @@ -60,7 +66,7 @@ // called when clicking asset with shift key pressed or with mouse const handleAssetSelect = (dayGroup: DayGroup, asset: TimelineAsset) => { - void actionLib.onSelectAssets(asset); + void onSelectAssets(asset); const assetsInDayGroup = dayGroup.getAssets(); const groupTitle = dayGroup.groupTitle; @@ -83,14 +89,163 @@ } }; + const handleSelectAsset = (asset: TimelineAsset) => { + if (!timelineManager.albumAssets.has(asset.id)) { + assetInteraction.selectAsset(asset); + } + }; + + 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; + } + }; + const handleOnHover = (dayGroup: DayGroup, asset: TimelineAsset) => { if (assetInteraction.selectionActive) { - actionLib.onSelectAssetCandidates(asset); + void selectAssetCandidates(asset); } + lastAssetMouseEvent = asset; + }; + + const handleDayGroupSelect = (dayGroup: DayGroup, assets: TimelineAsset[]) => { + const group = dayGroup.groupTitle; + 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 onSelectAssets = async (asset: TimelineAsset) => { + if (!asset) { + return; + } + onSelect(asset); + + if (singleSelect) { + onScrollToTop(); + + 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); }; - + true} - isAssetSelected={(asset) => true} - isAssetSelectionCandidate={(asset) => true} - isAssetDisabled={(asset) => true} + onDayGroupSelect={handleDayGroupSelect} + isDayGroupSelected={(dayGroup: DayGroup) => assetInteraction.selectedGroup.has(dayGroup.groupTitle)} + isAssetSelected={(asset) => assetInteraction.hasSelectedAsset(asset.id) || timelineManager.albumAssets.has(asset.id)} + isAssetSelectionCandidate={(asset) => assetInteraction.hasSelectionCandidate(asset.id)} + isAssetDisabled={(asset) => timelineManager.albumAssets.has(asset.id)} >