1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

fix: memory lane assets in ascending order (#8309)

* fix: memory lane asset order

* chore: deprecate title

* chore: open-api

* chore: rename years => yearsAgo
This commit is contained in:
Jason Rasmussen 2024-03-27 16:14:29 -04:00 committed by GitHub
parent 13b11a39a9
commit 9fe80c25eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 48 additions and 26 deletions

Binary file not shown.

View file

@ -8427,12 +8427,17 @@
"type": "array"
},
"title": {
"deprecated": true,
"type": "string"
},
"yearsAgo": {
"type": "number"
}
},
"required": [
"assets",
"title"
"title",
"yearsAgo"
],
"type": "object"
},

View file

@ -273,6 +273,7 @@ export type MapMarkerResponseDto = {
export type MemoryLaneResponseDto = {
assets: AssetResponseDto[];
title: string;
yearsAgo: number;
};
export type UpdateStackParentDto = {
newParentId: string;

View file

@ -131,7 +131,9 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
}
export class MemoryLaneResponseDto {
@ApiProperty({ deprecated: true })
title!: string;
yearsAgo!: number;
assets!: AssetResponseDto[];
}

View file

@ -93,7 +93,7 @@ export class AssetRepository implements IAssetRepository {
},
)
.leftJoinAndSelect('entity.exifInfo', 'exifInfo')
.orderBy('entity.localDateTime', 'DESC')
.orderBy('entity.localDateTime', 'ASC')
.getMany();
}

View file

@ -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 });

View file

@ -174,20 +174,25 @@ export class AssetService {
userIds.push(...partnersIds);
const assets = await this.assetRepository.getByDayOfYear(userIds, dto);
const groups: Record<number, AssetEntity[]> = {};
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) {

View file

@ -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 @@
<ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} forceDark>
<svelte:fragment slot="leading">
<p class="text-lg">
{currentMemory.title}
{memoryLaneTitle(currentMemory.yearsAgo)}
</p>
</svelte:fragment>
@ -181,7 +181,7 @@
{#if previousMemory}
<div class="absolute bottom-4 right-4 text-left text-white">
<p class="text-xs font-semibold text-gray-200">PREVIOUS</p>
<p class="text-xl">{previousMemory.title}</p>
<p class="text-xl">{memoryLaneTitle(previousMemory.yearsAgo)}</p>
</div>
{/if}
</button>
@ -254,7 +254,7 @@
{#if nextMemory}
<div class="absolute bottom-4 left-4 text-left text-white">
<p class="text-xs font-semibold text-gray-200">UP NEXT</p>
<p class="text-xl">{nextMemory.title}</p>
<p class="text-xl">{memoryLaneTitle(nextMemory.yearsAgo)}</p>
</div>
{/if}
</button>

View file

@ -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 @@
</div>
{/if}
<div class="inline-block" bind:offsetWidth={innerWidth}>
{#each $memoryStore as memory, index (memory.title)}
{#each $memoryStore as memory, index (memory.yearsAgo)}
<button
class="memory-card relative mr-8 inline-block aspect-video h-[215px] rounded-xl"
on:click={() => goto(`${AppRoute.MEMORY}?${QueryParameter.MEMORY_INDEX}=${index}`)}
@ -77,7 +77,9 @@
alt={`Memory Lane ${getAltText(memory.assets[0])}`}
draggable="false"
/>
<p class="absolute bottom-2 left-4 z-10 text-lg text-white">{memory.title}</p>
<p class="absolute bottom-2 left-4 z-10 text-lg text-white">
{memoryLaneTitle(memory.yearsAgo)}
</p>
<div
class="absolute left-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
/>

View file

@ -277,3 +277,5 @@ export const asyncTimeout = (ms: number) => {
export const handlePromiseError = <T>(promise: Promise<T>): void => {
promise.catch((error) => console.error(`[utils.ts]:handlePromiseError ${error}`, error));
};
export const memoryLaneTitle = (yearsAgo: number) => `${yearsAgo} ${yearsAgo ? 'years' : 'year'} since...`;