mirror of
https://github.com/immich-app/immich.git
synced 2026-07-28 14:47:30 -07:00
fix(web): use correct date field for shift-click range in Recently Added (#30071)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { AssetOrder, type AssetResponseDto } from '@immich/sdk';
|
import { AssetOrder, AssetOrderBy, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util';
|
import { getOrderingDate, plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util';
|
||||||
import { TimelineManager } from '../timeline-manager.svelte';
|
import { TimelineManager } from '../timeline-manager.svelte';
|
||||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||||
import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
|
import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
|
||||||
@@ -125,7 +125,14 @@ export async function retrieveRange(timelineManager: TimelineManager, start: Ass
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const assetOrder: AssetOrder = timelineManager.getAssetOrder();
|
const assetOrder: AssetOrder = timelineManager.getAssetOrder();
|
||||||
if (plainDateTimeCompare(assetOrder === AssetOrder.Desc, startAsset.localDateTime, endAsset.localDateTime) < 0) {
|
const orderBy: AssetOrderBy = timelineManager.getOrderBy();
|
||||||
|
if (
|
||||||
|
plainDateTimeCompare(
|
||||||
|
assetOrder === AssetOrder.Desc,
|
||||||
|
getOrderingDate(startAsset, orderBy),
|
||||||
|
getOrderingDate(endAsset, orderBy),
|
||||||
|
) < 0
|
||||||
|
) {
|
||||||
[startAsset, endAsset] = [endAsset, startAsset];
|
[startAsset, endAsset] = [endAsset, startAsset];
|
||||||
// eslint-disable-next-line no-useless-assignment
|
// eslint-disable-next-line no-useless-assignment
|
||||||
[startTimelineMonth, endTimelineMonth] = [endTimelineMonth, startTimelineMonth];
|
[startTimelineMonth, endTimelineMonth] = [endTimelineMonth, startTimelineMonth];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
import { AssetOrderBy, AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { sdkMock } from '$lib/__mocks__/sdk.mock';
|
import { sdkMock } from '$lib/__mocks__/sdk.mock';
|
||||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
@@ -808,4 +808,44 @@ describe('TimelineManager', () => {
|
|||||||
expect(b.showAssetOwners).toBe(true);
|
expect(b.showAssetOwners).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('retrieveRange', () => {
|
||||||
|
it('uses createdAt ordering in the Recently Added view (orderBy=CreatedAt)', async () => {
|
||||||
|
// Simulate the "Recently Added" bug: two assets whose localDateTime order is
|
||||||
|
// the reverse of their createdAt (upload) order. Before the fix, retrieveRange
|
||||||
|
// compared localDateTime and selected the wrong range direction.
|
||||||
|
const timelineManager = new TimelineManager();
|
||||||
|
sdkMock.getTimeBuckets.mockResolvedValue([]);
|
||||||
|
await timelineManager.updateOptions({ orderBy: AssetOrderBy.CreatedAt });
|
||||||
|
|
||||||
|
// assetA was taken recently (2024) but uploaded first (2025-01)
|
||||||
|
const assetA = timelineAssetFactory.build({
|
||||||
|
localDateTime: fromISODateTimeUTCToObject('2024-06-01T00:00:00.000Z'),
|
||||||
|
createdAt: fromISODateTimeUTCToObject('2025-01-20T00:00:00.000Z'),
|
||||||
|
fileCreatedAt: fromISODateTimeUTCToObject('2025-01-20T00:00:00.000Z'),
|
||||||
|
});
|
||||||
|
// assetB was taken long ago (2018) but uploaded second (2025-02)
|
||||||
|
const assetB = timelineAssetFactory.build({
|
||||||
|
localDateTime: fromISODateTimeUTCToObject('2018-03-15T00:00:00.000Z'),
|
||||||
|
createdAt: fromISODateTimeUTCToObject('2025-02-10T00:00:00.000Z'),
|
||||||
|
fileCreatedAt: fromISODateTimeUTCToObject('2025-02-10T00:00:00.000Z'),
|
||||||
|
});
|
||||||
|
// assetC is an unrelated asset that should not appear in the selection
|
||||||
|
const assetC = timelineAssetFactory.build({
|
||||||
|
localDateTime: fromISODateTimeUTCToObject('2022-01-01T00:00:00.000Z'),
|
||||||
|
createdAt: fromISODateTimeUTCToObject('2025-03-01T00:00:00.000Z'),
|
||||||
|
fileCreatedAt: fromISODateTimeUTCToObject('2025-03-01T00:00:00.000Z'),
|
||||||
|
});
|
||||||
|
|
||||||
|
timelineManager.upsertAssets([assetA, assetB, assetC]);
|
||||||
|
|
||||||
|
// Shift-click from assetB (uploaded most recently) to assetA — the range
|
||||||
|
// between them in the "Recently Added" timeline should contain only those two.
|
||||||
|
const range = await timelineManager.retrieveRange({ id: assetB.id }, { id: assetA.id });
|
||||||
|
const ids = range.map((a) => a.id);
|
||||||
|
expect(ids).toContain(assetA.id);
|
||||||
|
expect(ids).toContain(assetB.id);
|
||||||
|
expect(ids).not.toContain(assetC.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -625,6 +625,10 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
return this.#options.order ?? AssetOrder.Desc;
|
return this.#options.order ?? AssetOrder.Desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrderBy() {
|
||||||
|
return this.#options.orderBy ?? AssetOrderBy.TakenAt;
|
||||||
|
}
|
||||||
|
|
||||||
protected postCreateSegments(): void {
|
protected postCreateSegments(): void {
|
||||||
this.months.sort((a, b) => {
|
this.months.sort((a, b) => {
|
||||||
return a.yearMonth.year === b.yearMonth.year
|
return a.yearMonth.year === b.yearMonth.year
|
||||||
|
|||||||
Reference in New Issue
Block a user