From c6abef186cefc5e82de27f87fca92e23906e1603 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Fri, 4 Aug 2023 17:07:15 -0400 Subject: [PATCH] refactor(server,web): time buckets for main timeline, archived, and favorites (1) (#3537) * refactor: time buckets * feat(web): use new time bucket api * feat(web): use asset grid in archive/favorites * chore: open api * chore: clean up uuid validation * refactor(web): move memory lane to photos page * Update web/src/routes/(user)/archive/+page.svelte Co-authored-by: Sergey Kondrikov * fix: hide archived photos on main timeline * fix: select exif info --------- Co-authored-by: Sergey Kondrikov --- cli/src/api/open-api/api.ts | 616 +++++++++-------- mobile/openapi/.openapi-generator/FILES | 21 +- mobile/openapi/README.md | Bin 18288 -> 18030 bytes mobile/openapi/doc/AssetApi.md | Bin 54046 -> 54776 bytes .../doc/AssetCountByTimeBucketResponseDto.md | Bin 533 -> 0 bytes mobile/openapi/doc/GetAssetByTimeBucketDto.md | Bin 570 -> 0 bytes .../doc/GetAssetCountByTimeBucketDto.md | Bin 574 -> 0 bytes ...TimeBucket.md => TimeBucketResponseDto.md} | Bin 448 -> 447 bytes .../{TimeGroupEnum.md => TimeBucketSize.md} | Bin 379 -> 380 bytes mobile/openapi/lib/api.dart | Bin 5934 -> 5772 bytes mobile/openapi/lib/api/asset_api.dart | Bin 49507 -> 51559 bytes mobile/openapi/lib/api_client.dart | Bin 18608 -> 18284 bytes mobile/openapi/lib/api_helper.dart | Bin 4362 -> 4364 bytes ...set_count_by_time_bucket_response_dto.dart | Bin 3410 -> 0 bytes .../model/get_asset_by_time_bucket_dto.dart | Bin 4511 -> 0 bytes .../get_asset_count_by_time_bucket_dto.dart | Bin 4491 -> 0 bytes ...ket.dart => time_bucket_response_dto.dart} | Bin 3152 -> 3134 bytes ..._group_enum.dart => time_bucket_size.dart} | Bin 2606 -> 2630 bytes mobile/openapi/test/asset_api_test.dart | Bin 5144 -> 5221 bytes ...ount_by_time_bucket_response_dto_test.dart | Bin 771 -> 0 bytes .../get_asset_by_time_bucket_dto_test.dart | Bin 876 -> 0 bytes ...t_asset_count_by_time_bucket_dto_test.dart | Bin 864 -> 0 bytes ...art => time_bucket_response_dto_test.dart} | Bin 686 -> 683 bytes ...m_test.dart => time_bucket_size_test.dart} | Bin 423 -> 425 bytes server/immich-openapi-specs.json | 318 +++++---- server/src/domain/asset/asset.repository.ts | 19 + server/src/domain/asset/asset.service.ts | 28 +- server/src/domain/asset/dto/index.ts | 1 + .../src/domain/asset/dto/time-bucket.dto.ts | 34 + server/src/domain/asset/response-dto/index.ts | 1 + .../response-dto/time-bucket-response.dto.ts | 9 + .../immich/api-v1/asset/asset-repository.ts | 56 -- .../immich/api-v1/asset/asset.controller.ts | 21 - .../immich/api-v1/asset/asset.service.spec.ts | 31 - .../src/immich/api-v1/asset/asset.service.ts | 23 - .../asset/dto/get-asset-by-time-bucket.dto.ts | 28 - .../dto/get-asset-count-by-time-bucket.dto.ts | 32 - .../asset-count-by-time-group-response.dto.ts | 23 - .../immich/controllers/asset.controller.ts | 16 + .../infra/repositories/asset.repository.ts | 51 ++ .../repositories/asset.repository.mock.ts | 7 +- web/src/api/open-api/api.ts | 627 ++++++++++-------- .../album-page/asset-selection.svelte | 4 +- .../layouts/user-page-layout.svelte | 5 +- .../components/photos-page/asset-grid.svelte | 2 +- .../empty-placeholder.svelte | 2 +- web/src/lib/stores/assets.store.ts | 20 +- web/src/routes/(user)/archive/+page.svelte | 83 +-- web/src/routes/(user)/favorites/+page.svelte | 66 +- .../(user)/partners/[userId]/+page.svelte | 4 +- web/src/routes/(user)/photos/+page.svelte | 22 +- 51 files changed, 1132 insertions(+), 1038 deletions(-) delete mode 100644 mobile/openapi/doc/AssetCountByTimeBucketResponseDto.md delete mode 100644 mobile/openapi/doc/GetAssetByTimeBucketDto.md delete mode 100644 mobile/openapi/doc/GetAssetCountByTimeBucketDto.md rename mobile/openapi/doc/{AssetCountByTimeBucket.md => TimeBucketResponseDto.md} (91%) rename mobile/openapi/doc/{TimeGroupEnum.md => TimeBucketSize.md} (91%) delete mode 100644 mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart delete mode 100644 mobile/openapi/lib/model/get_asset_by_time_bucket_dto.dart delete mode 100644 mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart rename mobile/openapi/lib/model/{asset_count_by_time_bucket.dart => time_bucket_response_dto.dart} (63%) rename mobile/openapi/lib/model/{time_group_enum.dart => time_bucket_size.dart} (51%) delete mode 100644 mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart delete mode 100644 mobile/openapi/test/get_asset_by_time_bucket_dto_test.dart delete mode 100644 mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart rename mobile/openapi/test/{asset_count_by_time_bucket_test.dart => time_bucket_response_dto_test.dart} (81%) rename mobile/openapi/test/{time_group_enum_test.dart => time_bucket_size_test.dart} (84%) create mode 100644 server/src/domain/asset/dto/time-bucket.dto.ts create mode 100644 server/src/domain/asset/response-dto/time-bucket-response.dto.ts delete mode 100644 server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts delete mode 100644 server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts delete mode 100644 server/src/immich/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 4e86e88387..63739dbd8b 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -410,44 +410,6 @@ export const AssetBulkUploadCheckResultReasonEnum = { export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum]; -/** - * - * @export - * @interface AssetCountByTimeBucket - */ -export interface AssetCountByTimeBucket { - /** - * - * @type {number} - * @memberof AssetCountByTimeBucket - */ - 'count': number; - /** - * - * @type {string} - * @memberof AssetCountByTimeBucket - */ - 'timeBucket': string; -} -/** - * - * @export - * @interface AssetCountByTimeBucketResponseDto - */ -export interface AssetCountByTimeBucketResponseDto { - /** - * - * @type {Array} - * @memberof AssetCountByTimeBucketResponseDto - */ - 'buckets': Array; - /** - * - * @type {number} - * @memberof AssetCountByTimeBucketResponseDto - */ - 'totalCount': number; -} /** * * @export @@ -1286,58 +1248,6 @@ export interface ExifResponseDto { */ 'timeZone'?: string | null; } -/** - * - * @export - * @interface GetAssetByTimeBucketDto - */ -export interface GetAssetByTimeBucketDto { - /** - * - * @type {Array} - * @memberof GetAssetByTimeBucketDto - */ - 'timeBucket': Array; - /** - * - * @type {string} - * @memberof GetAssetByTimeBucketDto - */ - 'userId'?: string; - /** - * Include assets without thumbnails - * @type {boolean} - * @memberof GetAssetByTimeBucketDto - */ - 'withoutThumbs'?: boolean; -} -/** - * - * @export - * @interface GetAssetCountByTimeBucketDto - */ -export interface GetAssetCountByTimeBucketDto { - /** - * - * @type {TimeGroupEnum} - * @memberof GetAssetCountByTimeBucketDto - */ - 'timeGroup': TimeGroupEnum; - /** - * - * @type {string} - * @memberof GetAssetCountByTimeBucketDto - */ - 'userId'?: string; - /** - * Include assets without thumbnails - * @type {boolean} - * @memberof GetAssetCountByTimeBucketDto - */ - 'withoutThumbs'?: boolean; -} - - /** * * @export @@ -2850,18 +2760,37 @@ export const ThumbnailFormat = { export type ThumbnailFormat = typeof ThumbnailFormat[keyof typeof ThumbnailFormat]; +/** + * + * @export + * @interface TimeBucketResponseDto + */ +export interface TimeBucketResponseDto { + /** + * + * @type {number} + * @memberof TimeBucketResponseDto + */ + 'count': number; + /** + * + * @type {string} + * @memberof TimeBucketResponseDto + */ + 'timeBucket': string; +} /** * * @export * @enum {string} */ -export const TimeGroupEnum = { - Day: 'day', - Month: 'month' +export const TimeBucketSize = { + Day: 'DAY', + Month: 'MONTH' } as const; -export type TimeGroupEnum = typeof TimeGroupEnum[keyof typeof TimeGroupEnum]; +export type TimeBucketSize = typeof TimeBucketSize[keyof typeof TimeBucketSize]; /** @@ -5038,94 +4967,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, - /** - * - * @param {GetAssetByTimeBucketDto} getAssetByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetByTimeBucket: async (getAssetByTimeBucketDto: GetAssetByTimeBucketDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'getAssetByTimeBucketDto' is not null or undefined - assertParamExists('getAssetByTimeBucket', 'getAssetByTimeBucketDto', getAssetByTimeBucketDto) - const localVarPath = `/asset/time-bucket`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(getAssetByTimeBucketDto, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetCountByTimeBucket: async (getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'getAssetCountByTimeBucketDto' is not null or undefined - assertParamExists('getAssetCountByTimeBucket', 'getAssetCountByTimeBucketDto', getAssetCountByTimeBucketDto) - const localVarPath = `/asset/count-by-time-bucket`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(getAssetCountByTimeBucketDto, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * * @param {*} [options] Override http request option. @@ -5255,6 +5096,83 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {TimeBucketSize} size + * @param {string} timeBucket + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getByTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'size' is not null or undefined + assertParamExists('getByTimeBucket', 'size', size) + // verify required parameter 'timeBucket' is not null or undefined + assertParamExists('getByTimeBucket', 'timeBucket', timeBucket) + const localVarPath = `/asset/time-bucket`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + + if (albumId !== undefined) { + localVarQueryParameter['albumId'] = albumId; + } + + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + + if (timeBucket !== undefined) { + localVarQueryParameter['timeBucket'] = timeBucket; + } + + if (key !== undefined) { + localVarQueryParameter['key'] = key; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -5498,6 +5416,76 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {TimeBucketSize} size + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'size' is not null or undefined + assertParamExists('getTimeBuckets', 'size', size) + const localVarPath = `/asset/time-buckets`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + + if (albumId !== undefined) { + localVarQueryParameter['albumId'] = albumId; + } + + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + + if (key !== undefined) { + localVarQueryParameter['key'] = key; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -5960,26 +5948,6 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetById(id, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {GetAssetByTimeBucketDto} getAssetByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getAssetByTimeBucket(getAssetByTimeBucketDto: GetAssetByTimeBucketDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetByTimeBucket(getAssetByTimeBucketDto, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * - * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {*} [options] Override http request option. @@ -6012,6 +5980,22 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetThumbnail(id, format, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {TimeBucketSize} size + * @param {string} timeBucket + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getByTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getByTimeBucket(size, timeBucket, userId, albumId, isArchived, isFavorite, key, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -6066,6 +6050,21 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {TimeBucketSize} size + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, isArchived, isFavorite, key, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * Get all asset of a device that are in the database, ID only. * @param {string} deviceId @@ -6224,24 +6223,6 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAssetById(requestParameters: AssetApiGetAssetByIdRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getAssetById(requestParameters.id, requestParameters.key, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {AssetApiGetAssetByTimeBucketRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetByTimeBucket(requestParameters: AssetApiGetAssetByTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAssetByTimeBucket(requestParameters.getAssetByTimeBucketDto, options).then((request) => request(axios, basePath)); - }, - /** - * - * @param {AssetApiGetAssetCountByTimeBucketRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetCountByTimeBucket(requestParameters: AssetApiGetAssetCountByTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getAssetCountByTimeBucket(requestParameters.getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath)); - }, /** * * @param {*} [options] Override http request option. @@ -6268,6 +6249,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAssetThumbnail(requestParameters: AssetApiGetAssetThumbnailRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getAssetThumbnail(requestParameters.id, requestParameters.format, requestParameters.key, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiGetByTimeBucketRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getByTimeBucket(requestParameters: AssetApiGetByTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise> { + return localVarFp.getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -6311,6 +6301,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getMemoryLane(requestParameters.timestamp, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig): AxiosPromise> { + return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(axios, basePath)); + }, /** * Get all asset of a device that are in the database, ID only. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. @@ -6543,34 +6542,6 @@ export interface AssetApiGetAssetByIdRequest { readonly key?: string } -/** - * Request parameters for getAssetByTimeBucket operation in AssetApi. - * @export - * @interface AssetApiGetAssetByTimeBucketRequest - */ -export interface AssetApiGetAssetByTimeBucketRequest { - /** - * - * @type {GetAssetByTimeBucketDto} - * @memberof AssetApiGetAssetByTimeBucket - */ - readonly getAssetByTimeBucketDto: GetAssetByTimeBucketDto -} - -/** - * Request parameters for getAssetCountByTimeBucket operation in AssetApi. - * @export - * @interface AssetApiGetAssetCountByTimeBucketRequest - */ -export interface AssetApiGetAssetCountByTimeBucketRequest { - /** - * - * @type {GetAssetCountByTimeBucketDto} - * @memberof AssetApiGetAssetCountByTimeBucket - */ - readonly getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto -} - /** * Request parameters for getAssetStats operation in AssetApi. * @export @@ -6620,6 +6591,62 @@ export interface AssetApiGetAssetThumbnailRequest { readonly key?: string } +/** + * Request parameters for getByTimeBucket operation in AssetApi. + * @export + * @interface AssetApiGetByTimeBucketRequest + */ +export interface AssetApiGetByTimeBucketRequest { + /** + * + * @type {TimeBucketSize} + * @memberof AssetApiGetByTimeBucket + */ + readonly size: TimeBucketSize + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly timeBucket: string + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly userId?: string + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly albumId?: string + + /** + * + * @type {boolean} + * @memberof AssetApiGetByTimeBucket + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof AssetApiGetByTimeBucket + */ + readonly isFavorite?: boolean + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly key?: string +} + /** * Request parameters for getDownloadInfo operation in AssetApi. * @export @@ -6704,6 +6731,55 @@ export interface AssetApiGetMemoryLaneRequest { readonly timestamp: string } +/** + * Request parameters for getTimeBuckets operation in AssetApi. + * @export + * @interface AssetApiGetTimeBucketsRequest + */ +export interface AssetApiGetTimeBucketsRequest { + /** + * + * @type {TimeBucketSize} + * @memberof AssetApiGetTimeBuckets + */ + readonly size: TimeBucketSize + + /** + * + * @type {string} + * @memberof AssetApiGetTimeBuckets + */ + readonly userId?: string + + /** + * + * @type {string} + * @memberof AssetApiGetTimeBuckets + */ + readonly albumId?: string + + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly isFavorite?: boolean + + /** + * + * @type {string} + * @memberof AssetApiGetTimeBuckets + */ + readonly key?: string +} + /** * Request parameters for getUserAssetsByDeviceId operation in AssetApi. * @export @@ -6995,28 +7071,6 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAssetById(requestParameters.id, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {AssetApiGetAssetByTimeBucketRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public getAssetByTimeBucket(requestParameters: AssetApiGetAssetByTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetByTimeBucket(requestParameters.getAssetByTimeBucketDto, options).then((request) => request(this.axios, this.basePath)); - } - - /** - * - * @param {AssetApiGetAssetCountByTimeBucketRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public getAssetCountByTimeBucket(requestParameters: AssetApiGetAssetCountByTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetCountByTimeBucket(requestParameters.getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {*} [options] Override http request option. @@ -7049,6 +7103,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAssetThumbnail(requestParameters.id, requestParameters.format, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiGetByTimeBucketRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public getByTimeBucket(requestParameters: AssetApiGetByTimeBucketRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. @@ -7102,6 +7167,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + } + /** * Get all asset of a device that are in the database, ID only. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index ed58e2bfe1..279bf838c6 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -19,8 +19,6 @@ doc/AssetBulkUploadCheckDto.md doc/AssetBulkUploadCheckItem.md doc/AssetBulkUploadCheckResponseDto.md doc/AssetBulkUploadCheckResult.md -doc/AssetCountByTimeBucket.md -doc/AssetCountByTimeBucketResponseDto.md doc/AssetFileUploadResponseDto.md doc/AssetIdsDto.md doc/AssetIdsResponseDto.md @@ -49,8 +47,6 @@ doc/DeleteAssetStatus.md doc/DownloadArchiveInfo.md doc/DownloadResponseDto.md doc/ExifResponseDto.md -doc/GetAssetByTimeBucketDto.md -doc/GetAssetCountByTimeBucketDto.md doc/ImportAssetDto.md doc/JobApi.md doc/JobCommand.md @@ -112,7 +108,8 @@ doc/TagApi.md doc/TagResponseDto.md doc/TagTypeEnum.md doc/ThumbnailFormat.md -doc/TimeGroupEnum.md +doc/TimeBucketResponseDto.md +doc/TimeBucketSize.md doc/TranscodeHWAccel.md doc/TranscodePolicy.md doc/UpdateAlbumDto.md @@ -162,8 +159,6 @@ lib/model/asset_bulk_upload_check_dto.dart lib/model/asset_bulk_upload_check_item.dart lib/model/asset_bulk_upload_check_response_dto.dart lib/model/asset_bulk_upload_check_result.dart -lib/model/asset_count_by_time_bucket.dart -lib/model/asset_count_by_time_bucket_response_dto.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_ids_dto.dart lib/model/asset_ids_response_dto.dart @@ -191,8 +186,6 @@ lib/model/delete_asset_status.dart lib/model/download_archive_info.dart lib/model/download_response_dto.dart lib/model/exif_response_dto.dart -lib/model/get_asset_by_time_bucket_dto.dart -lib/model/get_asset_count_by_time_bucket_dto.dart lib/model/import_asset_dto.dart lib/model/job_command.dart lib/model/job_command_dto.dart @@ -245,7 +238,8 @@ lib/model/system_config_template_storage_option_dto.dart lib/model/tag_response_dto.dart lib/model/tag_type_enum.dart lib/model/thumbnail_format.dart -lib/model/time_group_enum.dart +lib/model/time_bucket_response_dto.dart +lib/model/time_bucket_size.dart lib/model/transcode_hw_accel.dart lib/model/transcode_policy.dart lib/model/update_album_dto.dart @@ -274,8 +268,6 @@ test/asset_bulk_upload_check_dto_test.dart test/asset_bulk_upload_check_item_test.dart test/asset_bulk_upload_check_response_dto_test.dart test/asset_bulk_upload_check_result_test.dart -test/asset_count_by_time_bucket_response_dto_test.dart -test/asset_count_by_time_bucket_test.dart test/asset_file_upload_response_dto_test.dart test/asset_ids_dto_test.dart test/asset_ids_response_dto_test.dart @@ -304,8 +296,6 @@ test/delete_asset_status_test.dart test/download_archive_info_test.dart test/download_response_dto_test.dart test/exif_response_dto_test.dart -test/get_asset_by_time_bucket_dto_test.dart -test/get_asset_count_by_time_bucket_dto_test.dart test/import_asset_dto_test.dart test/job_api_test.dart test/job_command_dto_test.dart @@ -367,7 +357,8 @@ test/tag_api_test.dart test/tag_response_dto_test.dart test/tag_type_enum_test.dart test/thumbnail_format_test.dart -test/time_group_enum_test.dart +test/time_bucket_response_dto_test.dart +test/time_bucket_size_test.dart test/transcode_hw_accel_test.dart test/transcode_policy_test.dart test/update_album_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 7e2721348aa5589a44ea53a2f8566e840ddf43e7..5114c85bdba5969fd8afc19db8897186e5982b60 100644 GIT binary patch delta 182 zcmey+$M~*?af5~6W^Ex;#>s01RYa01OEPm)lS-4bQ%f`zY814z++8P^3F>Z65N2lL z3<1hHf#fFN7nagOs4Rvl4bjq4&`&HbPA$;~$?8H3DxQ2mRAh64Xr%0BcjJ|;o69Wc kaZNIoKr&9zP*fRicu8<(Rcfq8N`A7wKC0kmLl=8y08e5*vj6}9 delta 379 zcmaFY!}y_(af5~6YT;qPj_yx*%I@Wsk)Do}+vPCW>`B1xX$igjy p$z!+`(+F%z^l~>FS}f&~buY>b5CmXSRnY^1>UL>iqBr`WPsWdq|wM0{)MnOx<-F5OrW`)ga ztScG0LV$8kV7bYLEF!`PrNu~U#W;$A8o8piv?jluE57+L+XL6lb7CFDCm;6Fc5|vk zn4wXeS(U1z05Sz)sE$HuacYrgijG2JPEu(uh>=4u3)QRt)Q>3fG{-}Bnq@YEi*4M2gz`tN`y_5<;BD&$Dd}KtkvN>`9-_ase9<#v^6IFGrXXi(X#t%C3=vdkYH7u4=;fwp)+m4+3=C@|2P$3= znf&1}>*SIyO*TkiOjf)QF!`3F=Hv&vEhgtTvTo*g-p083PRVNxQG~I@T3TFo3O<>| zB{q{4LPhQD6w*^mkffnuPD(gS;ST4?3LU=a!DmUfZJ-cCPGS!$6(_AuRUqMhqpeu++qm!Q~`qsB*A?bWEi{@aO`p2!+Tpxj}6ty@O07>2-V7Zy=hc zF?t|jdyfOj$+iD*wU|Yl2$(!ZXj0k_{5g9He8C|gd4WL4K}LrA(y z5hf?4tfTZ|ju2I~;ItxV8v~aePf^#?vYZ{(^VN3E`kBFY?7@+a+ZQJsij;H6f1q zKQy1Hu+r(;pn|Sm0!>2zdckOEcHL!$C<; zOdE%u_^|gs_3D$HAp^tbj25M>Pmi871h$a|_!r+@T|VXzTCW{xw8`2aC8D}n$$4W{ z)n7Ifg3>>h9-|W?(R(5C$BJWNqY@8q>T6(b8_sZw@L z20t~OG1BfF%`x=+5wR=Rm2JDIfXm$7z4Oo)7SJemls!Sz}`ET@eyZUErno$K-In%TQ)zN`YDsWrRcfq4F_4^WV5`Da0+#@BC!f<5pBzx2F!^sK$7DV|`N^+!r6+GF zP@b&oDCvo62G|H4h2WB+%)E33gfTh_)lfluh0@~GBF_|lIdu1le z=yOd@DU+GJKGc5lVMn*g3K|`g|Fx*|fn27p12$b#(|U4+LHgt~MTwI=4YMZSH%Obj ztXN$NVHr>%f&)~(xo}}BBZZz7oBVo~WMq13iBl!Q#~KJGC;+MBc{5P38iPEH>)QsCcm~QoqRr1b@Pv8B{d*JmIo9px=G*| zFP{8R!V_np;EHlTgpZ5EGfOf&N=gbm^V0IMhXl!S4o-Lw2Top>b(C<_XKk)p7=t@* zad<MhOps_LBPxMTo&4KE8sy~3vF=ur N6D(Ob%Xhh$0|5NjeHH)! delta 787 zcmaDpiTQC8^M+D~$-P^7CdY(^XuGGDI2ISDmN-?0Wag$il_qDWmbjGU$10>_NKDpW zuR8gR8^`2LVUk)n^kLIhxFC44S10%61>x4B3bt5OY!+O&ijhQ@NKbyhNYWnDk*Osb zBsooFvcqEW$^AiKUkPEFw|Rj~9Mk027NwIFX3B9U=a=S{=q6R_g2K)eW|1>U6lVaU z$-+XB1tdOsW0;f)K4ajag>DKkP=%9Wt|0!ADpmoX`5WQ03H?apuy>r1u{<I5%I_v=m}-DoxIwe85m~vbMY_Ur1(dDo7x;BsjAwbuy!@ P2)dB3mj32+8&4hpGLIV$ delta 238 zcmaFU$GBl40A!Z6vv$q`-RdE0S diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index c41cea12b60757aa7982e528a6e8696257cff5d8..a6dccf564c76e59aa8988c7ad01435a23bde3bbd 100644 GIT binary patch delta 56 zcmeBD>QUMd&Z+KHnw*_l5}a9;s;N-Tr2qs)sU@XFc?uz!xv2=r$#v}Zn|E{ivjYGb C`4dF| delta 36 jcmeBC>QdSe&dKRslwVrlnpc`TIgwKs!P{KN*~bn5=Q0eS diff --git a/mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart b/mobile/openapi/lib/model/asset_count_by_time_bucket_response_dto.dart deleted file mode 100644 index 0375649b7267931465b84370efe07787e1181998..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3410 zcmbtWZExE)5dQ98aRG{2##DLhr@@K5rcE+5Yhobv1`LKF&@yGSl|_}LY8a{i`|e1+ zxpg9=3y?&j?)`c0c*9|DIE2f;r;}HIUf*9oTu!gA;O6~@YagyAa6g&C-Q?>1&A(?T zMv`xqOquX?@Z0Miy^2*P6^|0BqC}*81VvV8zKnRvH#{?b^)5EKROYA#OSf#Nqr5PY z;$I4(P(5QC{F^g{{|#3fjVpazJ(ig^EHjZMRu**HJ376^fZP%c9?Z zjp8*^y4OQ@24n&<=NZdIfWPOxUYs(m;ZkdE-pL{}Z+BC%;ctuhGdB-h=jfha83~U) zfFV}oftx~Q8W?1J3$sqeIS89T%mcL0DejRU0J9c4SlA#mgkXyRv$=0SR%LWjMjDpd z(CO=bfH?tt5ZYWEcQ%I7t`!uSfyRP8dLzGbrLf#+XCY-u1yjsO1!3q-7N2;GWi)H9 z1`v+H!4k97qnQ2b%eQat3B)PIefiQR=2V)z!zxrb6lem*ke&;lvRiQjYo^!l#eHZZXukbWypKkSY=!Z;2hY~4e;kz- z)n=vF@J0FHw^9sWG_L1;>++7}7tVtZ%N@3sh{vdLt7V2R9iI}Bu@pYx5whtA>Bs%K ztG|_)seb9WKS${{6~7Z}LTMzMGbgNUzgWX`uqSr@zdiz%Ex`>O4Z>*n%F=>sNR-@^ z#%J~jTQ5O@J~itMt2Fl^D^lb$k#{S!?Z_4ta>WxI8@Ab05hLSuM70&fFvJ;BL#{B%ThiN`d*srO>TCT6T}ursaN`vTf85vX!E^7-!UV60-aXU z0a5=J-Fui?Sx>5IL$9;YQorqk2t$bmY`JtKY-u_)DpU@zDYcBGJiV%zBdDIN72D<| zaP9fjT~Q)X&AB#wRqU>CDAk^1-FB-P*G38>dlo2*Xy|}^s{3%5m0QvX>V+m^TI1?O zE0ViO4nW6KwX0wc=uL24rAN5A6n_GKYdNrrEigN1fTf%{#m{_46W{7PUQ;yBZ~;~8 zHpM#NE6rM4Ht80HtuSjmqNb$Xs}rYJP7lqLa~+2Z>eE(A=9}4j*ShtHi6=Dcm@JU& zQGWSw+IT=4ZRVZo`)BxfBDGGTX*u&-qo9@*>(U3BP8jBiO-^ zLEkN64Pma<0_nBjfH$~Cx71qeb14S zCB;^n4cNB@XkwFh$KyTEy|5=Ios$!K`TJ!2^zHfO`Nx-&^D}yL@%}ubvoT$cC-h-_ zcJb!VBZ#r&i$oYVeAfTzS%<%>>r5LtTWB*|s8kLp&vGl1nM~zMW-cnftJPW?cThv5 z8?m*sb?#k(B}{T}>>1mEJfR0=C8(+)^Z-~g=+qG8;Y zEX<|}EyY(!b15?jWlys_O~pKwNAyW$v4qc(>{`Y^Ar?;QjF=RK@kC3@rV)cU%@c`O zC2}G1)X`Uw=90R(g?rw}qhe?9Xy-vMqNU!*uhK+bEh0h7v70UHbew@j$i?hO2o%YTB)Th8chf09b~r0cfTT z4Ts(F{IiTvBfxXhqv41`&51?YW$2@F=i9gNi9m;zET<<=Z~}h3u|}c~YYBQsEU*Z! z6nwg~-N=5HYAo#CPi2m-r9*Pqmgq{YbFp%hl1obi+?}yr$IHtLg4tC!`vZF?{HHgpOO{Kdqb}KCV8a zVR;{ikHg=n8N%V?hBsHo-iZ%lUDVcyk}Z0dicw_sJF7D|5z6^OWg?}|SVAS=lfJoq z9@yX|rslZv==cf>cbY*ERtrGDU80EfQ3zXkB9*qnc4a4g4>oQ|rl|PAN8o-yhoLBh zy^p(N8=;`K(;HHi6OHBjeQAh#3wK>IzWaLL+0-p}Jg%Gnv@4Uy5(=Y=C55`|8D16qmoA3SS}7ZbGqav<_wgz9EeY(IT0rhcw3c9xD8;?sfQAk1IeZb__&!y}1l7U^CAuxa56~NFdDKGj zX**+j6+C4Uv1JC1okElGxb~I3)b4z>cH7c4c!kc`El$uIo{?C!+Ao+RYz%X&u_QBF&p5w&D5~moy z|5`A48l<6I^aURK@rL17Ba9-wi$wlV8wc)9>JjU)#6jLXG_hf$&ZzrO{5QOMAY~px zzoJmripF=@h3hquZ-J-J@a+OwBUwacUJrJkq8;-BaT?_TC)Nmaf`_o&aijbn3IYu+ z#xeoy`LQ{d+~X~dvN2NaU`G@=S4ocA9?wY_5 diff --git a/mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart b/mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart deleted file mode 100644 index e42d0d514c24629f610d8200c5865e7a3355aafb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4491 zcmeHK+in{-5PjEIjDsMS(T$wusc#s0pTQ^DnH)S4r3mYlyqO zoYfZA$}ddrM~i$pb-aFeg!>S&^FjGs=S!+gg*{vY5`3B8Gbx-TZ9GsN;ZRyxM8^6J zIhf66v=E;qO{LTj(vNkXWn!Ai1NxwJD&eOjw~{FkNrhKN6BDE`J~Ps>X+$B-@|i@) zW^yj_%+qI)<&wI&Q~D+tc~I<}Jg7W4N@!u$^0Tx_5V?_BTH)oaFz-xm;X-O)d6_#; zbCG2<73n7eT=1K11Es=c@|#t_f$#kad;*m$iP-&Vz%i^J zyoJS6ib$35rpq!0OfW|UY%l=av{0F=4E&GFe}c=@nC!HSdrX%A&T|8XS%3)uScWSD zXl5++``zL6qfAjRz;k;<{Q<@LlZvd%&?n{2moM=XfevkXPLCep82ki_+YHB9+Z_E$ zC=!V!icd`&jRL?@jfI^nD(&e;dgO;&yyDd94}wQkndj=RWh$K$cEi;f1$QBxVuBKN zt4h)Z=K$d+B(r#Zug}j8Z3P6k_~-0_|B&8xao1}{Gt9#LeD6UFOC%I zb7)YG=GH6=yW_BgmOh8TA@~0=7;v3L-iCZ$D_1mvVq`i&Gm~=+Az^;8d5W^)CR}hP z7-zgnCzyN|X2!V{7h0>wY49fytRP(UvqYa!ik%f@2+o!3gsxoUVG!x1!ucIWcH{8C znO?-VEtk4*JN{HG#S~E=JbE0!;4tSvli^1Pag1RLk)9{C6stdZG#JiD=+=dMgDxv4 zWDg5gA8i#IJlWCbdcM_e$R1WkaAkJkF8nQSU6L)ZszBzgLcrR>F`x`0uge>k5Jl&2 zLvOjUX3g^xvK!NilgV9)QAEf}$Kw%dMe9N1>*zBB&gQx8uzTnnAt8FoT^WDAT=`9D z9~?kS>lVkDH4itey4^2X5*gHQW#SfBT&M^j2XV{5ozlSD@p?TBU6H3X%^3a?h6Wgg zmN$IGZW+Q8DnA7)b)@Q*xiyZ=x4n3kQHkM#RK0w+nk;L29UA!4icEm8ACdc3TB5dX zi%R)qn&8!f#~DAuVta0&j8(fSaqjUqC0%(lh9lnY-{BpM_JU3gz3cT*5HS;nGfrzPaqjWMt__{XQ3V!?3eST)UM2h{gyn4&epl=5 z%)0h;9I!Hr?d4KJ_sf0QsI%-I$NmV{?n$HjP_n50wYu_M#^H`lBDA5^V@Ruz)`*u> zslP1(NyWxUrbY$9u{9DMxXINlV^CS*GBSVP`GDAv2M6jRaN~*eT>p*5GoGFG?*g z%_&i^Rj|RTZ1Og?G#o}7v9H1*^NHOYhm1YPZX7ZKoU3ujT;#OEA*09@Er!+Ac9ZQn POede<>c^omm-{gQEjGW| delta 515 zcmdldaY15(I-{6ladB#ibAD-FiBn}rW^SreX>xXI$z&Tw2`lVUu?m@a#U+V($*Fo= z3JTcOOzvUafy?Abrsue%LYd#xXINpNOW>SR+!cVuoW<9dG=$4CWRg^-MPw7bm`#wm?aV>Q+(*n=$lOqtYsg#^RtrUhYhv}dl5-M^i_v^P zxtmo1S<@C)U1aVnRwZPvD4P~C*MrRsiMx3^+fI}Sh~&r*1_i8@LQ!f-X;B`U)p|fV lP>@4Fa$<3+LXkQ+kZ|gNgt{c>9wcig-{aIk=JIpN0|44du#Nx# delta 501 zcmX>mvQA`!4kM>~QGRKGYhG#YWHUy0Bwjn?df$}9N(Ebmkj&gvxGKGPjUsiRxVok_ zmx4lZNn%N6vO;oxUU7*+Zhl@#23GZ8naQn8R!HWbWKyt!n`##gG)zGOXs!;J0oDLw z##(bBtU+as#OiS+=Oh*vqk4Jr zOjZRX9fw(Uk$AsYm5_L9Y+6XXC^k0)Z}Td)oyeh`%aI=l3P&r2qSTVoqC8YP^?*{K k0EU3%#Nt$iB6V=6;L-pIV|~s&2rDOl<TqhYPe`ga5ajFc-%uRJFP0mg&(LgYRGpkY+ih+cVLU2h@W?s5N z30#?uLN!dVv^cfMGX<_DF(;`s*E3~uoUjy!TVh#$QD#Z%iA*YmJ% z{>*K|Joy1PPc*`^pw!}m{Ji2+my&!tJB9Sr5`Q%f9+i&IOSDnl}JQ=Lkavr|hn+_4C{l;kU5DxPe^Dl^%i zgKM%Nw+#>2Am{wjJfLZl7xIbu!!m2gKQ6)B-Anv7LfR4K5m!IlQ>Q?ZVuvh LX5L)G|B?j&EZ|P; diff --git a/mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart b/mobile/openapi/test/asset_count_by_time_bucket_response_dto_test.dart deleted file mode 100644 index 3df5ba0d311d4a934d0f3053152ce68176e1d9aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 771 zcma)(PjA~W48`yM6x=BZkk)NaL;nm-=X5C0)j;7LhG9@yn@)tvk|t3df}-Djw9;aS z1;`GTMdLmG_~^1M$`Tgen)=|&@@{#*XqFXR-`p@e`f z$>H%yF_wB|T_DxFpn7fS6p~GmIz@&CvOIsU_26CLoeng=aTAqKtOET?I*8rkAg9M5 zZ>A8Xd5F91t+SEQveKOZb+0L8d)x^#+EP@;*_Ft6jaGd;QtcW;EQ;-nF!9(v;fhY3 zCyQQ}@*Iw-Cujdim?D?s@h%vlV;wY*|}Ym0Tt;iu`+)RNNkF zqs>8-Ry(sZvr?XCc@B%)s(gRByk34?RLcS`R#(e86eV1j6?`d+)y21W42#IKMmjn@ zJotE+#Zq;~IuxySqE#A?Aei8>5vZ|8L-TE|`oTKd$&mUYiY!%529X6=d*0|2$Mn953<7} zJD`z+%K18osVB#OK?ptN@z?^QXskW(O5hS{;XTs%rcynghx!4jAe%i})j%(mna z2Ee3N8L2^~EH%REKJIjOoH3(wHVo5Al-h@!%;9x>61Y;HPHxC4(|rc|Z3m_ewlZjf zN2vpUz_d^J{$s`}kg~9hO%Fh8gu(G%9Btt4&yc%OOZtteQ=mxYP*qu!Y0UHJI=vh{ z0RJX#)%;UrguO{%WojK71Q}QF;Yrcj24eG}zc*58pG4W-y|sGVu3~rW#HH@b{5otz M|DP*XOYxtx7a{Q)#Q*>R diff --git a/mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart b/mobile/openapi/test/get_asset_count_by_time_bucket_dto_test.dart deleted file mode 100644 index ca0f586a6b0dfb4e289690cd5c8c7f9850a4b095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 864 zcmb7C%}(4f5WeRrrl(z`EonJbAfzr$Sgq6&sf0sM!6dU;3um0#8&Ntin!6880w8im+1bWnbCy4F_wCP@m5ju=|j_QmGfCPWrW$N+I!XGsU4Bj zsB?La5EaXA5{TN>(w;DpE@d1Mt2QK}Z9LqD>dz zEyCzz8IE^w`xkjT2`ay%Hsy~@nW3(VDyck9`O?R0cA9#SU&F1t|9VWYHyNy{HK9XL vaorvsG;eKS$sUF~Qrh@rD)H{DHT!lQyHh8w?_VQ7iB0I&r4qL^{$utBV%it> diff --git a/mobile/openapi/test/asset_count_by_time_bucket_test.dart b/mobile/openapi/test/time_bucket_response_dto_test.dart similarity index 81% rename from mobile/openapi/test/asset_count_by_time_bucket_test.dart rename to mobile/openapi/test/time_bucket_response_dto_test.dart index 7dbe5bc4be52e66eddda4e5896d89ca2db6df55f..9a09f2017d8ba632d854969ced8072d69dd1c835 100644 GIT binary patch delta 99 zcmZ3-x|(%EETd>hW^SreX>xXINlLsbgB??&8O}@@}9RT;AB$ogH delta 102 zcmZ3@x{h^2ETfoXadB#ibAD-FiBn}rW^SreX>xXI$>btNReS7G8k*KzTnY;5Mfs%# S8tNsf#U%>Z6;8gwcpU&<xXINpNOW>f|g&B^d-)T}MGfQ=yuROF^Ml)0&H`mJ0x|mJZMW delta 49 xcmZ3; getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise; getStatistics(ownerId: string, options: AssetStatsOptions): Promise; + getTimeBuckets(userId: string, options: TimeBucketOptions): Promise; + getByTimeBucket(userId: string, timeBucket: string, options: TimeBucketOptions): Promise; } diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index cb2701ea12..560a0d85f9 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -10,11 +10,20 @@ import { mimeTypes } from '../domain.constant'; import { HumanReadableSize, usePagination } from '../domain.util'; import { ImmichReadStream, IStorageRepository, StorageCore, StorageFolder } from '../storage'; import { IAssetRepository } from './asset.repository'; -import { AssetIdsDto, DownloadArchiveInfo, DownloadDto, DownloadResponseDto, MemoryLaneDto } from './dto'; +import { + AssetIdsDto, + DownloadArchiveInfo, + DownloadDto, + DownloadResponseDto, + MemoryLaneDto, + TimeBucketAssetDto, + TimeBucketDto, +} from './dto'; import { AssetStatsDto, mapStats } from './dto/asset-statistics.dto'; import { MapMarkerDto } from './dto/map-marker.dto'; -import { mapAsset, MapMarkerResponseDto } from './response-dto'; +import { AssetResponseDto, mapAsset, MapMarkerResponseDto } from './response-dto'; import { MemoryLaneResponseDto } from './response-dto/memory-lane-response.dto'; +import { TimeBucketResponseDto } from './response-dto/time-bucket-response.dto'; export enum UploadFieldName { ASSET_DATA = 'assetData', @@ -135,6 +144,21 @@ export class AssetService { return Promise.all(requests).then((results) => results.filter((result) => result.assets.length > 0)); } + async getTimeBuckets(authUser: AuthUserDto, dto: TimeBucketDto): Promise { + const { userId, ...options } = dto; + const targetId = userId || authUser.id; + await this.access.requirePermission(authUser, Permission.LIBRARY_READ, [targetId]); + return this.assetRepository.getTimeBuckets(targetId, options); + } + + async getByTimeBucket(authUser: AuthUserDto, dto: TimeBucketAssetDto): Promise { + const { userId, timeBucket, ...options } = dto; + const targetId = userId || authUser.id; + await this.access.requirePermission(authUser, Permission.LIBRARY_READ, [targetId]); + const assets = await this.assetRepository.getByTimeBucket(targetId, timeBucket, options); + return assets.map(mapAsset); + } + async downloadFile(authUser: AuthUserDto, id: string): Promise { await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, id); diff --git a/server/src/domain/asset/dto/index.ts b/server/src/domain/asset/dto/index.ts index f22534d35e..8e9440f027 100644 --- a/server/src/domain/asset/dto/index.ts +++ b/server/src/domain/asset/dto/index.ts @@ -3,3 +3,4 @@ export * from './asset-statistics.dto'; export * from './download.dto'; export * from './map-marker.dto'; export * from './memory-lane.dto'; +export * from './time-bucket.dto'; diff --git a/server/src/domain/asset/dto/time-bucket.dto.ts b/server/src/domain/asset/dto/time-bucket.dto.ts new file mode 100644 index 0000000000..c42366f39a --- /dev/null +++ b/server/src/domain/asset/dto/time-bucket.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { toBoolean, ValidateUUID } from '../../domain.util'; +import { TimeBucketSize } from '../asset.repository'; + +export class TimeBucketDto { + @IsNotEmpty() + @IsEnum(TimeBucketSize) + @ApiProperty({ enum: TimeBucketSize, enumName: 'TimeBucketSize' }) + size!: TimeBucketSize; + + @ValidateUUID({ optional: true }) + userId?: string; + + @ValidateUUID({ optional: true }) + albumId?: string; + + @IsOptional() + @IsBoolean() + @Transform(toBoolean) + isArchived?: boolean; + + @IsOptional() + @IsBoolean() + @Transform(toBoolean) + isFavorite?: boolean; +} + +export class TimeBucketAssetDto extends TimeBucketDto { + @IsString() + @IsNotEmpty() + timeBucket!: string; +} diff --git a/server/src/domain/asset/response-dto/index.ts b/server/src/domain/asset/response-dto/index.ts index b82249d2ba..7ed99db130 100644 --- a/server/src/domain/asset/response-dto/index.ts +++ b/server/src/domain/asset/response-dto/index.ts @@ -3,3 +3,4 @@ export * from './asset-response.dto'; export * from './exif-response.dto'; export * from './map-marker-response.dto'; export * from './smart-info-response.dto'; +export * from './time-bucket-response.dto'; diff --git a/server/src/domain/asset/response-dto/time-bucket-response.dto.ts b/server/src/domain/asset/response-dto/time-bucket-response.dto.ts new file mode 100644 index 0000000000..e143dde464 --- /dev/null +++ b/server/src/domain/asset/response-dto/time-bucket-response.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class TimeBucketResponseDto { + @ApiProperty({ type: 'string' }) + timeBucket!: string; + + @ApiProperty({ type: 'integer' }) + count!: number; +} diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index 22c25d6ef4..ce85bd369b 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -6,11 +6,8 @@ import { In } from 'typeorm/find-options/operator/In'; import { Repository } from 'typeorm/repository/Repository'; import { AssetSearchDto } from './dto/asset-search.dto'; import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; -import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; -import { GetAssetCountByTimeBucketDto, TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; import { SearchPropertiesDto } from './dto/search-properties.dto'; import { UpdateAssetDto } from './dto/update-asset.dto'; -import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; @@ -36,8 +33,6 @@ export interface IAssetRepository { getLocationsByUserId(userId: string): Promise; getDetectedObjectsByUserId(userId: string): Promise; getSearchPropertiesByUserId(userId: string): Promise; - getAssetCountByTimeBucket(userId: string, dto: GetAssetCountByTimeBucketDto): Promise; - getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise; getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise; getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise; getByOriginalPath(originalPath: string): Promise; @@ -52,57 +47,6 @@ export class AssetRepository implements IAssetRepository { @InjectRepository(ExifEntity) private exifRepository: Repository, ) {} - async getAssetByTimeBucket(userId: string, dto: GetAssetByTimeBucketDto): Promise { - // Get asset entity from a list of time buckets - let builder = this.assetRepository - .createQueryBuilder('asset') - .leftJoinAndSelect('asset.exifInfo', 'exifInfo') - .where('asset.ownerId = :userId', { userId: userId }) - .andWhere(`date_trunc('month', "fileCreatedAt") IN (:...buckets)`, { - buckets: [...dto.timeBucket], - }) - .andWhere('asset.isVisible = true') - .andWhere('asset.isArchived = false') - .orderBy('asset.fileCreatedAt', 'DESC'); - - if (!dto.withoutThumbs) { - builder = builder.andWhere('asset.resizePath is not NULL'); - } - - return builder.getMany(); - } - - async getAssetCountByTimeBucket( - userId: string, - dto: GetAssetCountByTimeBucketDto, - ): Promise { - const builder = this.assetRepository - .createQueryBuilder('asset') - .select(`COUNT(asset.id)::int`, 'count') - .where('"ownerId" = :userId', { userId: userId }) - .andWhere('asset.isVisible = true') - .andWhere('asset.isArchived = false'); - - // Using a parameter for this doesn't work https://github.com/typeorm/typeorm/issues/7308 - if (dto.timeGroup === TimeGroupEnum.Month) { - builder - .addSelect(`date_trunc('month', "fileCreatedAt")`, 'timeBucket') - .groupBy(`date_trunc('month', "fileCreatedAt")`) - .orderBy(`date_trunc('month', "fileCreatedAt")`, 'DESC'); - } else if (dto.timeGroup === TimeGroupEnum.Day) { - builder - .addSelect(`date_trunc('day', "fileCreatedAt")`, 'timeBucket') - .groupBy(`date_trunc('day', "fileCreatedAt")`) - .orderBy(`date_trunc('day', "fileCreatedAt")`, 'DESC'); - } - - if (!dto.withoutThumbs) { - builder.andWhere('asset.resizePath is not NULL'); - } - - return builder.getRawMany(); - } - getSearchPropertiesByUserId(userId: string): Promise { return this.assetRepository .createQueryBuilder('asset') diff --git a/server/src/immich/api-v1/asset/asset.controller.ts b/server/src/immich/api-v1/asset/asset.controller.ts index 1e22bf3baa..44973a7af3 100644 --- a/server/src/immich/api-v1/asset/asset.controller.ts +++ b/server/src/immich/api-v1/asset/asset.controller.ts @@ -30,14 +30,11 @@ import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { CreateAssetDto, ImportAssetDto } from './dto/create-asset.dto'; import { DeleteAssetDto } from './dto/delete-asset.dto'; import { DeviceIdDto } from './dto/device-id.dto'; -import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; -import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto'; import { SearchAssetDto } from './dto/search-asset.dto'; import { ServeFileDto } from './dto/serve-file.dto'; import { UpdateAssetDto } from './dto/update-asset.dto'; import { AssetBulkUploadCheckResponseDto } from './response-dto/asset-check-response.dto'; -import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by-time-group-response.dto'; import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; @@ -163,15 +160,6 @@ export class AssetController { return this.assetService.searchAsset(authUser, dto); } - @Post('/count-by-time-bucket') - @HttpCode(HttpStatus.OK) - getAssetCountByTimeBucket( - @AuthUser() authUser: AuthUserDto, - @Body(ValidationPipe) dto: GetAssetCountByTimeBucketDto, - ): Promise { - return this.assetService.getAssetCountByTimeBucket(authUser, dto); - } - /** * Get all AssetEntity belong to the user */ @@ -189,15 +177,6 @@ export class AssetController { return this.assetService.getAllAssets(authUser, dto); } - @Post('/time-bucket') - @HttpCode(HttpStatus.OK) - getAssetByTimeBucket( - @AuthUser() authUser: AuthUserDto, - @Body(ValidationPipe) dto: GetAssetByTimeBucketDto, - ): Promise { - return this.assetService.getAssetByTimeBucket(authUser, dto); - } - /** * Get all asset of a device that are in the database, ID only. */ diff --git a/server/src/immich/api-v1/asset/asset.service.spec.ts b/server/src/immich/api-v1/asset/asset.service.spec.ts index bf7441b4ad..3b8f9a1b9d 100644 --- a/server/src/immich/api-v1/asset/asset.service.spec.ts +++ b/server/src/immich/api-v1/asset/asset.service.spec.ts @@ -16,9 +16,7 @@ import { QueryFailedError, Repository } from 'typeorm'; import { IAssetRepository } from './asset-repository'; import { AssetService } from './asset.service'; import { CreateAssetDto } from './dto/create-asset.dto'; -import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; import { AssetRejectReason, AssetUploadAction } from './response-dto/asset-check-response.dto'; -import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto'; const _getCreateAssetDto = (): CreateAssetDto => { const createAssetDto = new CreateAssetDto(); @@ -83,18 +81,6 @@ const _getAssets = () => { return [_getAsset_1(), _getAsset_2()]; }; -const _getAssetCountByTimeBucket = (): AssetCountByTimeBucket[] => { - const result1 = new AssetCountByTimeBucket(); - result1.count = 2; - result1.timeBucket = '2022-06-01T00:00:00.000Z'; - - const result2 = new AssetCountByTimeBucket(); - result1.count = 5; - result1.timeBucket = '2022-07-01T00:00:00.000Z'; - - return [result1, result2]; -}; - describe('AssetService', () => { let sut: AssetService; let a: Repository; // TO BE DELETED AFTER FINISHED REFACTORING @@ -113,12 +99,10 @@ describe('AssetService', () => { update: jest.fn(), getAllByUserId: jest.fn(), getAllByDeviceId: jest.fn(), - getAssetCountByTimeBucket: jest.fn(), getById: jest.fn(), getDetectedObjectsByUserId: jest.fn(), getLocationsByUserId: jest.fn(), getSearchPropertiesByUserId: jest.fn(), - getAssetByTimeBucket: jest.fn(), getAssetsByChecksums: jest.fn(), getExistingAssets: jest.fn(), getByOriginalPath: jest.fn(), @@ -221,21 +205,6 @@ describe('AssetService', () => { expect(result).toEqual(assets.map((asset) => asset.deviceAssetId)); }); - it('get assets count by time bucket', async () => { - const assetCountByTimeBucket = _getAssetCountByTimeBucket(); - - assetRepositoryMock.getAssetCountByTimeBucket.mockImplementation(() => - Promise.resolve(assetCountByTimeBucket), - ); - - const result = await sut.getAssetCountByTimeBucket(authStub.user1, { - timeGroup: TimeGroupEnum.Month, - }); - - expect(result.totalCount).toEqual(assetCountByTimeBucket.reduce((a, b) => a + b.count, 0)); - expect(result.buckets.length).toEqual(2); - }); - describe('deleteAll', () => { it('should return failed status when an asset is missing', async () => { assetRepositoryMock.get.mockResolvedValue(null); diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index 7fe2bb80ee..b397b2688b 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -37,8 +37,6 @@ import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { CreateAssetDto, ImportAssetDto } from './dto/create-asset.dto'; import { DeleteAssetDto } from './dto/delete-asset.dto'; -import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; -import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto'; import { SearchAssetDto } from './dto/search-asset.dto'; import { SearchPropertiesDto } from './dto/search-properties.dto'; @@ -49,10 +47,6 @@ import { AssetRejectReason, AssetUploadAction, } from './response-dto/asset-check-response.dto'; -import { - AssetCountByTimeBucketResponseDto, - mapAssetCountByTimeBucket, -} from './response-dto/asset-count-by-time-group-response.dto'; import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; @@ -195,13 +189,6 @@ export class AssetService { return assets.map((asset) => mapAsset(asset)); } - public async getAssetByTimeBucket(authUser: AuthUserDto, dto: GetAssetByTimeBucketDto): Promise { - const userId = dto.userId || authUser.id; - await this.access.requirePermission(authUser, Permission.LIBRARY_READ, userId); - const assets = await this._assetRepository.getAssetByTimeBucket(userId, dto); - return assets.map((asset) => mapAsset(asset)); - } - public async getAssetById(authUser: AuthUserDto, assetId: string): Promise { await this.access.requirePermission(authUser, Permission.ASSET_READ, assetId); @@ -457,16 +444,6 @@ export class AssetService { }; } - async getAssetCountByTimeBucket( - authUser: AuthUserDto, - dto: GetAssetCountByTimeBucketDto, - ): Promise { - const userId = dto.userId || authUser.id; - await this.access.requirePermission(authUser, Permission.LIBRARY_READ, userId); - const result = await this._assetRepository.getAssetCountByTimeBucket(userId, dto); - return mapAssetCountByTimeBucket(result); - } - getExifPermission(authUser: AuthUserDto) { return !authUser.isPublicUser || authUser.isShowExif; } diff --git a/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts b/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts deleted file mode 100644 index ddf2d2aa51..0000000000 --- a/server/src/immich/api-v1/asset/dto/get-asset-by-time-bucket.dto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { toBoolean } from '@app/domain'; -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; - -export class GetAssetByTimeBucketDto { - @IsNotEmpty() - @ApiProperty({ - isArray: true, - type: String, - title: 'Array of date time buckets', - example: ['2015-06-01T00:00:00.000Z', '2016-02-01T00:00:00.000Z', '2016-03-01T00:00:00.000Z'], - }) - timeBucket!: string[]; - - @IsOptional() - @IsUUID('4') - @ApiProperty({ format: 'uuid' }) - userId?: string; - - /** - * Include assets without thumbnails - */ - @IsOptional() - @IsBoolean() - @Transform(toBoolean) - withoutThumbs?: boolean; -} diff --git a/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts b/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts deleted file mode 100644 index f1f564a3b5..0000000000 --- a/server/src/immich/api-v1/asset/dto/get-asset-count-by-time-bucket.dto.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { toBoolean } from '@app/domain'; -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; - -export enum TimeGroupEnum { - Day = 'day', - Month = 'month', -} - -export class GetAssetCountByTimeBucketDto { - @IsNotEmpty() - @ApiProperty({ - type: String, - enum: TimeGroupEnum, - enumName: 'TimeGroupEnum', - }) - timeGroup!: TimeGroupEnum; - - @IsOptional() - @IsUUID('4') - @ApiProperty({ format: 'uuid' }) - userId?: string; - - /** - * Include assets without thumbnails - */ - @IsOptional() - @IsBoolean() - @Transform(toBoolean) - withoutThumbs?: boolean; -} diff --git a/server/src/immich/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts b/server/src/immich/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts deleted file mode 100644 index 97579d72c3..0000000000 --- a/server/src/immich/api-v1/asset/response-dto/asset-count-by-time-group-response.dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class AssetCountByTimeBucket { - @ApiProperty({ type: 'string' }) - timeBucket!: string; - - @ApiProperty({ type: 'integer' }) - count!: number; -} - -export class AssetCountByTimeBucketResponseDto { - buckets!: AssetCountByTimeBucket[]; - - @ApiProperty({ type: 'integer' }) - totalCount!: number; -} - -export function mapAssetCountByTimeBucket(result: AssetCountByTimeBucket[]): AssetCountByTimeBucketResponseDto { - return { - buckets: result, - totalCount: result.map((group) => group.count).reduce((a, b) => a + b, 0), - }; -} diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/immich/controllers/asset.controller.ts index 5c4e19ccf2..ba3de02cc5 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/immich/controllers/asset.controller.ts @@ -1,5 +1,6 @@ import { AssetIdsDto, + AssetResponseDto, AssetService, AssetStatsDto, AssetStatsResponseDto, @@ -8,6 +9,9 @@ import { DownloadResponseDto, MapMarkerResponseDto, MemoryLaneDto, + TimeBucketAssetDto, + TimeBucketDto, + TimeBucketResponseDto, } from '@app/domain'; import { MapMarkerDto } from '@app/domain/asset/dto/map-marker.dto'; import { MemoryLaneResponseDto } from '@app/domain/asset/response-dto/memory-lane-response.dto'; @@ -60,4 +64,16 @@ export class AssetController { getAssetStats(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetStatsDto): Promise { return this.service.getStatistics(authUser, dto); } + + @Authenticated({ isShared: true }) + @Get('time-buckets') + getTimeBuckets(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketDto): Promise { + return this.service.getTimeBuckets(authUser, dto); + } + + @Authenticated({ isShared: true }) + @Get('time-bucket') + getByTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise { + return this.service.getByTimeBucket(authUser, dto); + } } diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 82fd0abc31..e5f997f56c 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -8,6 +8,9 @@ import { MapMarkerSearchOptions, Paginated, PaginationOptions, + TimeBucketItem, + TimeBucketOptions, + TimeBucketSize, WithoutProperty, WithProperty, } from '@app/domain'; @@ -19,6 +22,11 @@ import { AssetEntity, AssetType } from '../entities'; import OptionalBetween from '../utils/optional-between.util'; import { paginate } from '../utils/pagination.util'; +const truncateMap: Record = { + [TimeBucketSize.DAY]: 'day', + [TimeBucketSize.MONTH]: 'month', +}; + @Injectable() export class AssetRepository implements IAssetRepository { constructor(@InjectRepository(AssetEntity) private repository: Repository) {} @@ -357,4 +365,47 @@ export class AssetRepository implements IAssetRepository { return result; } + + getTimeBuckets(userId: string, options: TimeBucketOptions): Promise { + const truncateValue = truncateMap[options.size]; + + return this.getBuilder(userId, options) + .select(`COUNT(asset.id)::int`, 'count') + .addSelect(`date_trunc('${truncateValue}', "fileCreatedAt")`, 'timeBucket') + .groupBy(`date_trunc('${truncateValue}', "fileCreatedAt")`) + .orderBy(`date_trunc('${truncateValue}', "fileCreatedAt")`, 'DESC') + .getRawMany(); + } + + getByTimeBucket(userId: string, timeBucket: string, options: TimeBucketOptions): Promise { + const truncateValue = truncateMap[options.size]; + return this.getBuilder(userId, options) + .andWhere(`date_trunc('${truncateValue}', "fileCreatedAt") = :timeBucket`, { timeBucket }) + .orderBy('asset.fileCreatedAt', 'DESC') + .getMany(); + } + + private getBuilder(userId: string, options: TimeBucketOptions) { + const { isArchived, isFavorite, albumId } = options; + + let builder = this.repository + .createQueryBuilder('asset') + .where('asset.ownerId = :userId', { userId }) + .andWhere('asset.isVisible = true') + .leftJoinAndSelect('asset.exifInfo', 'exifInfo'); + + if (albumId) { + builder = builder.leftJoin('asset.albums', 'album').andWhere('album.id = :albumId', { albumId }); + } + + if (isArchived != undefined) { + builder = builder.andWhere('asset.isArchived = :isArchived', { isArchived }); + } + + if (isFavorite !== undefined) { + builder = builder.andWhere('asset.isFavorite = :isFavorite', { isFavorite }); + } + + return builder; + } } diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 5eb69a9e5a..7f1eac8319 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -10,14 +10,13 @@ export const newAssetRepositoryMock = (): jest.Mocked => { getWith: jest.fn(), getFirstAssetForAlbumId: jest.fn(), getLastUpdatedAssetForAlbumId: jest.fn(), - getAll: jest.fn().mockResolvedValue({ - items: [], - hasNextPage: false, - }), + getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }), deleteAll: jest.fn(), save: jest.fn(), findLivePhotoMatch: jest.fn(), getMapMarkers: jest.fn(), getStatistics: jest.fn(), + getByTimeBucket: jest.fn(), + getTimeBuckets: jest.fn(), }; }; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 2d330f6f27..55549dba1b 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -410,44 +410,6 @@ export const AssetBulkUploadCheckResultReasonEnum = { export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum]; -/** - * - * @export - * @interface AssetCountByTimeBucket - */ -export interface AssetCountByTimeBucket { - /** - * - * @type {number} - * @memberof AssetCountByTimeBucket - */ - 'count': number; - /** - * - * @type {string} - * @memberof AssetCountByTimeBucket - */ - 'timeBucket': string; -} -/** - * - * @export - * @interface AssetCountByTimeBucketResponseDto - */ -export interface AssetCountByTimeBucketResponseDto { - /** - * - * @type {Array} - * @memberof AssetCountByTimeBucketResponseDto - */ - 'buckets': Array; - /** - * - * @type {number} - * @memberof AssetCountByTimeBucketResponseDto - */ - 'totalCount': number; -} /** * * @export @@ -1286,58 +1248,6 @@ export interface ExifResponseDto { */ 'timeZone'?: string | null; } -/** - * - * @export - * @interface GetAssetByTimeBucketDto - */ -export interface GetAssetByTimeBucketDto { - /** - * - * @type {Array} - * @memberof GetAssetByTimeBucketDto - */ - 'timeBucket': Array; - /** - * - * @type {string} - * @memberof GetAssetByTimeBucketDto - */ - 'userId'?: string; - /** - * Include assets without thumbnails - * @type {boolean} - * @memberof GetAssetByTimeBucketDto - */ - 'withoutThumbs'?: boolean; -} -/** - * - * @export - * @interface GetAssetCountByTimeBucketDto - */ -export interface GetAssetCountByTimeBucketDto { - /** - * - * @type {TimeGroupEnum} - * @memberof GetAssetCountByTimeBucketDto - */ - 'timeGroup': TimeGroupEnum; - /** - * - * @type {string} - * @memberof GetAssetCountByTimeBucketDto - */ - 'userId'?: string; - /** - * Include assets without thumbnails - * @type {boolean} - * @memberof GetAssetCountByTimeBucketDto - */ - 'withoutThumbs'?: boolean; -} - - /** * * @export @@ -2850,18 +2760,37 @@ export const ThumbnailFormat = { export type ThumbnailFormat = typeof ThumbnailFormat[keyof typeof ThumbnailFormat]; +/** + * + * @export + * @interface TimeBucketResponseDto + */ +export interface TimeBucketResponseDto { + /** + * + * @type {number} + * @memberof TimeBucketResponseDto + */ + 'count': number; + /** + * + * @type {string} + * @memberof TimeBucketResponseDto + */ + 'timeBucket': string; +} /** * * @export * @enum {string} */ -export const TimeGroupEnum = { - Day: 'day', - Month: 'month' +export const TimeBucketSize = { + Day: 'DAY', + Month: 'MONTH' } as const; -export type TimeGroupEnum = typeof TimeGroupEnum[keyof typeof TimeGroupEnum]; +export type TimeBucketSize = typeof TimeBucketSize[keyof typeof TimeBucketSize]; /** @@ -5047,94 +4976,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, - /** - * - * @param {GetAssetByTimeBucketDto} getAssetByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetByTimeBucket: async (getAssetByTimeBucketDto: GetAssetByTimeBucketDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'getAssetByTimeBucketDto' is not null or undefined - assertParamExists('getAssetByTimeBucket', 'getAssetByTimeBucketDto', getAssetByTimeBucketDto) - const localVarPath = `/asset/time-bucket`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(getAssetByTimeBucketDto, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetCountByTimeBucket: async (getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'getAssetCountByTimeBucketDto' is not null or undefined - assertParamExists('getAssetCountByTimeBucket', 'getAssetCountByTimeBucketDto', getAssetCountByTimeBucketDto) - const localVarPath = `/asset/count-by-time-bucket`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(getAssetCountByTimeBucketDto, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * * @param {*} [options] Override http request option. @@ -5264,6 +5105,83 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {TimeBucketSize} size + * @param {string} timeBucket + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getByTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'size' is not null or undefined + assertParamExists('getByTimeBucket', 'size', size) + // verify required parameter 'timeBucket' is not null or undefined + assertParamExists('getByTimeBucket', 'timeBucket', timeBucket) + const localVarPath = `/asset/time-bucket`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + + if (albumId !== undefined) { + localVarQueryParameter['albumId'] = albumId; + } + + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + + if (timeBucket !== undefined) { + localVarQueryParameter['timeBucket'] = timeBucket; + } + + if (key !== undefined) { + localVarQueryParameter['key'] = key; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -5507,6 +5425,76 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {TimeBucketSize} size + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'size' is not null or undefined + assertParamExists('getTimeBuckets', 'size', size) + const localVarPath = `/asset/time-buckets`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + + if (albumId !== undefined) { + localVarQueryParameter['albumId'] = albumId; + } + + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + + if (key !== undefined) { + localVarQueryParameter['key'] = key; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -5969,26 +5957,6 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetById(id, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {GetAssetByTimeBucketDto} getAssetByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getAssetByTimeBucket(getAssetByTimeBucketDto: GetAssetByTimeBucketDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetByTimeBucket(getAssetByTimeBucketDto, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * - * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {*} [options] Override http request option. @@ -6021,6 +5989,22 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetThumbnail(id, format, key, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {TimeBucketSize} size + * @param {string} timeBucket + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getByTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getByTimeBucket(size, timeBucket, userId, albumId, isArchived, isFavorite, key, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -6075,6 +6059,21 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {TimeBucketSize} size + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, isArchived, isFavorite, key, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * Get all asset of a device that are in the database, ID only. * @param {string} deviceId @@ -6242,24 +6241,6 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAssetById(id: string, key?: string, options?: any): AxiosPromise { return localVarFp.getAssetById(id, key, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {GetAssetByTimeBucketDto} getAssetByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetByTimeBucket(getAssetByTimeBucketDto: GetAssetByTimeBucketDto, options?: any): AxiosPromise> { - return localVarFp.getAssetByTimeBucket(getAssetByTimeBucketDto, options).then((request) => request(axios, basePath)); - }, - /** - * - * @param {GetAssetCountByTimeBucketDto} getAssetCountByTimeBucketDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: any): AxiosPromise { - return localVarFp.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath)); - }, /** * * @param {*} [options] Override http request option. @@ -6289,6 +6270,21 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAssetThumbnail(id: string, format?: ThumbnailFormat, key?: string, options?: any): AxiosPromise { return localVarFp.getAssetThumbnail(id, format, key, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {TimeBucketSize} size + * @param {string} timeBucket + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getByTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: any): AxiosPromise> { + return localVarFp.getByTimeBucket(size, timeBucket, userId, albumId, isArchived, isFavorite, key, options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -6338,6 +6334,20 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getMemoryLane(timestamp: string, options?: any): AxiosPromise> { return localVarFp.getMemoryLane(timestamp, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {TimeBucketSize} size + * @param {string} [userId] + * @param {string} [albumId] + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] + * @param {string} [key] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: any): AxiosPromise> { + return localVarFp.getTimeBuckets(size, userId, albumId, isArchived, isFavorite, key, options).then((request) => request(axios, basePath)); + }, /** * Get all asset of a device that are in the database, ID only. * @param {string} deviceId @@ -6586,34 +6596,6 @@ export interface AssetApiGetAssetByIdRequest { readonly key?: string } -/** - * Request parameters for getAssetByTimeBucket operation in AssetApi. - * @export - * @interface AssetApiGetAssetByTimeBucketRequest - */ -export interface AssetApiGetAssetByTimeBucketRequest { - /** - * - * @type {GetAssetByTimeBucketDto} - * @memberof AssetApiGetAssetByTimeBucket - */ - readonly getAssetByTimeBucketDto: GetAssetByTimeBucketDto -} - -/** - * Request parameters for getAssetCountByTimeBucket operation in AssetApi. - * @export - * @interface AssetApiGetAssetCountByTimeBucketRequest - */ -export interface AssetApiGetAssetCountByTimeBucketRequest { - /** - * - * @type {GetAssetCountByTimeBucketDto} - * @memberof AssetApiGetAssetCountByTimeBucket - */ - readonly getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto -} - /** * Request parameters for getAssetStats operation in AssetApi. * @export @@ -6663,6 +6645,62 @@ export interface AssetApiGetAssetThumbnailRequest { readonly key?: string } +/** + * Request parameters for getByTimeBucket operation in AssetApi. + * @export + * @interface AssetApiGetByTimeBucketRequest + */ +export interface AssetApiGetByTimeBucketRequest { + /** + * + * @type {TimeBucketSize} + * @memberof AssetApiGetByTimeBucket + */ + readonly size: TimeBucketSize + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly timeBucket: string + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly userId?: string + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly albumId?: string + + /** + * + * @type {boolean} + * @memberof AssetApiGetByTimeBucket + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof AssetApiGetByTimeBucket + */ + readonly isFavorite?: boolean + + /** + * + * @type {string} + * @memberof AssetApiGetByTimeBucket + */ + readonly key?: string +} + /** * Request parameters for getDownloadInfo operation in AssetApi. * @export @@ -6747,6 +6785,55 @@ export interface AssetApiGetMemoryLaneRequest { readonly timestamp: string } +/** + * Request parameters for getTimeBuckets operation in AssetApi. + * @export + * @interface AssetApiGetTimeBucketsRequest + */ +export interface AssetApiGetTimeBucketsRequest { + /** + * + * @type {TimeBucketSize} + * @memberof AssetApiGetTimeBuckets + */ + readonly size: TimeBucketSize + + /** + * + * @type {string} + * @memberof AssetApiGetTimeBuckets + */ + readonly userId?: string + + /** + * + * @type {string} + * @memberof AssetApiGetTimeBuckets + */ + readonly albumId?: string + + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof AssetApiGetTimeBuckets + */ + readonly isFavorite?: boolean + + /** + * + * @type {string} + * @memberof AssetApiGetTimeBuckets + */ + readonly key?: string +} + /** * Request parameters for getUserAssetsByDeviceId operation in AssetApi. * @export @@ -7038,28 +7125,6 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAssetById(requestParameters.id, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {AssetApiGetAssetByTimeBucketRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public getAssetByTimeBucket(requestParameters: AssetApiGetAssetByTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetByTimeBucket(requestParameters.getAssetByTimeBucketDto, options).then((request) => request(this.axios, this.basePath)); - } - - /** - * - * @param {AssetApiGetAssetCountByTimeBucketRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public getAssetCountByTimeBucket(requestParameters: AssetApiGetAssetCountByTimeBucketRequest, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetCountByTimeBucket(requestParameters.getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {*} [options] Override http request option. @@ -7092,6 +7157,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAssetThumbnail(requestParameters.id, requestParameters.format, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiGetByTimeBucketRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public getByTimeBucket(requestParameters: AssetApiGetByTimeBucketRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. @@ -7145,6 +7221,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {AssetApiGetTimeBucketsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(this.axios, this.basePath)); + } + /** * Get all asset of a device that are in the database, ID only. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. diff --git a/web/src/lib/components/album-page/asset-selection.svelte b/web/src/lib/components/album-page/asset-selection.svelte index 2cac754950..2035256942 100644 --- a/web/src/lib/components/album-page/asset-selection.svelte +++ b/web/src/lib/components/album-page/asset-selection.svelte @@ -3,7 +3,7 @@ import { AssetStore } from '$lib/stores/assets.store'; import { locale } from '$lib/stores/preferences.store'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; - import { TimeGroupEnum, type AssetResponseDto } from '@api'; + import { TimeBucketSize, type AssetResponseDto } from '@api'; import { createEventDispatcher, onMount } from 'svelte'; import { quintOut } from 'svelte/easing'; import { fly } from 'svelte/transition'; @@ -13,7 +13,7 @@ const dispatch = createEventDispatcher(); - const assetStore = new AssetStore({ timeGroup: TimeGroupEnum.Month }); + const assetStore = new AssetStore({ size: TimeBucketSize.Month }); const assetInteractionStore = createAssetInteractionStore(); const { selectedAssets, assetsInAlbumState } = assetInteractionStore; diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 8ce2ddef78..bc7d97d9b7 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -7,6 +7,9 @@ export let hideNavbar = false; export let showUploadButton = false; export let title: string | undefined = undefined; + export let scrollbar = true; + + $: scrollbarClass = scrollbar ? 'immich-scrollbar p-4 pb-8' : 'scrollbar-hidden pl-4';
@@ -32,7 +35,7 @@ -
+
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 4369ecfda7..85800471cb 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -274,7 +274,7 @@
import empty1Url from '$lib/assets/empty-1.svg'; - export let actionHandler: undefined | (() => Promise) = undefined; + export let actionHandler: undefined | (() => unknown) = undefined; export let text = ''; export let alt = ''; diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 4480128207..2e0f06d9cb 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -1,4 +1,4 @@ -import { api, AssetResponseDto, GetAssetCountByTimeBucketDto } from '@api'; +import { api, AssetApiGetTimeBucketsRequest, AssetResponseDto } from '@api'; import { writable } from 'svelte/store'; import { handleError } from '../utils/handle-error'; @@ -9,7 +9,7 @@ export enum BucketPosition { Unknown = 'unknown', } -export type AssetStoreOptions = GetAssetCountByTimeBucketDto; +export type AssetStoreOptions = AssetApiGetTimeBucketsRequest; export interface Viewport { width: number; @@ -51,11 +51,9 @@ export class AssetStore { subscribe = this.store$.subscribe; async init(viewport: Viewport) { - const { data } = await api.assetApi.getAssetCountByTimeBucket({ - getAssetCountByTimeBucketDto: { ...this.options, withoutThumbs: true }, - }); + const { data: buckets } = await api.assetApi.getTimeBuckets(this.options); - this.buckets = data.buckets.map((bucket) => { + this.buckets = buckets.map((bucket) => { const unwrappedWidth = (3 / 2) * bucket.count * THUMBNAIL_HEIGHT * (7 / 10); const rows = Math.ceil(unwrappedWidth / viewport.width); const height = rows * THUMBNAIL_HEIGHT; @@ -101,14 +99,8 @@ export class AssetStore { bucket.cancelToken = new AbortController(); - const { data: assets } = await api.assetApi.getAssetByTimeBucket( - { - getAssetByTimeBucketDto: { - timeBucket: [bucketDate], - ...this.options, - withoutThumbs: true, - }, - }, + const { data: assets } = await api.assetApi.getByTimeBucket( + { ...this.options, timeBucket: bucketDate }, { signal: bucket.cancelToken.signal }, ); diff --git a/web/src/routes/(user)/archive/+page.svelte b/web/src/routes/(user)/archive/+page.svelte index 25bc1f2ff9..13a472e050 100644 --- a/web/src/routes/(user)/archive/+page.svelte +++ b/web/src/routes/(user)/archive/+page.svelte @@ -6,70 +6,55 @@ import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte'; import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte'; import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte'; + import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte'; + import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; - import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte'; - import SelectAll from 'svelte-material-icons/SelectAll.svelte'; - import { archivedAsset } from '$lib/stores/archived-asset.store'; - import { handleError } from '$lib/utils/handle-error'; - import { api, AssetResponseDto } from '@api'; + import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; + import { AssetStore } from '$lib/stores/assets.store'; + import { api, TimeBucketSize } from '@api'; import { onMount } from 'svelte'; import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; import Plus from 'svelte-material-icons/Plus.svelte'; import type { PageData } from './$types'; - import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; export let data: PageData; + let assetCount = 1; - let selectedAssets: Set = new Set(); - $: isMultiSelectionMode = selectedAssets.size > 0; - $: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite); + const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: true }); + const assetInteractionStore = createAssetInteractionStore(); + const { isMultiSelectState, selectedAssets } = assetInteractionStore; + + $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite); onMount(async () => { - try { - const { data: assets } = await api.assetApi.getAllAssets({ - isArchived: true, - withoutThumbs: true, - }); - $archivedAsset = assets; - } catch { - handleError(Error, 'Unable to load archived assets'); - } + const { data: stats } = await api.assetApi.getAssetStats({ isArchived: true }); + assetCount = stats.total; }); - - const onAssetDelete = (assetId: string) => { - $archivedAsset = $archivedAsset.filter((a) => a.id !== assetId); - }; - const handleSelectAll = () => { - selectedAssets = new Set($archivedAsset); - }; - - - {#if $archivedAsset.length === 0} +{#if $isMultiSelectState} + assetInteractionStore.clearMultiselect()}> + assetStore.removeAsset(asset.id)} /> + + + + + + + assetStore.removeAsset(assetId)} /> + + + + + +{/if} + + + {#if assetCount} + + {:else} {/if} - - - {#if isMultiSelectionMode} - (selectedAssets = new Set())}> - onAssetDelete(asset.id)} /> - - - - - - - - - - - - - {/if} - - - diff --git a/web/src/routes/(user)/favorites/+page.svelte b/web/src/routes/(user)/favorites/+page.svelte index 3a397ceb6e..959ab7f81c 100644 --- a/web/src/routes/(user)/favorites/+page.svelte +++ b/web/src/routes/(user)/favorites/+page.svelte @@ -6,60 +6,45 @@ import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte'; import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte'; import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte'; + import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte'; + import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; - import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte'; - import { handleError } from '$lib/utils/handle-error'; - import { api, AssetResponseDto } from '@api'; + import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; + import { AssetStore } from '$lib/stores/assets.store'; + import { api, TimeBucketSize } from '@api'; import { onMount } from 'svelte'; import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; import Plus from 'svelte-material-icons/Plus.svelte'; - import Error from '../../+error.svelte'; import type { PageData } from './$types'; - import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; - import SelectAll from 'svelte-material-icons/SelectAll.svelte'; - - let favorites: AssetResponseDto[] = []; - let selectedAssets: Set = new Set(); export let data: PageData; + let assetCount = 1; - $: isMultiSelectionMode = selectedAssets.size > 0; - $: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived); + const assetStore = new AssetStore({ size: TimeBucketSize.Month, isFavorite: true }); + const assetInteractionStore = createAssetInteractionStore(); + const { isMultiSelectState, selectedAssets } = assetInteractionStore; + + $: isAllArchive = Array.from($selectedAssets).every((asset) => asset.isArchived); onMount(async () => { - try { - const { data: assets } = await api.assetApi.getAllAssets({ - isFavorite: true, - withoutThumbs: true, - }); - favorites = assets; - } catch { - handleError(Error, 'Unable to load favorites'); - } + const { data: stats } = await api.assetApi.getAssetStats({ isFavorite: true }); + assetCount = stats.total; }); - - const handleSelectAll = () => { - selectedAssets = new Set(favorites); - }; - - const onAssetDelete = (assetId: string) => { - favorites = favorites.filter((a) => a.id !== assetId); - }; -{#if isMultiSelectionMode} - (selectedAssets = new Set())}> - onAssetDelete(asset.id)} /> +{#if $isMultiSelectState} + assetInteractionStore.clearMultiselect()}> + assetStore.removeAsset(asset.id)} /> - + - + assetStore.removeAsset(assetId)} /> @@ -67,13 +52,10 @@ {/if} - -
- - {#if favorites.length === 0} - - {/if} - - -
+ + {#if assetCount} + + {:else} + + {/if} diff --git a/web/src/routes/(user)/partners/[userId]/+page.svelte b/web/src/routes/(user)/partners/[userId]/+page.svelte index f09a36d1ab..c7fd51f2a8 100644 --- a/web/src/routes/(user)/partners/[userId]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/+page.svelte @@ -10,7 +10,7 @@ import { AppRoute } from '$lib/constants'; import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { AssetStore } from '$lib/stores/assets.store'; - import { TimeGroupEnum } from '@api'; + import { TimeBucketSize } from '@api'; import { onDestroy } from 'svelte'; import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; import Plus from 'svelte-material-icons/Plus.svelte'; @@ -18,7 +18,7 @@ export let data: PageData; - const assetStore = new AssetStore({ timeGroup: TimeGroupEnum.Month, userId: data.partner.id }); + const assetStore = new AssetStore({ size: TimeBucketSize.Month, userId: data.partner.id }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index 3fe74c27d8..eb2f82e4f2 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -15,8 +15,8 @@ import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { AssetStore } from '$lib/stores/assets.store'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; - import { TimeGroupEnum, api } from '@api'; - import { onDestroy, onMount } from 'svelte'; + import { TimeBucketSize, api } from '@api'; + import { onMount } from 'svelte'; import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; import Plus from 'svelte-material-icons/Plus.svelte'; import type { PageData } from './$types'; @@ -24,30 +24,22 @@ export let data: PageData; let assetCount = 1; - const assetStore = new AssetStore({ timeGroup: TimeGroupEnum.Month }); + const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: false }); const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; + $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite); + onMount(async () => { const { data: stats } = await api.assetApi.getAssetStats(); assetCount = stats.total; }); - - onDestroy(() => { - assetInteractionStore.clearMultiselect(); - }); - - $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite); - - const handleUpload = async () => { - openFileUploadDialog(); - }; {#if $isMultiSelectState} - + assetInteractionStore.clearMultiselect()}> @@ -69,7 +61,7 @@ {:else} - + openFileUploadDialog()} /> {/if}