Refactor date group actions from Svelte component to TypeScript class
• Convert asset-date-group-actions.svelte to date-group-actions-lib.svelte.ts class • Remove complex prop binding between asset-grid-without-scrubber and asset-date-group • Use class instance in asset-date-group with direct method access
This commit is contained in:
@@ -1,218 +0,0 @@
|
|||||||
<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} />
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import { assetSnapshot, assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
import { assetSnapshot, assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
||||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { fly, scale } from 'svelte/transition';
|
import { fly, scale } from 'svelte/transition';
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { DateGroupActionLib } from './date-group-actions-lib.svelte';
|
||||||
|
|
||||||
let { isUploading } = uploadAssetsStore;
|
let { isUploading } = uploadAssetsStore;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -29,11 +32,13 @@
|
|||||||
timelineManager: TimelineManager;
|
timelineManager: TimelineManager;
|
||||||
assetInteraction: AssetInteraction;
|
assetInteraction: AssetInteraction;
|
||||||
customLayout?: Snippet<[TimelineAsset]>;
|
customLayout?: Snippet<[TimelineAsset]>;
|
||||||
|
onSelect: (asset: TimelineAsset) => void;
|
||||||
|
|
||||||
onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
|
// onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
|
||||||
onSelectAssets: (asset: TimelineAsset) => void;
|
// onSelectAssets: (asset: TimelineAsset) => void;
|
||||||
onSelectAssetCandidates: (asset: TimelineAsset | null) => void;
|
// onSelectAssetCandidates: (asset: TimelineAsset | null) => void;
|
||||||
onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void;
|
onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void;
|
||||||
|
scrollTop: (top: number) => void;
|
||||||
onThumbnailClick?: (
|
onThumbnailClick?: (
|
||||||
asset: TimelineAsset,
|
asset: TimelineAsset,
|
||||||
timelineManager: TimelineManager,
|
timelineManager: TimelineManager,
|
||||||
@@ -57,12 +62,21 @@
|
|||||||
timelineManager,
|
timelineManager,
|
||||||
customLayout,
|
customLayout,
|
||||||
onSelect,
|
onSelect,
|
||||||
onSelectAssets,
|
|
||||||
onSelectAssetCandidates,
|
|
||||||
onScrollCompensation,
|
onScrollCompensation,
|
||||||
|
scrollTop,
|
||||||
onThumbnailClick,
|
onThumbnailClick,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
const actionLib = new DateGroupActionLib();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
actionLib.assetInteraction = assetInteraction;
|
||||||
|
actionLib.timelineManager = timelineManager;
|
||||||
|
actionLib.singleSelect = singleSelect;
|
||||||
|
actionLib.onSelect = onSelect;
|
||||||
|
actionLib.scrollTop = scrollTop;
|
||||||
|
});
|
||||||
|
|
||||||
let isMouseOverGroup = $state(false);
|
let isMouseOverGroup = $state(false);
|
||||||
let hoveredDayGroup = $state();
|
let hoveredDayGroup = $state();
|
||||||
|
|
||||||
@@ -83,15 +97,14 @@
|
|||||||
void navigate({ targetRoute: 'current', assetId: asset.id });
|
void navigate({ targetRoute: 'current', assetId: asset.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectGroup = (title: string, assets: TimelineAsset[]) => onSelect({ title, assets });
|
// called when clicking asset with shift key pressed or with mouse
|
||||||
|
|
||||||
const assetSelectHandler = (
|
const assetSelectHandler = (
|
||||||
timelineManager: TimelineManager,
|
timelineManager: TimelineManager,
|
||||||
asset: TimelineAsset,
|
asset: TimelineAsset,
|
||||||
assetsInDayGroup: TimelineAsset[],
|
assetsInDayGroup: TimelineAsset[],
|
||||||
groupTitle: string,
|
groupTitle: string,
|
||||||
) => {
|
) => {
|
||||||
onSelectAssets(asset);
|
void actionLib.onSelectAssets(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 = assetsInDayGroup.filter((asset) =>
|
let selectedAssetsInGroupCount = assetsInDayGroup.filter((asset) =>
|
||||||
@@ -117,7 +130,7 @@
|
|||||||
hoveredDayGroup = groupTitle;
|
hoveredDayGroup = groupTitle;
|
||||||
|
|
||||||
if (assetInteraction.selectionActive) {
|
if (assetInteraction.selectionActive) {
|
||||||
onSelectAssetCandidates(asset);
|
actionLib.onSelectAssetCandidates(asset);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,6 +156,8 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:document onkeydown={actionLib.onKeyDown} onkeyup={actionLib.onKeyUp} />
|
||||||
|
|
||||||
{#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
|
{#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
|
||||||
{@const absoluteWidth = dayGroup.left}
|
{@const absoluteWidth = dayGroup.left}
|
||||||
|
|
||||||
@@ -173,8 +188,10 @@
|
|||||||
<div
|
<div
|
||||||
transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}
|
transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}
|
||||||
class="inline-block pe-2 hover:cursor-pointer"
|
class="inline-block pe-2 hover:cursor-pointer"
|
||||||
onclick={() => handleSelectGroup(dayGroup.groupTitle, assetsSnapshot(dayGroup.getAssets()))}
|
onclick={() =>
|
||||||
onkeydown={() => handleSelectGroup(dayGroup.groupTitle, assetsSnapshot(dayGroup.getAssets()))}
|
actionLib.onDateGroupSelect({ title: dayGroup.groupTitle, assets: assetsSnapshot(dayGroup.getAssets()) })}
|
||||||
|
onkeydown={() =>
|
||||||
|
actionLib.onDateGroupSelect({ title: dayGroup.groupTitle, assets: assetsSnapshot(dayGroup.getAssets()) })}
|
||||||
>
|
>
|
||||||
{#if assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
|
{#if assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
|
||||||
<Icon path={mdiCheckCircle} size="24" class="text-primary" />
|
<Icon path={mdiCheckCircle} size="24" class="text-primary" />
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer';
|
import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer';
|
||||||
import AssetDateGroupActions from '$lib/components/photos-page/asset-date-group-actions.svelte';
|
|
||||||
import AssetGridActions from '$lib/components/photos-page/asset-grid-actions.svelte';
|
import AssetGridActions from '$lib/components/photos-page/asset-grid-actions.svelte';
|
||||||
import AssetViewerAndActions from '$lib/components/photos-page/asset-viewer-and-actions.svelte';
|
import AssetViewerAndActions from '$lib/components/photos-page/asset-viewer-and-actions.svelte';
|
||||||
import Skeleton from '$lib/components/photos-page/skeleton.svelte';
|
import Skeleton from '$lib/components/photos-page/skeleton.svelte';
|
||||||
@@ -75,7 +74,7 @@
|
|||||||
album = null,
|
album = null,
|
||||||
person = null,
|
person = null,
|
||||||
isShowDeleteConfirmation = $bindable(false),
|
isShowDeleteConfirmation = $bindable(false),
|
||||||
onSelect = () => {},
|
onSelect = (asset: TimelineAsset) => void 0,
|
||||||
onEscape = () => {},
|
onEscape = () => {},
|
||||||
children,
|
children,
|
||||||
empty,
|
empty,
|
||||||
@@ -278,16 +277,6 @@
|
|||||||
let onSelectAssetCandidates = <(asset: TimelineAsset | null) => void>$state();
|
let onSelectAssetCandidates = <(asset: TimelineAsset | null) => void>$state();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AssetDateGroupActions
|
|
||||||
{timelineManager}
|
|
||||||
{assetInteraction}
|
|
||||||
handleScrollTop={scrollTop}
|
|
||||||
{onSelect}
|
|
||||||
bind:onDateGroupSelect
|
|
||||||
bind:onSelectAssets
|
|
||||||
bind:onSelectAssetCandidates
|
|
||||||
></AssetDateGroupActions>
|
|
||||||
|
|
||||||
<AssetGridActions {scrollToAsset} {timelineManager} {assetInteraction} bind:isShowDeleteConfirmation {onEscape}
|
<AssetGridActions {scrollToAsset} {timelineManager} {assetInteraction} bind:isShowDeleteConfirmation {onEscape}
|
||||||
></AssetGridActions>
|
></AssetGridActions>
|
||||||
|
|
||||||
@@ -356,9 +345,8 @@
|
|||||||
{isSelectionMode}
|
{isSelectionMode}
|
||||||
{singleSelect}
|
{singleSelect}
|
||||||
{monthGroup}
|
{monthGroup}
|
||||||
onSelect={onDateGroupSelect}
|
{onSelect}
|
||||||
{onSelectAssetCandidates}
|
{scrollTop}
|
||||||
{onSelectAssets}
|
|
||||||
onScrollCompensation={handleScrollCompensation}
|
onScrollCompensation={handleScrollCompensation}
|
||||||
{customLayout}
|
{customLayout}
|
||||||
{onThumbnailClick}
|
{onThumbnailClick}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
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 { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
|
|
||||||
|
/** Glue functions that update the assetInteraction (asset selection) in response to AssetDateGroup UI
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class DateGroupActionLib {
|
||||||
|
onSelect: (asset: TimelineAsset) => void = () => void 0;
|
||||||
|
scrollTop: (top: number) => void = () => void 0;
|
||||||
|
assetInteraction: AssetInteraction = $state(new AssetInteraction());
|
||||||
|
timelineManager: TimelineManager = $state(new TimelineManager());
|
||||||
|
singleSelect: boolean = $state(false);
|
||||||
|
lastAssetMouseEvent: TimelineAsset | null = $state(null);
|
||||||
|
shiftKeyIsDown = $state(false);
|
||||||
|
isEmpty = $derived(this.timelineManager.isInitialized && this.timelineManager.months.length === 0);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
$effect(() => {
|
||||||
|
if (!this.lastAssetMouseEvent || !this.lastAssetMouseEvent) {
|
||||||
|
this.assetInteraction.clearAssetSelectionCandidates();
|
||||||
|
}
|
||||||
|
if (this.shiftKeyIsDown && this.lastAssetMouseEvent) {
|
||||||
|
void this.selectAssetCandidates(this.lastAssetMouseEvent);
|
||||||
|
}
|
||||||
|
if (this.isEmpty) {
|
||||||
|
this.assetInteraction.clearMultiselect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectAsset(asset: TimelineAsset) {
|
||||||
|
if (!this.timelineManager.albumAssets.has(asset.id)) {
|
||||||
|
this.assetInteraction.selectAsset(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (searchStore.isSearchEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Shift') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.shiftKeyIsDown = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyUp = (event: KeyboardEvent) => {
|
||||||
|
if (searchStore.isSearchEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Shift') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.shiftKeyIsDown = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectAssetCandidates = (asset: TimelineAsset | null) => {
|
||||||
|
if (asset) {
|
||||||
|
void this.selectAssetCandidates(asset);
|
||||||
|
}
|
||||||
|
this.lastAssetMouseEvent = asset;
|
||||||
|
};
|
||||||
|
|
||||||
|
onDateGroupSelect = ({ title: group, assets }: { title: string; assets: TimelineAsset[] }) => {
|
||||||
|
if (this.assetInteraction.selectedGroup.has(group)) {
|
||||||
|
this.assetInteraction.removeGroupFromMultiselectGroup(group);
|
||||||
|
for (const asset of assets) {
|
||||||
|
this.assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.assetInteraction.addGroupToMultiselectGroup(group);
|
||||||
|
for (const asset of assets) {
|
||||||
|
this.handleSelectAsset(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timelineManager.assetCount == this.assetInteraction.selectedAssets.length) {
|
||||||
|
isSelectingAllAssets.set(true);
|
||||||
|
} else {
|
||||||
|
isSelectingAllAssets.set(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectAssets = async (asset: TimelineAsset) => {
|
||||||
|
if (!asset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onSelect(asset);
|
||||||
|
|
||||||
|
if (this.singleSelect) {
|
||||||
|
this.scrollTop(0);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeSelection = this.assetInteraction.assetSelectionCandidates.length > 0;
|
||||||
|
const deselect = this.assetInteraction.hasSelectedAsset(asset.id);
|
||||||
|
|
||||||
|
// Select/deselect already loaded assets
|
||||||
|
if (deselect) {
|
||||||
|
for (const candidate of this.assetInteraction.assetSelectionCandidates) {
|
||||||
|
this.assetInteraction.removeAssetFromMultiselectGroup(candidate.id);
|
||||||
|
}
|
||||||
|
this.assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||||
|
} else {
|
||||||
|
for (const candidate of this.assetInteraction.assetSelectionCandidates) {
|
||||||
|
this.handleSelectAsset(candidate);
|
||||||
|
}
|
||||||
|
this.handleSelectAsset(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.assetInteraction.clearAssetSelectionCandidates();
|
||||||
|
|
||||||
|
if (this.assetInteraction.assetSelectionStart && rangeSelection) {
|
||||||
|
let startBucket = this.timelineManager.getMonthGroupByAssetId(this.assetInteraction.assetSelectionStart.id);
|
||||||
|
let endBucket = this.timelineManager.getMonthGroupByAssetId(asset.id);
|
||||||
|
|
||||||
|
if (startBucket === null || endBucket === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select/deselect assets in range (start,end)
|
||||||
|
let started = false;
|
||||||
|
for (const monthGroup of this.timelineManager.months) {
|
||||||
|
if (monthGroup === endBucket) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (started) {
|
||||||
|
await this.timelineManager.loadMonthGroup(monthGroup.yearMonth);
|
||||||
|
for (const asset of monthGroup.assetsIterator()) {
|
||||||
|
if (deselect) {
|
||||||
|
this.assetInteraction.removeAssetFromMultiselectGroup(asset.id);
|
||||||
|
} else {
|
||||||
|
this.handleSelectAsset(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (monthGroup === startBucket) {
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update date group selection in range [start,end]
|
||||||
|
started = false;
|
||||||
|
for (const monthGroup of this.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) => this.assetInteraction.hasSelectedAsset(a.id))) {
|
||||||
|
this.assetInteraction.addGroupToMultiselectGroup(dayGroupTitle);
|
||||||
|
} else {
|
||||||
|
this.assetInteraction.removeGroupFromMultiselectGroup(dayGroupTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (monthGroup === endBucket) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.assetInteraction.setAssetSelectionStart(deselect ? null : asset);
|
||||||
|
};
|
||||||
|
|
||||||
|
selectAssetCandidates = async (endAsset: TimelineAsset) => {
|
||||||
|
if (!this.shiftKeyIsDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startAsset = this.assetInteraction.assetSelectionStart;
|
||||||
|
if (!startAsset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assets = assetsSnapshot(await this.timelineManager.retrieveRange(startAsset, endAsset));
|
||||||
|
this.assetInteraction.setAssetSelectionCandidates(assets);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user