mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
Merge a0ffa6f90c into 72f5ca4420
This commit is contained in:
commit
783bf4d2b7
16 changed files with 360 additions and 24 deletions
1
mobile/openapi/README.md
generated
1
mobile/openapi/README.md
generated
|
|
@ -340,6 +340,7 @@ Class | Method | HTTP request | Description
|
||||||
- [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md)
|
- [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md)
|
||||||
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
||||||
- [AssetOrder](doc//AssetOrder.md)
|
- [AssetOrder](doc//AssetOrder.md)
|
||||||
|
- [AssetOrderBy](doc//AssetOrderBy.md)
|
||||||
- [AssetResponseDto](doc//AssetResponseDto.md)
|
- [AssetResponseDto](doc//AssetResponseDto.md)
|
||||||
- [AssetStackResponseDto](doc//AssetStackResponseDto.md)
|
- [AssetStackResponseDto](doc//AssetStackResponseDto.md)
|
||||||
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
|
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
|
||||||
|
|
|
||||||
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
|
|
@ -111,6 +111,7 @@ part 'model/asset_metadata_response_dto.dart';
|
||||||
part 'model/asset_metadata_upsert_dto.dart';
|
part 'model/asset_metadata_upsert_dto.dart';
|
||||||
part 'model/asset_metadata_upsert_item_dto.dart';
|
part 'model/asset_metadata_upsert_item_dto.dart';
|
||||||
part 'model/asset_order.dart';
|
part 'model/asset_order.dart';
|
||||||
|
part 'model/asset_order_by.dart';
|
||||||
part 'model/asset_response_dto.dart';
|
part 'model/asset_response_dto.dart';
|
||||||
part 'model/asset_stack_response_dto.dart';
|
part 'model/asset_stack_response_dto.dart';
|
||||||
part 'model/asset_stats_response_dto.dart';
|
part 'model/asset_stats_response_dto.dart';
|
||||||
|
|
|
||||||
30
mobile/openapi/lib/api/timeline_api.dart
generated
30
mobile/openapi/lib/api/timeline_api.dart
generated
|
|
@ -39,6 +39,9 @@ class TimelineApi {
|
||||||
/// * [AssetOrder] order:
|
/// * [AssetOrder] order:
|
||||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||||
///
|
///
|
||||||
|
/// * [AssetOrderBy] orderBy:
|
||||||
|
/// The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)
|
||||||
|
///
|
||||||
/// * [String] personId:
|
/// * [String] personId:
|
||||||
/// Filter assets containing a specific person (face recognition)
|
/// Filter assets containing a specific person (face recognition)
|
||||||
///
|
///
|
||||||
|
|
@ -61,7 +64,7 @@ class TimelineApi {
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/timeline/bucket';
|
final apiPath = r'/timeline/bucket';
|
||||||
|
|
||||||
|
|
@ -87,6 +90,9 @@ class TimelineApi {
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
queryParams.addAll(_queryParams('', 'order', order));
|
queryParams.addAll(_queryParams('', 'order', order));
|
||||||
}
|
}
|
||||||
|
if (orderBy != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'orderBy', orderBy));
|
||||||
|
}
|
||||||
if (personId != null) {
|
if (personId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +154,9 @@ class TimelineApi {
|
||||||
/// * [AssetOrder] order:
|
/// * [AssetOrder] order:
|
||||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||||
///
|
///
|
||||||
|
/// * [AssetOrderBy] orderBy:
|
||||||
|
/// The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)
|
||||||
|
///
|
||||||
/// * [String] personId:
|
/// * [String] personId:
|
||||||
/// Filter assets containing a specific person (face recognition)
|
/// Filter assets containing a specific person (face recognition)
|
||||||
///
|
///
|
||||||
|
|
@ -170,8 +179,8 @@ class TimelineApi {
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, orderBy: orderBy, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
@ -205,6 +214,9 @@ class TimelineApi {
|
||||||
/// * [AssetOrder] order:
|
/// * [AssetOrder] order:
|
||||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||||
///
|
///
|
||||||
|
/// * [AssetOrderBy] orderBy:
|
||||||
|
/// The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)
|
||||||
|
///
|
||||||
/// * [String] personId:
|
/// * [String] personId:
|
||||||
/// Filter assets containing a specific person (face recognition)
|
/// Filter assets containing a specific person (face recognition)
|
||||||
///
|
///
|
||||||
|
|
@ -227,7 +239,7 @@ class TimelineApi {
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/timeline/buckets';
|
final apiPath = r'/timeline/buckets';
|
||||||
|
|
||||||
|
|
@ -253,6 +265,9 @@ class TimelineApi {
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
queryParams.addAll(_queryParams('', 'order', order));
|
queryParams.addAll(_queryParams('', 'order', order));
|
||||||
}
|
}
|
||||||
|
if (orderBy != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'orderBy', orderBy));
|
||||||
|
}
|
||||||
if (personId != null) {
|
if (personId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||||
}
|
}
|
||||||
|
|
@ -310,6 +325,9 @@ class TimelineApi {
|
||||||
/// * [AssetOrder] order:
|
/// * [AssetOrder] order:
|
||||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||||
///
|
///
|
||||||
|
/// * [AssetOrderBy] orderBy:
|
||||||
|
/// The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)
|
||||||
|
///
|
||||||
/// * [String] personId:
|
/// * [String] personId:
|
||||||
/// Filter assets containing a specific person (face recognition)
|
/// Filter assets containing a specific person (face recognition)
|
||||||
///
|
///
|
||||||
|
|
@ -332,8 +350,8 @@ class TimelineApi {
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||||
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||||
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, orderBy: orderBy, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
|
|
@ -276,6 +276,8 @@ class ApiClient {
|
||||||
return AssetMetadataUpsertItemDto.fromJson(value);
|
return AssetMetadataUpsertItemDto.fromJson(value);
|
||||||
case 'AssetOrder':
|
case 'AssetOrder':
|
||||||
return AssetOrderTypeTransformer().decode(value);
|
return AssetOrderTypeTransformer().decode(value);
|
||||||
|
case 'AssetOrderBy':
|
||||||
|
return AssetOrderByTypeTransformer().decode(value);
|
||||||
case 'AssetResponseDto':
|
case 'AssetResponseDto':
|
||||||
return AssetResponseDto.fromJson(value);
|
return AssetResponseDto.fromJson(value);
|
||||||
case 'AssetStackResponseDto':
|
case 'AssetStackResponseDto':
|
||||||
|
|
|
||||||
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
|
|
@ -73,6 +73,9 @@ String parameterToString(dynamic value) {
|
||||||
if (value is AssetOrder) {
|
if (value is AssetOrder) {
|
||||||
return AssetOrderTypeTransformer().encode(value).toString();
|
return AssetOrderTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is AssetOrderBy) {
|
||||||
|
return AssetOrderByTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is AssetTypeEnum) {
|
if (value is AssetTypeEnum) {
|
||||||
return AssetTypeEnumTypeTransformer().encode(value).toString();
|
return AssetTypeEnumTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
88
mobile/openapi/lib/model/asset_order_by.dart
generated
Normal file
88
mobile/openapi/lib/model/asset_order_by.dart
generated
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
//
|
||||||
|
// 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 AssetOrderBy {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const AssetOrderBy._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const ADDED = AssetOrderBy._(r'DATE_ADDED');
|
||||||
|
static const DELETED = AssetOrderBy._(r'DATE_DELETED');
|
||||||
|
static const TAKEN = AssetOrderBy._(r'DATE_TAKEN');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][AssetOrderBy].
|
||||||
|
static const values = <AssetOrderBy>[
|
||||||
|
ADDED,
|
||||||
|
DELETED,
|
||||||
|
TAKEN,
|
||||||
|
];
|
||||||
|
|
||||||
|
static AssetOrderBy? fromJson(dynamic value) => AssetOrderByTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<AssetOrderBy> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetOrderBy>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetOrderBy.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [AssetOrderBy] to String,
|
||||||
|
/// and [decode] dynamic data back to [AssetOrderBy].
|
||||||
|
class AssetOrderByTypeTransformer {
|
||||||
|
factory AssetOrderByTypeTransformer() => _instance ??= const AssetOrderByTypeTransformer._();
|
||||||
|
|
||||||
|
const AssetOrderByTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(AssetOrderBy data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a AssetOrderBy.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
AssetOrderBy? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case r'DATE_ADDED': return AssetOrderBy.ADDED;
|
||||||
|
case r'DATE_DELETED': return AssetOrderBy.DELETED;
|
||||||
|
case r'DATE_TAKEN': return AssetOrderBy.TAKEN;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [AssetOrderByTypeTransformer] instance.
|
||||||
|
static AssetOrderByTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -8841,6 +8841,15 @@
|
||||||
"$ref": "#/components/schemas/AssetOrder"
|
"$ref": "#/components/schemas/AssetOrder"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "orderBy",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AssetOrderBy"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "personId",
|
"name": "personId",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
|
@ -9005,6 +9014,15 @@
|
||||||
"$ref": "#/components/schemas/AssetOrder"
|
"$ref": "#/components/schemas/AssetOrder"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "orderBy",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/AssetOrderBy"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "personId",
|
"name": "personId",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
|
@ -11056,6 +11074,14 @@
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"AssetOrderBy": {
|
||||||
|
"enum": [
|
||||||
|
"DATE_ADDED",
|
||||||
|
"DATE_DELETED",
|
||||||
|
"DATE_TAKEN"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"AssetResponseDto": {
|
"AssetResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"checksum": {
|
"checksum": {
|
||||||
|
|
|
||||||
|
|
@ -4306,12 +4306,13 @@ export function tagAssets({ id, bulkIdsDto }: {
|
||||||
/**
|
/**
|
||||||
* This endpoint requires the `asset.read` permission.
|
* This endpoint requires the `asset.read` permission.
|
||||||
*/
|
*/
|
||||||
export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, orderBy, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
isTrashed?: boolean;
|
isTrashed?: boolean;
|
||||||
key?: string;
|
key?: string;
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
|
orderBy?: AssetOrderBy;
|
||||||
personId?: string;
|
personId?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
tagId?: string;
|
tagId?: string;
|
||||||
|
|
@ -4331,6 +4332,7 @@ export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, pers
|
||||||
isTrashed,
|
isTrashed,
|
||||||
key,
|
key,
|
||||||
order,
|
order,
|
||||||
|
orderBy,
|
||||||
personId,
|
personId,
|
||||||
slug,
|
slug,
|
||||||
tagId,
|
tagId,
|
||||||
|
|
@ -4347,12 +4349,13 @@ export function getTimeBucket({ albumId, isFavorite, isTrashed, key, order, pers
|
||||||
/**
|
/**
|
||||||
* This endpoint requires the `asset.read` permission.
|
* This endpoint requires the `asset.read` permission.
|
||||||
*/
|
*/
|
||||||
export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, orderBy, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
isTrashed?: boolean;
|
isTrashed?: boolean;
|
||||||
key?: string;
|
key?: string;
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
|
orderBy?: AssetOrderBy;
|
||||||
personId?: string;
|
personId?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
tagId?: string;
|
tagId?: string;
|
||||||
|
|
@ -4371,6 +4374,7 @@ export function getTimeBuckets({ albumId, isFavorite, isTrashed, key, order, per
|
||||||
isTrashed,
|
isTrashed,
|
||||||
key,
|
key,
|
||||||
order,
|
order,
|
||||||
|
orderBy,
|
||||||
personId,
|
personId,
|
||||||
slug,
|
slug,
|
||||||
tagId,
|
tagId,
|
||||||
|
|
@ -5047,3 +5051,8 @@ export enum OAuthTokenEndpointAuthMethod {
|
||||||
ClientSecretPost = "client_secret_post",
|
ClientSecretPost = "client_secret_post",
|
||||||
ClientSecretBasic = "client_secret_basic"
|
ClientSecretBasic = "client_secret_basic"
|
||||||
}
|
}
|
||||||
|
export enum AssetOrderBy {
|
||||||
|
DateAdded = "DATE_ADDED",
|
||||||
|
DateDeleted = "DATE_DELETED",
|
||||||
|
DateTaken = "DATE_TAKEN"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
import { AssetOrder, AssetVisibility } from 'src/enum';
|
import { AssetOrder, AssetOrderBy, AssetVisibility } from 'src/enum';
|
||||||
import { ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
|
import { ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export class TimeBucketDto {
|
export class TimeBucketDto {
|
||||||
|
|
@ -46,6 +46,14 @@ export class TimeBucketDto {
|
||||||
})
|
})
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
|
|
||||||
|
@ValidateEnum({
|
||||||
|
enum: AssetOrderBy,
|
||||||
|
name: 'AssetOrderBy',
|
||||||
|
description: 'The field to sort time bucket assets by (DATE_TAKEN, DATE_ADDED, DATE_DELETED)',
|
||||||
|
optional: true,
|
||||||
|
})
|
||||||
|
orderBy?: AssetOrderBy;
|
||||||
|
|
||||||
@ValidateEnum({
|
@ValidateEnum({
|
||||||
enum: AssetVisibility,
|
enum: AssetVisibility,
|
||||||
name: 'AssetVisibility',
|
name: 'AssetVisibility',
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ export enum AssetOrder {
|
||||||
Desc = 'desc',
|
Desc = 'desc',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AssetOrderBy {
|
||||||
|
DateAdded = 'DATE_ADDED',
|
||||||
|
DateDeleted = 'DATE_DELETED',
|
||||||
|
DateTaken = 'DATE_TAKEN',
|
||||||
|
}
|
||||||
|
|
||||||
export enum DatabaseAction {
|
export enum DatabaseAction {
|
||||||
Create = 'CREATE',
|
Create = 'CREATE',
|
||||||
Update = 'UPDATE',
|
Update = 'UPDATE',
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,15 @@ import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { Stack } from 'src/database';
|
import { Stack } from 'src/database';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AssetFileType, AssetMetadataKey, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
import {
|
||||||
|
AssetFileType,
|
||||||
|
AssetMetadataKey,
|
||||||
|
AssetOrder,
|
||||||
|
AssetOrderBy,
|
||||||
|
AssetStatus,
|
||||||
|
AssetType,
|
||||||
|
AssetVisibility,
|
||||||
|
} from 'src/enum';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||||
|
|
@ -14,9 +22,11 @@ import { AssetMetadataItem } from 'src/types';
|
||||||
import {
|
import {
|
||||||
anyUuid,
|
anyUuid,
|
||||||
asUuid,
|
asUuid,
|
||||||
|
getTimeBucket,
|
||||||
|
getTimeBucketOffset,
|
||||||
|
getTimeBucketRef,
|
||||||
hasPeople,
|
hasPeople,
|
||||||
removeUndefinedKeys,
|
removeUndefinedKeys,
|
||||||
truncatedDate,
|
|
||||||
unnest,
|
unnest,
|
||||||
withDefaultVisibility,
|
withDefaultVisibility,
|
||||||
withExif,
|
withExif,
|
||||||
|
|
@ -65,6 +75,7 @@ interface AssetBuilderOptions {
|
||||||
|
|
||||||
export interface TimeBucketOptions extends AssetBuilderOptions {
|
export interface TimeBucketOptions extends AssetBuilderOptions {
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
|
orderBy?: AssetOrderBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeBucketItem {
|
export interface TimeBucketItem {
|
||||||
|
|
@ -550,11 +561,13 @@ export class AssetRepository {
|
||||||
|
|
||||||
@GenerateSql({ params: [{}] })
|
@GenerateSql({ params: [{}] })
|
||||||
async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
|
async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
|
||||||
|
const orderBy = options.orderBy ?? AssetOrderBy.DateTaken;
|
||||||
|
|
||||||
return this.db
|
return this.db
|
||||||
.with('asset', (qb) =>
|
.with('asset', (qb) =>
|
||||||
qb
|
qb
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select(truncatedDate<Date>().as('timeBucket'))
|
.select((eb) => getTimeBucket<Date>(eb, orderBy).as('timeBucket'))
|
||||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||||
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.$if(options.visibility === undefined, withDefaultVisibility)
|
.$if(options.visibility === undefined, withDefaultVisibility)
|
||||||
|
|
@ -581,7 +594,7 @@ export class AssetRepository {
|
||||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
|
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
|
||||||
)
|
)
|
||||||
.selectFrom('asset')
|
.selectFrom('asset')
|
||||||
.select(sql<string>`("timeBucket" AT TIME ZONE 'UTC')::date::text`.as('timeBucket'))
|
.select('timeBucket')
|
||||||
.select((eb) => eb.fn.countAll<number>().as('count'))
|
.select((eb) => eb.fn.countAll<number>().as('count'))
|
||||||
.groupBy('timeBucket')
|
.groupBy('timeBucket')
|
||||||
.orderBy('timeBucket', options.order ?? 'desc')
|
.orderBy('timeBucket', options.order ?? 'desc')
|
||||||
|
|
@ -592,6 +605,8 @@ export class AssetRepository {
|
||||||
params: [DummyValue.TIME_BUCKET, { withStacked: true }],
|
params: [DummyValue.TIME_BUCKET, { withStacked: true }],
|
||||||
})
|
})
|
||||||
getTimeBucket(timeBucket: string, options: TimeBucketOptions) {
|
getTimeBucket(timeBucket: string, options: TimeBucketOptions) {
|
||||||
|
const orderBy = options.orderBy ?? AssetOrderBy.DateTaken;
|
||||||
|
|
||||||
const query = this.db
|
const query = this.db
|
||||||
.with('cte', (qb) =>
|
.with('cte', (qb) =>
|
||||||
qb
|
qb
|
||||||
|
|
@ -605,12 +620,10 @@ export class AssetRepository {
|
||||||
sql`asset.type = 'IMAGE'`.as('isImage'),
|
sql`asset.type = 'IMAGE'`.as('isImage'),
|
||||||
sql`asset."deletedAt" is not null`.as('isTrashed'),
|
sql`asset."deletedAt" is not null`.as('isTrashed'),
|
||||||
'asset.livePhotoVideoId',
|
'asset.livePhotoVideoId',
|
||||||
sql`extract(epoch from (asset."localDateTime" AT TIME ZONE 'UTC' - asset."fileCreatedAt" at time zone 'UTC'))::real / 3600`.as(
|
getTimeBucketOffset(eb, orderBy).as('localOffsetHours'),
|
||||||
'localOffsetHours',
|
|
||||||
),
|
|
||||||
'asset.ownerId',
|
'asset.ownerId',
|
||||||
'asset.status',
|
'asset.status',
|
||||||
sql`asset."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'),
|
sql`${getTimeBucketRef(eb, orderBy)} at time zone 'utc'`.as('fileCreatedAt'),
|
||||||
eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'),
|
eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'),
|
||||||
'asset_exif.city',
|
'asset_exif.city',
|
||||||
'asset_exif.country',
|
'asset_exif.country',
|
||||||
|
|
@ -633,7 +646,7 @@ export class AssetRepository {
|
||||||
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.$if(options.visibility == undefined, withDefaultVisibility)
|
.$if(options.visibility == undefined, withDefaultVisibility)
|
||||||
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
|
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
|
||||||
.where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, ''))
|
.where((eb) => eb(getTimeBucket<string>(eb, orderBy), '=', timeBucket.replace(/^[+-]/, '')))
|
||||||
.$if(!!options.albumId, (qb) =>
|
.$if(!!options.albumId, (qb) =>
|
||||||
qb.where((eb) =>
|
qb.where((eb) =>
|
||||||
eb.exists(
|
eb.exists(
|
||||||
|
|
@ -679,7 +692,7 @@ export class AssetRepository {
|
||||||
)
|
)
|
||||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
||||||
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
|
.orderBy((eb) => getTimeBucketRef(eb, orderBy), options.order ?? 'desc'),
|
||||||
)
|
)
|
||||||
.with('agg', (qb) =>
|
.with('agg', (qb) =>
|
||||||
qb
|
qb
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||||
import { parse } from 'pg-connection-string';
|
import { parse } from 'pg-connection-string';
|
||||||
import postgres, { Notice, PostgresError } from 'postgres';
|
import postgres, { Notice, PostgresError } from 'postgres';
|
||||||
import { columns, Exif, Person } from 'src/database';
|
import { columns, Exif, Person } from 'src/database';
|
||||||
import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum';
|
import { AssetFileType, AssetOrderBy, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum';
|
||||||
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
|
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
|
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
|
||||||
|
|
@ -282,8 +282,30 @@ export function withTags(eb: ExpressionBuilder<DB, 'asset'>) {
|
||||||
).as('tags');
|
).as('tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function truncatedDate<O>() {
|
export function getTimeBucketRef(eb: ExpressionBuilder<DB, 'asset'>, orderBy: AssetOrderBy) {
|
||||||
return sql<O>`date_trunc(${sql.lit('MONTH')}, "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
|
if (orderBy === AssetOrderBy.DateAdded) {
|
||||||
|
return eb.ref('asset.createdAt');
|
||||||
|
} else if (orderBy === AssetOrderBy.DateDeleted) {
|
||||||
|
return eb.ref('asset.deletedAt');
|
||||||
|
}
|
||||||
|
|
||||||
|
return eb.ref('asset.localDateTime');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimeBucketOffset(eb: ExpressionBuilder<DB, 'asset'>, orderBy: AssetOrderBy) {
|
||||||
|
if (orderBy === AssetOrderBy.DateTaken) {
|
||||||
|
return sql`extract(epoch from (asset."localDateTime" AT TIME ZONE 'UTC' - asset."fileCreatedAt" at time zone 'UTC'))::real / 3600`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.lit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimeBucket<O>(
|
||||||
|
eb: ExpressionBuilder<DB, 'asset'>,
|
||||||
|
orderBy: AssetOrderBy,
|
||||||
|
timezone: Expression<string> = sql.lit('UTC'),
|
||||||
|
) {
|
||||||
|
return sql<O>`date_trunc(${sql.lit('MONTH')}, ${getTimeBucketRef(eb, orderBy)}, ${timezone})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'asset', O>, tagId: string) {
|
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'asset', O>, tagId: string) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
mdiFolderOutline,
|
mdiFolderOutline,
|
||||||
mdiHeart,
|
mdiHeart,
|
||||||
mdiHeartOutline,
|
mdiHeartOutline,
|
||||||
|
mdiClock,
|
||||||
|
mdiClockOutline,
|
||||||
mdiImageAlbum,
|
mdiImageAlbum,
|
||||||
mdiImageMultiple,
|
mdiImageMultiple,
|
||||||
mdiImageMultipleOutline,
|
mdiImageMultipleOutline,
|
||||||
|
|
@ -40,6 +42,7 @@
|
||||||
let isMapSelected: boolean = $state(false);
|
let isMapSelected: boolean = $state(false);
|
||||||
let isPeopleSelected: boolean = $state(false);
|
let isPeopleSelected: boolean = $state(false);
|
||||||
let isPhotosSelected: boolean = $state(false);
|
let isPhotosSelected: boolean = $state(false);
|
||||||
|
let isRecentlyAddedSelected: boolean = $state(false);
|
||||||
let isSharingSelected: boolean = $state(false);
|
let isSharingSelected: boolean = $state(false);
|
||||||
let isTrashSelected: boolean = $state(false);
|
let isTrashSelected: boolean = $state(false);
|
||||||
let isUtilitiesSelected: boolean = $state(false);
|
let isUtilitiesSelected: boolean = $state(false);
|
||||||
|
|
@ -118,6 +121,13 @@
|
||||||
<SideBarLink title={$t('folders')} href={resolve('/(user)/folders')} icon={mdiFolderOutline} flippedLogo />
|
<SideBarLink title={$t('folders')} href={resolve('/(user)/folders')} icon={mdiFolderOutline} flippedLogo />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<SideBarLink
|
||||||
|
title={$t('recently_added_page_title')}
|
||||||
|
href={resolve('/(user)/recently-added')}
|
||||||
|
bind:isSelected={isRecentlyAddedSelected}
|
||||||
|
icon={isRecentlyAddedSelected ? mdiClock : mdiClockOutline}
|
||||||
|
></SideBarLink>
|
||||||
|
|
||||||
<SideBarLink
|
<SideBarLink
|
||||||
title={$t('utilities')}
|
title={$t('utilities')}
|
||||||
href={resolve('/(user)/utilities')}
|
href={resolve('/(user)/utilities')}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
|
import AddToAlbum from '$lib/components/timeline/actions/AddToAlbumAction.svelte';
|
||||||
|
import ArchiveAction from '$lib/components/timeline/actions/ArchiveAction.svelte';
|
||||||
|
import AssetJobActions from '$lib/components/timeline/actions/AssetJobActions.svelte';
|
||||||
|
import ChangeDate from '$lib/components/timeline/actions/ChangeDateAction.svelte';
|
||||||
|
import ChangeDescription from '$lib/components/timeline/actions/ChangeDescriptionAction.svelte';
|
||||||
|
import ChangeLocation from '$lib/components/timeline/actions/ChangeLocationAction.svelte';
|
||||||
|
import CreateSharedLink from '$lib/components/timeline/actions/CreateSharedLinkAction.svelte';
|
||||||
|
import DeleteAssets from '$lib/components/timeline/actions/DeleteAssetsAction.svelte';
|
||||||
|
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
|
||||||
|
import FavoriteAction from '$lib/components/timeline/actions/FavoriteAction.svelte';
|
||||||
|
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
|
||||||
|
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
|
||||||
|
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
||||||
|
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||||
|
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||||
|
import { AssetAction } from '$lib/constants';
|
||||||
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
|
import { AssetOrderBy, AssetVisibility } from '@immich/sdk';
|
||||||
|
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: PageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data }: Props = $props();
|
||||||
|
const timelineManager = new TimelineManager();
|
||||||
|
void timelineManager.updateOptions({
|
||||||
|
visibility: AssetVisibility.Timeline,
|
||||||
|
withStacked: true,
|
||||||
|
withPartners: true,
|
||||||
|
orderBy: AssetOrderBy.DateAdded,
|
||||||
|
});
|
||||||
|
onDestroy(() => timelineManager.destroy());
|
||||||
|
|
||||||
|
const assetInteraction = new AssetInteraction();
|
||||||
|
|
||||||
|
const handleEscape = () => {
|
||||||
|
if (assetInteraction.selectionActive) {
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
|
timelineManager.removeAssets(assetIds);
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
|
||||||
|
<Timeline
|
||||||
|
enableRouting={true}
|
||||||
|
{timelineManager}
|
||||||
|
{assetInteraction}
|
||||||
|
removeAction={AssetAction.UNARCHIVE}
|
||||||
|
onEscape={handleEscape}
|
||||||
|
>
|
||||||
|
{#snippet empty()}
|
||||||
|
<EmptyPlaceholder text={$t('no_archived_assets_message')} />
|
||||||
|
{/snippet}
|
||||||
|
</Timeline>
|
||||||
|
</UserPageLayout>
|
||||||
|
|
||||||
|
{#if assetInteraction.selectionActive}
|
||||||
|
<AssetSelectControlBar
|
||||||
|
ownerId={$user.id}
|
||||||
|
assets={assetInteraction.selectedAssets}
|
||||||
|
clearSelect={() => assetInteraction.clearMultiselect()}
|
||||||
|
>
|
||||||
|
<CreateSharedLink />
|
||||||
|
<SelectAllAssets {timelineManager} {assetInteraction} />
|
||||||
|
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||||
|
<AddToAlbum />
|
||||||
|
<AddToAlbum shared />
|
||||||
|
</ButtonContextMenu>
|
||||||
|
<FavoriteAction
|
||||||
|
removeFavorite={assetInteraction.isAllFavorite}
|
||||||
|
onFavorite={(ids, isFavorite) =>
|
||||||
|
timelineManager.updateAssetOperation(ids, (asset) => {
|
||||||
|
asset.isFavorite = isFavorite;
|
||||||
|
return { remove: false };
|
||||||
|
})}
|
||||||
|
></FavoriteAction>
|
||||||
|
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||||
|
<DownloadAction menuItem />
|
||||||
|
<ChangeDate menuItem />
|
||||||
|
<ChangeDescription menuItem />
|
||||||
|
<ChangeLocation menuItem />
|
||||||
|
<ArchiveAction menuItem onArchive={(assetIds) => timelineManager.removeAssets(assetIds)} />
|
||||||
|
{#if $preferences.tags.enabled}
|
||||||
|
<TagAction menuItem />
|
||||||
|
{/if}
|
||||||
|
<DeleteAssets
|
||||||
|
menuItem
|
||||||
|
onAssetDelete={(assetIds) => timelineManager.removeAssets(assetIds)}
|
||||||
|
onUndoDelete={(assets) => timelineManager.addAssets(assets)}
|
||||||
|
/>
|
||||||
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
|
<hr />
|
||||||
|
<AssetJobActions />
|
||||||
|
</ButtonContextMenu>
|
||||||
|
</AssetSelectControlBar>
|
||||||
|
{/if}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { authenticate } from '$lib/utils/auth';
|
||||||
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
|
import { getAssetInfoFromParam } from '$lib/utils/navigation';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (async ({ params, url }) => {
|
||||||
|
await authenticate(url);
|
||||||
|
const asset = await getAssetInfoFromParam(params);
|
||||||
|
const $t = await getFormatter();
|
||||||
|
|
||||||
|
return {
|
||||||
|
asset,
|
||||||
|
meta: {
|
||||||
|
title: $t('recently_added'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) satisfies PageLoad;
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
|
import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { emptyTrash, restoreTrash } from '@immich/sdk';
|
import { AssetOrderBy, emptyTrash, restoreTrash } from '@immich/sdk';
|
||||||
import { Button, HStack, modalManager, Text } from '@immich/ui';
|
import { Button, HStack, modalManager, Text } from '@immich/ui';
|
||||||
import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js';
|
import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const timelineManager = new TimelineManager();
|
const timelineManager = new TimelineManager();
|
||||||
void timelineManager.updateOptions({ isTrashed: true });
|
void timelineManager.updateOptions({ isTrashed: true, orderBy: AssetOrderBy.DateDeleted });
|
||||||
onDestroy(() => timelineManager.destroy());
|
onDestroy(() => timelineManager.destroy());
|
||||||
|
|
||||||
const assetInteraction = new AssetInteraction();
|
const assetInteraction = new AssetInteraction();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue