mirror of
https://github.com/immich-app/immich.git
synced 2026-05-11 10:15:07 +03:00
refactor(web): rename MonthGroup to TimelineMonth (#27447)
Rename MonthGroup class to TimelineMonth to better convey that it represents a single month within the timeline. Updates the file, class, and all references across 16 files. Change-Id: Id50fd6d4b7d0e431571b67c0f81c0e316a6a6964
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import AssetLayout from '$lib/components/timeline/AssetLayout.svelte';
|
||||
import type { AssetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { TimelineDay } from '$lib/managers/timeline-manager/timeline-day.svelte';
|
||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||
import type { TimelineMonth } from '$lib/managers/timeline-manager/timeline-month.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { assetsSnapshot, filterIsInOrNearViewport } from '$lib/managers/timeline-manager/utils.svelte';
|
||||
import type { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
||||
@@ -27,7 +27,7 @@
|
||||
customThumbnailLayout?: Snippet<[TimelineAsset]>;
|
||||
singleSelect: boolean;
|
||||
assetInteraction: AssetMultiSelectManager;
|
||||
monthGroup: MonthGroup;
|
||||
timelineMonth: TimelineMonth;
|
||||
manager: VirtualScrollManager;
|
||||
onTimelineDaySelect: (timelineDay: TimelineDay, assets: TimelineAsset[]) => void;
|
||||
};
|
||||
@@ -36,7 +36,7 @@
|
||||
customThumbnailLayout,
|
||||
singleSelect,
|
||||
assetInteraction,
|
||||
monthGroup,
|
||||
timelineMonth,
|
||||
manager,
|
||||
onTimelineDaySelect,
|
||||
}: Props = $props();
|
||||
@@ -44,10 +44,10 @@
|
||||
let { isUploading } = uploadAssetsStore;
|
||||
let hoveredTimelineDay = $state<string | null>(null);
|
||||
|
||||
const transitionDuration = $derived(monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150);
|
||||
const transitionDuration = $derived(timelineMonth.timelineManager.suspendTransitions && !$isUploading ? 0 : 150);
|
||||
|
||||
const getTimelineDayFullDate = (timelineDay: TimelineDay): string => {
|
||||
const { month, year } = timelineDay.monthGroup.yearMonth;
|
||||
const { month, year } = timelineDay.timelineMonth.yearMonth;
|
||||
const date = fromTimelinePlainDate({
|
||||
year,
|
||||
month,
|
||||
@@ -57,13 +57,13 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
{#each filterIsInOrNearViewport(monthGroup.timelineDays) as timelineDay, groupIndex (timelineDay.day)}
|
||||
{#each filterIsInOrNearViewport(timelineMonth.timelineDays) as timelineDay, groupIndex (timelineDay.day)}
|
||||
{@const isTimelineDaySelected = assetInteraction.selectedGroup.has(timelineDay.groupTitle)}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<section
|
||||
class={[
|
||||
{ 'transition-all': !monthGroup.timelineManager.suspendTransitions },
|
||||
!monthGroup.timelineManager.suspendTransitions && `delay-${transitionDuration}`,
|
||||
{ 'transition-all': !timelineMonth.timelineManager.suspendTransitions },
|
||||
!timelineMonth.timelineManager.suspendTransitions && `delay-${transitionDuration}`,
|
||||
]}
|
||||
data-group
|
||||
style:position="absolute"
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
scrubberWidth = usingMobileDevice ? MOBILE_WIDTH : DESKTOP_WIDTH;
|
||||
});
|
||||
|
||||
const toScrollFromMonthGroupPercentage = (
|
||||
const toScrollFromTimelineMonthPercentage = (
|
||||
scrubberMonth: ViewportTopMonth,
|
||||
scrubberMonthPercent: number,
|
||||
scrubOverallPercent: number,
|
||||
@@ -125,7 +125,7 @@
|
||||
}
|
||||
};
|
||||
const scrollY = $derived(
|
||||
toScrollFromMonthGroupPercentage(viewportTopMonth, viewportTopMonthScrollPercent, timelineScrollPercent),
|
||||
toScrollFromTimelineMonthPercentage(viewportTopMonth, viewportTopMonthScrollPercent, timelineScrollPercent),
|
||||
);
|
||||
const timelineFullHeight = $derived(timelineManager.scrubberTimelineHeight);
|
||||
const relativeTopOffset = $derived(toScrollY(timelineTopOffset / timelineFullHeight));
|
||||
@@ -281,12 +281,12 @@
|
||||
const boundingClientRect = bestElement.boundingClientRect;
|
||||
const sy = boundingClientRect.y;
|
||||
const relativeY = y - sy;
|
||||
const monthGroupPercentY = relativeY / boundingClientRect.height;
|
||||
const timelineMonthPercentY = relativeY / boundingClientRect.height;
|
||||
return {
|
||||
isOnPaddingTop: false,
|
||||
isOnPaddingBottom: false,
|
||||
segment,
|
||||
monthGroupPercentY,
|
||||
timelineMonthPercentY,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
isOnPaddingTop,
|
||||
isOnPaddingBottom,
|
||||
segment: undefined,
|
||||
monthGroupPercentY: 0,
|
||||
timelineMonthPercentY: 0,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -328,7 +328,7 @@
|
||||
const upper = rect?.height - (PADDING_TOP + PADDING_BOTTOM);
|
||||
hoverY = clamp(clientY - rect?.top - PADDING_TOP, lower, upper);
|
||||
const x = rect!.left + rect!.width / 2;
|
||||
const { segment, monthGroupPercentY, isOnPaddingTop, isOnPaddingBottom } = getActive(x, clientY);
|
||||
const { segment, timelineMonthPercentY, isOnPaddingTop, isOnPaddingBottom } = getActive(x, clientY);
|
||||
activeSegment = segment;
|
||||
isHoverOnPaddingTop = isOnPaddingTop;
|
||||
isHoverOnPaddingBottom = isOnPaddingBottom;
|
||||
@@ -336,7 +336,7 @@
|
||||
const scrubData = {
|
||||
scrubberMonth: segmentDate,
|
||||
overallScrollPercent: toTimelineY(hoverY),
|
||||
scrubberMonthScrollPercent: monthGroupPercentY,
|
||||
scrubberMonthScrollPercent: timelineMonthPercentY,
|
||||
};
|
||||
if (wasDragging === false && isDragging) {
|
||||
void startScrub?.(scrubData);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import type { TimelineDay } from '$lib/managers/timeline-manager/timeline-day.svelte';
|
||||
import { isIntersecting } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||
import type { TimelineMonth } from '$lib/managers/timeline-manager/timeline-month.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset, TimelineManagerOptions, ViewportTopMonth } from '$lib/managers/timeline-manager/types';
|
||||
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
|
||||
@@ -121,10 +121,11 @@
|
||||
timelineManager.scrollableElement = scrollableElement;
|
||||
});
|
||||
|
||||
const getAssetPosition = (assetId: string, monthGroup: MonthGroup) => monthGroup.findAssetAbsolutePosition(assetId);
|
||||
const getAssetPosition = (assetId: string, timelineMonth: TimelineMonth) =>
|
||||
timelineMonth.findAssetAbsolutePosition(assetId);
|
||||
|
||||
const scrollToAssetPosition = (assetId: string, monthGroup: MonthGroup) => {
|
||||
const position = getAssetPosition(assetId, monthGroup);
|
||||
const scrollToAssetPosition = (assetId: string, timelineMonth: TimelineMonth) => {
|
||||
const position = getAssetPosition(assetId, timelineMonth);
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
@@ -176,11 +177,11 @@
|
||||
// the performance benefits of deferred layouts while still supporting deep linking
|
||||
// to assets at the end of the timeline.
|
||||
timelineManager.isScrollingOnLoad = true;
|
||||
const monthGroup = await timelineManager.findMonthGroupForAsset({ id: assetId });
|
||||
if (!monthGroup) {
|
||||
const timelineMonth = await timelineManager.findTimelineMonthForAsset({ id: assetId });
|
||||
if (!timelineMonth) {
|
||||
return false;
|
||||
}
|
||||
scrollToAssetPosition(assetId, monthGroup);
|
||||
scrollToAssetPosition(assetId, timelineMonth);
|
||||
return true;
|
||||
} finally {
|
||||
timelineManager.isScrollingOnLoad = false;
|
||||
@@ -188,11 +189,11 @@
|
||||
};
|
||||
|
||||
const scrollToAsset = (asset: TimelineAsset) => {
|
||||
const monthGroup = timelineManager.getMonthGroupByAssetId(asset.id);
|
||||
if (!monthGroup) {
|
||||
const timelineMonth = timelineManager.getTimelineMonthByAssetId(asset.id);
|
||||
if (!timelineMonth) {
|
||||
return false;
|
||||
}
|
||||
scrollToAssetPosition(asset.id, monthGroup);
|
||||
scrollToAssetPosition(asset.id, timelineMonth);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -262,10 +263,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
const scrollToSegmentPercentage = (segmentTop: number, segmentHeight: number, monthGroupScrollPercent: number) => {
|
||||
const scrollToSegmentPercentage = (segmentTop: number, segmentHeight: number, timelineMonthScrollPercent: number) => {
|
||||
const topOffset = segmentTop;
|
||||
const maxScrollPercent = timelineManager.maxScrollPercent;
|
||||
const delta = segmentHeight * monthGroupScrollPercent;
|
||||
const delta = segmentHeight * timelineMonthScrollPercent;
|
||||
const scrollToTop = (topOffset + delta) * maxScrollPercent;
|
||||
|
||||
timelineManager.scrollTo(scrollToTop);
|
||||
@@ -294,13 +295,13 @@
|
||||
scrubberMonthScrollPercent,
|
||||
);
|
||||
} else {
|
||||
const monthGroup = timelineManager.months.find(
|
||||
const timelineMonth = timelineManager.months.find(
|
||||
({ yearMonth: { year, month } }) => year === scrubberMonth.year && month === scrubberMonth.month,
|
||||
);
|
||||
if (!monthGroup) {
|
||||
if (!timelineMonth) {
|
||||
return;
|
||||
}
|
||||
scrollToSegmentPercentage(monthGroup.top, monthGroup.height, scrubberMonthScrollPercent);
|
||||
scrollToSegmentPercentage(timelineMonth.top, timelineMonth.height, scrubberMonthScrollPercent);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -325,28 +326,28 @@
|
||||
|
||||
const monthsLength = timelineManager.months.length;
|
||||
for (let i = -1; i < monthsLength + 1; i++) {
|
||||
let monthGroup: ViewportTopMonth;
|
||||
let monthGroupHeight: number;
|
||||
let timelineMonth: ViewportTopMonth;
|
||||
let timelineMonthHeight: number;
|
||||
if (i === -1) {
|
||||
// lead-in
|
||||
monthGroup = 'lead-in';
|
||||
monthGroupHeight = timelineManager.topSectionHeight;
|
||||
timelineMonth = 'lead-in';
|
||||
timelineMonthHeight = timelineManager.topSectionHeight;
|
||||
} else if (i === monthsLength) {
|
||||
// lead-out
|
||||
monthGroup = 'lead-out';
|
||||
monthGroupHeight = timelineManager.bottomSectionHeight;
|
||||
timelineMonth = 'lead-out';
|
||||
timelineMonthHeight = timelineManager.bottomSectionHeight;
|
||||
} else {
|
||||
monthGroup = timelineManager.months[i].yearMonth;
|
||||
monthGroupHeight = timelineManager.months[i].height;
|
||||
timelineMonth = timelineManager.months[i].yearMonth;
|
||||
timelineMonthHeight = timelineManager.months[i].height;
|
||||
}
|
||||
|
||||
let next = top - monthGroupHeight * maxScrollPercent;
|
||||
let next = top - timelineMonthHeight * maxScrollPercent;
|
||||
// instead of checking for < 0, add a little wiggle room for subpixel resolution
|
||||
if (next < -1 && monthGroup) {
|
||||
viewportTopMonth = monthGroup;
|
||||
if (next < -1 && timelineMonth) {
|
||||
viewportTopMonth = timelineMonth;
|
||||
|
||||
// allowing next to be at least 1 may cause percent to go negative, so ensure positive percentage
|
||||
viewportTopMonthScrollPercent = Math.max(0, top / (monthGroupHeight * maxScrollPercent));
|
||||
viewportTopMonthScrollPercent = Math.max(0, top / (timelineMonthHeight * maxScrollPercent));
|
||||
|
||||
// compensate for lost precision/rounding errors advance to the next bucket, if present
|
||||
if (viewportTopMonthScrollPercent > 0.9999 && i + 1 < monthsLength - 1) {
|
||||
@@ -432,16 +433,16 @@
|
||||
assetInteraction.clearCandidates();
|
||||
|
||||
if (assetInteraction.startAsset && rangeSelection) {
|
||||
const startBucket = timelineManager.getMonthGroupByAssetId(assetInteraction.startAsset.id);
|
||||
const endBucket = timelineManager.getMonthGroupByAssetId(asset.id);
|
||||
const startBucket = timelineManager.getTimelineMonthByAssetId(assetInteraction.startAsset.id);
|
||||
const endBucket = timelineManager.getTimelineMonthByAssetId(asset.id);
|
||||
|
||||
if (!startBucket || !endBucket) {
|
||||
return;
|
||||
}
|
||||
|
||||
const monthGroups = timelineManager.months;
|
||||
const startBucketIndex = monthGroups.indexOf(startBucket);
|
||||
const endBucketIndex = monthGroups.indexOf(endBucket);
|
||||
const timelineMonths = timelineManager.months;
|
||||
const startBucketIndex = timelineMonths.indexOf(startBucket);
|
||||
const endBucketIndex = timelineMonths.indexOf(endBucket);
|
||||
|
||||
if (startBucketIndex === -1 || endBucketIndex === -1) {
|
||||
return;
|
||||
@@ -452,9 +453,9 @@
|
||||
|
||||
// Select/deselect assets in range (start,end)
|
||||
for (let index = rangeStartIndex + 1; index < rangeEndIndex; index++) {
|
||||
const monthGroup = monthGroups[index];
|
||||
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
|
||||
for (const monthAsset of monthGroup.assetsIterator()) {
|
||||
const timelineMonth = timelineMonths[index];
|
||||
await timelineManager.loadTimelineMonth(timelineMonth.yearMonth);
|
||||
for (const monthAsset of timelineMonth.assetsIterator()) {
|
||||
if (deselect) {
|
||||
assetInteraction.removeAssetFromMultiselectGroup(monthAsset.id);
|
||||
} else {
|
||||
@@ -465,10 +466,10 @@
|
||||
|
||||
// Update date group selection in range [start,end]
|
||||
for (let index = rangeStartIndex; index <= rangeEndIndex; index++) {
|
||||
const monthGroup = monthGroups[index];
|
||||
const timelineMonth = timelineMonths[index];
|
||||
|
||||
// Split month group into day groups and check each group
|
||||
for (const timelineDay of monthGroup.timelineDays) {
|
||||
for (const timelineDay of timelineMonth.timelineDays) {
|
||||
const timelineDayTitle = timelineDay.groupTitle;
|
||||
if (timelineDay.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
||||
assetInteraction.addGroupToMultiselectGroup(timelineDayTitle);
|
||||
@@ -517,7 +518,7 @@
|
||||
$effect(() => {
|
||||
if (assetViewerManager.asset && assetViewerManager.isViewing) {
|
||||
const { localDateTime } = getTimes(assetViewerManager.asset.fileCreatedAt, DateTime.local().offset / 60);
|
||||
void timelineManager.loadMonthGroup({ year: localDateTime.year, month: localDateTime.month });
|
||||
void timelineManager.loadTimelineMonth({ year: localDateTime.year, month: localDateTime.month });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -643,23 +644,23 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#each timelineManager.months as monthGroup (monthGroup.viewId)}
|
||||
{@const isInOrNearViewport = monthGroup.isInOrNearViewport}
|
||||
{@const absoluteHeight = monthGroup.top}
|
||||
{#each timelineManager.months as timelineMonth (timelineMonth.viewId)}
|
||||
{@const isInOrNearViewport = timelineMonth.isInOrNearViewport}
|
||||
{@const absoluteHeight = timelineMonth.top}
|
||||
|
||||
{#if !monthGroup.isLoaded}
|
||||
{#if !timelineMonth.isLoaded}
|
||||
<div
|
||||
style:height={monthGroup.height + 'px'}
|
||||
style:height={timelineMonth.height + 'px'}
|
||||
style:position="absolute"
|
||||
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
|
||||
style:width="100%"
|
||||
>
|
||||
<Skeleton {invisible} height={monthGroup.height} title={monthGroup.monthGroupTitle} />
|
||||
<Skeleton {invisible} height={timelineMonth.height} title={timelineMonth.title} />
|
||||
</div>
|
||||
{:else if isInOrNearViewport}
|
||||
<div
|
||||
class="month-group"
|
||||
style:height={monthGroup.height + 'px'}
|
||||
class="timeline-month"
|
||||
style:height={timelineMonth.height + 'px'}
|
||||
style:position="absolute"
|
||||
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
|
||||
style:width="100%"
|
||||
@@ -668,7 +669,7 @@
|
||||
{assetInteraction}
|
||||
{customThumbnailLayout}
|
||||
{singleSelect}
|
||||
{monthGroup}
|
||||
{timelineMonth}
|
||||
manager={timelineManager}
|
||||
onTimelineDaySelect={handleGroupSelect}
|
||||
>
|
||||
@@ -735,7 +736,7 @@
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.month-group {
|
||||
.timeline-month {
|
||||
contain: layout size paint;
|
||||
transform-style: flat;
|
||||
backface-visibility: hidden;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { setDifference, type TimelineDate } from '$lib/utils/timeline-util';
|
||||
import { AssetOrder } from '@immich/sdk';
|
||||
import type { MonthGroup } from './month-group.svelte';
|
||||
import type { TimelineDay } from './timeline-day.svelte';
|
||||
import type { TimelineMonth } from './timeline-month.svelte';
|
||||
import type { TimelineAsset } from './types';
|
||||
|
||||
export class GroupInsertionCache {
|
||||
@@ -34,23 +34,23 @@ export class GroupInsertionCache {
|
||||
|
||||
get updatedBuckets() {
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const updated = new Set<MonthGroup>();
|
||||
const updated = new Set<TimelineMonth>();
|
||||
for (const group of this.changedTimelineDays) {
|
||||
updated.add(group.monthGroup);
|
||||
updated.add(group.timelineMonth);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
get bucketsWithNewTimelineDays() {
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const updated = new Set<MonthGroup>();
|
||||
const updated = new Set<TimelineMonth>();
|
||||
for (const group of this.newTimelineDays) {
|
||||
updated.add(group.monthGroup);
|
||||
updated.add(group.timelineMonth);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
sort(monthGroup: MonthGroup, sortOrder: AssetOrder = AssetOrder.Desc) {
|
||||
sort(timelineMonth: TimelineMonth, sortOrder: AssetOrder = AssetOrder.Desc) {
|
||||
for (const group of this.changedTimelineDays) {
|
||||
group.sortAssets(sortOrder);
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export class GroupInsertionCache {
|
||||
group.sortAssets(sortOrder);
|
||||
}
|
||||
if (this.newTimelineDays.size > 0) {
|
||||
monthGroup.sortTimelineDays();
|
||||
timelineMonth.sortTimelineDays();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import type { MonthGroup } from '../month-group.svelte';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
|
||||
const {
|
||||
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
||||
@@ -40,7 +40,7 @@ function calculateViewportProximity(regionTop: number, regionBottom: number, win
|
||||
return ViewportProximity.InViewport;
|
||||
}
|
||||
|
||||
export function updateMonthGroupViewportProximity(timelineManager: TimelineManager, month: MonthGroup) {
|
||||
export function updateTimelineMonthViewportProximity(timelineManager: TimelineManager, month: TimelineMonth) {
|
||||
const proximity = calculateViewportProximity(
|
||||
month.top,
|
||||
month.top + month.height,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { MonthGroup } from '../month-group.svelte';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import type { UpdateGeometryOptions } from '../types';
|
||||
|
||||
export function updateGeometry(timelineManager: TimelineManager, month: MonthGroup, options: UpdateGeometryOptions) {
|
||||
export function updateGeometry(timelineManager: TimelineManager, month: TimelineMonth, options: UpdateGeometryOptions) {
|
||||
const { invalidateHeight, noDefer = false } = options;
|
||||
if (invalidateHeight) {
|
||||
month.isHeightActual = false;
|
||||
@@ -17,10 +17,10 @@ export function updateGeometry(timelineManager: TimelineManager, month: MonthGro
|
||||
}
|
||||
return;
|
||||
}
|
||||
layoutMonthGroup(timelineManager, month, noDefer);
|
||||
layoutTimelineMonth(timelineManager, month, noDefer);
|
||||
}
|
||||
|
||||
export function layoutMonthGroup(timelineManager: TimelineManager, month: MonthGroup, noDefer: boolean = false) {
|
||||
export function layoutTimelineMonth(timelineManager: TimelineManager, month: TimelineMonth, noDefer: boolean = false) {
|
||||
let cumulativeHeight = 0;
|
||||
let cumulativeWidth = 0;
|
||||
let currentRowHeight = 0;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { toISOYearMonthUTC } from '$lib/utils/timeline-util';
|
||||
import { getTimeBucket } from '@immich/sdk';
|
||||
import type { MonthGroup } from '../month-group.svelte';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import type { TimelineManagerOptions } from '../types';
|
||||
|
||||
export async function loadFromTimeBuckets(
|
||||
timelineManager: TimelineManager,
|
||||
monthGroup: MonthGroup,
|
||||
timelineMonth: TimelineMonth,
|
||||
options: TimelineManagerOptions,
|
||||
signal: AbortSignal,
|
||||
): Promise<void> {
|
||||
if (monthGroup.getFirstAsset()) {
|
||||
if (timelineMonth.getFirstAsset()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeBucket = toISOYearMonthUTC(monthGroup.yearMonth);
|
||||
const timeBucket = toISOYearMonthUTC(timelineMonth.yearMonth);
|
||||
const bucketResponse = await getTimeBucket(
|
||||
{
|
||||
...authManager.params,
|
||||
@@ -46,10 +46,10 @@ export async function loadFromTimeBuckets(
|
||||
}
|
||||
}
|
||||
|
||||
const unprocessedAssets = monthGroup.addAssets(bucketResponse, true);
|
||||
const unprocessedAssets = timelineMonth.addAssets(bucketResponse, true);
|
||||
if (unprocessedAssets.length > 0) {
|
||||
console.error(
|
||||
`Warning: getTimeBucket API returning assets not in requested month: ${monthGroup.yearMonth.month}, ${JSON.stringify(
|
||||
`Warning: getTimeBucket API returning assets not in requested month: ${timelineMonth.yearMonth.month}, ${JSON.stringify(
|
||||
unprocessedAssets.map((unprocessed) => ({
|
||||
id: unprocessed.id,
|
||||
localDateTime: unprocessed.localDateTime,
|
||||
|
||||
@@ -1,74 +1,86 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { MonthGroup } from '../month-group.svelte';
|
||||
import { findClosestGroupForDate } from './search-support.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import { findClosestTimelineMonthForDate } from './search-support.svelte';
|
||||
|
||||
function createMockMonthGroup(year: number, month: number): MonthGroup {
|
||||
function createMockTimelineMonth(year: number, month: number): TimelineMonth {
|
||||
return {
|
||||
yearMonth: { year, month },
|
||||
} as MonthGroup;
|
||||
} as TimelineMonth;
|
||||
}
|
||||
|
||||
describe('findClosestGroupForDate', () => {
|
||||
describe('findClosestTimelineMonthForDate', () => {
|
||||
it('should return undefined for empty months array', () => {
|
||||
const result = findClosestGroupForDate([], { year: 2024, month: 1 });
|
||||
const result = findClosestTimelineMonthForDate([], { year: 2024, month: 1 });
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the only month when there is only one month', () => {
|
||||
const months = [createMockMonthGroup(2024, 6)];
|
||||
const result = findClosestGroupForDate(months, { year: 2025, month: 1 });
|
||||
const months = [createMockTimelineMonth(2024, 6)];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2025, month: 1 });
|
||||
expect(result?.yearMonth).toEqual({ year: 2024, month: 6 });
|
||||
});
|
||||
|
||||
it('should return exact match when available', () => {
|
||||
const months = [createMockMonthGroup(2024, 1), createMockMonthGroup(2024, 6), createMockMonthGroup(2024, 12)];
|
||||
const result = findClosestGroupForDate(months, { year: 2024, month: 6 });
|
||||
const months = [
|
||||
createMockTimelineMonth(2024, 1),
|
||||
createMockTimelineMonth(2024, 6),
|
||||
createMockTimelineMonth(2024, 12),
|
||||
];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2024, month: 6 });
|
||||
expect(result?.yearMonth).toEqual({ year: 2024, month: 6 });
|
||||
});
|
||||
|
||||
it('should find closest month when target is between two months', () => {
|
||||
const months = [createMockMonthGroup(2024, 1), createMockMonthGroup(2024, 6), createMockMonthGroup(2024, 12)];
|
||||
const result = findClosestGroupForDate(months, { year: 2024, month: 4 });
|
||||
const months = [
|
||||
createMockTimelineMonth(2024, 1),
|
||||
createMockTimelineMonth(2024, 6),
|
||||
createMockTimelineMonth(2024, 12),
|
||||
];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2024, month: 4 });
|
||||
expect(result?.yearMonth).toEqual({ year: 2024, month: 6 });
|
||||
});
|
||||
|
||||
it('should handle year boundaries correctly (2023-12 vs 2024-01)', () => {
|
||||
const months = [createMockMonthGroup(2023, 12), createMockMonthGroup(2024, 2)];
|
||||
const result = findClosestGroupForDate(months, { year: 2024, month: 1 });
|
||||
const months = [createMockTimelineMonth(2023, 12), createMockTimelineMonth(2024, 2)];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2024, month: 1 });
|
||||
// 2024-01 is 1 month from 2023-12 and 1 month from 2024-02
|
||||
// Should return first encountered with min distance (2023-12)
|
||||
expect(result?.yearMonth).toEqual({ year: 2023, month: 12 });
|
||||
});
|
||||
|
||||
it('should correctly calculate distance across years', () => {
|
||||
const months = [createMockMonthGroup(2022, 6), createMockMonthGroup(2024, 6)];
|
||||
const result = findClosestGroupForDate(months, { year: 2023, month: 6 });
|
||||
const months = [createMockTimelineMonth(2022, 6), createMockTimelineMonth(2024, 6)];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2023, month: 6 });
|
||||
// Both are exactly 12 months away, should return first encountered
|
||||
expect(result?.yearMonth).toEqual({ year: 2022, month: 6 });
|
||||
});
|
||||
|
||||
it('should handle target before all months', () => {
|
||||
const months = [createMockMonthGroup(2024, 6), createMockMonthGroup(2024, 12)];
|
||||
const result = findClosestGroupForDate(months, { year: 2024, month: 1 });
|
||||
const months = [createMockTimelineMonth(2024, 6), createMockTimelineMonth(2024, 12)];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2024, month: 1 });
|
||||
expect(result?.yearMonth).toEqual({ year: 2024, month: 6 });
|
||||
});
|
||||
|
||||
it('should handle target after all months', () => {
|
||||
const months = [createMockMonthGroup(2024, 1), createMockMonthGroup(2024, 6)];
|
||||
const result = findClosestGroupForDate(months, { year: 2025, month: 1 });
|
||||
const months = [createMockTimelineMonth(2024, 1), createMockTimelineMonth(2024, 6)];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2025, month: 1 });
|
||||
expect(result?.yearMonth).toEqual({ year: 2024, month: 6 });
|
||||
});
|
||||
|
||||
it('should handle multiple years correctly', () => {
|
||||
const months = [createMockMonthGroup(2020, 1), createMockMonthGroup(2022, 1), createMockMonthGroup(2024, 1)];
|
||||
const result = findClosestGroupForDate(months, { year: 2023, month: 1 });
|
||||
const months = [
|
||||
createMockTimelineMonth(2020, 1),
|
||||
createMockTimelineMonth(2022, 1),
|
||||
createMockTimelineMonth(2024, 1),
|
||||
];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2023, month: 1 });
|
||||
// 2023-01 is 12 months from 2022-01 and 12 months from 2024-01
|
||||
expect(result?.yearMonth).toEqual({ year: 2022, month: 1 });
|
||||
});
|
||||
|
||||
it('should prefer closer month when one is clearly closer', () => {
|
||||
const months = [createMockMonthGroup(2024, 1), createMockMonthGroup(2024, 10)];
|
||||
const result = findClosestGroupForDate(months, { year: 2024, month: 11 });
|
||||
const months = [createMockTimelineMonth(2024, 1), createMockTimelineMonth(2024, 10)];
|
||||
const result = findClosestTimelineMonthForDate(months, { year: 2024, month: 11 });
|
||||
// 2024-11 is 1 month from 2024-10 and 10 months from 2024-01
|
||||
expect(result?.yearMonth).toEqual({ year: 2024, month: 10 });
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util';
|
||||
import { AssetOrder, type AssetResponseDto } from '@immich/sdk';
|
||||
import { DateTime } from 'luxon';
|
||||
import type { MonthGroup } from '../month-group.svelte';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
|
||||
|
||||
export async function getAssetWithOffset(
|
||||
@@ -11,44 +11,44 @@ export async function getAssetWithOffset(
|
||||
interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
|
||||
direction: Direction,
|
||||
): Promise<TimelineAsset | undefined> {
|
||||
const monthGroup = await timelineManager.findMonthGroupForAsset(assetDescriptor);
|
||||
if (!monthGroup) {
|
||||
const timelineMonth = await timelineManager.findTimelineMonthForAsset(assetDescriptor);
|
||||
if (!timelineMonth) {
|
||||
return;
|
||||
}
|
||||
const asset = monthGroup.findAssetById(assetDescriptor);
|
||||
const asset = timelineMonth.findAssetById(assetDescriptor);
|
||||
if (!asset) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (interval) {
|
||||
case 'asset': {
|
||||
return getAssetByAssetOffset(timelineManager, asset, monthGroup, direction);
|
||||
return getAssetByAssetOffset(timelineManager, asset, timelineMonth, direction);
|
||||
}
|
||||
case 'day': {
|
||||
return getAssetByDayOffset(timelineManager, asset, monthGroup, direction);
|
||||
return getAssetByDayOffset(timelineManager, asset, timelineMonth, direction);
|
||||
}
|
||||
case 'month': {
|
||||
return getAssetByMonthOffset(timelineManager, monthGroup, direction);
|
||||
return getAssetByMonthOffset(timelineManager, timelineMonth, direction);
|
||||
}
|
||||
case 'year': {
|
||||
return getAssetByYearOffset(timelineManager, monthGroup, direction);
|
||||
return getAssetByYearOffset(timelineManager, timelineMonth, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findMonthGroupForAsset(timelineManager: TimelineManager, id: string) {
|
||||
export function findTimelineMonthForAsset(timelineManager: TimelineManager, id: string) {
|
||||
for (const month of timelineManager.months) {
|
||||
const asset = month.findAssetById({ id });
|
||||
if (asset) {
|
||||
return { monthGroup: month, asset };
|
||||
return { timelineMonth: month, asset };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getMonthGroupByDate(
|
||||
export function getTimelineMonthByDate(
|
||||
timelineManager: TimelineManager,
|
||||
targetYearMonth: TimelineYearMonth,
|
||||
): MonthGroup | undefined {
|
||||
): TimelineMonth | undefined {
|
||||
return timelineManager.months.find(
|
||||
(month) => month.yearMonth.year === targetYearMonth.year && month.yearMonth.month === targetYearMonth.month,
|
||||
);
|
||||
@@ -57,12 +57,12 @@ export function getMonthGroupByDate(
|
||||
async function getAssetByAssetOffset(
|
||||
timelineManager: TimelineManager,
|
||||
asset: TimelineAsset,
|
||||
monthGroup: MonthGroup,
|
||||
timelineMonth: TimelineMonth,
|
||||
direction: Direction,
|
||||
) {
|
||||
const timelineDay = monthGroup.findTimelineDayForAsset(asset);
|
||||
const timelineDay = timelineMonth.findTimelineDayForAsset(asset);
|
||||
for await (const targetAsset of timelineManager.assetsIterator({
|
||||
startMonthGroup: monthGroup,
|
||||
startTimelineMonth: timelineMonth,
|
||||
startTimelineDay: timelineDay,
|
||||
startAsset: asset,
|
||||
direction,
|
||||
@@ -76,12 +76,12 @@ async function getAssetByAssetOffset(
|
||||
async function getAssetByDayOffset(
|
||||
timelineManager: TimelineManager,
|
||||
asset: TimelineAsset,
|
||||
monthGroup: MonthGroup,
|
||||
timelineMonth: TimelineMonth,
|
||||
direction: Direction,
|
||||
) {
|
||||
const timelineDay = monthGroup.findTimelineDayForAsset(asset);
|
||||
const timelineDay = timelineMonth.findTimelineDayForAsset(asset);
|
||||
for await (const targetAsset of timelineManager.assetsIterator({
|
||||
startMonthGroup: monthGroup,
|
||||
startTimelineMonth: timelineMonth,
|
||||
startTimelineDay: timelineDay,
|
||||
startAsset: asset,
|
||||
direction,
|
||||
@@ -92,44 +92,49 @@ async function getAssetByDayOffset(
|
||||
}
|
||||
}
|
||||
|
||||
async function getAssetByMonthOffset(timelineManager: TimelineManager, month: MonthGroup, direction: Direction) {
|
||||
for (const targetMonth of timelineManager.monthGroupIterator({ startMonthGroup: month, direction })) {
|
||||
async function getAssetByMonthOffset(timelineManager: TimelineManager, month: TimelineMonth, direction: Direction) {
|
||||
for (const targetMonth of timelineManager.timelineMonthIterator({ startTimelineMonth: month, direction })) {
|
||||
if (targetMonth.yearMonth.month !== month.yearMonth.month) {
|
||||
const { value, done } = await timelineManager.assetsIterator({ startMonthGroup: targetMonth, direction }).next();
|
||||
const { value, done } = await timelineManager
|
||||
.assetsIterator({ startTimelineMonth: targetMonth, direction })
|
||||
.next();
|
||||
return done ? undefined : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getAssetByYearOffset(timelineManager: TimelineManager, month: MonthGroup, direction: Direction) {
|
||||
for (const targetMonth of timelineManager.monthGroupIterator({ startMonthGroup: month, direction })) {
|
||||
async function getAssetByYearOffset(timelineManager: TimelineManager, month: TimelineMonth, direction: Direction) {
|
||||
for (const targetMonth of timelineManager.timelineMonthIterator({ startTimelineMonth: month, direction })) {
|
||||
if (targetMonth.yearMonth.year !== month.yearMonth.year) {
|
||||
const { value, done } = await timelineManager.assetsIterator({ startMonthGroup: targetMonth, direction }).next();
|
||||
const { value, done } = await timelineManager
|
||||
.assetsIterator({ startTimelineMonth: targetMonth, direction })
|
||||
.next();
|
||||
return done ? undefined : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function retrieveRange(timelineManager: TimelineManager, start: AssetDescriptor, end: AssetDescriptor) {
|
||||
let { asset: startAsset, monthGroup: startMonthGroup } = findMonthGroupForAsset(timelineManager, start.id) ?? {};
|
||||
if (!startMonthGroup || !startAsset) {
|
||||
let { asset: startAsset, timelineMonth: startTimelineMonth } =
|
||||
findTimelineMonthForAsset(timelineManager, start.id) ?? {};
|
||||
if (!startTimelineMonth || !startAsset) {
|
||||
return [];
|
||||
}
|
||||
let { asset: endAsset, monthGroup: endMonthGroup } = findMonthGroupForAsset(timelineManager, end.id) ?? {};
|
||||
if (!endMonthGroup || !endAsset) {
|
||||
let { asset: endAsset, timelineMonth: endTimelineMonth } = findTimelineMonthForAsset(timelineManager, end.id) ?? {};
|
||||
if (!endTimelineMonth || !endAsset) {
|
||||
return [];
|
||||
}
|
||||
const assetOrder: AssetOrder = timelineManager.getAssetOrder();
|
||||
if (plainDateTimeCompare(assetOrder === AssetOrder.Desc, startAsset.localDateTime, endAsset.localDateTime) < 0) {
|
||||
[startAsset, endAsset] = [endAsset, startAsset];
|
||||
// eslint-disable-next-line no-useless-assignment
|
||||
[startMonthGroup, endMonthGroup] = [endMonthGroup, startMonthGroup];
|
||||
[startTimelineMonth, endTimelineMonth] = [endTimelineMonth, startTimelineMonth];
|
||||
}
|
||||
|
||||
const range: TimelineAsset[] = [];
|
||||
const startTimelineDay = startMonthGroup.findTimelineDayForAsset(startAsset);
|
||||
const startTimelineDay = startTimelineMonth.findTimelineDayForAsset(startAsset);
|
||||
for await (const targetAsset of timelineManager.assetsIterator({
|
||||
startMonthGroup,
|
||||
startTimelineMonth,
|
||||
startTimelineDay,
|
||||
startAsset,
|
||||
})) {
|
||||
@@ -141,7 +146,7 @@ export async function retrieveRange(timelineManager: TimelineManager, start: Ass
|
||||
return range;
|
||||
}
|
||||
|
||||
export function findMonthGroupForDate(timelineManager: TimelineManager, targetYearMonth: TimelineYearMonth) {
|
||||
export function findTimelineMonthForDate(timelineManager: TimelineManager, targetYearMonth: TimelineYearMonth) {
|
||||
for (const month of timelineManager.months) {
|
||||
const { year, month: monthNum } = month.yearMonth;
|
||||
if (monthNum === targetYearMonth.month && year === targetYearMonth.year) {
|
||||
@@ -150,10 +155,10 @@ export function findMonthGroupForDate(timelineManager: TimelineManager, targetYe
|
||||
}
|
||||
}
|
||||
|
||||
export function findClosestGroupForDate(months: MonthGroup[], targetYearMonth: TimelineYearMonth) {
|
||||
export function findClosestTimelineMonthForDate(months: TimelineMonth[], targetYearMonth: TimelineYearMonth) {
|
||||
const targetDate = DateTime.fromObject({ year: targetYearMonth.year, month: targetYearMonth.month });
|
||||
|
||||
let closestMonth: MonthGroup | undefined;
|
||||
let closestMonth: TimelineMonth | undefined;
|
||||
let minDifference = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
for (const month of months) {
|
||||
|
||||
@@ -5,12 +5,12 @@ import { getJustifiedLayoutFromAssets } from '$lib/utils/layout-utils';
|
||||
import { plainDateTimeCompare } from '$lib/utils/timeline-util';
|
||||
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import type { MonthGroup } from './month-group.svelte';
|
||||
import type { TimelineMonth } from './timeline-month.svelte';
|
||||
import type { Direction, MoveAsset, TimelineAsset } from './types';
|
||||
import { ViewerAsset } from './viewer-asset.svelte';
|
||||
|
||||
export class TimelineDay {
|
||||
readonly monthGroup: MonthGroup;
|
||||
readonly timelineMonth: TimelineMonth;
|
||||
readonly index: number;
|
||||
readonly groupTitle: string;
|
||||
readonly day: number;
|
||||
@@ -26,9 +26,9 @@ export class TimelineDay {
|
||||
#col = $state(0);
|
||||
#deferredLayout = false;
|
||||
|
||||
constructor(monthGroup: MonthGroup, index: number, day: number, groupTitle: string) {
|
||||
constructor(timelineMonth: TimelineMonth, index: number, day: number, groupTitle: string) {
|
||||
this.index = index;
|
||||
this.monthGroup = monthGroup;
|
||||
this.timelineMonth = timelineMonth;
|
||||
this.day = day;
|
||||
this.groupTitle = groupTitle;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export class TimelineDay {
|
||||
}
|
||||
unprocessedIds.delete(assetId);
|
||||
processedIds.add(assetId);
|
||||
if (remove || this.monthGroup.timelineManager.isExcluded(asset)) {
|
||||
if (remove || this.timelineMonth.timelineManager.isExcluded(asset)) {
|
||||
this.viewerAssets.splice(index, 1);
|
||||
changedGeometry = true;
|
||||
}
|
||||
@@ -137,7 +137,7 @@ export class TimelineDay {
|
||||
}
|
||||
|
||||
layout(options: CommonLayoutOptions, noDefer: boolean) {
|
||||
if (!noDefer && !this.monthGroup.isInOrNearViewport && !this.monthGroup.timelineManager.isScrollingOnLoad) {
|
||||
if (!noDefer && !this.timelineMonth.isInOrNearViewport && !this.timelineMonth.timelineManager.isScrollingOnLoad) {
|
||||
this.#deferredLayout = true;
|
||||
return;
|
||||
}
|
||||
@@ -152,6 +152,6 @@ export class TimelineDay {
|
||||
}
|
||||
|
||||
get absoluteTimelineDayTop() {
|
||||
return this.monthGroup.top + this.#top;
|
||||
return this.timelineMonth.top + this.#top;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sdkMock } from '$lib/__mocks__/sdk.mock';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { getMonthGroupByDate } from '$lib/managers/timeline-manager/internal/search-support.svelte';
|
||||
import { getTimelineMonthByDate } from '$lib/managers/timeline-manager/internal/search-support.svelte';
|
||||
import { AbortError } from '$lib/utils';
|
||||
import { fromISODateTimeUTCToObject } from '$lib/utils/timeline-util';
|
||||
import { AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
||||
@@ -95,7 +95,7 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadMonthGroup', () => {
|
||||
describe('loadTimelineMonth', () => {
|
||||
let timelineManager: TimelineManager;
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-01-03T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) =>
|
||||
@@ -131,47 +131,47 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
|
||||
it('loads a month', async () => {
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(0);
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(0);
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(3);
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(3);
|
||||
});
|
||||
|
||||
it('ignores invalid months', async () => {
|
||||
await timelineManager.loadMonthGroup({ year: 2023, month: 1 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2023, month: 1 });
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('cancels month loading', async () => {
|
||||
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||
void timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||
void timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
const abortSpy = vi.spyOn(month!.loader!.cancelToken!, 'abort');
|
||||
month?.cancel();
|
||||
expect(abortSpy).toBeCalledTimes(1);
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(3);
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(3);
|
||||
});
|
||||
|
||||
it('prevents loading months multiple times', async () => {
|
||||
await Promise.all([
|
||||
timelineManager.loadMonthGroup({ year: 2024, month: 1 }),
|
||||
timelineManager.loadMonthGroup({ year: 2024, month: 1 }),
|
||||
timelineManager.loadTimelineMonth({ year: 2024, month: 1 }),
|
||||
timelineManager.loadTimelineMonth({ year: 2024, month: 1 }),
|
||||
]);
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('allows loading a canceled month', async () => {
|
||||
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||
const loadPromise = timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||
const loadPromise = timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
|
||||
month.cancel();
|
||||
await loadPromise;
|
||||
expect(month?.getAssets().length).toEqual(0);
|
||||
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
expect(month!.getAssets().length).toEqual(3);
|
||||
});
|
||||
});
|
||||
@@ -241,7 +241,7 @@ describe('TimelineManager', () => {
|
||||
);
|
||||
timelineManager.upsertAssets([assetOne, assetTwo, assetThree]);
|
||||
|
||||
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 1 });
|
||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 });
|
||||
expect(month).not.toBeNull();
|
||||
expect(month?.getAssets().length).toEqual(3);
|
||||
expect(month?.getAssets()[0].id).toEqual(assetOne.id);
|
||||
@@ -346,15 +346,15 @@ describe('TimelineManager', () => {
|
||||
|
||||
timelineManager.upsertAssets([asset]);
|
||||
expect(timelineManager.months.length).toEqual(1);
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined();
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(1);
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined();
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(1);
|
||||
|
||||
timelineManager.upsertAssets([updatedAsset]);
|
||||
expect(timelineManager.months.length).toEqual(2);
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined();
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(0);
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 3 })).not.toBeUndefined();
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 3 })?.getAssets().length).toEqual(1);
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined();
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(0);
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 })).not.toBeUndefined();
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 })?.getAssets().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('yearMonth is not a shared reference with asset.localDateTime (reference bug)', () => {
|
||||
@@ -365,7 +365,7 @@ describe('TimelineManager', () => {
|
||||
);
|
||||
|
||||
timelineManager.upsertAssets([asset]);
|
||||
const januaryMonth = getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||
const januaryMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||
const monthYearMonth = januaryMonth.yearMonth;
|
||||
|
||||
const originalMonth = monthYearMonth.month;
|
||||
@@ -611,8 +611,8 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
|
||||
it('returns previous assetId', async () => {
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 1 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 });
|
||||
|
||||
const a = month!.getAssets()[0];
|
||||
const b = month!.getAssets()[1];
|
||||
@@ -621,11 +621,11 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
|
||||
it('returns previous assetId spanning multiple months', async () => {
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 2 });
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 3 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 2 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 3 });
|
||||
|
||||
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 2 });
|
||||
const previousMonth = getMonthGroupByDate(timelineManager, { year: 2024, month: 3 });
|
||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 2 });
|
||||
const previousMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 });
|
||||
const a = month!.getAssets()[0];
|
||||
const b = previousMonth!.getAssets()[0];
|
||||
const previous = await timelineManager.getLaterAsset(a);
|
||||
@@ -633,23 +633,23 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
|
||||
it('loads previous month', async () => {
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 2 });
|
||||
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 2 });
|
||||
const previousMonth = getMonthGroupByDate(timelineManager, { year: 2024, month: 3 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 2 });
|
||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 2 });
|
||||
const previousMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 });
|
||||
const a = month!.getFirstAsset();
|
||||
const b = previousMonth!.getFirstAsset();
|
||||
const loadMonthGroupSpy = vi.spyOn(month!.loader!, 'execute');
|
||||
const loadTimelineMonthSpy = vi.spyOn(month!.loader!, 'execute');
|
||||
const previousMonthSpy = vi.spyOn(previousMonth!.loader!, 'execute');
|
||||
const previous = await timelineManager.getLaterAsset(a);
|
||||
expect(previous).toEqual(b);
|
||||
expect(loadMonthGroupSpy).toBeCalledTimes(0);
|
||||
expect(loadTimelineMonthSpy).toBeCalledTimes(0);
|
||||
expect(previousMonthSpy).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('skips removed assets', async () => {
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 1 });
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 2 });
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 3 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 2 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 3 });
|
||||
|
||||
const [assetOne, assetTwo, assetThree] = await getAssets(timelineManager);
|
||||
timelineManager.removeAssets([assetTwo.id]);
|
||||
@@ -657,12 +657,12 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
|
||||
it('returns null when no more assets', async () => {
|
||||
await timelineManager.loadMonthGroup({ year: 2024, month: 3 });
|
||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 3 });
|
||||
expect(await timelineManager.getLaterAsset(timelineManager.months[0].getFirstAsset())).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMonthGroupIndexByAssetId', () => {
|
||||
describe('getTimelineMonthIndexByAssetId', () => {
|
||||
let timelineManager: TimelineManager;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -673,8 +673,8 @@ describe('TimelineManager', () => {
|
||||
});
|
||||
|
||||
it('returns null for invalid months', () => {
|
||||
expect(getMonthGroupByDate(timelineManager, { year: -1, month: -1 })).toBeUndefined();
|
||||
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 3 })).toBeUndefined();
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: -1, month: -1 })).toBeUndefined();
|
||||
expect(getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns the month index', () => {
|
||||
@@ -690,10 +690,10 @@ describe('TimelineManager', () => {
|
||||
);
|
||||
timelineManager.upsertAssets([assetOne, assetTwo]);
|
||||
|
||||
expect(timelineManager.getMonthGroupByAssetId(assetTwo.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(timelineManager.getMonthGroupByAssetId(assetTwo.id)?.yearMonth.month).toEqual(2);
|
||||
expect(timelineManager.getMonthGroupByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(timelineManager.getMonthGroupByAssetId(assetOne.id)?.yearMonth.month).toEqual(1);
|
||||
expect(timelineManager.getTimelineMonthByAssetId(assetTwo.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(timelineManager.getTimelineMonthByAssetId(assetTwo.id)?.yearMonth.month).toEqual(2);
|
||||
expect(timelineManager.getTimelineMonthByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(timelineManager.getTimelineMonthByAssetId(assetOne.id)?.yearMonth.month).toEqual(1);
|
||||
});
|
||||
|
||||
it('ignores removed months', () => {
|
||||
@@ -710,8 +710,8 @@ describe('TimelineManager', () => {
|
||||
timelineManager.upsertAssets([assetOne, assetTwo]);
|
||||
|
||||
timelineManager.removeAssets([assetTwo.id]);
|
||||
expect(timelineManager.getMonthGroupByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(timelineManager.getMonthGroupByAssetId(assetOne.id)?.yearMonth.month).toEqual(1);
|
||||
expect(timelineManager.getTimelineMonthByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(timelineManager.getTimelineMonthByAssetId(assetOne.id)?.yearMonth.month).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@ import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/Virtual
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { GroupInsertionCache } from '$lib/managers/timeline-manager/group-insertion-cache.svelte';
|
||||
import { updateMonthGroupViewportProximity } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||
import { updateTimelineMonthViewportProximity } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
|
||||
import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte';
|
||||
import { loadFromTimeBuckets } from '$lib/managers/timeline-manager/internal/load-support.svelte';
|
||||
import {
|
||||
findClosestGroupForDate,
|
||||
findMonthGroupForAsset as findMonthGroupForAssetUtil,
|
||||
findMonthGroupForDate,
|
||||
findClosestTimelineMonthForDate,
|
||||
findTimelineMonthForAsset as findTimelineMonthForAssetUtil,
|
||||
findTimelineMonthForDate,
|
||||
getAssetWithOffset,
|
||||
getMonthGroupByDate,
|
||||
getTimelineMonthByDate,
|
||||
retrieveRange as retrieveRangeUtil,
|
||||
} from '$lib/managers/timeline-manager/internal/search-support.svelte';
|
||||
import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte';
|
||||
@@ -27,8 +27,8 @@ import { AssetOrder, getAssetInfo, getTimeBuckets, type AssetResponseDto } from
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
|
||||
import { isMismatched, updateObject } from './internal/utils.svelte';
|
||||
import { MonthGroup } from './month-group.svelte';
|
||||
import { TimelineDay } from './timeline-day.svelte';
|
||||
import { TimelineMonth } from './timeline-month.svelte';
|
||||
import type {
|
||||
AssetDescriptor,
|
||||
Direction,
|
||||
@@ -40,7 +40,7 @@ import type {
|
||||
} from './types';
|
||||
|
||||
type ViewportTopMonthIntersection = {
|
||||
month: MonthGroup | undefined;
|
||||
month: TimelineMonth | undefined;
|
||||
// Where viewport top intersects month (0 = month top, 1 = month bottom)
|
||||
viewportTopRatioInMonth: number;
|
||||
// Where month bottom is in viewport (0 = viewport top, 1 = viewport bottom)
|
||||
@@ -67,7 +67,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
|
||||
isInitialized = $state(false);
|
||||
isScrollingOnLoad = false;
|
||||
months: MonthGroup[] = $state([]);
|
||||
months: TimelineMonth[] = $state([]);
|
||||
albumAssets: Set<string> = new SvelteSet();
|
||||
scrubberMonths: ScrubberMonth[] = $state([]);
|
||||
scrubberTimelineHeight: number = $state(0);
|
||||
@@ -137,24 +137,27 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
}
|
||||
|
||||
async *assetsIterator(options?: {
|
||||
startMonthGroup?: MonthGroup;
|
||||
startTimelineMonth?: TimelineMonth;
|
||||
startTimelineDay?: TimelineDay;
|
||||
startAsset?: TimelineAsset;
|
||||
direction?: Direction;
|
||||
}) {
|
||||
const direction = options?.direction ?? 'earlier';
|
||||
let { startTimelineDay, startAsset } = options ?? {};
|
||||
for (const monthGroup of this.monthGroupIterator({ direction, startMonthGroup: options?.startMonthGroup })) {
|
||||
await this.loadMonthGroup(monthGroup.yearMonth, { cancelable: false });
|
||||
yield* monthGroup.assetsIterator({ startTimelineDay, startAsset, direction });
|
||||
for (const timelineMonth of this.timelineMonthIterator({
|
||||
direction,
|
||||
startTimelineMonth: options?.startTimelineMonth,
|
||||
})) {
|
||||
await this.loadTimelineMonth(timelineMonth.yearMonth, { cancelable: false });
|
||||
yield* timelineMonth.assetsIterator({ startTimelineDay, startAsset, direction });
|
||||
startTimelineDay = startAsset = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
*monthGroupIterator(options?: { direction?: Direction; startMonthGroup?: MonthGroup }) {
|
||||
*timelineMonthIterator(options?: { direction?: Direction; startTimelineMonth?: TimelineMonth }) {
|
||||
const isEarlier = options?.direction === 'earlier';
|
||||
let startIndex = options?.startMonthGroup
|
||||
? this.months.indexOf(options.startMonthGroup)
|
||||
let startIndex = options?.startTimelineMonth
|
||||
? this.months.indexOf(options.startTimelineMonth)
|
||||
: isEarlier
|
||||
? 0
|
||||
: this.months.length - 1;
|
||||
@@ -181,7 +184,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
this.#websocketSupport = undefined;
|
||||
}
|
||||
|
||||
#calculateMonthBottomViewportRatio(month: MonthGroup | undefined) {
|
||||
#calculateMonthBottomViewportRatio(month: TimelineMonth | undefined) {
|
||||
if (!month) {
|
||||
return 0;
|
||||
}
|
||||
@@ -191,7 +194,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
return clamp(bottomOfMonthInViewport / windowHeight, 0, 1);
|
||||
}
|
||||
|
||||
#calculateVewportTopRatioInMonth(month: MonthGroup | undefined) {
|
||||
#calculateVewportTopRatioInMonth(month: TimelineMonth | undefined) {
|
||||
if (!month) {
|
||||
return 0;
|
||||
}
|
||||
@@ -209,7 +212,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
this.#updatingViewportProximities = true;
|
||||
|
||||
for (const month of this.months) {
|
||||
updateMonthGroupViewportProximity(this, month);
|
||||
updateTimelineMonthViewportProximity(this, month);
|
||||
}
|
||||
|
||||
const month = this.months.find((month) => month.isInViewport);
|
||||
@@ -225,7 +228,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
this.#updatingViewportProximities = false;
|
||||
}
|
||||
|
||||
clearDeferredLayout(month: MonthGroup) {
|
||||
clearDeferredLayout(month: TimelineMonth) {
|
||||
const hasDeferred = month.timelineDays.some((group) => group.deferredLayout);
|
||||
if (hasDeferred) {
|
||||
updateGeometry(this, month, { invalidateHeight: true, noDefer: true });
|
||||
@@ -235,7 +238,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
}
|
||||
}
|
||||
|
||||
async #initializeMonthGroups() {
|
||||
async #initializeTimelineMonths() {
|
||||
const timebuckets = await getTimeBuckets({
|
||||
...authManager.params,
|
||||
...this.#options,
|
||||
@@ -243,7 +246,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
|
||||
this.months = timebuckets.map((timeBucket) => {
|
||||
const date = new SvelteDate(timeBucket.timeBucket);
|
||||
return new MonthGroup(
|
||||
return new TimelineMonth(
|
||||
this,
|
||||
{ year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 },
|
||||
timeBucket.count,
|
||||
@@ -280,7 +283,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
this.albumAssets.clear();
|
||||
await this.initTask.execute(async () => {
|
||||
this.#options = options;
|
||||
await this.#initializeMonthGroups();
|
||||
await this.#initializeTimelineMonths();
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -332,31 +335,31 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
assetCount: month.assetsCount,
|
||||
year: month.yearMonth.year,
|
||||
month: month.yearMonth.month,
|
||||
title: month.monthGroupTitle,
|
||||
title: month.title,
|
||||
height: month.height,
|
||||
}));
|
||||
this.scrubberTimelineHeight = this.totalViewerHeight;
|
||||
}
|
||||
|
||||
async loadMonthGroup(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }): Promise<void> {
|
||||
async loadTimelineMonth(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }): Promise<void> {
|
||||
let cancelable = true;
|
||||
if (options) {
|
||||
cancelable = options.cancelable;
|
||||
}
|
||||
const monthGroup = getMonthGroupByDate(this, yearMonth);
|
||||
if (!monthGroup) {
|
||||
const timelineMonth = getTimelineMonthByDate(this, yearMonth);
|
||||
if (!timelineMonth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (monthGroup.loader?.executed) {
|
||||
if (timelineMonth.loader?.executed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const executionStatus = await monthGroup.loader?.execute(async (signal: AbortSignal) => {
|
||||
await loadFromTimeBuckets(this, monthGroup, this.#options, signal);
|
||||
const executionStatus = await timelineMonth.loader?.execute(async (signal: AbortSignal) => {
|
||||
await loadFromTimeBuckets(this, timelineMonth, this.#options, signal);
|
||||
}, cancelable);
|
||||
if (executionStatus === 'LOADED') {
|
||||
updateGeometry(this, monthGroup, { invalidateHeight: false });
|
||||
updateGeometry(this, timelineMonth, { invalidateHeight: false });
|
||||
this.updateViewportProximities();
|
||||
}
|
||||
}
|
||||
@@ -367,15 +370,15 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
this.addAssetsUpsertSegments([...notExcluded]);
|
||||
}
|
||||
|
||||
async findMonthGroupForAsset(asset: AssetDescriptor | AssetResponseDto) {
|
||||
async findTimelineMonthForAsset(asset: AssetDescriptor | AssetResponseDto) {
|
||||
if (!this.isInitialized) {
|
||||
await this.initTask.waitUntilExecution();
|
||||
}
|
||||
|
||||
const { id } = asset;
|
||||
let { monthGroup } = findMonthGroupForAssetUtil(this, id) ?? {};
|
||||
if (monthGroup) {
|
||||
return monthGroup;
|
||||
let { timelineMonth } = findTimelineMonthForAssetUtil(this, id) ?? {};
|
||||
if (timelineMonth) {
|
||||
return timelineMonth;
|
||||
}
|
||||
|
||||
const response = isAssetResponseDto(asset)
|
||||
@@ -390,20 +393,20 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
return;
|
||||
}
|
||||
|
||||
monthGroup = await this.#loadMonthGroupAtTime(timelineAsset.localDateTime, { cancelable: false });
|
||||
if (monthGroup?.findAssetById({ id })) {
|
||||
return monthGroup;
|
||||
timelineMonth = await this.#loadTimelineMonthAtTime(timelineAsset.localDateTime, { cancelable: false });
|
||||
if (timelineMonth?.findAssetById({ id })) {
|
||||
return timelineMonth;
|
||||
}
|
||||
}
|
||||
|
||||
async #loadMonthGroupAtTime(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }) {
|
||||
await this.loadMonthGroup(yearMonth, options);
|
||||
return getMonthGroupByDate(this, yearMonth);
|
||||
async #loadTimelineMonthAtTime(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }) {
|
||||
await this.loadTimelineMonth(yearMonth, options);
|
||||
return getTimelineMonthByDate(this, yearMonth);
|
||||
}
|
||||
|
||||
getMonthGroupByAssetId(assetId: string) {
|
||||
const monthGroupInfo = findMonthGroupForAssetUtil(this, assetId);
|
||||
return monthGroupInfo?.monthGroup;
|
||||
getTimelineMonthByAssetId(assetId: string) {
|
||||
const timelineMonthInfo = findTimelineMonthForAssetUtil(this, assetId);
|
||||
return timelineMonthInfo?.timelineMonth;
|
||||
}
|
||||
|
||||
// note: the `index` input is expected to be in the range [0, assetCount). This
|
||||
@@ -414,7 +417,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
|
||||
let accumulatedCount = 0;
|
||||
|
||||
let randomMonth: MonthGroup | undefined = undefined;
|
||||
let randomMonth: TimelineMonth | undefined = undefined;
|
||||
for (const month of this.months) {
|
||||
if (randomAssetIndex < accumulatedCount + month.assetsCount) {
|
||||
randomMonth = month;
|
||||
@@ -426,7 +429,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
if (!randomMonth) {
|
||||
return;
|
||||
}
|
||||
await this.loadMonthGroup(randomMonth.yearMonth, { cancelable: false });
|
||||
await this.loadTimelineMonth(randomMonth.yearMonth, { cancelable: false });
|
||||
|
||||
let randomDay: TimelineDay | undefined = undefined;
|
||||
for (const day of randomMonth.timelineDays) {
|
||||
@@ -459,10 +462,10 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
}
|
||||
|
||||
protected upsertSegmentForAsset(asset: TimelineAsset) {
|
||||
let month = getMonthGroupByDate(this, asset.localDateTime);
|
||||
let month = getTimelineMonthByDate(this, asset.localDateTime);
|
||||
|
||||
if (!month) {
|
||||
month = new MonthGroup(this, asset.localDateTime, 1, true, this.#options.order);
|
||||
month = new TimelineMonth(this, asset.localDateTime, 1, true, this.#options.order);
|
||||
this.months.push(month);
|
||||
}
|
||||
return month;
|
||||
@@ -508,7 +511,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
return { updated: new Set<string>(), notUpdated: ids, changedGeometry: false };
|
||||
}
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const changedMonthGroups = new Set<MonthGroup>();
|
||||
const changedTimelineMonths = new Set<TimelineMonth>();
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
let notUpdated = new Set(ids);
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
@@ -523,7 +526,7 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
assetsToMoveSegments.push(result.moveAssets);
|
||||
}
|
||||
if (result.changedGeometry) {
|
||||
changedMonthGroups.add(month);
|
||||
changedTimelineMonths.add(month);
|
||||
}
|
||||
notUpdated = setDifference(notUpdated, result.processedIds);
|
||||
for (const id of result.processedIds) {
|
||||
@@ -537,8 +540,8 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
}
|
||||
}
|
||||
this.addAssetsUpsertSegments(assetsToAdd);
|
||||
const changedGeometry = changedMonthGroups.size > 0;
|
||||
for (const month of changedMonthGroups) {
|
||||
const changedGeometry = changedTimelineMonths.size > 0;
|
||||
for (const month of changedTimelineMonths) {
|
||||
updateGeometry(this, month, { invalidateHeight: true });
|
||||
}
|
||||
if (changedGeometry) {
|
||||
@@ -573,20 +576,20 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
}
|
||||
|
||||
async getClosestAssetToDate(dateTime: TimelineDateTime) {
|
||||
let monthGroup = findMonthGroupForDate(this, dateTime);
|
||||
if (!monthGroup) {
|
||||
let timelineMonth = findTimelineMonthForDate(this, dateTime);
|
||||
if (!timelineMonth) {
|
||||
// if exact match not found, find closest
|
||||
monthGroup = findClosestGroupForDate(this.months, dateTime);
|
||||
if (!monthGroup) {
|
||||
timelineMonth = findClosestTimelineMonthForDate(this.months, dateTime);
|
||||
if (!timelineMonth) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.loadMonthGroup(dateTime, { cancelable: false });
|
||||
const asset = monthGroup.findClosest(dateTime);
|
||||
await this.loadTimelineMonth(dateTime, { cancelable: false });
|
||||
const asset = timelineMonth.findClosest(dateTime);
|
||||
if (asset) {
|
||||
return asset;
|
||||
}
|
||||
for await (const asset of this.assetsIterator({ startMonthGroup: monthGroup })) {
|
||||
for await (const asset of this.assetsIterator({ startTimelineMonth: timelineMonth })) {
|
||||
return asset;
|
||||
}
|
||||
}
|
||||
@@ -622,8 +625,8 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
group.sortAssets(this.#options.order);
|
||||
}
|
||||
|
||||
for (const monthGroup of context.bucketsWithNewTimelineDays) {
|
||||
monthGroup.sortTimelineDays();
|
||||
for (const timelineMonth of context.bucketsWithNewTimelineDays) {
|
||||
timelineMonth.sortTimelineDays();
|
||||
}
|
||||
|
||||
for (const month of context.updatedBuckets) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CancellableTask } from '$lib/utils/cancellable-task';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
formatGroupTitle,
|
||||
formatMonthGroupTitle,
|
||||
formatTimelineMonthTitle,
|
||||
fromTimelinePlainDate,
|
||||
fromTimelinePlainDateTime,
|
||||
fromTimelinePlainYearMonth,
|
||||
@@ -29,7 +29,7 @@ import type { TimelineManager } from './timeline-manager.svelte';
|
||||
import type { AssetDescriptor, Direction, MoveAsset, TimelineAsset } from './types';
|
||||
import { ViewerAsset } from './viewer-asset.svelte';
|
||||
|
||||
export class MonthGroup {
|
||||
export class TimelineMonth {
|
||||
#viewportProximity: ViewportProximity = $state(ViewportProximity.FarFromViewport);
|
||||
isLoaded: boolean = $state(false);
|
||||
timelineDays: TimelineDay[] = $state([]);
|
||||
@@ -50,7 +50,7 @@ export class MonthGroup {
|
||||
loader: CancellableTask | undefined;
|
||||
isHeightActual: boolean = $state(false);
|
||||
|
||||
readonly monthGroupTitle: string;
|
||||
readonly title: string;
|
||||
readonly yearMonth: TimelineYearMonth;
|
||||
|
||||
constructor(
|
||||
@@ -65,7 +65,7 @@ export class MonthGroup {
|
||||
this.#sortOrder = order;
|
||||
|
||||
this.yearMonth = { year: yearMonth.year, month: yearMonth.month };
|
||||
this.monthGroupTitle = formatMonthGroupTitle(fromTimelinePlainYearMonth(yearMonth));
|
||||
this.title = formatTimelineMonthTitle(fromTimelinePlainYearMonth(yearMonth));
|
||||
|
||||
this.loader = new CancellableTask(
|
||||
() => {
|
||||
@@ -89,7 +89,7 @@ export class MonthGroup {
|
||||
}
|
||||
this.#viewportProximity = newValue;
|
||||
if (isInOrNearViewportUtil(newValue)) {
|
||||
void this.timelineManager.loadMonthGroup(this.yearMonth);
|
||||
void this.timelineManager.loadTimelineMonth(this.yearMonth);
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
@@ -269,9 +269,9 @@ export class MonthGroup {
|
||||
const index = timelineManager.months.indexOf(this);
|
||||
const heightDelta = height - this.#height;
|
||||
this.#height = height;
|
||||
const prevMonthGroup = timelineManager.months[index - 1];
|
||||
if (prevMonthGroup) {
|
||||
const newTop = prevMonthGroup.#top + prevMonthGroup.#height;
|
||||
const previousTimelineMonth = timelineManager.months[index - 1];
|
||||
if (previousTimelineMonth) {
|
||||
const newTop = previousTimelineMonth.#top + previousTimelineMonth.#height;
|
||||
if (this.#top !== newTop) {
|
||||
this.#top = newTop;
|
||||
}
|
||||
@@ -280,10 +280,10 @@ export class MonthGroup {
|
||||
return;
|
||||
}
|
||||
for (let cursor = index + 1; cursor < timelineManager.months.length; cursor++) {
|
||||
const monthGroup = this.timelineManager.months[cursor];
|
||||
const newTop = monthGroup.#top + heightDelta;
|
||||
if (monthGroup.#top !== newTop) {
|
||||
monthGroup.#top = newTop;
|
||||
const timelineMonth = this.timelineManager.months[cursor];
|
||||
const newTop = timelineMonth.#top + heightDelta;
|
||||
if (timelineMonth.#top !== newTop) {
|
||||
timelineMonth.#top = newTop;
|
||||
}
|
||||
}
|
||||
if (!timelineManager.viewportTopMonthIntersection) {
|
||||
@@ -16,7 +16,7 @@ export class ViewerAsset {
|
||||
return ViewportProximity.FarFromViewport;
|
||||
}
|
||||
|
||||
const store = this.#group.monthGroup.timelineManager;
|
||||
const store = this.#group.timelineMonth.timelineManager;
|
||||
const positionTop = this.#group.absoluteTimelineDayTop + this.position.top;
|
||||
|
||||
return calculateViewerAssetViewportProximity(store, positionTop, this.position.height);
|
||||
|
||||
@@ -396,18 +396,18 @@ export const selectAllAssets = async (timelineManager: TimelineManager, assetInt
|
||||
assetInteraction.selectAll = true;
|
||||
|
||||
try {
|
||||
for (const monthGroup of timelineManager.months) {
|
||||
if (!monthGroup.isLoaded) {
|
||||
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
|
||||
for (const timelineMonth of timelineManager.months) {
|
||||
if (!timelineMonth.isLoaded) {
|
||||
await timelineManager.loadTimelineMonth(timelineMonth.yearMonth);
|
||||
}
|
||||
|
||||
if (!assetInteraction.selectAll) {
|
||||
assetInteraction.clear();
|
||||
break; // Cancelled
|
||||
}
|
||||
assetInteraction.selectAssets([...monthGroup.assetsIterator()]);
|
||||
assetInteraction.selectAssets([...timelineMonth.assetsIterator()]);
|
||||
|
||||
for (const dateGroup of monthGroup.timelineDays) {
|
||||
for (const dateGroup of timelineMonth.timelineDays) {
|
||||
assetInteraction.addGroupToMultiselectGroup(dateGroup.groupTitle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export const toISOYearMonthUTC = ({ year, month }: TimelineYearMonth): string =>
|
||||
return `${yearFull}-${monthFull}-01T00:00:00.000Z`;
|
||||
};
|
||||
|
||||
export function formatMonthGroupTitle(_date: DateTime): string {
|
||||
export function formatTimelineMonthTitle(_date: DateTime): string {
|
||||
if (!_date.isValid) {
|
||||
return _date.toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user