diff --git a/e2e/src/api/specs/album.e2e-spec.ts b/e2e/src/api/specs/album.e2e-spec.ts index 2310b4718cfc3..de320ee95f395 100644 --- a/e2e/src/api/specs/album.e2e-spec.ts +++ b/e2e/src/api/specs/album.e2e-spec.ts @@ -1,6 +1,7 @@ import { AlbumResponseDto, AssetFileUploadResponseDto, + AssetOrder, LoginResponseDto, SharedLinkType, deleteUser, @@ -353,6 +354,7 @@ describe('/album', () => { assetCount: 0, owner: expect.objectContaining({ email: user1.userEmail }), isActivityEnabled: true, + order: AssetOrder.Desc, }); }); }); diff --git a/mobile/openapi/doc/AlbumResponseDto.md b/mobile/openapi/doc/AlbumResponseDto.md index bc00d30af1a62..dd4a94e88310e 100644 --- a/mobile/openapi/doc/AlbumResponseDto.md +++ b/mobile/openapi/doc/AlbumResponseDto.md @@ -19,6 +19,7 @@ Name | Type | Description | Notes **id** | **String** | | **isActivityEnabled** | **bool** | | **lastModifiedAssetTimestamp** | [**DateTime**](DateTime.md) | | [optional] +**order** | [**AssetOrder**](AssetOrder.md) | | [optional] **owner** | [**UserResponseDto**](UserResponseDto.md) | | **ownerId** | **String** | | **shared** | **bool** | | diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index c65e6a605f7bd..1aaf195f3a97c 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -834,7 +834,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getTimeBucket** -> List getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked) +> List getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked) @@ -864,13 +864,14 @@ final isArchived = true; // bool | final isFavorite = true; // bool | final isTrashed = true; // bool | final key = key_example; // String | +final order = ; // AssetOrder | final personId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final withPartners = true; // bool | final withStacked = true; // bool | try { - final result = api_instance.getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked); + final result = api_instance.getTimeBucket(size, timeBucket, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked); print(result); } catch (e) { print('Exception when calling AssetApi->getTimeBucket: $e\n'); @@ -888,6 +889,7 @@ Name | Type | Description | Notes **isFavorite** | **bool**| | [optional] **isTrashed** | **bool**| | [optional] **key** | **String**| | [optional] + **order** | [**AssetOrder**](.md)| | [optional] **personId** | **String**| | [optional] **userId** | **String**| | [optional] **withPartners** | **bool**| | [optional] @@ -909,7 +911,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getTimeBuckets** -> List getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked) +> List getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked) @@ -938,13 +940,14 @@ final isArchived = true; // bool | final isFavorite = true; // bool | final isTrashed = true; // bool | final key = key_example; // String | +final order = ; // AssetOrder | final personId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | final withPartners = true; // bool | final withStacked = true; // bool | try { - final result = api_instance.getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, personId, userId, withPartners, withStacked); + final result = api_instance.getTimeBuckets(size, albumId, isArchived, isFavorite, isTrashed, key, order, personId, userId, withPartners, withStacked); print(result); } catch (e) { print('Exception when calling AssetApi->getTimeBuckets: $e\n'); @@ -961,6 +964,7 @@ Name | Type | Description | Notes **isFavorite** | **bool**| | [optional] **isTrashed** | **bool**| | [optional] **key** | **String**| | [optional] + **order** | [**AssetOrder**](.md)| | [optional] **personId** | **String**| | [optional] **userId** | **String**| | [optional] **withPartners** | **bool**| | [optional] diff --git a/mobile/openapi/doc/UpdateAlbumDto.md b/mobile/openapi/doc/UpdateAlbumDto.md index 4ded87d1bfbc5..89edf1c6efb5e 100644 --- a/mobile/openapi/doc/UpdateAlbumDto.md +++ b/mobile/openapi/doc/UpdateAlbumDto.md @@ -12,6 +12,7 @@ Name | Type | Description | Notes **albumThumbnailAssetId** | **String** | | [optional] **description** | **String** | | [optional] **isActivityEnabled** | **bool** | | [optional] +**order** | [**AssetOrder**](AssetOrder.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 786129b457ba1..b0395bfcbedb7 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -852,6 +852,8 @@ class AssetApi { /// /// * [String] key: /// + /// * [AssetOrder] order: + /// /// * [String] personId: /// /// * [String] userId: @@ -859,7 +861,7 @@ class AssetApi { /// * [bool] withPartners: /// /// * [bool] withStacked: - Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { + Future getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { // ignore: prefer_const_declarations final path = r'/asset/time-bucket'; @@ -885,6 +887,9 @@ class AssetApi { if (key != null) { queryParams.addAll(_queryParams('', 'key', key)); } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } if (personId != null) { queryParams.addAll(_queryParams('', 'personId', personId)); } @@ -930,6 +935,8 @@ class AssetApi { /// /// * [String] key: /// + /// * [AssetOrder] order: + /// /// * [String] personId: /// /// * [String] userId: @@ -937,8 +944,8 @@ class AssetApi { /// * [bool] withPartners: /// /// * [bool] withStacked: - Future?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { - final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); + Future?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { + final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -970,6 +977,8 @@ class AssetApi { /// /// * [String] key: /// + /// * [AssetOrder] order: + /// /// * [String] personId: /// /// * [String] userId: @@ -977,7 +986,7 @@ class AssetApi { /// * [bool] withPartners: /// /// * [bool] withStacked: - Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { + Future getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { // ignore: prefer_const_declarations final path = r'/asset/time-buckets'; @@ -1003,6 +1012,9 @@ class AssetApi { if (key != null) { queryParams.addAll(_queryParams('', 'key', key)); } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } if (personId != null) { queryParams.addAll(_queryParams('', 'personId', personId)); } @@ -1045,6 +1057,8 @@ class AssetApi { /// /// * [String] key: /// + /// * [AssetOrder] order: + /// /// * [String] personId: /// /// * [String] userId: @@ -1052,8 +1066,8 @@ class AssetApi { /// * [bool] withPartners: /// /// * [bool] withStacked: - Future?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { - final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); + Future?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async { + final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index 43e24f87beb95..d764028558b4b 100644 --- a/mobile/openapi/lib/model/album_response_dto.dart +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -24,6 +24,7 @@ class AlbumResponseDto { required this.id, required this.isActivityEnabled, this.lastModifiedAssetTimestamp, + this.order, required this.owner, required this.ownerId, required this.shared, @@ -66,6 +67,14 @@ class AlbumResponseDto { /// DateTime? lastModifiedAssetTimestamp; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetOrder? order; + UserResponseDto owner; String ownerId; @@ -97,6 +106,7 @@ class AlbumResponseDto { other.id == id && other.isActivityEnabled == isActivityEnabled && other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp && + other.order == order && other.owner == owner && other.ownerId == ownerId && other.shared == shared && @@ -118,6 +128,7 @@ class AlbumResponseDto { (id.hashCode) + (isActivityEnabled.hashCode) + (lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) + + (order == null ? 0 : order!.hashCode) + (owner.hashCode) + (ownerId.hashCode) + (shared.hashCode) + @@ -126,7 +137,7 @@ class AlbumResponseDto { (updatedAt.hashCode); @override - String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]'; + String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, sharedUsers=$sharedUsers, startDate=$startDate, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -152,6 +163,11 @@ class AlbumResponseDto { json[r'lastModifiedAssetTimestamp'] = this.lastModifiedAssetTimestamp!.toUtc().toIso8601String(); } else { // json[r'lastModifiedAssetTimestamp'] = null; + } + if (this.order != null) { + json[r'order'] = this.order; + } else { + // json[r'order'] = null; } json[r'owner'] = this.owner; json[r'ownerId'] = this.ownerId; @@ -185,6 +201,7 @@ class AlbumResponseDto { id: mapValueOfType(json, r'id')!, isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''), + order: AssetOrder.fromJson(json[r'order']), owner: UserResponseDto.fromJson(json[r'owner'])!, ownerId: mapValueOfType(json, r'ownerId')!, shared: mapValueOfType(json, r'shared')!, diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart index dfe245aaf8b08..d9408cedfbb78 100644 --- a/mobile/openapi/lib/model/update_album_dto.dart +++ b/mobile/openapi/lib/model/update_album_dto.dart @@ -17,6 +17,7 @@ class UpdateAlbumDto { this.albumThumbnailAssetId, this.description, this.isActivityEnabled, + this.order, }); /// @@ -51,12 +52,21 @@ class UpdateAlbumDto { /// bool? isActivityEnabled; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetOrder? order; + @override bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto && other.albumName == albumName && other.albumThumbnailAssetId == albumThumbnailAssetId && other.description == description && - other.isActivityEnabled == isActivityEnabled; + other.isActivityEnabled == isActivityEnabled && + other.order == order; @override int get hashCode => @@ -64,10 +74,11 @@ class UpdateAlbumDto { (albumName == null ? 0 : albumName!.hashCode) + (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + (description == null ? 0 : description!.hashCode) + - (isActivityEnabled == null ? 0 : isActivityEnabled!.hashCode); + (isActivityEnabled == null ? 0 : isActivityEnabled!.hashCode) + + (order == null ? 0 : order!.hashCode); @override - String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description, isActivityEnabled=$isActivityEnabled]'; + String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description, isActivityEnabled=$isActivityEnabled, order=$order]'; Map toJson() { final json = {}; @@ -91,6 +102,11 @@ class UpdateAlbumDto { } else { // json[r'isActivityEnabled'] = null; } + if (this.order != null) { + json[r'order'] = this.order; + } else { + // json[r'order'] = null; + } return json; } @@ -106,6 +122,7 @@ class UpdateAlbumDto { albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), description: mapValueOfType(json, r'description'), isActivityEnabled: mapValueOfType(json, r'isActivityEnabled'), + order: AssetOrder.fromJson(json[r'order']), ); } return null; diff --git a/mobile/openapi/test/album_response_dto_test.dart b/mobile/openapi/test/album_response_dto_test.dart index 933f77c19635b..5c79e5d2fc5d6 100644 --- a/mobile/openapi/test/album_response_dto_test.dart +++ b/mobile/openapi/test/album_response_dto_test.dart @@ -71,6 +71,11 @@ void main() { // TODO }); + // AssetOrder order + test('to test the property `order`', () async { + // TODO + }); + // UserResponseDto owner test('to test the property `owner`', () async { // TODO diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 846a5998cc88e..d210d0e4d94f7 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -95,12 +95,12 @@ void main() { // TODO }); - //Future> getTimeBucket(TimeBucketSize size, String timeBucket, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, String personId, String userId, bool withPartners, bool withStacked }) async + //Future> getTimeBucket(TimeBucketSize size, String timeBucket, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, AssetOrder order, String personId, String userId, bool withPartners, bool withStacked }) async test('test getTimeBucket', () async { // TODO }); - //Future> getTimeBuckets(TimeBucketSize size, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, String personId, String userId, bool withPartners, bool withStacked }) async + //Future> getTimeBuckets(TimeBucketSize size, { String albumId, bool isArchived, bool isFavorite, bool isTrashed, String key, AssetOrder order, String personId, String userId, bool withPartners, bool withStacked }) async test('test getTimeBuckets', () async { // TODO }); diff --git a/mobile/openapi/test/update_album_dto_test.dart b/mobile/openapi/test/update_album_dto_test.dart index 67ec80010ddb7..7f1591a52ca8d 100644 --- a/mobile/openapi/test/update_album_dto_test.dart +++ b/mobile/openapi/test/update_album_dto_test.dart @@ -36,6 +36,11 @@ void main() { // TODO }); + // AssetOrder order + test('to test the property `order`', () async { + // TODO + }); + }); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 2540baf7755e5..15ada078cbe28 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -1765,6 +1765,14 @@ "type": "string" } }, + { + "name": "order", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/AssetOrder" + } + }, { "name": "personId", "required": false, @@ -1901,6 +1909,14 @@ "type": "string" } }, + { + "name": "order", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/AssetOrder" + } + }, { "name": "personId", "required": false, @@ -6722,6 +6738,9 @@ "format": "date-time", "type": "string" }, + "order": { + "$ref": "#/components/schemas/AssetOrder" + }, "owner": { "$ref": "#/components/schemas/UserResponseDto" }, @@ -10335,6 +10354,9 @@ }, "isActivityEnabled": { "type": "boolean" + }, + "order": { + "$ref": "#/components/schemas/AssetOrder" } }, "type": "object" diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index acf540aff1df5..6a660f4e1bce0 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -153,6 +153,7 @@ export type AlbumResponseDto = { id: string; isActivityEnabled: boolean; lastModifiedAssetTimestamp?: string; + order?: AssetOrder; owner: UserResponseDto; ownerId: string; shared: boolean; @@ -176,6 +177,7 @@ export type UpdateAlbumDto = { albumThumbnailAssetId?: string; description?: string; isActivityEnabled?: boolean; + order?: AssetOrder; }; export type BulkIdsDto = { ids: string[]; @@ -1453,12 +1455,13 @@ export function getAssetThumbnail({ format, id, key }: { ...opts })); } -export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, personId, size, timeBucket, userId, withPartners, withStacked }: { +export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, timeBucket, userId, withPartners, withStacked }: { albumId?: string; isArchived?: boolean; isFavorite?: boolean; isTrashed?: boolean; key?: string; + order?: AssetOrder; personId?: string; size: TimeBucketSize; timeBucket: string; @@ -1475,6 +1478,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, isFavorite, isTrashed, key, + order, personId, size, timeBucket, @@ -1485,12 +1489,13 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, ...opts })); } -export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, personId, size, userId, withPartners, withStacked }: { +export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, userId, withPartners, withStacked }: { albumId?: string; isArchived?: boolean; isFavorite?: boolean; isTrashed?: boolean; key?: string; + order?: AssetOrder; personId?: string; size: TimeBucketSize; userId?: string; @@ -1506,6 +1511,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key isFavorite, isTrashed, key, + order, personId, size, userId, @@ -2747,6 +2753,10 @@ export enum AssetTypeEnum { Audio = "AUDIO", Other = "OTHER" } +export enum AssetOrder { + Asc = "asc", + Desc = "desc" +} export enum Error { Duplicate = "duplicate", NoPermission = "no_permission", @@ -2774,10 +2784,6 @@ export enum TimeBucketSize { Day = "DAY", Month = "MONTH" } -export enum AssetOrder { - Asc = "asc", - Desc = "desc" -} export enum EntityType { Asset = "ASSET", Album = "ALBUM" diff --git a/server/src/domain/album/album-response.dto.ts b/server/src/domain/album/album-response.dto.ts index 168b38592898a..bcca1cd31578c 100644 --- a/server/src/domain/album/album-response.dto.ts +++ b/server/src/domain/album/album-response.dto.ts @@ -1,4 +1,5 @@ -import { AlbumEntity } from '@app/infra/entities'; +import { AlbumEntity, AssetOrder } from '@app/infra/entities'; +import { Optional } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; import { AssetResponseDto, mapAsset } from '../asset'; import { AuthDto } from '../auth/auth.dto'; @@ -23,6 +24,9 @@ export class AlbumResponseDto { startDate?: Date; endDate?: Date; isActivityEnabled!: boolean; + @Optional() + @ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder }) + order?: AssetOrder; } export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDto): AlbumResponseDto => { @@ -63,6 +67,7 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt assets: (withAssets ? assets : []).map((asset) => mapAsset(asset, { auth })), assetCount: entity.assets?.length || 0, isActivityEnabled: entity.isActivityEnabled, + order: entity.order, }; }; diff --git a/server/src/domain/album/album.service.ts b/server/src/domain/album/album.service.ts index 9a7b940f77f2c..dc3d510d4b946 100644 --- a/server/src/domain/album/album.service.ts +++ b/server/src/domain/album/album.service.ts @@ -148,6 +148,7 @@ export class AlbumService { description: dto.description, albumThumbnailAssetId: dto.albumThumbnailAssetId, isActivityEnabled: dto.isActivityEnabled, + order: dto.order, }); return mapAlbumWithoutAssets(updatedAlbum); diff --git a/server/src/domain/album/dto/album-update.dto.ts b/server/src/domain/album/dto/album-update.dto.ts index 1b6c754f02fcb..4f88cefbbd067 100644 --- a/server/src/domain/album/dto/album-update.dto.ts +++ b/server/src/domain/album/dto/album-update.dto.ts @@ -1,4 +1,6 @@ -import { IsString } from 'class-validator'; +import { AssetOrder } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsString } from 'class-validator'; import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util'; export class UpdateAlbumDto { @@ -15,4 +17,9 @@ export class UpdateAlbumDto { @ValidateBoolean({ optional: true }) isActivityEnabled?: boolean; + + @IsEnum(AssetOrder) + @Optional() + @ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' }) + order?: AssetOrder; } diff --git a/server/src/domain/asset/dto/asset.dto.ts b/server/src/domain/asset/dto/asset.dto.ts index 8b5c675d89094..2abe31d0ad5f0 100644 --- a/server/src/domain/asset/dto/asset.dto.ts +++ b/server/src/domain/asset/dto/asset.dto.ts @@ -18,11 +18,6 @@ export class DeviceIdDto { deviceId!: string; } -export enum AssetOrder { - ASC = 'asc', - DESC = 'desc', -} - const hasGPS = (o: { latitude: undefined; longitude: undefined }) => o.latitude !== undefined || o.longitude !== undefined; const ValidateGPS = () => ValidateIf(hasGPS); diff --git a/server/src/domain/asset/dto/time-bucket.dto.ts b/server/src/domain/asset/dto/time-bucket.dto.ts index 597a5de3563d8..7c5b9c212ba1d 100644 --- a/server/src/domain/asset/dto/time-bucket.dto.ts +++ b/server/src/domain/asset/dto/time-bucket.dto.ts @@ -1,6 +1,7 @@ +import { AssetOrder } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { ValidateBoolean, ValidateUUID } from '../../domain.util'; +import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util'; import { TimeBucketSize } from '../../repositories'; export class TimeBucketDto { @@ -32,6 +33,11 @@ export class TimeBucketDto { @ValidateBoolean({ optional: true }) withPartners?: boolean; + + @IsEnum(AssetOrder) + @Optional() + @ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' }) + order?: AssetOrder; } export class TimeBucketAssetDto extends TimeBucketDto { diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts index 36270044219aa..8b14ce597e86a 100644 --- a/server/src/domain/repositories/asset.repository.ts +++ b/server/src/domain/repositories/asset.repository.ts @@ -1,5 +1,5 @@ import { AssetSearchOptions, ReverseGeocodeResult, SearchExploreItem } from '@app/domain'; -import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities'; +import { AssetEntity, AssetJobStatusEntity, AssetOrder, AssetType, ExifEntity } from '@app/infra/entities'; import { FindOptionsRelations, FindOptionsSelect } from 'typeorm'; import { Paginated, PaginationOptions } from '../domain.util'; @@ -66,6 +66,7 @@ export interface AssetBuilderOptions { export interface TimeBucketOptions extends AssetBuilderOptions { size: TimeBucketSize; + order?: AssetOrder; } export interface TimeBucketItem { diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts index 9fa7d8e8ba44b..1bc67266a3444 100644 --- a/server/src/domain/search/dto/search.dto.ts +++ b/server/src/domain/search/dto/search.dto.ts @@ -1,5 +1,4 @@ -import { AssetOrder } from '@app/domain/asset/dto/asset.dto'; -import { AssetType, GeodataPlacesEntity } from '@app/infra/entities'; +import { AssetOrder, AssetType, GeodataPlacesEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts index 4cb0665e088cd..56c4498bce5b7 100644 --- a/server/src/domain/search/search.service.ts +++ b/server/src/domain/search/search.service.ts @@ -1,6 +1,6 @@ -import { AssetEntity } from '@app/infra/entities'; +import { AssetEntity, AssetOrder } from '@app/infra/entities'; import { Inject, Injectable } from '@nestjs/common'; -import { AssetOrder, AssetResponseDto, mapAsset } from '../asset'; +import { AssetResponseDto, mapAsset } from '../asset'; import { AuthDto } from '../auth'; import { PersonResponseDto } from '../person'; import { diff --git a/server/src/infra/entities/album.entity.ts b/server/src/infra/entities/album.entity.ts index fbc125351ae79..daa8fcbc36d5e 100644 --- a/server/src/infra/entities/album.entity.ts +++ b/server/src/infra/entities/album.entity.ts @@ -14,6 +14,12 @@ import { AssetEntity } from './asset.entity'; import { SharedLinkEntity } from './shared-link.entity'; import { UserEntity } from './user.entity'; +// ran into issues when importing the enum from `asset.dto.ts` +export enum AssetOrder { + ASC = 'asc', + DESC = 'desc', +} + @Entity('albums') export class AlbumEntity { @PrimaryGeneratedColumn('uuid') @@ -59,4 +65,7 @@ export class AlbumEntity { @Column({ default: true }) isActivityEnabled!: boolean; + + @Column({ type: 'varchar', default: AssetOrder.DESC }) + order!: AssetOrder; } diff --git a/server/src/infra/migrations/1710182081326-AscendingOrderAlbum.ts b/server/src/infra/migrations/1710182081326-AscendingOrderAlbum.ts new file mode 100644 index 0000000000000..b672ff2b20579 --- /dev/null +++ b/server/src/infra/migrations/1710182081326-AscendingOrderAlbum.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AscendingOrderAlbum1710182081326 implements MigrationInterface { + name = 'AscendingOrderAlbum1710182081326' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "albums" ADD "order" character varying NOT NULL DEFAULT 'desc'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "order"`); + } + +} diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 5d571d11ebdad..871a44460bcb6 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -36,7 +36,7 @@ import { Not, Repository, } from 'typeorm'; -import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity, SmartInfoEntity } from '../entities'; +import { AssetEntity, AssetJobStatusEntity, AssetOrder, AssetType, ExifEntity, SmartInfoEntity } from '../entities'; import { DummyValue, GenerateSql } from '../infra.util'; import { Chunked, ChunkedArray, OptionalBetween, paginate, paginatedBuilder, searchAssetBuilder } from '../infra.utils'; import { Instrumentation } from '../instrumentation'; @@ -607,7 +607,7 @@ export class AssetRepository implements IAssetRepository { .select(`COUNT(asset.id)::int`, 'count') .addSelect(truncated, 'timeBucket') .groupBy(truncated) - .orderBy(truncated, 'DESC') + .orderBy(truncated, options.order === AssetOrder.ASC ? 'ASC' : 'DESC') .getRawMany(); } @@ -620,7 +620,7 @@ export class AssetRepository implements IAssetRepository { // First sort by the day in localtime (put it in the right bucket) .orderBy(truncated, 'DESC') // and then sort by the actual time - .addOrderBy('asset.fileCreatedAt', 'DESC') + .addOrderBy('asset.fileCreatedAt', options.order === AssetOrder.ASC ? 'ASC' : 'DESC') .getMany() ); } diff --git a/server/src/infra/sql/album.repository.sql b/server/src/infra/sql/album.repository.sql index d9b2e896e90f8..ddedc00959eeb 100644 --- a/server/src/infra/sql/album.repository.sql +++ b/server/src/infra/sql/album.repository.sql @@ -15,6 +15,7 @@ FROM "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor", @@ -91,6 +92,7 @@ SELECT "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor", @@ -149,6 +151,7 @@ SELECT "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor", @@ -279,6 +282,7 @@ SELECT "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id", "AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name", "AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor", @@ -352,6 +356,7 @@ SELECT "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id", "AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name", "AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor", @@ -462,6 +467,7 @@ SELECT "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id", "AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name", "AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor", @@ -553,6 +559,7 @@ SELECT "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt", "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId", "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled", + "AlbumEntity"."order" AS "AlbumEntity_order", "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id", "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name", "AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor", diff --git a/server/src/infra/sql/shared.link.repository.sql b/server/src/infra/sql/shared.link.repository.sql index b5e6894130d64..27531cfc9ee08 100644 --- a/server/src/infra/sql/shared.link.repository.sql +++ b/server/src/infra/sql/shared.link.repository.sql @@ -87,6 +87,7 @@ FROM "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt", "SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId", "SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled", + "SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_id", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceAssetId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceAssetId", "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."ownerId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_ownerId", @@ -248,6 +249,7 @@ SELECT "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt", "SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId", "SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled", + "SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order", "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id", "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name", "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."avatarColor" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_avatarColor", diff --git a/server/test/fixtures/album.stub.ts b/server/test/fixtures/album.stub.ts index 2fdc5b5dd4f66..bfb6acb6d1885 100644 --- a/server/test/fixtures/album.stub.ts +++ b/server/test/fixtures/album.stub.ts @@ -1,4 +1,4 @@ -import { AlbumEntity } from '@app/infra/entities'; +import { AlbumEntity, AssetOrder } from '@app/infra/entities'; import { assetStub } from './asset.stub'; import { authStub } from './auth.stub'; import { userStub } from './user.stub'; @@ -19,6 +19,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), sharedWithUser: Object.freeze({ id: 'album-2', @@ -35,6 +36,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [userStub.user1], isActivityEnabled: true, + order: AssetOrder.DESC, }), sharedWithMultiple: Object.freeze({ id: 'album-3', @@ -51,6 +53,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [userStub.user1, userStub.user2], isActivityEnabled: true, + order: AssetOrder.DESC, }), sharedWithAdmin: Object.freeze({ id: 'album-3', @@ -67,6 +70,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [userStub.admin], isActivityEnabled: true, + order: AssetOrder.DESC, }), oneAsset: Object.freeze({ id: 'album-4', @@ -83,6 +87,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), twoAssets: Object.freeze({ id: 'album-4a', @@ -99,6 +104,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), emptyWithInvalidThumbnail: Object.freeze({ id: 'album-5', @@ -115,6 +121,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), emptyWithValidThumbnail: Object.freeze({ id: 'album-5', @@ -131,6 +138,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), oneAssetInvalidThumbnail: Object.freeze({ id: 'album-6', @@ -147,6 +155,7 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), oneAssetValidThumbnail: Object.freeze({ id: 'album-6', @@ -163,5 +172,6 @@ export const albumStub = { sharedLinks: [], sharedUsers: [], isActivityEnabled: true, + order: AssetOrder.DESC, }), }; diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index 61b44a544aa32..109f051907b82 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -1,5 +1,5 @@ import { AlbumResponseDto, AssetResponseDto, ExifResponseDto, mapUser, SharedLinkResponseDto } from '@app/domain'; -import { AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities'; +import { AssetOrder, AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities'; import { assetStub } from './asset.stub'; import { authStub } from './auth.stub'; import { libraryStub } from './library.stub'; @@ -101,6 +101,7 @@ const albumResponse: AlbumResponseDto = { assets: [], assetCount: 1, isActivityEnabled: true, + order: AssetOrder.DESC, }; export const sharedLinkStub = { @@ -181,6 +182,7 @@ export const sharedLinkStub = { sharedUsers: [], sharedLinks: [], isActivityEnabled: true, + order: AssetOrder.DESC, assets: [ { id: 'id_1', diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte index 6cbce418ba7fa..d5f816047f1c5 100644 --- a/web/src/lib/components/album-page/album-options.svelte +++ b/web/src/lib/components/album-page/album-options.svelte @@ -1,22 +1,55 @@ dispatch('close')}> @@ -34,8 +67,16 @@
-

