mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -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* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories |
|
||||
*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} |
|
||||
*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /notifications/admin/templates/{name} |
|
||||
*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /notifications/admin/test-email |
|
||||
*NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} |
|
||||
*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* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |
|
||||
*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |
|
||||
@ -300,7 +307,6 @@ Class | Method | HTTP request | Description
|
||||
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
|
||||
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
||||
- [AudioCodec](doc//AudioCodec.md)
|
||||
- [AvatarResponse](doc//AvatarResponse.md)
|
||||
- [AvatarUpdate](doc//AvatarUpdate.md)
|
||||
- [BulkIdResponseDto](doc//BulkIdResponseDto.md)
|
||||
- [BulkIdsDto](doc//BulkIdsDto.md)
|
||||
@ -361,6 +367,13 @@ Class | Method | HTTP request | Description
|
||||
- [MemoryUpdateDto](doc//MemoryUpdateDto.md)
|
||||
- [MergePersonDto](doc//MergePersonDto.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)
|
||||
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
|
||||
- [OAuthConfigDto](doc//OAuthConfigDto.md)
|
||||
@ -475,8 +488,12 @@ Class | Method | HTTP request | Description
|
||||
- [TemplateDto](doc//TemplateDto.md)
|
||||
- [TemplateResponseDto](doc//TemplateResponseDto.md)
|
||||
- [TestEmailResponseDto](doc//TestEmailResponseDto.md)
|
||||
- [TimeBucketAssetResponseDto](doc//TimeBucketAssetResponseDto.md)
|
||||
- [TimeBucketAssetResponseDtoDurationInner](doc//TimeBucketAssetResponseDtoDurationInner.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)
|
||||
- [TranscodeHWAccel](doc//TranscodeHWAccel.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/map_api.dart';
|
||||
part 'api/memories_api.dart';
|
||||
part 'api/notifications_api.dart';
|
||||
part 'api/notifications_admin_api.dart';
|
||||
part 'api/o_auth_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_type_enum.dart';
|
||||
part 'model/audio_codec.dart';
|
||||
part 'model/avatar_response.dart';
|
||||
part 'model/avatar_update.dart';
|
||||
part 'model/bulk_id_response_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/merge_person_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_callback_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_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_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/transcode_hw_accel.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);
|
||||
case 'AudioCodec':
|
||||
return AudioCodecTypeTransformer().decode(value);
|
||||
case 'AvatarResponse':
|
||||
return AvatarResponse.fromJson(value);
|
||||
case 'AvatarUpdate':
|
||||
return AvatarUpdate.fromJson(value);
|
||||
case 'BulkIdResponseDto':
|
||||
@ -392,6 +390,20 @@ class ApiClient {
|
||||
return MergePersonDto.fromJson(value);
|
||||
case 'MetadataSearchDto':
|
||||
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':
|
||||
return OAuthAuthorizeResponseDto.fromJson(value);
|
||||
case 'OAuthCallbackDto':
|
||||
@ -620,10 +632,18 @@ class ApiClient {
|
||||
return TemplateResponseDto.fromJson(value);
|
||||
case 'TestEmailResponseDto':
|
||||
return TestEmailResponseDto.fromJson(value);
|
||||
case 'TimeBucketAssetResponseDto':
|
||||
return TimeBucketAssetResponseDto.fromJson(value);
|
||||
case 'TimeBucketAssetResponseDtoDurationInner':
|
||||
return TimeBucketAssetResponseDtoDurationInner.fromJson(value);
|
||||
case 'TimeBucketResponseDto':
|
||||
return TimeBucketResponseDto.fromJson(value);
|
||||
case 'TimeBucketSize':
|
||||
return TimeBucketSizeTypeTransformer().decode(value);
|
||||
case 'TimeBucketsResponseDto':
|
||||
return TimeBucketsResponseDto.fromJson(value);
|
||||
case 'TimelineAssetDescriptionDto':
|
||||
return TimelineAssetDescriptionDto.fromJson(value);
|
||||
case 'TimelineStackResponseDto':
|
||||
return TimelineStackResponseDto.fromJson(value);
|
||||
case 'ToneMapping':
|
||||
return ToneMappingTypeTransformer().decode(value);
|
||||
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) {
|
||||
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) {
|
||||
return PartnerDirectionTypeTransformer().encode(value).toString();
|
||||
}
|
||||
@ -133,9 +139,6 @@ String parameterToString(dynamic value) {
|
||||
if (value is SyncRequestType) {
|
||||
return SyncRequestTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is TimeBucketSize) {
|
||||
return TimeBucketSizeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is ToneMapping) {
|
||||
return ToneMappingTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ part of openapi.api;
|
||||
class TimeBucketAssetResponseDto {
|
||||
/// Returns a new [TimeBucketAssetResponseDto] instance.
|
||||
TimeBucketAssetResponseDto({
|
||||
this.description = const [],
|
||||
this.duration = const [],
|
||||
this.id = const [],
|
||||
this.isArchived = const [],
|
||||
@ -29,6 +30,8 @@ class TimeBucketAssetResponseDto {
|
||||
this.thumbhash = const [],
|
||||
});
|
||||
|
||||
List<TimelineAssetDescriptionDto> description;
|
||||
|
||||
List<TimeBucketAssetResponseDtoDurationInner> duration;
|
||||
|
||||
List<String> id;
|
||||
@ -59,6 +62,7 @@ class TimeBucketAssetResponseDto {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto &&
|
||||
_deepEquality.equals(other.description, description) &&
|
||||
_deepEquality.equals(other.duration, duration) &&
|
||||
_deepEquality.equals(other.id, id) &&
|
||||
_deepEquality.equals(other.isArchived, isArchived) &&
|
||||
@ -77,6 +81,7 @@ class TimeBucketAssetResponseDto {
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(description.hashCode) +
|
||||
(duration.hashCode) +
|
||||
(id.hashCode) +
|
||||
(isArchived.hashCode) +
|
||||
@ -93,10 +98,11 @@ class TimeBucketAssetResponseDto {
|
||||
(thumbhash.hashCode);
|
||||
|
||||
@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() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'description'] = this.description;
|
||||
json[r'duration'] = this.duration;
|
||||
json[r'id'] = this.id;
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
@ -123,6 +129,7 @@ class TimeBucketAssetResponseDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return TimeBucketAssetResponseDto(
|
||||
description: TimelineAssetDescriptionDto.listFromJson(json[r'description']),
|
||||
duration: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'duration']),
|
||||
id: json[r'id'] is Iterable
|
||||
? (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.
|
||||
static const requiredKeys = <String>{
|
||||
'description',
|
||||
'duration',
|
||||
'id',
|
||||
'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": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TimelineAssetDescriptionDto"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"duration": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@ -13976,6 +13983,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"description",
|
||||
"duration",
|
||||
"id",
|
||||
"isArchived",
|
||||
@ -14023,6 +14031,23 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TimelineAssetDescriptionDto": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"city",
|
||||
"country"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TimelineStackResponseDto": {
|
||||
"properties": {
|
||||
"assetCount": {
|
||||
|
@ -1407,12 +1407,17 @@ export type TagBulkAssetsResponseDto = {
|
||||
export type TagUpdateDto = {
|
||||
color?: string | null;
|
||||
};
|
||||
export type TimelineAssetDescriptionDto = {
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
};
|
||||
export type TimelineStackResponseDto = {
|
||||
assetCount: number;
|
||||
id: string;
|
||||
primaryAssetId: string;
|
||||
};
|
||||
export type TimeBucketAssetResponseDto = {
|
||||
description: TimelineAssetDescriptionDto[];
|
||||
duration: (string | number)[];
|
||||
id: string[];
|
||||
isArchived: number[];
|
||||
|
@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { IsEnum, IsInt, IsString, Min } from 'class-validator';
|
||||
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';
|
||||
|
||||
export class TimeBucketDto {
|
||||
@ -64,6 +64,13 @@ export class TimelineStackResponseDto implements TimelineStack {
|
||||
assetCount!: number;
|
||||
}
|
||||
|
||||
export class TimelineAssetDescriptionDto implements AssetDescription {
|
||||
@ApiProperty()
|
||||
city!: string | null;
|
||||
@ApiProperty()
|
||||
country!: string | null;
|
||||
}
|
||||
|
||||
export class TimeBucketAssetResponseDto implements TimeBucketAssets {
|
||||
@ApiProperty({ type: [String] })
|
||||
id: string[] = [];
|
||||
@ -154,6 +161,9 @@ export class TimeBucketAssetResponseDto implements TimeBucketAssets {
|
||||
},
|
||||
})
|
||||
livePhotoVideoId: (string | number)[] = [];
|
||||
|
||||
@ApiProperty()
|
||||
description: TimelineAssetDescriptionDto[] = [];
|
||||
}
|
||||
|
||||
export class TimeBucketsResponseDto {
|
||||
|
@ -701,7 +701,14 @@ export class AssetRepository {
|
||||
'livePhotoVideoId',
|
||||
])
|
||||
.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) =>
|
||||
qb
|
||||
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
|
||||
|
@ -58,6 +58,7 @@ export class TimelineService extends BaseService {
|
||||
duration: [],
|
||||
projectionType: [],
|
||||
livePhotoVideoId: [],
|
||||
description: [],
|
||||
};
|
||||
for (const item of items) {
|
||||
let width = item.width!;
|
||||
@ -82,6 +83,10 @@ export class TimelineService extends BaseService {
|
||||
bucketAssets.livePhotoVideoId.push(item.livePhotoVideoId || 0);
|
||||
bucketAssets.isImage.push(item.type === AssetType.IMAGE ? 1 : 0);
|
||||
bucketAssets.isVideo.push(item.type === AssetType.VIDEO ? 1 : 0);
|
||||
bucketAssets.description.push({
|
||||
city: item.city,
|
||||
country: item.country,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -4,6 +4,11 @@ export type TimelineStack = {
|
||||
assetCount: number;
|
||||
};
|
||||
|
||||
export type AssetDescription = {
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
};
|
||||
|
||||
export type TimeBucketAssets = {
|
||||
id: string[];
|
||||
ownerId: string[];
|
||||
@ -19,4 +24,5 @@ export type TimeBucketAssets = {
|
||||
duration: (string | number)[];
|
||||
projectionType: (string | number)[];
|
||||
livePhotoVideoId: (string | number)[];
|
||||
description: AssetDescription[];
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { sdkMock } from '$lib/__mocks__/sdk.mock';
|
||||
import { AbortError } from '$lib/utils';
|
||||
import { TimeBucketSize, type AssetResponseDto } from '@immich/sdk';
|
||||
import { assetFactory, timelineAssetFactory } from '@test-data/factories/asset-factory';
|
||||
import { AssetStore } from './assets-store.svelte';
|
||||
import { type AssetResponseDto, type TimeBucketResponseDto } from '@immich/sdk';
|
||||
import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory';
|
||||
import { AssetStore, type TimelineAsset } from './assets-store.svelte';
|
||||
|
||||
describe('AssetStore', () => {
|
||||
beforeEach(() => {
|
||||
@ -11,18 +11,22 @@ describe('AssetStore', () => {
|
||||
|
||||
describe('init', () => {
|
||||
let assetStore: AssetStore;
|
||||
const bucketAssets: Record<string, AssetResponseDto[]> = {
|
||||
'2024-03-01T00:00:00.000Z': assetFactory
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-03-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(1)
|
||||
.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)
|
||||
.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)
|
||||
.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 () => {
|
||||
assetStore = new AssetStore();
|
||||
sdkMock.getTimeBuckets.mockResolvedValue([
|
||||
@ -30,13 +34,14 @@ describe('AssetStore', () => {
|
||||
{ count: 100, timeBucket: '2024-02-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 });
|
||||
});
|
||||
|
||||
it('should load buckets in viewport', () => {
|
||||
expect(sdkMock.getTimeBuckets).toBeCalledTimes(1);
|
||||
expect(sdkMock.getTimeBuckets).toBeCalledWith({ size: TimeBucketSize.Month });
|
||||
|
||||
expect(sdkMock.getTimeBucket).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@ -48,29 +53,31 @@ describe('AssetStore', () => {
|
||||
|
||||
expect(plainBuckets).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 304 }),
|
||||
expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 4515.333_333_333_333 }),
|
||||
expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 186.5 }),
|
||||
expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 12_017 }),
|
||||
expect.objectContaining({ bucketDate: '2024-01-01T00:00:00.000Z', bucketHeight: 286 }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('calculates timeline height', () => {
|
||||
expect(assetStore.timelineHeight).toBe(5105.333_333_333_333);
|
||||
expect(assetStore.timelineHeight).toBe(12_489.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadBucket', () => {
|
||||
let assetStore: AssetStore;
|
||||
const bucketAssets: Record<string, AssetResponseDto[]> = {
|
||||
'2024-01-03T00:00:00.000Z': assetFactory
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-01-03T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(1)
|
||||
.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)
|
||||
.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 () => {
|
||||
assetStore = new AssetStore();
|
||||
sdkMock.getTimeBuckets.mockResolvedValue([
|
||||
@ -82,7 +89,7 @@ describe('AssetStore', () => {
|
||||
if (signal?.aborted) {
|
||||
throw new AbortError();
|
||||
}
|
||||
return bucketAssets[timeBucket];
|
||||
return bucketAssetsResponse[timeBucket];
|
||||
});
|
||||
await assetStore.updateViewport({ width: 1588, height: 0 });
|
||||
});
|
||||
@ -296,7 +303,9 @@ describe('AssetStore', () => {
|
||||
});
|
||||
|
||||
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.removeAssets([assetOne.id]);
|
||||
|
||||
@ -342,17 +351,20 @@ describe('AssetStore', () => {
|
||||
|
||||
describe('getPreviousAsset', () => {
|
||||
let assetStore: AssetStore;
|
||||
const bucketAssets: Record<string, AssetResponseDto[]> = {
|
||||
'2024-03-01T00:00:00.000Z': assetFactory
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-03-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(1)
|
||||
.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)
|
||||
.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)
|
||||
.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 () => {
|
||||
assetStore = new AssetStore();
|
||||
@ -361,8 +373,7 @@ describe('AssetStore', () => {
|
||||
{ count: 6, timeBucket: '2024-02-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 });
|
||||
});
|
||||
|
||||
|
@ -14,9 +14,8 @@ import {
|
||||
getAssetInfo,
|
||||
getTimeBucket,
|
||||
getTimeBuckets,
|
||||
TimeBucketSize,
|
||||
type AssetResponseDto,
|
||||
type AssetStackResponseDto,
|
||||
type TimeBucketResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { clamp, debounce, isEqual, throttle } from 'lodash-es';
|
||||
import { DateTime } from 'luxon';
|
||||
@ -84,7 +83,7 @@ export type TimelineAsset = {
|
||||
duration: string | null;
|
||||
projectionType: string | null;
|
||||
livePhotoVideoId: string | null;
|
||||
text: {
|
||||
description: {
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
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
|
||||
addAssets(bucketResponse: AssetResponseDto[]) {
|
||||
addAssets(bucketResponse: TimeBucketResponseDto) {
|
||||
const addContext = new AddContext();
|
||||
for (const asset of bucketResponse) {
|
||||
const timelineAsset = toTimelineAsset(asset);
|
||||
for (let i = 0; i < bucketResponse.bucketAssets.id.length; i++) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -878,7 +900,6 @@ export class AssetStore {
|
||||
async #initialiazeTimeBuckets() {
|
||||
const timebuckets = await getTimeBuckets({
|
||||
...this.#options,
|
||||
size: TimeBucketSize.Month,
|
||||
key: authManager.key,
|
||||
});
|
||||
|
||||
@ -1086,7 +1107,7 @@ export class AssetStore {
|
||||
{
|
||||
...this.#options,
|
||||
timeBucket: bucketDate,
|
||||
size: TimeBucketSize.Month,
|
||||
|
||||
key: authManager.key,
|
||||
},
|
||||
{ signal },
|
||||
@ -1097,12 +1118,11 @@ export class AssetStore {
|
||||
{
|
||||
albumId: this.#options.timelineAlbumId,
|
||||
timeBucket: bucketDate,
|
||||
size: TimeBucketSize.Month,
|
||||
key: authManager.key,
|
||||
},
|
||||
{ signal },
|
||||
);
|
||||
for (const { id } of albumAssets) {
|
||||
for (const id of albumAssets.bucketAssets.id) {
|
||||
this.albumAssets.add(id);
|
||||
}
|
||||
}
|
||||
|
@ -38,15 +38,10 @@ export function getThumbnailSize(assetCount: number, viewWidth: number): number
|
||||
return 300;
|
||||
}
|
||||
|
||||
export const getAltTextForTimelineAsset = (_: TimelineAsset) => {
|
||||
// TODO: implement this in a performant way
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getAltText = derived(t, ($t) => {
|
||||
return (asset: TimelineAsset) => {
|
||||
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 peopleCount = names.length;
|
||||
|
@ -71,7 +71,8 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
||||
const city = assetResponse.exifInfo?.city;
|
||||
const country = assetResponse.exifInfo?.country;
|
||||
const people = assetResponse.people?.map((person) => person.name) || [];
|
||||
const text = {
|
||||
|
||||
const description = {
|
||||
city: city || null,
|
||||
country: country || null,
|
||||
people,
|
||||
@ -91,7 +92,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
||||
duration: assetResponse.duration || null,
|
||||
projectionType: assetResponse.exifInfo?.projectionType || null,
|
||||
livePhotoVideoId: assetResponse.livePhotoVideoId || null,
|
||||
text,
|
||||
description,
|
||||
};
|
||||
};
|
||||
export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset =>
|
||||
|
@ -1,6 +1,12 @@
|
||||
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||
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';
|
||||
|
||||
export const assetFactory = Sync.makeFactory<AssetResponseDto>({
|
||||
@ -42,9 +48,51 @@ export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({
|
||||
stack: null,
|
||||
projectionType: null,
|
||||
livePhotoVideoId: Sync.each(() => faker.string.uuid()),
|
||||
text: Sync.each(() => ({
|
||||
description: Sync.each(() => ({
|
||||
city: faker.location.city(),
|
||||
country: faker.location.country(),
|
||||
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