diff --git a/web/src/lib/managers/timeline-manager/day-group.svelte.ts b/web/src/lib/managers/timeline-manager/day-group.svelte.ts index 9d5008bf83..57cf513a7d 100644 --- a/web/src/lib/managers/timeline-manager/day-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/day-group.svelte.ts @@ -82,11 +82,6 @@ export class DayGroup { return this.viewerAssets[0]?.asset; } - getRandomAsset() { - const random = Math.floor(Math.random() * this.viewerAssets.length); - return this.viewerAssets[random]; - } - *assetsIterator(options: { startAsset?: TimelineAsset; direction?: Direction } = {}) { const isEarlier = (options?.direction ?? 'earlier') === 'earlier'; let assetIndex = options?.startAsset diff --git a/web/src/lib/managers/timeline-manager/month-group.svelte.ts b/web/src/lib/managers/timeline-manager/month-group.svelte.ts index e406972900..5a90944107 100644 --- a/web/src/lib/managers/timeline-manager/month-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/month-group.svelte.ts @@ -233,15 +233,6 @@ export class MonthGroup { addContext.changedDayGroups.add(dayGroup); } - getRandomDayGroup() { - const random = Math.floor(Math.random() * this.dayGroups.length); - return this.dayGroups[random]; - } - - getRandomAsset() { - return this.getRandomDayGroup()?.getRandomAsset()?.asset; - } - get viewId() { const { year, month } = this.yearMonth; return year + '-' + month; 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 f845caa119..dabcb10479 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 @@ -580,4 +580,60 @@ describe('TimelineManager', () => { expect(timelineManager.getMonthGroupByAssetId(assetOne.id)?.yearMonth.month).toEqual(1); }); }); + + describe('getRandomAsset', () => { + let timelineManager: TimelineManager; + const bucketAssets: Record = { + '2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) => + deriveLocalDateTimeFromFileCreatedAt({ + ...asset, + fileCreatedAt: fromISODateTimeUTCToObject('2024-03-01T00:00:00.000Z'), + }), + ), + '2024-02-01T00:00:00.000Z': timelineAssetFactory.buildList(10).map((asset, idx) => + deriveLocalDateTimeFromFileCreatedAt({ + ...asset, + // here we make sure that not all assets are on the first day of the month + fileCreatedAt: fromISODateTimeUTCToObject(`2024-02-0${idx < 7 ? 1 : 2}T00:00:00.000Z`), + }), + ), + '2024-01-01T00:00:00.000Z': timelineAssetFactory.buildList(3).map((asset) => + deriveLocalDateTimeFromFileCreatedAt({ + ...asset, + fileCreatedAt: fromISODateTimeUTCToObject('2024-01-01T00:00:00.000Z'), + }), + ), + }; + + const bucketAssetsResponse: Record = Object.fromEntries( + Object.entries(bucketAssets).map(([key, assets]) => [key, toResponseDto(...assets)]), + ); + + beforeEach(async () => { + timelineManager = new TimelineManager(); + sdkMock.getTimeBuckets.mockResolvedValue([ + { count: 1, timeBucket: '2024-03-01' }, + { count: 10, timeBucket: '2024-02-01' }, + { count: 3, timeBucket: '2024-01-01' }, + ]); + + sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket])); + await timelineManager.updateViewport({ width: 1588, height: 0 }); + }); + + it('gets all assets once', async () => { + const assetCount = timelineManager.assetCount; + expect(assetCount).toBe(14); + const discoveredAssets: Set = new Set(); + for (let idx = 0; idx < assetCount; idx++) { + const asset = await timelineManager.getRandomAsset(idx); + expect(asset).toBeDefined(); + const id = asset!.id; + expect(discoveredAssets.has(id)).toBeFalsy(); + discoveredAssets.add(id); + } + + expect(discoveredAssets.size).toBe(assetCount); + }); + }); }); 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 172cd07a02..b5f097e496 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -451,16 +451,42 @@ export class TimelineManager { return monthGroupInfo?.monthGroup; } - async getRandomMonthGroup() { - const random = Math.floor(Math.random() * this.months.length); - const month = this.months[random]; - await this.loadMonthGroup(month.yearMonth, { cancelable: false }); - return month; - } + // note: the `index` input is expected to be in the range [0, assetCount). This + // value can be passed to make the method deterministic, which is mainly useful + // for testing. + async getRandomAsset(index?: number): Promise { + const randomAssetIndex = index ?? Math.floor(Math.random() * this.assetCount); - async getRandomAsset() { - const month = await this.getRandomMonthGroup(); - return month?.getRandomAsset(); + let accumulatedCount = 0; + + let randomMonth: MonthGroup | undefined = undefined; + for (const month of this.months) { + if (randomAssetIndex < accumulatedCount + month.assetsCount) { + randomMonth = month; + break; + } + + accumulatedCount += month.assetsCount; + } + if (!randomMonth) { + return; + } + await this.loadMonthGroup(randomMonth.yearMonth, { cancelable: false }); + + let randomDay: DayGroup | undefined = undefined; + for (const day of randomMonth.dayGroups) { + if (randomAssetIndex < accumulatedCount + day.viewerAssets.length) { + randomDay = day; + break; + } + + accumulatedCount += day.viewerAssets.length; + } + if (!randomDay) { + return; + } + + return randomDay.viewerAssets[randomAssetIndex - accumulatedCount].asset; } updateAssetOperation(ids: string[], operation: AssetOperation) {