From 9fe80c25ebf2ba31253ce412caa8b1c531aafde4 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 27 Mar 2024 16:14:29 -0400 Subject: [PATCH] fix: memory lane assets in ascending order (#8309) * fix: memory lane asset order * chore: deprecate title * chore: open-api * chore: rename years => yearsAgo --- mobile/openapi/doc/MemoryLaneResponseDto.md | Bin 506 -> 535 bytes .../lib/model/memory_lane_response_dto.dart | Bin 3134 -> 3369 bytes .../test/memory_lane_response_dto_test.dart | Bin 720 -> 818 bytes open-api/immich-openapi-specs.json | 7 +++- open-api/typescript-sdk/src/fetch-client.ts | 1 + server/src/dtos/asset-response.dto.ts | 2 ++ server/src/repositories/asset.repository.ts | 2 +- server/src/services/asset.service.spec.ts | 13 +++++--- server/src/services/asset.service.ts | 31 ++++++++++-------- .../memory-page/memory-viewer.svelte | 8 ++--- .../components/photos-page/memory-lane.svelte | 8 +++-- web/src/lib/utils.ts | 2 ++ 12 files changed, 48 insertions(+), 26 deletions(-) diff --git a/mobile/openapi/doc/MemoryLaneResponseDto.md b/mobile/openapi/doc/MemoryLaneResponseDto.md index 4712744466c3d29a2534fb62af780a9ea0999da3..102c45f1617f36fc72163679dec3025d58a0e896 100644 GIT binary patch delta 32 ncmeyxJe_4jD5Hp$R%L2pQL$rszLu6kje?d|UTN-RAI5b6uKWsw delta 11 ScmbQv@{4&xDC6WJ#-#unt^`T| diff --git a/mobile/openapi/lib/model/memory_lane_response_dto.dart b/mobile/openapi/lib/model/memory_lane_response_dto.dart index 7d761131d7a8620cbab4805c996fb07aade7ec89..4d6f86fb4752fe59981eaab8cc65e810903e363d 100644 GIT binary patch delta 253 zcmdldu~KToF-8dmg`(8L(#)dN6orzE%woOD)Wo7<$MpQkhZya6@=9|R-~zuHcPc5U zsc|U)L4HX_Y7tzmf~_q=*%qb>0R?TaDh;?^CFW!i9k?1>6}Utz^Gsz0g{MGS3`fJrSH?y#ru!$)sAWYQ(I;z&1 ItCouk03+a2*#H0l delta 44 zcmV+{0Mq}e8on5?#sQP!0XmZ{0<@C@19+2_16h*-1evqM1nvQ|=LReW27NmUeF_SJ C^beB& diff --git a/mobile/openapi/test/memory_lane_response_dto_test.dart b/mobile/openapi/test/memory_lane_response_dto_test.dart index 2dad2c356b7e177e23cb620a1f754cc95535da1e..1106ee7d95fd347a19fd96c0dfb4dd6b2d804db5 100644 GIT binary patch delta 55 zcmcb>x`}PWF(zRJAkf!W$SciNs7y^PDt1iIpDe|s$N}d#GO2NKDJaxxT61yLasdF` CcMqWe delta 18 ZcmdnQc7b)nF(xi91%+BoYc8%@E&w;t1l9lm diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index d04b91aa89..129b001a8d 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -8427,12 +8427,17 @@ "type": "array" }, "title": { + "deprecated": true, "type": "string" + }, + "yearsAgo": { + "type": "number" } }, "required": [ "assets", - "title" + "title", + "yearsAgo" ], "type": "object" }, diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 8834564031..35b7167d4a 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -273,6 +273,7 @@ export type MapMarkerResponseDto = { export type MemoryLaneResponseDto = { assets: AssetResponseDto[]; title: string; + yearsAgo: number; }; export type UpdateStackParentDto = { newParentId: string; diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 04e36645e1..59c9b90707 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -131,7 +131,9 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As } export class MemoryLaneResponseDto { + @ApiProperty({ deprecated: true }) title!: string; + yearsAgo!: number; assets!: AssetResponseDto[]; } diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index dc8d21f005..735cfa325d 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -93,7 +93,7 @@ export class AssetRepository implements IAssetRepository { }, ) .leftJoinAndSelect('entity.exifInfo', 'exifInfo') - .orderBy('entity.localDateTime', 'DESC') + .orderBy('entity.localDateTime', 'ASC') .getMany(); } diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index df1f819b47..d5bce3d1ec 100644 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -307,13 +307,17 @@ describe(AssetService.name, () => { jest.useRealTimers(); }); - it('should set the title correctly', async () => { + it('should group the assets correctly', async () => { + const image1 = { ...assetStub.image, localDateTime: new Date(2023, 1, 15, 0, 0, 0) }; + const image2 = { ...assetStub.image, localDateTime: new Date(2023, 1, 15, 1, 0, 0) }; + const image3 = { ...assetStub.image, localDateTime: new Date(2015, 1, 15) }; + partnerMock.getAll.mockResolvedValue([]); - assetMock.getByDayOfYear.mockResolvedValue([assetStub.image, assetStub.imageFrom2015]); + assetMock.getByDayOfYear.mockResolvedValue([image1, image2, image3]); await expect(sut.getMemoryLane(authStub.admin, { day: 15, month: 1 })).resolves.toEqual([ - { title: '1 year since...', assets: [mapAsset(assetStub.image)] }, - { title: '9 years since...', assets: [mapAsset(assetStub.imageFrom2015)] }, + { yearsAgo: 1, title: '1 year since...', assets: [mapAsset(image1), mapAsset(image2)] }, + { yearsAgo: 9, title: '9 years since...', assets: [mapAsset(image3)] }, ]); expect(assetMock.getByDayOfYear.mock.calls).toEqual([[[authStub.admin.user.id], { day: 15, month: 1 }]]); @@ -321,6 +325,7 @@ describe(AssetService.name, () => { it('should get memories with partners with inTimeline enabled', async () => { partnerMock.getAll.mockResolvedValue([partnerStub.user1ToAdmin1]); + assetMock.getByDayOfYear.mockResolvedValue([]); await sut.getMemoryLane(authStub.admin, { day: 15, month: 1 }); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 230415e80e..17fe147c01 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -174,20 +174,25 @@ export class AssetService { userIds.push(...partnersIds); const assets = await this.assetRepository.getByDayOfYear(userIds, dto); + const groups: Record = {}; + for (const asset of assets) { + const yearsAgo = currentYear - asset.localDateTime.getFullYear(); + if (!groups[yearsAgo]) { + groups[yearsAgo] = []; + } + groups[yearsAgo].push(asset); + } - return _.chain(assets) - .filter((asset) => asset.localDateTime.getFullYear() < currentYear) - .map((asset) => { - const years = currentYear - asset.localDateTime.getFullYear(); - - return { - title: `${years} year${years > 1 ? 's' : ''} since...`, - asset: mapAsset(asset, { auth }), - }; - }) - .groupBy((asset) => asset.title) - .map((items, title) => ({ title, assets: items.map(({ asset }) => asset) })) - .value(); + return Object.keys(groups) + .map(Number) + .sort() + .filter((yearsAgo) => yearsAgo > 0) + .map((yearsAgo) => ({ + yearsAgo, + // TODO move this to clients + title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} since...`, + assets: groups[yearsAgo].map((asset) => mapAsset(asset, { auth })), + })); } private async timeBucketChecks(auth: AuthDto, dto: TimeBucketDto) { diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index e4d21f3fc8..cc002897a2 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -8,7 +8,7 @@ import { AppRoute, QueryParameter } from '$lib/constants'; import type { Viewport } from '$lib/stores/assets.store'; import { memoryStore } from '$lib/stores/memory.store'; - import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; + import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; import { shortcuts } from '$lib/utils/shortcut'; import { fromLocalDateTime } from '$lib/utils/timeline-util'; import { ThumbnailFormat, getMemoryLane } from '@immich/sdk'; @@ -102,7 +102,7 @@ goto(AppRoute.PHOTOS)} forceDark>

- {currentMemory.title} + {memoryLaneTitle(currentMemory.yearsAgo)}

@@ -181,7 +181,7 @@ {#if previousMemory}

PREVIOUS

-

{previousMemory.title}

+

{memoryLaneTitle(previousMemory.yearsAgo)}

{/if} @@ -254,7 +254,7 @@ {#if nextMemory}

UP NEXT

-

{nextMemory.title}

+

{memoryLaneTitle(nextMemory.yearsAgo)}

{/if} diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte index 6faa41362f..e481d8fd3e 100644 --- a/web/src/lib/components/photos-page/memory-lane.svelte +++ b/web/src/lib/components/photos-page/memory-lane.svelte @@ -3,7 +3,7 @@ import Icon from '$lib/components/elements/icon.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; import { memoryStore } from '$lib/stores/memory.store'; - import { getAssetThumbnailUrl } from '$lib/utils'; + import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils'; import { getAltText } from '$lib/utils/thumbnail-util'; import { ThumbnailFormat, getMemoryLane } from '@immich/sdk'; import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; @@ -66,7 +66,7 @@ {/if}
- {#each $memoryStore as memory, index (memory.title)} + {#each $memoryStore as memory, index (memory.yearsAgo)}