From 649d14822a654a823541a8b7e5f82c41030f547c Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Fri, 3 Apr 2026 13:27:12 -0400 Subject: [PATCH] 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 --- web/src/lib/components/timeline/Month.svelte | 16 +-- .../lib/components/timeline/Scrubber.svelte | 14 +- .../lib/components/timeline/Timeline.svelte | 97 +++++++------- .../group-insertion-cache.svelte.ts | 14 +- .../internal/intersection-support.svelte.ts | 4 +- .../internal/layout-support.svelte.ts | 8 +- .../internal/load-support.svelte.ts | 12 +- .../internal/search-support.svelte.spec.ts | 60 +++++---- .../internal/search-support.svelte.ts | 73 ++++++----- .../timeline-manager/timeline-day.svelte.ts | 14 +- .../timeline-manager.svelte.spec.ts | 96 +++++++------- .../timeline-manager.svelte.ts | 123 +++++++++--------- ...oup.svelte.ts => timeline-month.svelte.ts} | 24 ++-- .../timeline-manager/viewer-asset.svelte.ts | 2 +- web/src/lib/utils/asset-utils.ts | 10 +- web/src/lib/utils/timeline-util.ts | 2 +- 16 files changed, 295 insertions(+), 274 deletions(-) rename web/src/lib/managers/timeline-manager/{month-group.svelte.ts => timeline-month.svelte.ts} (95%) diff --git a/web/src/lib/components/timeline/Month.svelte b/web/src/lib/components/timeline/Month.svelte index 7d3fb36f54..28996b204a 100644 --- a/web/src/lib/components/timeline/Month.svelte +++ b/web/src/lib/components/timeline/Month.svelte @@ -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(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 @@ }; -{#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)}
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}
- {#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}
- +
{:else if isInOrNearViewport}
@@ -735,7 +736,7 @@ scrollbar-width: none; } - .month-group { + .timeline-month { contain: layout size paint; transform-style: flat; backface-visibility: hidden; diff --git a/web/src/lib/managers/timeline-manager/group-insertion-cache.svelte.ts b/web/src/lib/managers/timeline-manager/group-insertion-cache.svelte.ts index 0e534c10d6..050b034d54 100644 --- a/web/src/lib/managers/timeline-manager/group-insertion-cache.svelte.ts +++ b/web/src/lib/managers/timeline-manager/group-insertion-cache.svelte.ts @@ -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(); + const updated = new Set(); 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(); + const updated = new Set(); 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(); } } } diff --git a/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts index 6fa8ab88c0..bb3ae72c81 100644 --- a/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts @@ -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, diff --git a/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts index a6d8d5d65f..fc2902bb63 100644 --- a/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts @@ -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; diff --git a/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts index 859e818583..2d57192db2 100644 --- a/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts @@ -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 { - 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, diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.spec.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.spec.ts index 2b8ca930eb..de0cad2213 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.spec.ts @@ -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 }); }); diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts index 028968d18a..68e2d355a9 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts @@ -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 { - 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) { diff --git a/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts index d78be71c70..ec4d0188ab 100644 --- a/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-day.svelte.ts @@ -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; } } diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts index 849011bea4..6abb170cf7 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts @@ -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 = { '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); }); }); diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index f02667ab47..215360b8f9 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -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 = 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 { + async loadTimelineMonth(yearMonth: TimelineYearMonth, options?: { cancelable: boolean }): Promise { 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(), notUpdated: ids, changedGeometry: false }; } // eslint-disable-next-line svelte/prefer-svelte-reactivity - const changedMonthGroups = new Set(); + const changedTimelineMonths = new Set(); // 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) { diff --git a/web/src/lib/managers/timeline-manager/month-group.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-month.svelte.ts similarity index 95% rename from web/src/lib/managers/timeline-manager/month-group.svelte.ts rename to web/src/lib/managers/timeline-manager/timeline-month.svelte.ts index 172e91f7b0..8d97a18131 100644 --- a/web/src/lib/managers/timeline-manager/month-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-month.svelte.ts @@ -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) { diff --git a/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts b/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts index d7691b7025..3eba6c9a04 100644 --- a/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts +++ b/web/src/lib/managers/timeline-manager/viewer-asset.svelte.ts @@ -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); diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 33a5424157..86c014f74a 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -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); } } diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts index d7dc5d6aa4..f40158b44a 100644 --- a/web/src/lib/utils/timeline-util.ts +++ b/web/src/lib/utils/timeline-util.ts @@ -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(); }