1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

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 <sergey.kondrikov@gmail.com>

* fix: hide archived photos on main timeline

* fix: select exif info

---------

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>
This commit is contained in:
Jason Rasmussen 2023-08-04 17:07:15 -04:00 committed by GitHub
parent e5bdf671b5
commit c6abef186c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1132 additions and 1038 deletions

View File

@ -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<AssetCountByTimeBucket>}
* @memberof AssetCountByTimeBucketResponseDto
*/
'buckets': Array<AssetCountByTimeBucket>;
/**
*
* @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<string>}
* @memberof GetAssetByTimeBucketDto
*/
'timeBucket': Array<string>;
/**
*
* @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<RequestArgs> => {
// 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<RequestArgs> => {
// 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<RequestArgs> => {
// 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<RequestArgs> => {
// 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<Array<AssetResponseDto>>> {
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<AssetCountByTimeBucketResponseDto>> {
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<Array<AssetResponseDto>>> {
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<Array<TimeBucketResponseDto>>> {
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<AssetResponseDto> {
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<Array<AssetResponseDto>> {
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<AssetCountByTimeBucketResponseDto> {
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<File> {
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<Array<AssetResponseDto>> {
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<Array<MemoryLaneResponseDto>> {
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<Array<TimeBucketResponseDto>> {
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.

View File

@ -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

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -948,48 +948,6 @@
]
}
},
"/asset/count-by-time-bucket": {
"post": {
"operationId": "getAssetCountByTimeBucket",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetAssetCountByTimeBucketDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetCountByTimeBucketResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Asset"
]
}
},
"/asset/curated-locations": {
"get": {
"operationId": "getCuratedLocations",
@ -1697,19 +1655,68 @@
}
},
"/asset/time-bucket": {
"post": {
"operationId": "getAssetByTimeBucket",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"get": {
"operationId": "getByTimeBucket",
"parameters": [
{
"name": "size",
"required": true,
"in": "query",
"schema": {
"$ref": "#/components/schemas/GetAssetByTimeBucketDto"
}
"$ref": "#/components/schemas/TimeBucketSize"
}
},
"required": true
{
"name": "userId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "albumId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "isArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isFavorite",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "timeBucket",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
@ -1726,6 +1733,110 @@
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
},
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Asset"
]
}
},
"/asset/time-buckets": {
"get": {
"operationId": "getTimeBuckets",
"parameters": [
{
"name": "size",
"required": true,
"in": "query",
"schema": {
"$ref": "#/components/schemas/TimeBucketSize"
}
},
{
"name": "userId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "albumId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "isArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isFavorite",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "key",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/TimeBucketResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
},
{
"bearer": []
},
@ -4787,39 +4898,6 @@
],
"type": "object"
},
"AssetCountByTimeBucket": {
"properties": {
"count": {
"type": "integer"
},
"timeBucket": {
"type": "string"
}
},
"required": [
"timeBucket",
"count"
],
"type": "object"
},
"AssetCountByTimeBucketResponseDto": {
"properties": {
"buckets": {
"items": {
"$ref": "#/components/schemas/AssetCountByTimeBucket"
},
"type": "array"
},
"totalCount": {
"type": "integer"
}
},
"required": [
"totalCount",
"buckets"
],
"type": "object"
},
"AssetFileUploadResponseDto": {
"properties": {
"duplicate": {
@ -5554,53 +5632,6 @@
},
"type": "object"
},
"GetAssetByTimeBucketDto": {
"properties": {
"timeBucket": {
"example": [
"2015-06-01T00:00:00.000Z",
"2016-02-01T00:00:00.000Z",
"2016-03-01T00:00:00.000Z"
],
"items": {
"type": "string"
},
"title": "Array of date time buckets",
"type": "array"
},
"userId": {
"format": "uuid",
"type": "string"
},
"withoutThumbs": {
"description": "Include assets without thumbnails",
"type": "boolean"
}
},
"required": [
"timeBucket"
],
"type": "object"
},
"GetAssetCountByTimeBucketDto": {
"properties": {
"timeGroup": {
"$ref": "#/components/schemas/TimeGroupEnum"
},
"userId": {
"format": "uuid",
"type": "string"
},
"withoutThumbs": {
"description": "Include assets without thumbnails",
"type": "boolean"
}
},
"required": [
"timeGroup"
],
"type": "object"
},
"ImportAssetDto": {
"properties": {
"assetPath": {
@ -6806,10 +6837,25 @@
],
"type": "string"
},
"TimeGroupEnum": {
"TimeBucketResponseDto": {
"properties": {
"count": {
"type": "integer"
},
"timeBucket": {
"type": "string"
}
},
"required": [
"timeBucket",
"count"
],
"type": "object"
},
"TimeBucketSize": {
"enum": [
"day",
"month"
"DAY",
"MONTH"
],
"type": "string"
},

View File

@ -47,6 +47,23 @@ export enum WithProperty {
SIDECAR = 'sidecar',
}
export enum TimeBucketSize {
DAY = 'DAY',
MONTH = 'MONTH',
}
export interface TimeBucketOptions {
size: TimeBucketSize;
isArchived?: boolean;
isFavorite?: boolean;
albumId?: string;
}
export interface TimeBucketItem {
timeBucket: string;
count: number;
}
export const IAssetRepository = 'IAssetRepository';
export interface IAssetRepository {
@ -64,4 +81,6 @@ export interface IAssetRepository {
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(userId: string, options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getByTimeBucket(userId: string, timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
}

View File

@ -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<TimeBucketResponseDto[]> {
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<AssetResponseDto[]> {
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<ImmichReadStream> {
await this.access.requirePermission(authUser, Permission.ASSET_DOWNLOAD, id);

View File

@ -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';

View File

@ -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;
}

View File

@ -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';

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class TimeBucketResponseDto {
@ApiProperty({ type: 'string' })
timeBucket!: string;
@ApiProperty({ type: 'integer' })
count!: number;
}

View File

@ -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<CuratedLocationsResponseDto[]>;
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
getAssetCountByTimeBucket(userId: string, dto: GetAssetCountByTimeBucketDto): Promise<AssetCountByTimeBucket[]>;
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise<AssetCheck[]>;
getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<string[]>;
getByOriginalPath(originalPath: string): Promise<AssetOwnerCheck | null>;
@ -52,57 +47,6 @@ export class AssetRepository implements IAssetRepository {
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
) {}
async getAssetByTimeBucket(userId: string, dto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> {
// 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<AssetCountByTimeBucket[]> {
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<SearchPropertiesDto[]> {
return this.assetRepository
.createQueryBuilder('asset')

View File

@ -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<AssetCountByTimeBucketResponseDto> {
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<AssetResponseDto[]> {
return this.assetService.getAssetByTimeBucket(authUser, dto);
}
/**
* Get all asset of a device that are in the database, ID only.
*/

