fix: navigate to time action (#20928)

* fix: navigate to time action

* change-date -> DateSelectionModal; use luxon; use handle* for callback fn name

* refactor change-date dialogs

* Review comments

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Min Idzelis
2025-10-16 13:44:09 -04:00
committed by GitHub
parent d0eae97037
commit 2919ee4c65
19 changed files with 647 additions and 477 deletions
@@ -0,0 +1,75 @@
import { describe, expect, it } from 'vitest';
import type { MonthGroup } from '../month-group.svelte';
import { findClosestGroupForDate } from './search-support.svelte';
function createMockMonthGroup(year: number, month: number): MonthGroup {
return {
yearMonth: { year, month },
} as MonthGroup;
}
describe('findClosestGroupForDate', () => {
it('should return undefined for empty months array', () => {
const result = findClosestGroupForDate([], { 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 });
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 });
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 });
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 });
// 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 });
// 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 });
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 });
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 });
// 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 });
// 2024-11 is 1 month from 2024-10 and 10 months from 2024-01
expect(result?.yearMonth).toEqual({ year: 2024, month: 10 });
});
});
@@ -1,5 +1,6 @@
import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util';
import { AssetOrder } from '@immich/sdk';
import { DateTime } from 'luxon';
import type { MonthGroup } from '../month-group.svelte';
import type { TimelineManager } from '../timeline-manager.svelte';
import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
@@ -143,3 +144,22 @@ export function findMonthGroupForDate(timelineManager: TimelineManager, targetYe
}
}
}
export function findClosestGroupForDate(months: MonthGroup[], targetYearMonth: TimelineYearMonth) {
const targetDate = DateTime.fromObject({ year: targetYearMonth.year, month: targetYearMonth.month });
let closestMonth: MonthGroup | undefined;
let minDifference = Number.MAX_SAFE_INTEGER;
for (const month of months) {
const monthDate = DateTime.fromObject({ year: month.yearMonth.year, month: month.yearMonth.month });
const totalDiff = Math.abs(monthDate.diff(targetDate, 'months').months);
if (totalDiff < minDifference) {
minDifference = totalDiff;
closestMonth = month;
}
}
return closestMonth;
}
@@ -16,6 +16,7 @@ import {
runAssetOperation,
} from '$lib/managers/timeline-manager/internal/operations-support.svelte';
import {
findClosestGroupForDate,
findMonthGroupForAsset as findMonthGroupForAssetUtil,
findMonthGroupForDate,
getAssetWithOffset,
@@ -584,9 +585,13 @@ export class TimelineManager {
}
async getClosestAssetToDate(dateTime: TimelineDateTime) {
const monthGroup = findMonthGroupForDate(this, dateTime);
let monthGroup = findMonthGroupForDate(this, dateTime);
if (!monthGroup) {
return;
// if exact match not found, find closest
monthGroup = findClosestGroupForDate(this.months, dateTime);
if (!monthGroup) {
return;
}
}
await this.loadMonthGroup(dateTime, { cancelable: false });
const asset = monthGroup.findClosest(dateTime);