From bdf19ce3316cca170ab6d85855b140202ffb62a4 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 20 May 2025 10:53:34 -0500 Subject: [PATCH] fix: TimelineAsset visibility (#18395) * fix: TimelineAsset visibility * fix enum values --- mobile/lib/entities/asset.entity.dart | 10 +-- mobile/lib/utils/openapi_patching.dart | 1 - .../openapi/lib/model/asset_response_dto.dart | 84 +------------------ open-api/immich-openapi-specs.json | 12 ++- open-api/typescript-sdk/src/fetch-client.ts | 8 +- server/src/dtos/asset-response.dto.ts | 1 + .../actions/set-visibility-action.svelte | 4 +- .../asset-viewer/asset-viewer-nav-bar.svelte | 4 +- .../actions/set-visibility-action.svelte | 6 +- web/src/lib/utils/timeline-util.ts | 5 +- web/src/routes/auth/pin-prompt/+page.svelte | 2 +- web/src/test-data/factories/asset-factory.ts | 10 +-- 12 files changed, 27 insertions(+), 120 deletions(-) diff --git a/mobile/lib/entities/asset.entity.dart b/mobile/lib/entities/asset.entity.dart index 9119d96a63..d8d2bd23c3 100644 --- a/mobile/lib/entities/asset.entity.dart +++ b/mobile/lib/entities/asset.entity.dart @@ -554,15 +554,15 @@ class Asset { }"""; } - static getVisibility(AssetResponseDtoVisibilityEnum visibility) { + static getVisibility(AssetVisibility visibility) { switch (visibility) { - case AssetResponseDtoVisibilityEnum.timeline: + case AssetVisibility.timeline: return AssetVisibilityEnum.timeline; - case AssetResponseDtoVisibilityEnum.archive: + case AssetVisibility.archive: return AssetVisibilityEnum.archive; - case AssetResponseDtoVisibilityEnum.hidden: + case AssetVisibility.hidden: return AssetVisibilityEnum.hidden; - case AssetResponseDtoVisibilityEnum.locked: + case AssetVisibility.locked: return AssetVisibilityEnum.locked; } } diff --git a/mobile/lib/utils/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index 1ffe05c781..7c7d9bab88 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -29,7 +29,6 @@ dynamic upgradeDto(dynamic value, String targetType) { case 'UserResponseDto': if (value is Map) { addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); - addDefault(value, 'visibility', AssetVisibility.timeline); } break; case 'AssetResponseDto': diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 74af8bd1eb..3d85b779cc 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -133,7 +133,7 @@ class AssetResponseDto { DateTime updatedAt; - AssetResponseDtoVisibilityEnum visibility; + AssetVisibility visibility; @override bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && @@ -318,7 +318,7 @@ class AssetResponseDto { type: AssetTypeEnum.fromJson(json[r'type'])!, unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']), updatedAt: mapDateTime(json, r'updatedAt', r'')!, - visibility: AssetResponseDtoVisibilityEnum.fromJson(json[r'visibility'])!, + visibility: AssetVisibility.fromJson(json[r'visibility'])!, ); } return null; @@ -389,83 +389,3 @@ class AssetResponseDto { }; } - -class AssetResponseDtoVisibilityEnum { - /// Instantiate a new enum with the provided [value]. - const AssetResponseDtoVisibilityEnum._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const archive = AssetResponseDtoVisibilityEnum._(r'archive'); - static const timeline = AssetResponseDtoVisibilityEnum._(r'timeline'); - static const hidden = AssetResponseDtoVisibilityEnum._(r'hidden'); - static const locked = AssetResponseDtoVisibilityEnum._(r'locked'); - - /// List of all possible values in this [enum][AssetResponseDtoVisibilityEnum]. - static const values = [ - archive, - timeline, - hidden, - locked, - ]; - - static AssetResponseDtoVisibilityEnum? fromJson(dynamic value) => AssetResponseDtoVisibilityEnumTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = AssetResponseDtoVisibilityEnum.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [AssetResponseDtoVisibilityEnum] to String, -/// and [decode] dynamic data back to [AssetResponseDtoVisibilityEnum]. -class AssetResponseDtoVisibilityEnumTypeTransformer { - factory AssetResponseDtoVisibilityEnumTypeTransformer() => _instance ??= const AssetResponseDtoVisibilityEnumTypeTransformer._(); - - const AssetResponseDtoVisibilityEnumTypeTransformer._(); - - String encode(AssetResponseDtoVisibilityEnum data) => data.value; - - /// Decodes a [dynamic value][data] to a AssetResponseDtoVisibilityEnum. - /// - /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, - /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] - /// cannot be decoded successfully, then an [UnimplementedError] is thrown. - /// - /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, - /// and users are still using an old app with the old code. - AssetResponseDtoVisibilityEnum? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'archive': return AssetResponseDtoVisibilityEnum.archive; - case r'timeline': return AssetResponseDtoVisibilityEnum.timeline; - case r'hidden': return AssetResponseDtoVisibilityEnum.hidden; - case r'locked': return AssetResponseDtoVisibilityEnum.locked; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [AssetResponseDtoVisibilityEnumTypeTransformer] instance. - static AssetResponseDtoVisibilityEnumTypeTransformer? _instance; -} - - diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 8d21c3ef90..2a8555f82c 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -9289,13 +9289,11 @@ "type": "string" }, "visibility": { - "enum": [ - "archive", - "timeline", - "hidden", - "locked" - ], - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/AssetVisibility" + } + ] } }, "required": [ diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 5358cdfec9..c27c9bc194 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -329,7 +329,7 @@ export type AssetResponseDto = { "type": AssetTypeEnum; unassignedFaces?: AssetFaceWithoutPersonResponseDto[]; updatedAt: string; - visibility: Visibility; + visibility: AssetVisibility; }; export type AlbumResponseDto = { albumName: string; @@ -3675,12 +3675,6 @@ export enum AssetTypeEnum { Audio = "AUDIO", Other = "OTHER" } -export enum Visibility { - Archive = "archive", - Timeline = "timeline", - Hidden = "hidden", - Locked = "locked" -} export enum AssetOrder { Asc = "asc", Desc = "desc" diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index 4c1f2571e8..9bbfb450b2 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -44,6 +44,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { isArchived!: boolean; isTrashed!: boolean; isOffline!: boolean; + @ApiProperty({ enum: AssetVisibility, enumName: 'AssetVisibility' }) visibility!: AssetVisibility; exifInfo?: ExifResponseDto; tags?: TagResponseDto[]; diff --git a/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte b/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte index 91db84b172..dff470f456 100644 --- a/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte @@ -6,7 +6,7 @@ import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; import { handleError } from '$lib/utils/handle-error'; import { AssetVisibility, updateAssets } from '@immich/sdk'; - import { mdiEyeOffOutline, mdiFolderMoveOutline } from '@mdi/js'; + import { mdiLockOpenVariantOutline, mdiLockOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { OnAction, PreAction } from './action'; @@ -57,5 +57,5 @@ toggleLockedVisibility()} text={isLocked ? $t('move_off_locked_folder') : $t('add_to_locked_folder')} - icon={isLocked ? mdiFolderMoveOutline : mdiEyeOffOutline} + icon={isLocked ? mdiLockOpenVariantOutline : mdiLockOutline} /> diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index 70600e6208..19705f05b6 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -29,7 +29,7 @@ import { AssetJobName, AssetTypeEnum, - Visibility, + AssetVisibility, type AlbumResponseDto, type AssetResponseDto, type PersonResponseDto, @@ -94,7 +94,7 @@ const sharedLink = getSharedLink(); let isOwner = $derived($user && asset.ownerId === $user?.id); let showDownloadButton = $derived(sharedLink ? sharedLink.allowDownload : !asset.isOffline); - let isLocked = $derived(asset.visibility === Visibility.Locked); + let isLocked = $derived(asset.visibility === AssetVisibility.Locked); // $: showEditorButton = // isOwner && diff --git a/web/src/lib/components/photos-page/actions/set-visibility-action.svelte b/web/src/lib/components/photos-page/actions/set-visibility-action.svelte index c11ba114ce..407a92fadc 100644 --- a/web/src/lib/components/photos-page/actions/set-visibility-action.svelte +++ b/web/src/lib/components/photos-page/actions/set-visibility-action.svelte @@ -7,7 +7,7 @@ import { handleError } from '$lib/utils/handle-error'; import { AssetVisibility, updateAssets } from '@immich/sdk'; import { Button } from '@immich/ui'; - import { mdiEyeOffOutline, mdiFolderMoveOutline } from '@mdi/js'; + import { mdiLockOpenVariantOutline, mdiLockOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; interface Props { @@ -56,11 +56,11 @@ {:else} + {:else} diff --git a/web/src/test-data/factories/asset-factory.ts b/web/src/test-data/factories/asset-factory.ts index e36bec6c4e..f68c3a1a1a 100644 --- a/web/src/test-data/factories/asset-factory.ts +++ b/web/src/test-data/factories/asset-factory.ts @@ -1,12 +1,6 @@ import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; import { faker } from '@faker-js/faker'; -import { - AssetTypeEnum, - AssetVisibility, - Visibility, - type AssetResponseDto, - type TimeBucketAssetResponseDto, -} from '@immich/sdk'; +import { AssetTypeEnum, AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk'; import { Sync } from 'factory.ts'; export const assetFactory = Sync.makeFactory({ @@ -31,7 +25,7 @@ export const assetFactory = Sync.makeFactory({ checksum: Sync.each(() => faker.string.alphanumeric(28)), isOffline: Sync.each(() => faker.datatype.boolean()), hasMetadata: Sync.each(() => faker.datatype.boolean()), - visibility: Visibility.Timeline, + visibility: AssetVisibility.Timeline, }); export const timelineAssetFactory = Sync.makeFactory({