View File

@ -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<AssetEntity>; // 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[]>(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);

View File

@ -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<AssetResponseDto[]> {
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<AssetResponseDto> {
await this.access.requirePermission(authUser, Permission.ASSET_READ, assetId);
@ -457,16 +444,6 @@ export class AssetService {
};
}
async getAssetCountByTimeBucket(
authUser: AuthUserDto,
dto: GetAssetCountByTimeBucketDto,
): Promise<AssetCountByTimeBucketResponseDto> {
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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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),
};
}

View File

@ -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<AssetStatsResponseDto> {
return this.service.getStatistics(authUser, dto);
}
@Authenticated({ isShared: true })
@Get('time-buckets')
getTimeBuckets(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
return this.service.getTimeBuckets(authUser, dto);
}
@Authenticated({ isShared: true })
@Get('time-bucket')
getByTimeBucket(@AuthUser() authUser: AuthUserDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
return this.service.getByTimeBucket(authUser, dto);
}
}

View File

@ -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, string> = {
[TimeBucketSize.DAY]: 'day',
[TimeBucketSize.MONTH]: 'month',
};
@Injectable()
export class AssetRepository implements IAssetRepository {
constructor(@InjectRepository(AssetEntity) private repository: Repository<AssetEntity>) {}
@ -357,4 +365,47 @@ export class AssetRepository implements IAssetRepository {
return result;
}
getTimeBuckets(userId: string, options: TimeBucketOptions): Promise<TimeBucketItem[]> {
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<AssetEntity[]> {
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;
}
}

View File

@ -10,14 +10,13 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
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(),
};
};

View File