SHARING

-
+

SETTINGS

+
+ {#if order} + + {/if} { + const handleToggle = (selectedOption: RenderedOption) => { for (const [key, option] of Object.entries(options)) { if (option === selectedOption) { $slideshowNavigation = key as SlideshowNavigation; diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 15519b4b267d5..a61f9ed35adef 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -161,7 +161,10 @@ export class AssetStore { this.assetToBucket = {}; this.albumAssets = new Set(); - const buckets = await getTimeBuckets({ ...this.options, key: getKey() }); + const buckets = await getTimeBuckets({ + ...this.options, + key: getKey(), + }); this.initialized = true; diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index 05d94bf3e175d..9e272ce3f67f5 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -58,6 +58,7 @@ updateAlbumInfo, type ActivityResponseDto, type UserResponseDto, + AssetOrder, } from '@immich/sdk'; import { mdiArrowLeft, @@ -83,6 +84,7 @@ $: album = data.album; $: albumId = album.id; + $: albumKey = `${albumId}_${albumOrder}`; $: { if (!album.isActivityEnabled && $numberOfComments === 0) { @@ -112,8 +114,9 @@ let globalWidth: number; let assetGridWidth: number; let textArea: HTMLTextAreaElement; + let albumOrder: AssetOrder | undefined = data.album.order; - $: assetStore = new AssetStore({ albumId }); + $: assetStore = new AssetStore({ albumId, order: albumOrder }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; @@ -512,7 +515,7 @@ style={`width:${assetGridWidth}px`} > - {#key albumId} + {#key albumKey} {#if viewMode === ViewMode.SELECT_ASSETS} (albumOrder = order)} on:close={() => (viewMode = ViewMode.VIEW)} on:toggleEnableActivity={handleToggleEnableActivity} on:showSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)} diff --git a/web/src/test-data/factories/album-factory.ts b/web/src/test-data/factories/album-factory.ts index fd941f51f7309..3d761fcf35721 100644 --- a/web/src/test-data/factories/album-factory.ts +++ b/web/src/test-data/factories/album-factory.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker'; -import type { AlbumResponseDto } from '@immich/sdk'; +import { AssetOrder, type AlbumResponseDto } from '@immich/sdk'; import { Sync } from 'factory.ts'; import { userFactory } from './user-factory'; @@ -18,4 +18,5 @@ export const albumFactory = Sync.makeFactory({ sharedUsers: [], hasSharedLink: false, isActivityEnabled: true, + order: AssetOrder.Desc, });