mirror of
https://github.com/immich-app/immich.git
synced 2025-11-02 15:17:43 +09:00
feat: tags (#11980)
* feat: tags * fix: folder tree icons * navigate to tag from detail panel * delete tag * Tag position and add tag button * Tag asset in detail panel * refactor form * feat: navigate to tag page from clicking on a tag * feat: delete tags from the tag page * refactor: moving tag section in detail panel and add + tag button * feat: tag asset action in detail panel * refactor add tag form * fdisable add tag button when there is no selection * feat: tag bulk endpoint * feat: tag colors * chore: clean up * chore: unit tests * feat: write tags to sidecar * Remove tag and auto focus on tag creation form opened * chore: regenerate migration * chore: linting * add color picker to tag edit form * fix: force render tags timeline on navigating back from asset viewer * feat: read tags from keywords * chore: clean up --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
208
mobile/openapi/lib/api/tags_api.dart
generated
208
mobile/openapi/lib/api/tags_api.dart
generated
@@ -16,16 +16,63 @@ class TagsApi {
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'PUT /tags/assets' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [TagBulkAssetsDto] tagBulkAssetsDto (required):
|
||||
Future<Response> bulkTagAssetsWithHttpInfo(TagBulkAssetsDto tagBulkAssetsDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags/assets';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = tagBulkAssetsDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [TagBulkAssetsDto] tagBulkAssetsDto (required):
|
||||
Future<TagBulkAssetsResponseDto?> bulkTagAssets(TagBulkAssetsDto tagBulkAssetsDto,) async {
|
||||
final response = await bulkTagAssetsWithHttpInfo(tagBulkAssetsDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TagBulkAssetsResponseDto',) as TagBulkAssetsResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /tags' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [CreateTagDto] createTagDto (required):
|
||||
Future<Response> createTagWithHttpInfo(CreateTagDto createTagDto,) async {
|
||||
/// * [TagCreateDto] tagCreateDto (required):
|
||||
Future<Response> createTagWithHttpInfo(TagCreateDto tagCreateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = createTagDto;
|
||||
Object? postBody = tagCreateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
@@ -47,9 +94,9 @@ class TagsApi {
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [CreateTagDto] createTagDto (required):
|
||||
Future<TagResponseDto?> createTag(CreateTagDto createTagDto,) async {
|
||||
final response = await createTagWithHttpInfo(createTagDto,);
|
||||
/// * [TagCreateDto] tagCreateDto (required):
|
||||
Future<TagResponseDto?> createTag(TagCreateDto tagCreateDto,) async {
|
||||
final response = await createTagWithHttpInfo(tagCreateDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -147,57 +194,6 @@ class TagsApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /tags/{id}/assets' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> getTagAssetsWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags/{id}/assets'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<List<AssetResponseDto>?> getTagAssets(String id,) async {
|
||||
final response = await getTagAssetsWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
|
||||
.cast<AssetResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /tags/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
@@ -251,14 +247,14 @@ class TagsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetIdsDto] assetIdsDto (required):
|
||||
Future<Response> tagAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async {
|
||||
/// * [BulkIdsDto] bulkIdsDto (required):
|
||||
Future<Response> tagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags/{id}/assets'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetIdsDto;
|
||||
Object? postBody = bulkIdsDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
@@ -282,9 +278,9 @@ class TagsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetIdsDto] assetIdsDto (required):
|
||||
Future<List<AssetIdsResponseDto>?> tagAssets(String id, AssetIdsDto assetIdsDto,) async {
|
||||
final response = await tagAssetsWithHttpInfo(id, assetIdsDto,);
|
||||
/// * [BulkIdsDto] bulkIdsDto (required):
|
||||
Future<List<BulkIdResponseDto>?> tagAssets(String id, BulkIdsDto bulkIdsDto,) async {
|
||||
final response = await tagAssetsWithHttpInfo(id, bulkIdsDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -293,8 +289,8 @@ class TagsApi {
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetIdsResponseDto>') as List)
|
||||
.cast<AssetIdsResponseDto>()
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<BulkIdResponseDto>') as List)
|
||||
.cast<BulkIdResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
@@ -306,14 +302,14 @@ class TagsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetIdsDto] assetIdsDto (required):
|
||||
Future<Response> untagAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async {
|
||||
/// * [BulkIdsDto] bulkIdsDto (required):
|
||||
Future<Response> untagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags/{id}/assets'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = assetIdsDto;
|
||||
Object? postBody = bulkIdsDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
@@ -337,9 +333,9 @@ class TagsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [AssetIdsDto] assetIdsDto (required):
|
||||
Future<List<AssetIdsResponseDto>?> untagAssets(String id, AssetIdsDto assetIdsDto,) async {
|
||||
final response = await untagAssetsWithHttpInfo(id, assetIdsDto,);
|
||||
/// * [BulkIdsDto] bulkIdsDto (required):
|
||||
Future<List<BulkIdResponseDto>?> untagAssets(String id, BulkIdsDto bulkIdsDto,) async {
|
||||
final response = await untagAssetsWithHttpInfo(id, bulkIdsDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -348,27 +344,27 @@ class TagsApi {
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetIdsResponseDto>') as List)
|
||||
.cast<AssetIdsResponseDto>()
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<BulkIdResponseDto>') as List)
|
||||
.cast<BulkIdResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'PATCH /tags/{id}' operation and returns the [Response].
|
||||
/// Performs an HTTP 'PUT /tags/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateTagDto] updateTagDto (required):
|
||||
Future<Response> updateTagWithHttpInfo(String id, UpdateTagDto updateTagDto,) async {
|
||||
/// * [TagUpdateDto] tagUpdateDto (required):
|
||||
Future<Response> updateTagWithHttpInfo(String id, TagUpdateDto tagUpdateDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = updateTagDto;
|
||||
Object? postBody = tagUpdateDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
@@ -379,7 +375,7 @@ class TagsApi {
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'PATCH',
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
@@ -392,9 +388,9 @@ class TagsApi {
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateTagDto] updateTagDto (required):
|
||||
Future<TagResponseDto?> updateTag(String id, UpdateTagDto updateTagDto,) async {
|
||||
final response = await updateTagWithHttpInfo(id, updateTagDto,);
|
||||
/// * [TagUpdateDto] tagUpdateDto (required):
|
||||
Future<TagResponseDto?> updateTag(String id, TagUpdateDto tagUpdateDto,) async {
|
||||
final response = await updateTagWithHttpInfo(id, tagUpdateDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -407,4 +403,54 @@ class TagsApi {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'PUT /tags' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [TagUpsertDto] tagUpsertDto (required):
|
||||
Future<Response> upsertTagsWithHttpInfo(TagUpsertDto tagUpsertDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/tags';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = tagUpsertDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [TagUpsertDto] tagUpsertDto (required):
|
||||
Future<List<TagResponseDto>?> upsertTags(TagUpsertDto tagUpsertDto,) async {
|
||||
final response = await upsertTagsWithHttpInfo(tagUpsertDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<TagResponseDto>') as List)
|
||||
.cast<TagResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
26
mobile/openapi/lib/api/timeline_api.dart
generated
26
mobile/openapi/lib/api/timeline_api.dart
generated
@@ -37,12 +37,14 @@ class TimelineApi {
|
||||
///
|
||||
/// * [String] personId:
|
||||
///
|
||||
/// * [String] tagId:
|
||||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/timeline/bucket';
|
||||
|
||||
@@ -75,6 +77,9 @@ class TimelineApi {
|
||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||
}
|
||||
queryParams.addAll(_queryParams('', 'size', size));
|
||||
if (tagId != null) {
|
||||
queryParams.addAll(_queryParams('', 'tagId', tagId));
|
||||
}
|
||||
queryParams.addAll(_queryParams('', 'timeBucket', timeBucket));
|
||||
if (userId != null) {
|
||||
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||
@@ -120,13 +125,15 @@ class TimelineApi {
|
||||
///
|
||||
/// * [String] personId:
|
||||
///
|
||||
/// * [String] tagId:
|
||||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
@@ -162,12 +169,14 @@ class TimelineApi {
|
||||
///
|
||||
/// * [String] personId:
|
||||
///
|
||||
/// * [String] tagId:
|
||||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/timeline/buckets';
|
||||
|
||||
@@ -200,6 +209,9 @@ class TimelineApi {
|
||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||
}
|
||||
queryParams.addAll(_queryParams('', 'size', size));
|
||||
if (tagId != null) {
|
||||
queryParams.addAll(_queryParams('', 'tagId', tagId));
|
||||
}
|
||||
if (userId != null) {
|
||||
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||
}
|
||||
@@ -242,13 +254,15 @@ class TimelineApi {
|
||||
///
|
||||
/// * [String] personId:
|
||||
///
|
||||
/// * [String] tagId:
|
||||
///
|
||||
/// * [String] userId:
|
||||
///
|
||||
/// * [bool] withPartners:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user