@ -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<AssetCountByTimeBucket>}
* @memberof AssetCountByTimeBucketResponseDto
*/
'buckets': Array<AssetCountByTimeBucket>;
/**
*
* @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<string>}
* @memberof GetAssetByTimeBucketDto
*/
'timeBucket': Array<string>;
/**
*
* @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<RequestArgs> => {
// 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<RequestArgs> => {
// 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<RequestArgs> => {
// 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<RequestArgs> => {
// 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<Array<AssetResponseDto>>> {
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<AssetCountByTimeBucketResponseDto>> {
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<Array<AssetResponseDto>>> {
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<Array<TimeBucketResponseDto>>> {
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<AssetResponseDto> {
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<Array<AssetResponseDto>> {
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<AssetCountByTimeBucketResponseDto> {
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<File> {
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<Array<AssetResponseDto>> {
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<Array<MemoryLaneResponseDto>> {
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<Array<TimeBucketResponseDto>> {
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.

View File

@ -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;

View File

@ -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';
</script>
<header>
@ -32,7 +35,7 @@
<slot name="buttons" />
</div>
<div class="immich-scrollbar absolute top-16 h-[calc(100%-theme(spacing.16))] w-full overflow-y-auto p-4 pb-8">
<div class="{scrollbarClass} absolute top-16 h-[calc(100%-theme(spacing.16))] w-full overflow-y-auto">
<slot />
</div>
</section>

View File

@ -274,7 +274,7 @@
<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
<section
id="asset-grid"
class="scrollbar-hidden ml-4 mr-[60px] overflow-y-auto pb-4"
class="scrollbar-hidden ml-4 mr-[60px] h-full overflow-y-auto pb-4"
bind:clientHeight={viewport.height}
bind:clientWidth={viewport.width}
bind:this={element}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import empty1Url from '$lib/assets/empty-1.svg';
export let actionHandler: undefined | (() => Promise<void>) = undefined;
export let actionHandler: undefined | (() => unknown) = undefined;
export let text = '';
export let alt = '';

View File

@ -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 },
);

View File

@ -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<AssetResponseDto> = 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,
const { data: stats } = await api.assetApi.getAssetStats({ isArchived: true });
assetCount = stats.total;
});
$archivedAsset = assets;
} catch {
handleError(Error, 'Unable to load archived assets');
}
});
const onAssetDelete = (assetId: string) => {
$archivedAsset = $archivedAsset.filter((a) => a.id !== assetId);
};
const handleSelectAll = () => {
selectedAssets = new Set($archivedAsset);
};
</script>
<UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode} title={data.meta.title}>
<!-- Empty Message -->
{#if $archivedAsset.length === 0}
<EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" />
{/if}
<svelte:fragment slot="header">
{#if isMultiSelectionMode}
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
<ArchiveAction unarchive onAssetArchive={(asset) => onAssetDelete(asset.id)} />
<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
{#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<ArchiveAction unarchive onAssetArchive={(asset) => assetStore.removeAsset(asset.id)} />
<CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={Plus} title="Add">
<AddToAlbum />
<AddToAlbum shared />
</AssetSelectContextMenu>
<DeleteAssets {onAssetDelete} />
<DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
<AssetSelectContextMenu icon={DotsVertical} title="Add">
<DownloadAction menuItem />
<FavoriteAction menuItem removeFavorite={isAllFavorite} />
</AssetSelectContextMenu>
</AssetSelectControlBar>
{/if}
</svelte:fragment>
{/if}
<GalleryViewer assets={$archivedAsset} bind:selectedAssets viewFrom="archive-page" />
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
{#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} />
{:else}
<EmptyPlaceholder text="Archive photos and videos to hide them from your Photos view" alt="Empty archive" />
{/if}
</UserPageLayout>

View File

@ -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<AssetResponseDto> = 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,
const { data: stats } = await api.assetApi.getAssetStats({ isFavorite: true });
assetCount = stats.total;
});
favorites = assets;
} catch {
handleError(Error, 'Unable to load favorites');
}
});
const handleSelectAll = () => {
selectedAssets = new Set(favorites);
};
const onAssetDelete = (assetId: string) => {
favorites = favorites.filter((a) => a.id !== assetId);
};
</script>
<!-- Multiselection mode app bar -->
{#if isMultiSelectionMode}
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
<FavoriteAction removeFavorite onAssetFavorite={(asset) => onAssetDelete(asset.id)} />
{#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<FavoriteAction removeFavorite onAssetFavorite={(asset) => assetStore.removeAsset(asset.id)} />
<CreateSharedLink />
<CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
<SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={Plus} title="Add">
<AddToAlbum />
<AddToAlbum shared />
</AssetSelectContextMenu>
<DeleteAssets {onAssetDelete} />
<DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
<AssetSelectContextMenu icon={DotsVertical} title="Menu">
<DownloadAction menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} />
@ -67,13 +52,10 @@
</AssetSelectControlBar>
{/if}
<UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode} title={data.meta.title}>
<section>
<!-- Empty Message -->
{#if favorites.length === 0}
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={!assetCount}>
{#if assetCount}
<AssetGrid {assetStore} {assetInteractionStore} />
{:else}
<EmptyPlaceholder text="Add favorites to quickly find your best pictures and videos" alt="Empty favorites" />
{/if}
<GalleryViewer assets={favorites} bind:selectedAssets viewFrom="favorites-page" />
</section>
</UserPageLayout>

View File

@ -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;

View File

@ -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();
};
</script>
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} showUploadButton>
<svelte:fragment slot="header">
{#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={Plus} title="Add">
@ -69,7 +61,7 @@
<MemoryLane />
</AssetGrid>
{:else}
<EmptyPlaceholder text="CLICK TO UPLOAD YOUR FIRST PHOTO" actionHandler={handleUpload} />
<EmptyPlaceholder text="CLICK TO UPLOAD YOUR FIRST PHOTO" actionHandler={() => openFileUploadDialog()} />
{/if}
</svelte:fragment>
</UserPageLayout>