mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +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:
parent
13b11a39a9
commit
9fe80c25eb
12 changed files with 48 additions and 26 deletions
BIN
mobile/openapi/doc/MemoryLaneResponseDto.md
generated
BIN
mobile/openapi/doc/MemoryLaneResponseDto.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/memory_lane_response_dto.dart
generated
BIN
mobile/openapi/lib/model/memory_lane_response_dto.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/memory_lane_response_dto_test.dart
generated
BIN
mobile/openapi/test/memory_lane_response_dto_test.dart
generated
Binary file not shown.
|
@ -8427,12 +8427,17 @@
|
|||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"deprecated": true,
|
||||
"type": "string"
|
||||
},
|
||||
"yearsAgo": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assets",
|
||||
"title"
|
||||
"title",
|
||||
"yearsAgo"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
|
|
@ -273,6 +273,7 @@ export type MapMarkerResponseDto = {
|
|||
export type MemoryLaneResponseDto = {
|
||||
assets: AssetResponseDto[];
|
||||
title: string;
|
||||
yearsAgo: number;
|
||||
};
|
||||
export type UpdateStackParentDto = {
|
||||
newParentId: string;
|
||||
|
|
|
@ -131,7 +131,9 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
}
|
||||
|
||||
export class MemoryLaneResponseDto {
|
||||
@ApiProperty({ deprecated: true })
|
||||
title!: string;
|
||||
yearsAgo!: number;
|
||||
assets!: AssetResponseDto[];
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
},
|
||||
)
|
||||
.leftJoinAndSelect('entity.exifInfo', 'exifInfo')
|
||||
.orderBy('entity.localDateTime', 'DESC')
|
||||
.orderBy('entity.localDateTime', 'ASC')
|
||||
.getMany();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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...`;
|
||||
|
|
Loading…
Reference in a new issue