From 746ca5d5ed22c255ce0654e619290e27c4720592 Mon Sep 17 00:00:00 2001 From: Krisjanis Lejejs Date: Wed, 21 Jun 2023 04:00:59 +0300 Subject: [PATCH] feat(web): Add album sorting to albums view (#2861) * Add album sorting to web albums view * generate api --------- Co-authored-by: Alex Tran --- mobile/openapi/doc/AlbumResponseDto.md | Bin 919 -> 997 bytes .../openapi/lib/model/album_response_dto.dart | Bin 6118 -> 7075 bytes .../openapi/test/album_response_dto_test.dart | Bin 1687 -> 1826 bytes server/immich-openapi-specs.json | 4 ++ server/src/domain/album/album-response.dto.ts | 1 + server/src/domain/album/album.service.ts | 22 ++++---- server/src/domain/asset/asset.repository.ts | 1 + .../infra/repositories/asset.repository.ts | 7 +++ .../repositories/asset.repository.mock.ts | 1 + web/src/api/open-api/api.ts | 6 +++ web/src/routes/(user)/albums/+page.svelte | 48 +++++++++++++++++- 11 files changed, 79 insertions(+), 11 deletions(-) diff --git a/mobile/openapi/doc/AlbumResponseDto.md b/mobile/openapi/doc/AlbumResponseDto.md index d257dea2e9920fbf442212d4949aa7e995c3483e..08425de5cb14ae04d300ac4220da7349b74b413b 100644 GIT binary patch delta 55 zcmbQv{*-;gCngy!t(?T-65srk%(TqZ6vyJ?)RK_Q+|=Td#N2|({fuf{(fI`>nfZB% LIkA&JFf9iFREibF delta 11 ScmaFLKAnBTC#J~`%u4|s>;$R+ diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index 4661d5f281b01ff579c9b1744bf2874a80279426..3ef986cd6a88e4ff4889eb6c7b55c953b7b3cd6d 100644 GIT binary patch delta 892 zcmaiyv2GJV5Qb$#0yqVNB2uIjze5x|!Nlnj8Cezti6%%$qy=exI~T85@1Ab=j8dRH z1G-0m2SB=-0;Nb59)mZa&+I0lvM*O`Gyi<^&p-FK|9$`G?`9kD6XkZbaQ>Z@Dpe$3 zIY<6jP04vN&8lC`)y7%B-){l@;=Lx}2*!FE;Fx=n(r7MT~O z&{~W&wedljgm@gmWt8x!M0jNlQl;qzE-kr$21|7z2?CN*QJiUyna~BHTR3G-B4s0% zv>$Bzx3JyA#D1X}<-I65Aw#+FByaf67CC!JETgA|^GJo(7>ndHybZpAYTXjhCPPFN0@(z`|%Qg?)_`L#nu+L z@n<>7yykEi4j0fO?0l(g9N~Fnb>~kFx { - return { - ...album, - assets: album?.assets?.map(mapAsset), - sharedLinks: undefined, // Don't return shared links - shared: album.sharedLinks?.length > 0 || album.sharedUsers?.length > 0, - assetCount: albumsAssetCountObj[album.id], - } as AlbumResponseDto; - }); + return Promise.all( + albums.map(async (album) => { + const lastModifiedAsset = await this.assetRepository.getLastUpdatedAssetForAlbumId(album.id); + return { + ...album, + assets: album?.assets?.map(mapAsset), + sharedLinks: undefined, // Don't return shared links + shared: album.sharedLinks?.length > 0 || album.sharedUsers?.length > 0, + assetCount: albumsAssetCountObj[album.id], + lastModifiedAssetTimestamp: lastModifiedAsset?.fileModifiedAt, + } as AlbumResponseDto; + }), + ); } private async updateInvalidThumbnails(): Promise { diff --git a/server/src/domain/asset/asset.repository.ts b/server/src/domain/asset/asset.repository.ts index 16214931a..9479d3c12 100644 --- a/server/src/domain/asset/asset.repository.ts +++ b/server/src/domain/asset/asset.repository.ts @@ -47,6 +47,7 @@ export interface IAssetRepository { getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated; getWith(pagination: PaginationOptions, property: WithProperty): Paginated; getFirstAssetForAlbumId(albumId: string): Promise; + getLastUpdatedAssetForAlbumId(albumId: string): Promise; deleteAll(ownerId: string): Promise; getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated; save(asset: Partial): Promise; diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 54b4523e4..1139dbf11 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -248,6 +248,13 @@ export class AssetRepository implements IAssetRepository { }); } + getLastUpdatedAssetForAlbumId(albumId: string): Promise { + return this.repository.findOne({ + where: { albums: { id: albumId } }, + order: { updatedAt: 'DESC' }, + }); + } + async getMapMarkers(ownerId: string, options: MapMarkerSearchOptions = {}): Promise { const { isFavorite, fileCreatedAfter, fileCreatedBefore } = options; diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 5418176f3..51dbb3a27 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -7,6 +7,7 @@ export const newAssetRepositoryMock = (): jest.Mocked => { getWithout: jest.fn(), getWith: jest.fn(), getFirstAssetForAlbumId: jest.fn(), + getLastUpdatedAssetForAlbumId: jest.fn(), getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index b49965514..97a52ec8e 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -284,6 +284,12 @@ export interface AlbumResponseDto { * @memberof AlbumResponseDto */ 'owner': UserResponseDto; + /** + * + * @type {string} + * @memberof AlbumResponseDto + */ + 'lastModifiedAssetTimestamp'?: string; } /** * diff --git a/web/src/routes/(user)/albums/+page.svelte b/web/src/routes/(user)/albums/+page.svelte index da7acc74b..851c37c16 100644 --- a/web/src/routes/(user)/albums/+page.svelte +++ b/web/src/routes/(user)/albums/+page.svelte @@ -15,8 +15,17 @@ export let data: PageData; + const sortByOptions = ['Most recent photo', 'Last modified', 'Album title']; + + let selectedSortBy = sortByOptions[0]; + + const handleChangeSortBy = (e: Event) => { + const target = e.target as HTMLSelectElement; + selectedSortBy = target.value; + }; + const { - albums, + albums: unsortedAlbums, isShowContextMenu, contextMenuPosition, createAlbum, @@ -26,6 +35,28 @@ closeAlbumContextMenu } = useAlbums({ albums: data.albums }); + let albums = unsortedAlbums; + + const sortByDate = (a: string, b: string) => { + const aDate = new Date(a); + const bDate = new Date(b); + return bDate.getTime() - aDate.getTime(); + }; + + $: { + if (selectedSortBy === 'Most recent photo') { + $albums = $unsortedAlbums.sort((a, b) => + a.lastModifiedAssetTimestamp && b.lastModifiedAssetTimestamp + ? sortByDate(a.lastModifiedAssetTimestamp, b.lastModifiedAssetTimestamp) + : sortByDate(a.updatedAt, b.updatedAt) + ); + } else if (selectedSortBy === 'Last modified') { + $albums = $unsortedAlbums.sort((a, b) => sortByDate(a.updatedAt, b.updatedAt)); + } else if (selectedSortBy === 'Album title') { + $albums = $unsortedAlbums.sort((a, b) => a.albumName.localeCompare(b.albumName)); + } + } + const handleCreateAlbum = async () => { const newAlbum = await createAlbum(); if (newAlbum) { @@ -52,7 +83,20 @@ -
+
+ + +