mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-30 18:22:37 -04:00 
			
		
		
		
	Adapt web client to consume new server response format
This commit is contained in:
		
							parent
							
								
									077703adcc
								
							
						
					
					
						commit
						bc5d4b45a6
					
				
							
								
								
									
										25
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										25
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @ -145,8 +145,15 @@ Class | Method | HTTP request | Description | |||||||
| *MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets |  | *MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets |  | ||||||
| *MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories |  | *MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories |  | ||||||
| *MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} |  | *MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} |  | ||||||
| *NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /notifications/admin/templates/{name} |  | *NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} |  | ||||||
| *NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /notifications/admin/test-email |  | *NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications |  | ||||||
|  | *NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} |  | ||||||
|  | *NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications |  | ||||||
|  | *NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} |  | ||||||
|  | *NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications |  | ||||||
|  | *NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications |  | ||||||
|  | *NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} |  | ||||||
|  | *NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email |  | ||||||
| *OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback |  | *OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback |  | ||||||
| *OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |  | *OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |  | ||||||
| *OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |  | *OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |  | ||||||
| @ -300,7 +307,6 @@ Class | Method | HTTP request | Description | |||||||
|  - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) |  - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) | ||||||
|  - [AssetTypeEnum](doc//AssetTypeEnum.md) |  - [AssetTypeEnum](doc//AssetTypeEnum.md) | ||||||
|  - [AudioCodec](doc//AudioCodec.md) |  - [AudioCodec](doc//AudioCodec.md) | ||||||
|  - [AvatarResponse](doc//AvatarResponse.md) |  | ||||||
|  - [AvatarUpdate](doc//AvatarUpdate.md) |  - [AvatarUpdate](doc//AvatarUpdate.md) | ||||||
|  - [BulkIdResponseDto](doc//BulkIdResponseDto.md) |  - [BulkIdResponseDto](doc//BulkIdResponseDto.md) | ||||||
|  - [BulkIdsDto](doc//BulkIdsDto.md) |  - [BulkIdsDto](doc//BulkIdsDto.md) | ||||||
| @ -361,6 +367,13 @@ Class | Method | HTTP request | Description | |||||||
|  - [MemoryUpdateDto](doc//MemoryUpdateDto.md) |  - [MemoryUpdateDto](doc//MemoryUpdateDto.md) | ||||||
|  - [MergePersonDto](doc//MergePersonDto.md) |  - [MergePersonDto](doc//MergePersonDto.md) | ||||||
|  - [MetadataSearchDto](doc//MetadataSearchDto.md) |  - [MetadataSearchDto](doc//MetadataSearchDto.md) | ||||||
|  |  - [NotificationCreateDto](doc//NotificationCreateDto.md) | ||||||
|  |  - [NotificationDeleteAllDto](doc//NotificationDeleteAllDto.md) | ||||||
|  |  - [NotificationDto](doc//NotificationDto.md) | ||||||
|  |  - [NotificationLevel](doc//NotificationLevel.md) | ||||||
|  |  - [NotificationType](doc//NotificationType.md) | ||||||
|  |  - [NotificationUpdateAllDto](doc//NotificationUpdateAllDto.md) | ||||||
|  |  - [NotificationUpdateDto](doc//NotificationUpdateDto.md) | ||||||
|  - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md) |  - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md) | ||||||
|  - [OAuthCallbackDto](doc//OAuthCallbackDto.md) |  - [OAuthCallbackDto](doc//OAuthCallbackDto.md) | ||||||
|  - [OAuthConfigDto](doc//OAuthConfigDto.md) |  - [OAuthConfigDto](doc//OAuthConfigDto.md) | ||||||
| @ -475,8 +488,12 @@ Class | Method | HTTP request | Description | |||||||
|  - [TemplateDto](doc//TemplateDto.md) |  - [TemplateDto](doc//TemplateDto.md) | ||||||
|  - [TemplateResponseDto](doc//TemplateResponseDto.md) |  - [TemplateResponseDto](doc//TemplateResponseDto.md) | ||||||
|  - [TestEmailResponseDto](doc//TestEmailResponseDto.md) |  - [TestEmailResponseDto](doc//TestEmailResponseDto.md) | ||||||
|  |  - [TimeBucketAssetResponseDto](doc//TimeBucketAssetResponseDto.md) | ||||||
|  |  - [TimeBucketAssetResponseDtoDurationInner](doc//TimeBucketAssetResponseDtoDurationInner.md) | ||||||
|  - [TimeBucketResponseDto](doc//TimeBucketResponseDto.md) |  - [TimeBucketResponseDto](doc//TimeBucketResponseDto.md) | ||||||
|  - [TimeBucketSize](doc//TimeBucketSize.md) |  - [TimeBucketsResponseDto](doc//TimeBucketsResponseDto.md) | ||||||
|  |  - [TimelineAssetDescriptionDto](doc//TimelineAssetDescriptionDto.md) | ||||||
|  |  - [TimelineStackResponseDto](doc//TimelineStackResponseDto.md) | ||||||
|  - [ToneMapping](doc//ToneMapping.md) |  - [ToneMapping](doc//ToneMapping.md) | ||||||
|  - [TranscodeHWAccel](doc//TranscodeHWAccel.md) |  - [TranscodeHWAccel](doc//TranscodeHWAccel.md) | ||||||
|  - [TranscodePolicy](doc//TranscodePolicy.md) |  - [TranscodePolicy](doc//TranscodePolicy.md) | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @ -44,6 +44,7 @@ part 'api/jobs_api.dart'; | |||||||
| part 'api/libraries_api.dart'; | part 'api/libraries_api.dart'; | ||||||
| part 'api/map_api.dart'; | part 'api/map_api.dart'; | ||||||
| part 'api/memories_api.dart'; | part 'api/memories_api.dart'; | ||||||
|  | part 'api/notifications_api.dart'; | ||||||
| part 'api/notifications_admin_api.dart'; | part 'api/notifications_admin_api.dart'; | ||||||
| part 'api/o_auth_api.dart'; | part 'api/o_auth_api.dart'; | ||||||
| part 'api/partners_api.dart'; | part 'api/partners_api.dart'; | ||||||
| @ -107,7 +108,6 @@ part 'model/asset_stack_response_dto.dart'; | |||||||
| part 'model/asset_stats_response_dto.dart'; | part 'model/asset_stats_response_dto.dart'; | ||||||
| part 'model/asset_type_enum.dart'; | part 'model/asset_type_enum.dart'; | ||||||
| part 'model/audio_codec.dart'; | part 'model/audio_codec.dart'; | ||||||
| part 'model/avatar_response.dart'; |  | ||||||
| part 'model/avatar_update.dart'; | part 'model/avatar_update.dart'; | ||||||
| part 'model/bulk_id_response_dto.dart'; | part 'model/bulk_id_response_dto.dart'; | ||||||
| part 'model/bulk_ids_dto.dart'; | part 'model/bulk_ids_dto.dart'; | ||||||
| @ -168,6 +168,13 @@ part 'model/memory_type.dart'; | |||||||
| part 'model/memory_update_dto.dart'; | part 'model/memory_update_dto.dart'; | ||||||
| part 'model/merge_person_dto.dart'; | part 'model/merge_person_dto.dart'; | ||||||
| part 'model/metadata_search_dto.dart'; | part 'model/metadata_search_dto.dart'; | ||||||
|  | part 'model/notification_create_dto.dart'; | ||||||
|  | part 'model/notification_delete_all_dto.dart'; | ||||||
|  | part 'model/notification_dto.dart'; | ||||||
|  | part 'model/notification_level.dart'; | ||||||
|  | part 'model/notification_type.dart'; | ||||||
|  | part 'model/notification_update_all_dto.dart'; | ||||||
|  | part 'model/notification_update_dto.dart'; | ||||||
| part 'model/o_auth_authorize_response_dto.dart'; | part 'model/o_auth_authorize_response_dto.dart'; | ||||||
| part 'model/o_auth_callback_dto.dart'; | part 'model/o_auth_callback_dto.dart'; | ||||||
| part 'model/o_auth_config_dto.dart'; | part 'model/o_auth_config_dto.dart'; | ||||||
| @ -282,8 +289,12 @@ part 'model/tags_update.dart'; | |||||||
| part 'model/template_dto.dart'; | part 'model/template_dto.dart'; | ||||||
| part 'model/template_response_dto.dart'; | part 'model/template_response_dto.dart'; | ||||||
| part 'model/test_email_response_dto.dart'; | part 'model/test_email_response_dto.dart'; | ||||||
|  | part 'model/time_bucket_asset_response_dto.dart'; | ||||||
|  | part 'model/time_bucket_asset_response_dto_duration_inner.dart'; | ||||||
| part 'model/time_bucket_response_dto.dart'; | part 'model/time_bucket_response_dto.dart'; | ||||||
| part 'model/time_bucket_size.dart'; | part 'model/time_buckets_response_dto.dart'; | ||||||
|  | part 'model/timeline_asset_description_dto.dart'; | ||||||
|  | part 'model/timeline_stack_response_dto.dart'; | ||||||
| part 'model/tone_mapping.dart'; | part 'model/tone_mapping.dart'; | ||||||
| part 'model/transcode_hw_accel.dart'; | part 'model/transcode_hw_accel.dart'; | ||||||
| part 'model/transcode_policy.dart'; | part 'model/transcode_policy.dart'; | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @ -270,8 +270,6 @@ class ApiClient { | |||||||
|           return AssetTypeEnumTypeTransformer().decode(value); |           return AssetTypeEnumTypeTransformer().decode(value); | ||||||
|         case 'AudioCodec': |         case 'AudioCodec': | ||||||
|           return AudioCodecTypeTransformer().decode(value); |           return AudioCodecTypeTransformer().decode(value); | ||||||
|         case 'AvatarResponse': |  | ||||||
|           return AvatarResponse.fromJson(value); |  | ||||||
|         case 'AvatarUpdate': |         case 'AvatarUpdate': | ||||||
|           return AvatarUpdate.fromJson(value); |           return AvatarUpdate.fromJson(value); | ||||||
|         case 'BulkIdResponseDto': |         case 'BulkIdResponseDto': | ||||||
| @ -392,6 +390,20 @@ class ApiClient { | |||||||
|           return MergePersonDto.fromJson(value); |           return MergePersonDto.fromJson(value); | ||||||
|         case 'MetadataSearchDto': |         case 'MetadataSearchDto': | ||||||
|           return MetadataSearchDto.fromJson(value); |           return MetadataSearchDto.fromJson(value); | ||||||
|  |         case 'NotificationCreateDto': | ||||||
|  |           return NotificationCreateDto.fromJson(value); | ||||||
|  |         case 'NotificationDeleteAllDto': | ||||||
|  |           return NotificationDeleteAllDto.fromJson(value); | ||||||
|  |         case 'NotificationDto': | ||||||
|  |           return NotificationDto.fromJson(value); | ||||||
|  |         case 'NotificationLevel': | ||||||
|  |           return NotificationLevelTypeTransformer().decode(value); | ||||||
|  |         case 'NotificationType': | ||||||
|  |           return NotificationTypeTypeTransformer().decode(value); | ||||||
|  |         case 'NotificationUpdateAllDto': | ||||||
|  |           return NotificationUpdateAllDto.fromJson(value); | ||||||
|  |         case 'NotificationUpdateDto': | ||||||
|  |           return NotificationUpdateDto.fromJson(value); | ||||||
|         case 'OAuthAuthorizeResponseDto': |         case 'OAuthAuthorizeResponseDto': | ||||||
|           return OAuthAuthorizeResponseDto.fromJson(value); |           return OAuthAuthorizeResponseDto.fromJson(value); | ||||||
|         case 'OAuthCallbackDto': |         case 'OAuthCallbackDto': | ||||||
| @ -620,10 +632,18 @@ class ApiClient { | |||||||
|           return TemplateResponseDto.fromJson(value); |           return TemplateResponseDto.fromJson(value); | ||||||
|         case 'TestEmailResponseDto': |         case 'TestEmailResponseDto': | ||||||
|           return TestEmailResponseDto.fromJson(value); |           return TestEmailResponseDto.fromJson(value); | ||||||
|  |         case 'TimeBucketAssetResponseDto': | ||||||
|  |           return TimeBucketAssetResponseDto.fromJson(value); | ||||||
|  |         case 'TimeBucketAssetResponseDtoDurationInner': | ||||||
|  |           return TimeBucketAssetResponseDtoDurationInner.fromJson(value); | ||||||
|         case 'TimeBucketResponseDto': |         case 'TimeBucketResponseDto': | ||||||
|           return TimeBucketResponseDto.fromJson(value); |           return TimeBucketResponseDto.fromJson(value); | ||||||
|         case 'TimeBucketSize': |         case 'TimeBucketsResponseDto': | ||||||
|           return TimeBucketSizeTypeTransformer().decode(value); |           return TimeBucketsResponseDto.fromJson(value); | ||||||
|  |         case 'TimelineAssetDescriptionDto': | ||||||
|  |           return TimelineAssetDescriptionDto.fromJson(value); | ||||||
|  |         case 'TimelineStackResponseDto': | ||||||
|  |           return TimelineStackResponseDto.fromJson(value); | ||||||
|         case 'ToneMapping': |         case 'ToneMapping': | ||||||
|           return ToneMappingTypeTransformer().decode(value); |           return ToneMappingTypeTransformer().decode(value); | ||||||
|         case 'TranscodeHWAccel': |         case 'TranscodeHWAccel': | ||||||
|  | |||||||
							
								
								
									
										9
									
								
								mobile/openapi/lib/api_helper.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								mobile/openapi/lib/api_helper.dart
									
									
									
										generated
									
									
									
								
							| @ -100,6 +100,12 @@ String parameterToString(dynamic value) { | |||||||
|   if (value is MemoryType) { |   if (value is MemoryType) { | ||||||
|     return MemoryTypeTypeTransformer().encode(value).toString(); |     return MemoryTypeTypeTransformer().encode(value).toString(); | ||||||
|   } |   } | ||||||
|  |   if (value is NotificationLevel) { | ||||||
|  |     return NotificationLevelTypeTransformer().encode(value).toString(); | ||||||
|  |   } | ||||||
|  |   if (value is NotificationType) { | ||||||
|  |     return NotificationTypeTypeTransformer().encode(value).toString(); | ||||||
|  |   } | ||||||
|   if (value is PartnerDirection) { |   if (value is PartnerDirection) { | ||||||
|     return PartnerDirectionTypeTransformer().encode(value).toString(); |     return PartnerDirectionTypeTransformer().encode(value).toString(); | ||||||
|   } |   } | ||||||
| @ -133,9 +139,6 @@ String parameterToString(dynamic value) { | |||||||
|   if (value is SyncRequestType) { |   if (value is SyncRequestType) { | ||||||
|     return SyncRequestTypeTypeTransformer().encode(value).toString(); |     return SyncRequestTypeTypeTransformer().encode(value).toString(); | ||||||
|   } |   } | ||||||
|   if (value is TimeBucketSize) { |  | ||||||
|     return TimeBucketSizeTypeTransformer().encode(value).toString(); |  | ||||||
|   } |  | ||||||
|   if (value is ToneMapping) { |   if (value is ToneMapping) { | ||||||
|     return ToneMappingTypeTransformer().encode(value).toString(); |     return ToneMappingTypeTransformer().encode(value).toString(); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ part of openapi.api; | |||||||
| class TimeBucketAssetResponseDto { | class TimeBucketAssetResponseDto { | ||||||
|   /// Returns a new [TimeBucketAssetResponseDto] instance. |   /// Returns a new [TimeBucketAssetResponseDto] instance. | ||||||
|   TimeBucketAssetResponseDto({ |   TimeBucketAssetResponseDto({ | ||||||
|  |     this.description = const [], | ||||||
|     this.duration = const [], |     this.duration = const [], | ||||||
|     this.id = const [], |     this.id = const [], | ||||||
|     this.isArchived = const [], |     this.isArchived = const [], | ||||||
| @ -29,6 +30,8 @@ class TimeBucketAssetResponseDto { | |||||||
|     this.thumbhash = const [], |     this.thumbhash = const [], | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   List<TimelineAssetDescriptionDto> description; | ||||||
|  | 
 | ||||||
|   List<TimeBucketAssetResponseDtoDurationInner> duration; |   List<TimeBucketAssetResponseDtoDurationInner> duration; | ||||||
| 
 | 
 | ||||||
|   List<String> id; |   List<String> id; | ||||||
| @ -59,6 +62,7 @@ class TimeBucketAssetResponseDto { | |||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto && |   bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto && | ||||||
|  |     _deepEquality.equals(other.description, description) && | ||||||
|     _deepEquality.equals(other.duration, duration) && |     _deepEquality.equals(other.duration, duration) && | ||||||
|     _deepEquality.equals(other.id, id) && |     _deepEquality.equals(other.id, id) && | ||||||
|     _deepEquality.equals(other.isArchived, isArchived) && |     _deepEquality.equals(other.isArchived, isArchived) && | ||||||
| @ -77,6 +81,7 @@ class TimeBucketAssetResponseDto { | |||||||
|   @override |   @override | ||||||
|   int get hashCode => |   int get hashCode => | ||||||
|     // ignore: unnecessary_parenthesis |     // ignore: unnecessary_parenthesis | ||||||
|  |     (description.hashCode) + | ||||||
|     (duration.hashCode) + |     (duration.hashCode) + | ||||||
|     (id.hashCode) + |     (id.hashCode) + | ||||||
|     (isArchived.hashCode) + |     (isArchived.hashCode) + | ||||||
| @ -93,10 +98,11 @@ class TimeBucketAssetResponseDto { | |||||||
|     (thumbhash.hashCode); |     (thumbhash.hashCode); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   String toString() => 'TimeBucketAssetResponseDto[duration=$duration, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, isVideo=$isVideo, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash]'; |   String toString() => 'TimeBucketAssetResponseDto[description=$description, duration=$duration, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, isVideo=$isVideo, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash]'; | ||||||
| 
 | 
 | ||||||
|   Map<String, dynamic> toJson() { |   Map<String, dynamic> toJson() { | ||||||
|     final json = <String, dynamic>{}; |     final json = <String, dynamic>{}; | ||||||
|  |       json[r'description'] = this.description; | ||||||
|       json[r'duration'] = this.duration; |       json[r'duration'] = this.duration; | ||||||
|       json[r'id'] = this.id; |       json[r'id'] = this.id; | ||||||
|       json[r'isArchived'] = this.isArchived; |       json[r'isArchived'] = this.isArchived; | ||||||
| @ -123,6 +129,7 @@ class TimeBucketAssetResponseDto { | |||||||
|       final json = value.cast<String, dynamic>(); |       final json = value.cast<String, dynamic>(); | ||||||
| 
 | 
 | ||||||
|       return TimeBucketAssetResponseDto( |       return TimeBucketAssetResponseDto( | ||||||
|  |         description: TimelineAssetDescriptionDto.listFromJson(json[r'description']), | ||||||
|         duration: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'duration']), |         duration: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'duration']), | ||||||
|         id: json[r'id'] is Iterable |         id: json[r'id'] is Iterable | ||||||
|             ? (json[r'id'] as Iterable).cast<String>().toList(growable: false) |             ? (json[r'id'] as Iterable).cast<String>().toList(growable: false) | ||||||
| @ -200,6 +207,7 @@ class TimeBucketAssetResponseDto { | |||||||
| 
 | 
 | ||||||
|   /// The list of required keys that must be present in a JSON. |   /// The list of required keys that must be present in a JSON. | ||||||
|   static const requiredKeys = <String>{ |   static const requiredKeys = <String>{ | ||||||
|  |     'description', | ||||||
|     'duration', |     'duration', | ||||||
|     'id', |     'id', | ||||||
|     'isArchived', |     'isArchived', | ||||||
|  | |||||||
							
								
								
									
										115
									
								
								mobile/openapi/lib/model/timeline_asset_description_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								mobile/openapi/lib/model/timeline_asset_description_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | |||||||
|  | // | ||||||
|  | // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||||
|  | // | ||||||
|  | // @dart=2.18 | ||||||
|  | 
 | ||||||
|  | // ignore_for_file: unused_element, unused_import | ||||||
|  | // ignore_for_file: always_put_required_named_parameters_first | ||||||
|  | // ignore_for_file: constant_identifier_names | ||||||
|  | // ignore_for_file: lines_longer_than_80_chars | ||||||
|  | 
 | ||||||
|  | part of openapi.api; | ||||||
|  | 
 | ||||||
|  | class TimelineAssetDescriptionDto { | ||||||
|  |   /// Returns a new [TimelineAssetDescriptionDto] instance. | ||||||
|  |   TimelineAssetDescriptionDto({ | ||||||
|  |     required this.city, | ||||||
|  |     required this.country, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   String? city; | ||||||
|  | 
 | ||||||
|  |   String? country; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) => identical(this, other) || other is TimelineAssetDescriptionDto && | ||||||
|  |     other.city == city && | ||||||
|  |     other.country == country; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   int get hashCode => | ||||||
|  |     // ignore: unnecessary_parenthesis | ||||||
|  |     (city == null ? 0 : city!.hashCode) + | ||||||
|  |     (country == null ? 0 : country!.hashCode); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   String toString() => 'TimelineAssetDescriptionDto[city=$city, country=$country]'; | ||||||
|  | 
 | ||||||
|  |   Map<String, dynamic> toJson() { | ||||||
|  |     final json = <String, dynamic>{}; | ||||||
|  |     if (this.city != null) { | ||||||
|  |       json[r'city'] = this.city; | ||||||
|  |     } else { | ||||||
|  |     //  json[r'city'] = null; | ||||||
|  |     } | ||||||
|  |     if (this.country != null) { | ||||||
|  |       json[r'country'] = this.country; | ||||||
|  |     } else { | ||||||
|  |     //  json[r'country'] = null; | ||||||
|  |     } | ||||||
|  |     return json; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Returns a new [TimelineAssetDescriptionDto] instance and imports its values from | ||||||
|  |   /// [value] if it's a [Map], null otherwise. | ||||||
|  |   // ignore: prefer_constructors_over_static_methods | ||||||
|  |   static TimelineAssetDescriptionDto? fromJson(dynamic value) { | ||||||
|  |     upgradeDto(value, "TimelineAssetDescriptionDto"); | ||||||
|  |     if (value is Map) { | ||||||
|  |       final json = value.cast<String, dynamic>(); | ||||||
|  | 
 | ||||||
|  |       return TimelineAssetDescriptionDto( | ||||||
|  |         city: mapValueOfType<String>(json, r'city'), | ||||||
|  |         country: mapValueOfType<String>(json, r'country'), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static List<TimelineAssetDescriptionDto> listFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final result = <TimelineAssetDescriptionDto>[]; | ||||||
|  |     if (json is List && json.isNotEmpty) { | ||||||
|  |       for (final row in json) { | ||||||
|  |         final value = TimelineAssetDescriptionDto.fromJson(row); | ||||||
|  |         if (value != null) { | ||||||
|  |           result.add(value); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return result.toList(growable: growable); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static Map<String, TimelineAssetDescriptionDto> mapFromJson(dynamic json) { | ||||||
|  |     final map = <String, TimelineAssetDescriptionDto>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         final value = TimelineAssetDescriptionDto.fromJson(entry.value); | ||||||
|  |         if (value != null) { | ||||||
|  |           map[entry.key] = value; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // maps a json object with a list of TimelineAssetDescriptionDto-objects as value to a dart map | ||||||
|  |   static Map<String, List<TimelineAssetDescriptionDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||||
|  |     final map = <String, List<TimelineAssetDescriptionDto>>{}; | ||||||
|  |     if (json is Map && json.isNotEmpty) { | ||||||
|  |       // ignore: parameter_assignments | ||||||
|  |       json = json.cast<String, dynamic>(); | ||||||
|  |       for (final entry in json.entries) { | ||||||
|  |         map[entry.key] = TimelineAssetDescriptionDto.listFromJson(entry.value, growable: growable,); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return map; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// The list of required keys that must be present in a JSON. | ||||||
|  |   static const requiredKeys = <String>{ | ||||||
|  |     'city', | ||||||
|  |     'country', | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @ -13847,6 +13847,13 @@ | |||||||
|       }, |       }, | ||||||
|       "TimeBucketAssetResponseDto": { |       "TimeBucketAssetResponseDto": { | ||||||
|         "properties": { |         "properties": { | ||||||
|  |           "description": { | ||||||
|  |             "default": [], | ||||||
|  |             "items": { | ||||||
|  |               "$ref": "#/components/schemas/TimelineAssetDescriptionDto" | ||||||
|  |             }, | ||||||
|  |             "type": "array" | ||||||
|  |           }, | ||||||
|           "duration": { |           "duration": { | ||||||
|             "default": [], |             "default": [], | ||||||
|             "items": { |             "items": { | ||||||
| @ -13976,6 +13983,7 @@ | |||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "required": [ |         "required": [ | ||||||
|  |           "description", | ||||||
|           "duration", |           "duration", | ||||||
|           "id", |           "id", | ||||||
|           "isArchived", |           "isArchived", | ||||||
| @ -14023,6 +14031,23 @@ | |||||||
|         ], |         ], | ||||||
|         "type": "object" |         "type": "object" | ||||||
|       }, |       }, | ||||||
|  |       "TimelineAssetDescriptionDto": { | ||||||
|  |         "properties": { | ||||||
|  |           "city": { | ||||||
|  |             "nullable": true, | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "country": { | ||||||
|  |             "nullable": true, | ||||||
|  |             "type": "string" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "required": [ | ||||||
|  |           "city", | ||||||
|  |           "country" | ||||||
|  |         ], | ||||||
|  |         "type": "object" | ||||||
|  |       }, | ||||||
|       "TimelineStackResponseDto": { |       "TimelineStackResponseDto": { | ||||||
|         "properties": { |         "properties": { | ||||||
|           "assetCount": { |           "assetCount": { | ||||||
|  | |||||||
| @ -1407,12 +1407,17 @@ export type TagBulkAssetsResponseDto = { | |||||||
| export type TagUpdateDto = { | export type TagUpdateDto = { | ||||||
|     color?: string | null; |     color?: string | null; | ||||||
| }; | }; | ||||||
|  | export type TimelineAssetDescriptionDto = { | ||||||
|  |     city: string | null; | ||||||
|  |     country: string | null; | ||||||
|  | }; | ||||||
| export type TimelineStackResponseDto = { | export type TimelineStackResponseDto = { | ||||||
|     assetCount: number; |     assetCount: number; | ||||||
|     id: string; |     id: string; | ||||||
|     primaryAssetId: string; |     primaryAssetId: string; | ||||||
| }; | }; | ||||||
| export type TimeBucketAssetResponseDto = { | export type TimeBucketAssetResponseDto = { | ||||||
|  |     description: TimelineAssetDescriptionDto[]; | ||||||
|     duration: (string | number)[]; |     duration: (string | number)[]; | ||||||
|     id: string[]; |     id: string[]; | ||||||
|     isArchived: number[]; |     isArchived: number[]; | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; | |||||||
| 
 | 
 | ||||||
| import { IsEnum, IsInt, IsString, Min } from 'class-validator'; | import { IsEnum, IsInt, IsString, Min } from 'class-validator'; | ||||||
| import { AssetOrder } from 'src/enum'; | import { AssetOrder } from 'src/enum'; | ||||||
| import { TimeBucketAssets, TimelineStack } from 'src/services/timeline.service.types'; | import { AssetDescription, TimeBucketAssets, TimelineStack } from 'src/services/timeline.service.types'; | ||||||
| import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; | import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; | ||||||
| 
 | 
 | ||||||
| export class TimeBucketDto { | export class TimeBucketDto { | ||||||
| @ -64,6 +64,13 @@ export class TimelineStackResponseDto implements TimelineStack { | |||||||
|   assetCount!: number; |   assetCount!: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export class TimelineAssetDescriptionDto implements AssetDescription { | ||||||
|  |   @ApiProperty() | ||||||
|  |   city!: string | null; | ||||||
|  |   @ApiProperty() | ||||||
|  |   country!: string | null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export class TimeBucketAssetResponseDto implements TimeBucketAssets { | export class TimeBucketAssetResponseDto implements TimeBucketAssets { | ||||||
|   @ApiProperty({ type: [String] }) |   @ApiProperty({ type: [String] }) | ||||||
|   id: string[] = []; |   id: string[] = []; | ||||||
| @ -154,6 +161,9 @@ export class TimeBucketAssetResponseDto implements TimeBucketAssets { | |||||||
|     }, |     }, | ||||||
|   }) |   }) | ||||||
|   livePhotoVideoId: (string | number)[] = []; |   livePhotoVideoId: (string | number)[] = []; | ||||||
|  | 
 | ||||||
|  |   @ApiProperty() | ||||||
|  |   description: TimelineAssetDescriptionDto[] = []; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class TimeBucketsResponseDto { | export class TimeBucketsResponseDto { | ||||||
|  | |||||||
| @ -701,7 +701,14 @@ export class AssetRepository { | |||||||
|         'livePhotoVideoId', |         'livePhotoVideoId', | ||||||
|       ]) |       ]) | ||||||
|       .leftJoin('exif', 'assets.id', 'exif.assetId') |       .leftJoin('exif', 'assets.id', 'exif.assetId') | ||||||
|       .select(['exif.exifImageHeight as height', 'exifImageWidth as width', 'exif.orientation', 'exif.projectionType']) |       .select([ | ||||||
|  |         'exif.exifImageHeight as height', | ||||||
|  |         'exifImageWidth as width', | ||||||
|  |         'exif.orientation', | ||||||
|  |         'exif.projectionType', | ||||||
|  |         'exif.city as city', | ||||||
|  |         'exif.country as country', | ||||||
|  |       ]) | ||||||
|       .$if(!!options.albumId, (qb) => |       .$if(!!options.albumId, (qb) => | ||||||
|         qb |         qb | ||||||
|           .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id') |           .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id') | ||||||
|  | |||||||
| @ -58,6 +58,7 @@ export class TimelineService extends BaseService { | |||||||
|       duration: [], |       duration: [], | ||||||
|       projectionType: [], |       projectionType: [], | ||||||
|       livePhotoVideoId: [], |       livePhotoVideoId: [], | ||||||
|  |       description: [], | ||||||
|     }; |     }; | ||||||
|     for (const item of items) { |     for (const item of items) { | ||||||
|       let width = item.width!; |       let width = item.width!; | ||||||
| @ -82,6 +83,10 @@ export class TimelineService extends BaseService { | |||||||
|       bucketAssets.livePhotoVideoId.push(item.livePhotoVideoId || 0); |       bucketAssets.livePhotoVideoId.push(item.livePhotoVideoId || 0); | ||||||
|       bucketAssets.isImage.push(item.type === AssetType.IMAGE ? 1 : 0); |       bucketAssets.isImage.push(item.type === AssetType.IMAGE ? 1 : 0); | ||||||
|       bucketAssets.isVideo.push(item.type === AssetType.VIDEO ? 1 : 0); |       bucketAssets.isVideo.push(item.type === AssetType.VIDEO ? 1 : 0); | ||||||
|  |       bucketAssets.description.push({ | ||||||
|  |         city: item.city, | ||||||
|  |         country: item.country, | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|  | |||||||
| @ -4,6 +4,11 @@ export type TimelineStack = { | |||||||
|   assetCount: number; |   assetCount: number; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | export type AssetDescription = { | ||||||
|  |   city: string | null; | ||||||
|  |   country: string | null; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export type TimeBucketAssets = { | export type TimeBucketAssets = { | ||||||
|   id: string[]; |   id: string[]; | ||||||
|   ownerId: string[]; |   ownerId: string[]; | ||||||
| @ -19,4 +24,5 @@ export type TimeBucketAssets = { | |||||||
|   duration: (string | number)[]; |   duration: (string | number)[]; | ||||||
|   projectionType: (string | number)[]; |   projectionType: (string | number)[]; | ||||||
|   livePhotoVideoId: (string | number)[]; |   livePhotoVideoId: (string | number)[]; | ||||||
|  |   description: AssetDescription[]; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| import { sdkMock } from '$lib/__mocks__/sdk.mock'; | import { sdkMock } from '$lib/__mocks__/sdk.mock'; | ||||||
| import { AbortError } from '$lib/utils'; | import { AbortError } from '$lib/utils'; | ||||||
| import { TimeBucketSize, type AssetResponseDto } from '@immich/sdk'; | import { type AssetResponseDto, type TimeBucketResponseDto } from '@immich/sdk'; | ||||||
| import { assetFactory, timelineAssetFactory } from '@test-data/factories/asset-factory'; | import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory'; | ||||||
| import { AssetStore } from './assets-store.svelte'; | import { AssetStore, type TimelineAsset } from './assets-store.svelte'; | ||||||
| 
 | 
 | ||||||
| describe('AssetStore', () => { | describe('AssetStore', () => { | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
| @ -11,18 +11,22 @@ describe('AssetStore', () => { | |||||||
| 
 | 
 | ||||||
|   describe('init', () => { |   describe('init', () => { | ||||||
|     let assetStore: AssetStore; |     let assetStore: AssetStore; | ||||||
|     const bucketAssets: Record<string, AssetResponseDto[]> = { |     const bucketAssets: Record<string, TimelineAsset[]> = { | ||||||
|       '2024-03-01T00:00:00.000Z': assetFactory |       '2024-03-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(1) |         .buildList(1) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })), | ||||||
|       '2024-02-01T00:00:00.000Z': assetFactory |       '2024-02-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(100) |         .buildList(100) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-02-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-02-01T00:00:00.000Z' })), | ||||||
|       '2024-01-01T00:00:00.000Z': assetFactory |       '2024-01-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(3) |         .buildList(3) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     const bucketAssetsResponse: Record<string, TimeBucketResponseDto> = Object.fromEntries( | ||||||
|  |       Object.entries(bucketAssets).map(([key, assets]) => [key, toResponseDto(...assets)]), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|     beforeEach(async () => { |     beforeEach(async () => { | ||||||
|       assetStore = new AssetStore(); |       assetStore = new AssetStore(); | ||||||
|       sdkMock.getTimeBuckets.mockResolvedValue([ |       sdkMock.getTimeBuckets.mockResolvedValue([ | ||||||
| @ -30,13 +34,14 @@ describe('AssetStore', () => { | |||||||
|         { count: 100, timeBucket: '2024-02-01T00:00:00.000Z' }, |         { count: 100, timeBucket: '2024-02-01T00:00:00.000Z' }, | ||||||
|         { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, |         { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, | ||||||
|       ]); |       ]); | ||||||
|       sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssets[timeBucket])); | 
 | ||||||
|  |       sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket])); | ||||||
|       await assetStore.updateViewport({ width: 1588, height: 1000 }); |       await assetStore.updateViewport({ width: 1588, height: 1000 }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should load buckets in viewport', () => { |     it('should load buckets in viewport', () => { | ||||||
|       expect(sdkMock.getTimeBuckets).toBeCalledTimes(1); |       expect(sdkMock.getTimeBuckets).toBeCalledTimes(1); | ||||||
|       expect(sdkMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.Month }); | 
 | ||||||
|       expect(sdkMock.getTimeBucket).toHaveBeenCalledTimes(2); |       expect(sdkMock.getTimeBucket).toHaveBeenCalledTimes(2); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -48,29 +53,31 @@ describe('AssetStore', () => { | |||||||
| 
 | 
 | ||||||
|       expect(plainBuckets).toEqual( |       expect(plainBuckets).toEqual( | ||||||
|         expect.arrayContaining([ |         expect.arrayContaining([ | ||||||
|           expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 304 }), |           expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 186.5 }), | ||||||
|           expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 4515.333_333_333_333 }), |           expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 12_017 }), | ||||||
|           expect.objectContaining({ bucketDate: '2024-01-01T00:00:00.000Z', bucketHeight: 286 }), |           expect.objectContaining({ bucketDate: '2024-01-01T00:00:00.000Z', bucketHeight: 286 }), | ||||||
|         ]), |         ]), | ||||||
|       ); |       ); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('calculates timeline height', () => { |     it('calculates timeline height', () => { | ||||||
|       expect(assetStore.timelineHeight).toBe(5105.333_333_333_333); |       expect(assetStore.timelineHeight).toBe(12_489.5); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe('loadBucket', () => { |   describe('loadBucket', () => { | ||||||
|     let assetStore: AssetStore; |     let assetStore: AssetStore; | ||||||
|     const bucketAssets: Record<string, AssetResponseDto[]> = { |     const bucketAssets: Record<string, TimelineAsset[]> = { | ||||||
|       '2024-01-03T00:00:00.000Z': assetFactory |       '2024-01-03T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(1) |         .buildList(1) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })), | ||||||
|       '2024-01-01T00:00:00.000Z': assetFactory |       '2024-01-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(3) |         .buildList(3) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })), | ||||||
|     }; |     }; | ||||||
| 
 |     const bucketAssetsResponse: Record<string, TimeBucketResponseDto> = Object.fromEntries( | ||||||
|  |       Object.entries(bucketAssets).map(([key, assets]) => [key, toResponseDto(...assets)]), | ||||||
|  |     ); | ||||||
|     beforeEach(async () => { |     beforeEach(async () => { | ||||||
|       assetStore = new AssetStore(); |       assetStore = new AssetStore(); | ||||||
|       sdkMock.getTimeBuckets.mockResolvedValue([ |       sdkMock.getTimeBuckets.mockResolvedValue([ | ||||||
| @ -82,7 +89,7 @@ describe('AssetStore', () => { | |||||||
|         if (signal?.aborted) { |         if (signal?.aborted) { | ||||||
|           throw new AbortError(); |           throw new AbortError(); | ||||||
|         } |         } | ||||||
|         return bucketAssets[timeBucket]; |         return bucketAssetsResponse[timeBucket]; | ||||||
|       }); |       }); | ||||||
|       await assetStore.updateViewport({ width: 1588, height: 0 }); |       await assetStore.updateViewport({ width: 1588, height: 0 }); | ||||||
|     }); |     }); | ||||||
| @ -296,7 +303,9 @@ describe('AssetStore', () => { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('removes asset from bucket', () => { |     it('removes asset from bucket', () => { | ||||||
|       const [assetOne, assetTwo] = timelineAssetFactory.buildList(2, { localDateTime: '2024-01-20T12:00:00.000Z' }); |       const [assetOne, assetTwo] = timelineAssetFactory.buildList(2, { | ||||||
|  |         localDateTime: '2024-01-20T12:00:00.000Z', | ||||||
|  |       }); | ||||||
|       assetStore.addAssets([assetOne, assetTwo]); |       assetStore.addAssets([assetOne, assetTwo]); | ||||||
|       assetStore.removeAssets([assetOne.id]); |       assetStore.removeAssets([assetOne.id]); | ||||||
| 
 | 
 | ||||||
| @ -342,17 +351,20 @@ describe('AssetStore', () => { | |||||||
| 
 | 
 | ||||||
|   describe('getPreviousAsset', () => { |   describe('getPreviousAsset', () => { | ||||||
|     let assetStore: AssetStore; |     let assetStore: AssetStore; | ||||||
|     const bucketAssets: Record<string, AssetResponseDto[]> = { |     const bucketAssets: Record<string, TimelineAsset[]> = { | ||||||
|       '2024-03-01T00:00:00.000Z': assetFactory |       '2024-03-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(1) |         .buildList(1) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })), | ||||||
|       '2024-02-01T00:00:00.000Z': assetFactory |       '2024-02-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(6) |         .buildList(6) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-02-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-02-01T00:00:00.000Z' })), | ||||||
|       '2024-01-01T00:00:00.000Z': assetFactory |       '2024-01-01T00:00:00.000Z': timelineAssetFactory | ||||||
|         .buildList(3) |         .buildList(3) | ||||||
|         .map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })), |         .map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })), | ||||||
|     }; |     }; | ||||||
|  |     const bucketAssetsResponse: Record<string, TimeBucketResponseDto> = Object.fromEntries( | ||||||
|  |       Object.entries(bucketAssets).map(([key, assets]) => [key, toResponseDto(...assets)]), | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     beforeEach(async () => { |     beforeEach(async () => { | ||||||
|       assetStore = new AssetStore(); |       assetStore = new AssetStore(); | ||||||
| @ -361,8 +373,7 @@ describe('AssetStore', () => { | |||||||
|         { count: 6, timeBucket: '2024-02-01T00:00:00.000Z' }, |         { count: 6, timeBucket: '2024-02-01T00:00:00.000Z' }, | ||||||
|         { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, |         { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, | ||||||
|       ]); |       ]); | ||||||
|       sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssets[timeBucket])); |       sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket])); | ||||||
| 
 |  | ||||||
|       await assetStore.updateViewport({ width: 1588, height: 1000 }); |       await assetStore.updateViewport({ width: 1588, height: 1000 }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -14,9 +14,8 @@ import { | |||||||
|   getAssetInfo, |   getAssetInfo, | ||||||
|   getTimeBucket, |   getTimeBucket, | ||||||
|   getTimeBuckets, |   getTimeBuckets, | ||||||
|   TimeBucketSize, |  | ||||||
|   type AssetResponseDto, |  | ||||||
|   type AssetStackResponseDto, |   type AssetStackResponseDto, | ||||||
|  |   type TimeBucketResponseDto, | ||||||
| } from '@immich/sdk'; | } from '@immich/sdk'; | ||||||
| import { clamp, debounce, isEqual, throttle } from 'lodash-es'; | import { clamp, debounce, isEqual, throttle } from 'lodash-es'; | ||||||
| import { DateTime } from 'luxon'; | import { DateTime } from 'luxon'; | ||||||
| @ -84,7 +83,7 @@ export type TimelineAsset = { | |||||||
|   duration: string | null; |   duration: string | null; | ||||||
|   projectionType: string | null; |   projectionType: string | null; | ||||||
|   livePhotoVideoId: string | null; |   livePhotoVideoId: string | null; | ||||||
|   text: { |   description: { | ||||||
|     city: string | null; |     city: string | null; | ||||||
|     country: string | null; |     country: string | null; | ||||||
|     people: string[]; |     people: string[]; | ||||||
| @ -418,11 +417,34 @@ export class AssetBucket { | |||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   #decodeString(stringOrNumber: string | number) { | ||||||
|  |     return typeof stringOrNumber === 'number' ? null : (stringOrNumber as string); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // note - if the assets are not part of this bucket, they will not be added
 |   // note - if the assets are not part of this bucket, they will not be added
 | ||||||
|   addAssets(bucketResponse: AssetResponseDto[]) { |   addAssets(bucketResponse: TimeBucketResponseDto) { | ||||||
|     const addContext = new AddContext(); |     const addContext = new AddContext(); | ||||||
|     for (const asset of bucketResponse) { |     for (let i = 0; i < bucketResponse.bucketAssets.id.length; i++) { | ||||||
|       const timelineAsset = toTimelineAsset(asset); |       const timelineAsset: TimelineAsset = { | ||||||
|  |         description: { | ||||||
|  |           ...bucketResponse.bucketAssets.description[i], | ||||||
|  |           people: [], | ||||||
|  |         }, | ||||||
|  |         duration: this.#decodeString(bucketResponse.bucketAssets.duration[i]), | ||||||
|  |         id: bucketResponse.bucketAssets.id[i], | ||||||
|  |         isArchived: !!bucketResponse.bucketAssets.isArchived[i], | ||||||
|  |         isFavorite: !!bucketResponse.bucketAssets.isFavorite[i], | ||||||
|  |         isImage: !!bucketResponse.bucketAssets.isImage[i], | ||||||
|  |         isTrashed: !!bucketResponse.bucketAssets.isTrashed[i], | ||||||
|  |         isVideo: !!bucketResponse.bucketAssets.isVideo[i], | ||||||
|  |         livePhotoVideoId: this.#decodeString(bucketResponse.bucketAssets.livePhotoVideoId[i]), | ||||||
|  |         localDateTime: bucketResponse.bucketAssets.localDateTime[i], | ||||||
|  |         ownerId: bucketResponse.bucketAssets.ownerId[i], | ||||||
|  |         projectionType: this.#decodeString(bucketResponse.bucketAssets.projectionType[i]), | ||||||
|  |         ratio: bucketResponse.bucketAssets.ratio[i], | ||||||
|  |         stack: bucketResponse.bucketAssets.stack[i], | ||||||
|  |         thumbhash: this.#decodeString(bucketResponse.bucketAssets.thumbhash[i]), | ||||||
|  |       }; | ||||||
|       this.addTimelineAsset(timelineAsset, addContext); |       this.addTimelineAsset(timelineAsset, addContext); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -878,7 +900,6 @@ export class AssetStore { | |||||||
|   async #initialiazeTimeBuckets() { |   async #initialiazeTimeBuckets() { | ||||||
|     const timebuckets = await getTimeBuckets({ |     const timebuckets = await getTimeBuckets({ | ||||||
|       ...this.#options, |       ...this.#options, | ||||||
|       size: TimeBucketSize.Month, |  | ||||||
|       key: authManager.key, |       key: authManager.key, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -1086,7 +1107,7 @@ export class AssetStore { | |||||||
|         { |         { | ||||||
|           ...this.#options, |           ...this.#options, | ||||||
|           timeBucket: bucketDate, |           timeBucket: bucketDate, | ||||||
|           size: TimeBucketSize.Month, | 
 | ||||||
|           key: authManager.key, |           key: authManager.key, | ||||||
|         }, |         }, | ||||||
|         { signal }, |         { signal }, | ||||||
| @ -1097,12 +1118,11 @@ export class AssetStore { | |||||||
|             { |             { | ||||||
|               albumId: this.#options.timelineAlbumId, |               albumId: this.#options.timelineAlbumId, | ||||||
|               timeBucket: bucketDate, |               timeBucket: bucketDate, | ||||||
|               size: TimeBucketSize.Month, |  | ||||||
|               key: authManager.key, |               key: authManager.key, | ||||||
|             }, |             }, | ||||||
|             { signal }, |             { signal }, | ||||||
|           ); |           ); | ||||||
|           for (const { id } of albumAssets) { |           for (const id of albumAssets.bucketAssets.id) { | ||||||
|             this.albumAssets.add(id); |             this.albumAssets.add(id); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -38,15 +38,10 @@ export function getThumbnailSize(assetCount: number, viewWidth: number): number | |||||||
|   return 300; |   return 300; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getAltTextForTimelineAsset = (_: TimelineAsset) => { |  | ||||||
|   // TODO: implement this in a performant way
 |  | ||||||
|   return ''; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const getAltText = derived(t, ($t) => { | export const getAltText = derived(t, ($t) => { | ||||||
|   return (asset: TimelineAsset) => { |   return (asset: TimelineAsset) => { | ||||||
|     const date = fromLocalDateTime(asset.localDateTime).toLocaleString({ dateStyle: 'long' }, { locale: get(locale) }); |     const date = fromLocalDateTime(asset.localDateTime).toLocaleString({ dateStyle: 'long' }, { locale: get(locale) }); | ||||||
|     const { city, country, people: names } = asset.text; |     const { city, country, people: names } = asset.description; | ||||||
|     const hasPlace = city && country; |     const hasPlace = city && country; | ||||||
| 
 | 
 | ||||||
|     const peopleCount = names.length; |     const peopleCount = names.length; | ||||||
|  | |||||||
| @ -71,7 +71,8 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): | |||||||
|   const city = assetResponse.exifInfo?.city; |   const city = assetResponse.exifInfo?.city; | ||||||
|   const country = assetResponse.exifInfo?.country; |   const country = assetResponse.exifInfo?.country; | ||||||
|   const people = assetResponse.people?.map((person) => person.name) || []; |   const people = assetResponse.people?.map((person) => person.name) || []; | ||||||
|   const text = { | 
 | ||||||
|  |   const description = { | ||||||
|     city: city || null, |     city: city || null, | ||||||
|     country: country || null, |     country: country || null, | ||||||
|     people, |     people, | ||||||
| @ -91,7 +92,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): | |||||||
|     duration: assetResponse.duration || null, |     duration: assetResponse.duration || null, | ||||||
|     projectionType: assetResponse.exifInfo?.projectionType || null, |     projectionType: assetResponse.exifInfo?.projectionType || null, | ||||||
|     livePhotoVideoId: assetResponse.livePhotoVideoId || null, |     livePhotoVideoId: assetResponse.livePhotoVideoId || null, | ||||||
|     text, |     description, | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset => | export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset => | ||||||
|  | |||||||
| @ -1,6 +1,12 @@ | |||||||
| import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; | import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; | ||||||
| import { faker } from '@faker-js/faker'; | import { faker } from '@faker-js/faker'; | ||||||
| import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; | import { | ||||||
|  |   AssetTypeEnum, | ||||||
|  |   type AssetResponseDto, | ||||||
|  |   type TimeBucketAssetResponseDto, | ||||||
|  |   type TimeBucketResponseDto, | ||||||
|  |   type TimelineStackResponseDto, | ||||||
|  | } from '@immich/sdk'; | ||||||
| import { Sync } from 'factory.ts'; | import { Sync } from 'factory.ts'; | ||||||
| 
 | 
 | ||||||
| export const assetFactory = Sync.makeFactory<AssetResponseDto>({ | export const assetFactory = Sync.makeFactory<AssetResponseDto>({ | ||||||
| @ -42,9 +48,51 @@ export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({ | |||||||
|   stack: null, |   stack: null, | ||||||
|   projectionType: null, |   projectionType: null, | ||||||
|   livePhotoVideoId: Sync.each(() => faker.string.uuid()), |   livePhotoVideoId: Sync.each(() => faker.string.uuid()), | ||||||
|   text: Sync.each(() => ({ |   description: Sync.each(() => ({ | ||||||
|     city: faker.location.city(), |     city: faker.location.city(), | ||||||
|     country: faker.location.country(), |     country: faker.location.country(), | ||||||
|     people: [faker.person.fullName()], |     people: [faker.person.fullName()], | ||||||
|   })), |   })), | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | export const toResponseDto = (...timelineAsset: TimelineAsset[]) => { | ||||||
|  |   const bucketAssets: TimeBucketAssetResponseDto = { | ||||||
|  |     description: [], | ||||||
|  |     duration: [], | ||||||
|  |     id: [], | ||||||
|  |     isArchived: [], | ||||||
|  |     isFavorite: [], | ||||||
|  |     isImage: [], | ||||||
|  |     isTrashed: [], | ||||||
|  |     isVideo: [], | ||||||
|  |     livePhotoVideoId: [], | ||||||
|  |     localDateTime: [], | ||||||
|  |     ownerId: [], | ||||||
|  |     projectionType: [], | ||||||
|  |     ratio: [], | ||||||
|  |     stack: [], | ||||||
|  |     thumbhash: [], | ||||||
|  |   }; | ||||||
|  |   for (const asset of timelineAsset) { | ||||||
|  |     bucketAssets.description.push(asset.description); | ||||||
|  |     bucketAssets.duration.push(asset.duration || 0); | ||||||
|  |     bucketAssets.id.push(asset.id); | ||||||
|  |     bucketAssets.isArchived.push(asset.isArchived ? 1 : 0); | ||||||
|  |     bucketAssets.isFavorite.push(asset.isFavorite ? 1 : 0); | ||||||
|  |     bucketAssets.isImage.push(asset.isImage ? 1 : 0); | ||||||
|  |     bucketAssets.isTrashed.push(asset.isTrashed ? 1 : 0); | ||||||
|  |     bucketAssets.isVideo.push(asset.isVideo ? 1 : 0); | ||||||
|  |     bucketAssets.livePhotoVideoId.push(asset.livePhotoVideoId || 0); | ||||||
|  |     bucketAssets.localDateTime.push(asset.localDateTime); | ||||||
|  |     bucketAssets.ownerId.push(asset.ownerId); | ||||||
|  |     bucketAssets.projectionType.push(asset.projectionType || 0); | ||||||
|  |     bucketAssets.ratio.push(asset.ratio); | ||||||
|  |     bucketAssets.stack.push(asset.stack as TimelineStackResponseDto); | ||||||
|  |     bucketAssets.thumbhash.push(asset.thumbhash || 0); | ||||||
|  |   } | ||||||
|  |   const response: TimeBucketResponseDto = { | ||||||
|  |     bucketAssets, | ||||||
|  |     hasNextPage: false, | ||||||
|  |   }; | ||||||
|  |   return response; | ||||||
|  | }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user