Compare commits

...

31 commits

Author SHA1 Message Date
martabal
c9feb474be
use icons 2023-10-26 00:20:20 +02:00
martabal
00ee2aca62
fix merge 2023-10-25 23:54:29 +02:00
martabal
fc0d358d4e
merge main 2023-10-25 23:41:01 +02:00
martabal
c5ca5a528e
feat: unassign faces 2023-10-25 23:36:51 +02:00
martabal
9247eb4c1a
add available person 2023-10-25 15:44:11 +02:00
martabal
c8d173697f
feat: add number of unassigned faces 2023-10-24 23:34:55 +02:00
martabal
f42cf33605
regenerate api 2023-10-24 22:21:06 +02:00
martabal
6027a2fda6
merge main 2023-10-24 22:07:17 +02:00
martabal
24f9968606
regenerate api 2023-10-21 15:03:36 +02:00
martabal
7a557c08db
merge main 2023-10-21 15:01:25 +02:00
martabal
89ad9d58c5
fix: migration 2023-10-21 15:00:06 +02:00
martabal
1a1b3dd996
Merge branch 'main' into feat/unmerge-people 2023-10-13 17:07:32 +02:00
martabal
aa6c90c66c
simplify 2023-10-12 10:56:48 +02:00
martabal
7a0047f631
feat: add remote search 2023-10-11 23:47:32 +02:00
martabal
6ef7844613
regenerate api 2023-10-11 20:51:42 +02:00
martabal
00d7052b84
merge main 2023-10-11 20:50:36 +02:00
martabal
47b3a8bad9
Merge branch 'main' into feat/unmerge-people 2023-10-09 10:13:00 +02:00
martabal
d7489cf808
use fonction 2023-10-08 14:33:56 +02:00
martabal
348a827281
feat: change feature photo if it's unmerged 2023-10-08 12:49:45 +02:00
martabal
2babfe4ad4
fix: change name when switching face 2023-10-07 17:16:47 +02:00
martabal
8699d8cfbf
regenerate api 2023-10-07 17:02:44 +02:00
martabal
26b2f66f0f
merge main 2023-10-07 16:53:37 +02:00
martabal
8c37a7cb60
feat: add modularity 2023-10-07 16:51:53 +02:00
martabal
02f1b828d3
feat: add notifications on person page 2023-10-04 20:47:40 +02:00
martabal
977a17f9c0
fix: people page 2023-10-04 17:21:53 +02:00
martabal
4dcc1131fa
regenerate api 2023-10-04 14:06:18 +02:00
martabal
698e45a5cb
fix merge 2023-10-04 13:52:51 +02:00
martabal
f6170e5f7f
merge main 2023-10-04 13:40:00 +02:00
martabal
79f7a95e66
feat: rework ui 2023-10-04 13:37:12 +02:00
martabal
a1f54de082
improve webui 2023-09-21 10:56:37 +02:00
martabal
c30a5db8f4
feat: unmerge people 2023-09-21 00:45:16 +02:00
45 changed files with 3961 additions and 310 deletions

View file

@ -502,6 +502,81 @@ export const AssetBulkUploadCheckResultReasonEnum = {
export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum]; export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum];
/**
*
* @export
* @interface AssetFaceBoxDto
*/
export interface AssetFaceBoxDto {
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxX1': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxX2': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxY1': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxY2': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'imageHeight': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'imageWidth': number;
}
/**
*
* @export
* @interface AssetFaceUpdateDto
*/
export interface AssetFaceUpdateDto {
/**
*
* @type {Array<AssetFaceUpdateItem>}
* @memberof AssetFaceUpdateDto
*/
'data': Array<AssetFaceUpdateItem>;
}
/**
*
* @export
* @interface AssetFaceUpdateItem
*/
export interface AssetFaceUpdateItem {
/**
*
* @type {string}
* @memberof AssetFaceUpdateItem
*/
'assetId': string;
/**
*
* @type {string}
* @memberof AssetFaceUpdateItem
*/
'personId': string;
}
/** /**
* *
* @export * @export
@ -744,10 +819,10 @@ export interface AssetResponseDto {
'ownerId': string; 'ownerId': string;
/** /**
* *
* @type {Array<PersonResponseDto>} * @type {Array<PeopleAssetResponseDto>}
* @memberof AssetResponseDto * @memberof AssetResponseDto
*/ */
'people'?: Array<PersonResponseDto>; 'people'?: Array<PeopleAssetResponseDto>;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -796,6 +871,12 @@ export interface AssetResponseDto {
* @memberof AssetResponseDto * @memberof AssetResponseDto
*/ */
'type': AssetTypeEnum; 'type': AssetTypeEnum;
/**
*
* @type {Array<UnassignedFacesResponseDto>}
* @memberof AssetResponseDto
*/
'unassignedPeople'?: Array<UnassignedFacesResponseDto>;
/** /**
* *
* @type {string} * @type {string}
@ -2315,6 +2396,25 @@ export const PathType = {
export type PathType = typeof PathType[keyof typeof PathType]; export type PathType = typeof PathType[keyof typeof PathType];
/**
*
* @export
* @interface PeopleAssetResponseDto
*/
export interface PeopleAssetResponseDto {
/**
*
* @type {string}
* @memberof PeopleAssetResponseDto
*/
'assetFaceId': string;
/**
*
* @type {PersonResponseDto}
* @memberof PeopleAssetResponseDto
*/
'person': PersonResponseDto;
}
/** /**
* *
* @export * @export
@ -3949,6 +4049,31 @@ export const TranscodePolicy = {
export type TranscodePolicy = typeof TranscodePolicy[keyof typeof TranscodePolicy]; export type TranscodePolicy = typeof TranscodePolicy[keyof typeof TranscodePolicy];
/**
*
* @export
* @interface UnassignedFacesResponseDto
*/
export interface UnassignedFacesResponseDto {
/**
*
* @type {string}
* @memberof UnassignedFacesResponseDto
*/
'assetFaceId': string;
/**
*
* @type {string}
* @memberof UnassignedFacesResponseDto
*/
'assetId': string;
/**
*
* @type {AssetFaceBoxDto}
* @memberof UnassignedFacesResponseDto
*/
'boudinxBox': AssetFaceBoxDto;
}
/** /**
* *
* @export * @export
@ -11766,6 +11891,50 @@ export class PartnerApi extends BaseAPI {
*/ */
export const PersonApiAxiosParamCreator = function (configuration?: Configuration) { export const PersonApiAxiosParamCreator = function (configuration?: Configuration) {
return { return {
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createPerson: async (assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('createPerson', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person`;
// 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(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {boolean} [withHidden] * @param {boolean} [withHidden]
@ -11800,6 +11969,52 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} id
* @param {string} assetId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAssetFace: async (id: string, assetId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('getAssetFace', 'id', id)
// verify required parameter 'assetId' is not null or undefined
assertParamExists('getAssetFace', 'assetId', assetId)
const localVarPath = `/person/{id}/{assetId}/faceasset`
.replace(`{${"id"}}`, encodeURIComponent(String(id)))
.replace(`{${"assetId"}}`, encodeURIComponent(String(assetId)));
// 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)
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@ -12025,6 +12240,98 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
options: localVarRequestOptions, options: localVarRequestOptions,
}; };
}, },
/**
*
* @param {string} id
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFaces: async (id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('reassignFaces', 'id', id)
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('reassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person/{id}/reassign`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// 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: 'PUT', ...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(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces: async (assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('unassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person`;
// 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: 'DELETE', ...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(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {PeopleUpdateDto} peopleUpdateDto * @param {PeopleUpdateDto} peopleUpdateDto
@ -12127,6 +12434,16 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
export const PersonApiFp = function(configuration?: Configuration) { export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration) const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration)
return { return {
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createPerson(assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createPerson(assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {boolean} [withHidden] * @param {boolean} [withHidden]
@ -12137,6 +12454,17 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @param {string} id
* @param {string} assetId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getAssetFace(id: string, assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetFaceBoxDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetFace(id, assetId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {string} id * @param {string} id
@ -12188,6 +12516,27 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options); const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @param {string} id
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async reassignFaces(id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<PersonResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFaces(id, assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async unassignFaces(assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BulkIdResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.unassignFaces(assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {PeopleUpdateDto} peopleUpdateDto * @param {PeopleUpdateDto} peopleUpdateDto
@ -12219,6 +12568,15 @@ export const PersonApiFp = function(configuration?: Configuration) {
export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = PersonApiFp(configuration) const localVarFp = PersonApiFp(configuration)
return { return {
/**
*
* @param {PersonApiCreatePersonRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createPerson(requestParameters: PersonApiCreatePersonRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.createPerson(requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
@ -12228,6 +12586,15 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig): AxiosPromise<PeopleResponseDto> { getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig): AxiosPromise<PeopleResponseDto> {
return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath)); return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath));
}, },
/**
*
* @param {PersonApiGetAssetFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAssetFace(requestParameters: PersonApiGetAssetFaceRequest, options?: AxiosRequestConfig): AxiosPromise<AssetFaceBoxDto> {
return localVarFp.getAssetFace(requestParameters.id, requestParameters.assetId, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiGetPersonRequest} requestParameters Request parameters. * @param {PersonApiGetPersonRequest} requestParameters Request parameters.
@ -12273,6 +12640,24 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> { mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath)); return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath));
}, },
/**
*
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
return localVarFp.reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters. * @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
@ -12294,6 +12679,20 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
}; };
}; };
/**
* Request parameters for createPerson operation in PersonApi.
* @export
* @interface PersonApiCreatePersonRequest
*/
export interface PersonApiCreatePersonRequest {
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiCreatePerson
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/** /**
* Request parameters for getAllPeople operation in PersonApi. * Request parameters for getAllPeople operation in PersonApi.
* @export * @export
@ -12308,6 +12707,27 @@ export interface PersonApiGetAllPeopleRequest {
readonly withHidden?: boolean readonly withHidden?: boolean
} }
/**
* Request parameters for getAssetFace operation in PersonApi.
* @export
* @interface PersonApiGetAssetFaceRequest
*/
export interface PersonApiGetAssetFaceRequest {
/**
*
* @type {string}
* @memberof PersonApiGetAssetFace
*/
readonly id: string
/**
*
* @type {string}
* @memberof PersonApiGetAssetFace
*/
readonly assetId: string
}
/** /**
* Request parameters for getPerson operation in PersonApi. * Request parameters for getPerson operation in PersonApi.
* @export * @export
@ -12385,6 +12805,41 @@ export interface PersonApiMergePersonRequest {
readonly mergePersonDto: MergePersonDto readonly mergePersonDto: MergePersonDto
} }
/**
* Request parameters for reassignFaces operation in PersonApi.
* @export
* @interface PersonApiReassignFacesRequest
*/
export interface PersonApiReassignFacesRequest {
/**
*
* @type {string}
* @memberof PersonApiReassignFaces
*/
readonly id: string
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiReassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/**
* Request parameters for unassignFaces operation in PersonApi.
* @export
* @interface PersonApiUnassignFacesRequest
*/
export interface PersonApiUnassignFacesRequest {
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiUnassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/** /**
* Request parameters for updatePeople operation in PersonApi. * Request parameters for updatePeople operation in PersonApi.
* @export * @export
@ -12427,6 +12882,17 @@ export interface PersonApiUpdatePersonRequest {
* @extends {BaseAPI} * @extends {BaseAPI}
*/ */
export class PersonApi extends BaseAPI { export class PersonApi extends BaseAPI {
/**
*
* @param {PersonApiCreatePersonRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public createPerson(requestParameters: PersonApiCreatePersonRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).createPerson(requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
@ -12438,6 +12904,17 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @param {PersonApiGetAssetFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public getAssetFace(requestParameters: PersonApiGetAssetFaceRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).getAssetFace(requestParameters.id, requestParameters.assetId, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiGetPersonRequest} requestParameters Request parameters. * @param {PersonApiGetPersonRequest} requestParameters Request parameters.
@ -12493,6 +12970,28 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath)); return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters. * @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.

View file

@ -21,6 +21,9 @@ doc/AssetBulkUploadCheckDto.md
doc/AssetBulkUploadCheckItem.md doc/AssetBulkUploadCheckItem.md
doc/AssetBulkUploadCheckResponseDto.md doc/AssetBulkUploadCheckResponseDto.md
doc/AssetBulkUploadCheckResult.md doc/AssetBulkUploadCheckResult.md
doc/AssetFaceBoxDto.md
doc/AssetFaceUpdateDto.md
doc/AssetFaceUpdateItem.md
doc/AssetFileUploadResponseDto.md doc/AssetFileUploadResponseDto.md
doc/AssetIdsDto.md doc/AssetIdsDto.md
doc/AssetIdsResponseDto.md doc/AssetIdsResponseDto.md
@ -89,6 +92,7 @@ doc/OAuthConfigResponseDto.md
doc/PartnerApi.md doc/PartnerApi.md
doc/PathEntityType.md doc/PathEntityType.md
doc/PathType.md doc/PathType.md
doc/PeopleAssetResponseDto.md
doc/PeopleResponseDto.md doc/PeopleResponseDto.md
doc/PeopleUpdateDto.md doc/PeopleUpdateDto.md
doc/PeopleUpdateItem.md doc/PeopleUpdateItem.md
@ -147,6 +151,7 @@ doc/TimeBucketSize.md
doc/ToneMapping.md doc/ToneMapping.md
doc/TranscodeHWAccel.md doc/TranscodeHWAccel.md
doc/TranscodePolicy.md doc/TranscodePolicy.md
doc/UnassignedFacesResponseDto.md
doc/UpdateAlbumDto.md doc/UpdateAlbumDto.md
doc/UpdateAssetDto.md doc/UpdateAssetDto.md
doc/UpdateLibraryDto.md doc/UpdateLibraryDto.md
@ -200,6 +205,9 @@ lib/model/asset_bulk_upload_check_dto.dart
lib/model/asset_bulk_upload_check_item.dart lib/model/asset_bulk_upload_check_item.dart
lib/model/asset_bulk_upload_check_response_dto.dart lib/model/asset_bulk_upload_check_response_dto.dart
lib/model/asset_bulk_upload_check_result.dart lib/model/asset_bulk_upload_check_result.dart
lib/model/asset_face_box_dto.dart
lib/model/asset_face_update_dto.dart
lib/model/asset_face_update_item.dart
lib/model/asset_file_upload_response_dto.dart lib/model/asset_file_upload_response_dto.dart
lib/model/asset_ids_dto.dart lib/model/asset_ids_dto.dart
lib/model/asset_ids_response_dto.dart lib/model/asset_ids_response_dto.dart
@ -262,6 +270,7 @@ lib/model/o_auth_config_dto.dart
lib/model/o_auth_config_response_dto.dart lib/model/o_auth_config_response_dto.dart
lib/model/path_entity_type.dart lib/model/path_entity_type.dart
lib/model/path_type.dart lib/model/path_type.dart
lib/model/people_asset_response_dto.dart
lib/model/people_response_dto.dart lib/model/people_response_dto.dart
lib/model/people_update_dto.dart lib/model/people_update_dto.dart
lib/model/people_update_item.dart lib/model/people_update_item.dart
@ -314,6 +323,7 @@ lib/model/time_bucket_size.dart
lib/model/tone_mapping.dart lib/model/tone_mapping.dart
lib/model/transcode_hw_accel.dart lib/model/transcode_hw_accel.dart
lib/model/transcode_policy.dart lib/model/transcode_policy.dart
lib/model/unassigned_faces_response_dto.dart
lib/model/update_album_dto.dart lib/model/update_album_dto.dart
lib/model/update_asset_dto.dart lib/model/update_asset_dto.dart
lib/model/update_library_dto.dart lib/model/update_library_dto.dart
@ -344,6 +354,9 @@ test/asset_bulk_upload_check_dto_test.dart
test/asset_bulk_upload_check_item_test.dart test/asset_bulk_upload_check_item_test.dart
test/asset_bulk_upload_check_response_dto_test.dart test/asset_bulk_upload_check_response_dto_test.dart
test/asset_bulk_upload_check_result_test.dart test/asset_bulk_upload_check_result_test.dart
test/asset_face_box_dto_test.dart
test/asset_face_update_dto_test.dart
test/asset_face_update_item_test.dart
test/asset_file_upload_response_dto_test.dart test/asset_file_upload_response_dto_test.dart
test/asset_ids_dto_test.dart test/asset_ids_dto_test.dart
test/asset_ids_response_dto_test.dart test/asset_ids_response_dto_test.dart
@ -412,6 +425,7 @@ test/o_auth_config_response_dto_test.dart
test/partner_api_test.dart test/partner_api_test.dart
test/path_entity_type_test.dart test/path_entity_type_test.dart
test/path_type_test.dart test/path_type_test.dart
test/people_asset_response_dto_test.dart
test/people_response_dto_test.dart test/people_response_dto_test.dart
test/people_update_dto_test.dart test/people_update_dto_test.dart
test/people_update_item_test.dart test/people_update_item_test.dart
@ -470,6 +484,7 @@ test/time_bucket_size_test.dart
test/tone_mapping_test.dart test/tone_mapping_test.dart
test/transcode_hw_accel_test.dart test/transcode_hw_accel_test.dart
test/transcode_policy_test.dart test/transcode_policy_test.dart
test/unassigned_faces_response_dto_test.dart
test/update_album_dto_test.dart test/update_album_dto_test.dart
test/update_asset_dto_test.dart test/update_asset_dto_test.dart
test/update_library_dto_test.dart test/update_library_dto_test.dart

View file

@ -148,12 +148,16 @@ Class | Method | HTTP request | Description
*PartnerApi* | [**createPartner**](doc//PartnerApi.md#createpartner) | **POST** /partner/{id} | *PartnerApi* | [**createPartner**](doc//PartnerApi.md#createpartner) | **POST** /partner/{id} |
*PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner | *PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner |
*PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} | *PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} |
*PersonApi* | [**createPerson**](doc//PersonApi.md#createperson) | **POST** /person |
*PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person | *PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person |
*PersonApi* | [**getAssetFace**](doc//PersonApi.md#getassetface) | **GET** /person/{id}/{assetId}/faceasset |
*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} | *PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} |
*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | *PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
*PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics | *PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | *PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge | *PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
*PersonApi* | [**reassignFaces**](doc//PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
*PersonApi* | [**unassignFaces**](doc//PersonApi.md#unassignfaces) | **DELETE** /person |
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person | *PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person |
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} | *PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | *SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
@ -215,6 +219,9 @@ Class | Method | HTTP request | Description
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md) - [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
- [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md) - [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md)
- [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md) - [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md)
- [AssetFaceBoxDto](doc//AssetFaceBoxDto.md)
- [AssetFaceUpdateDto](doc//AssetFaceUpdateDto.md)
- [AssetFaceUpdateItem](doc//AssetFaceUpdateItem.md)
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md) - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
- [AssetIdsDto](doc//AssetIdsDto.md) - [AssetIdsDto](doc//AssetIdsDto.md)
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md) - [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
@ -277,6 +284,7 @@ Class | Method | HTTP request | Description
- [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md)
- [PathEntityType](doc//PathEntityType.md) - [PathEntityType](doc//PathEntityType.md)
- [PathType](doc//PathType.md) - [PathType](doc//PathType.md)
- [PeopleAssetResponseDto](doc//PeopleAssetResponseDto.md)
- [PeopleResponseDto](doc//PeopleResponseDto.md) - [PeopleResponseDto](doc//PeopleResponseDto.md)
- [PeopleUpdateDto](doc//PeopleUpdateDto.md) - [PeopleUpdateDto](doc//PeopleUpdateDto.md)
- [PeopleUpdateItem](doc//PeopleUpdateItem.md) - [PeopleUpdateItem](doc//PeopleUpdateItem.md)
@ -329,6 +337,7 @@ Class | Method | HTTP request | Description
- [ToneMapping](doc//ToneMapping.md) - [ToneMapping](doc//ToneMapping.md)
- [TranscodeHWAccel](doc//TranscodeHWAccel.md) - [TranscodeHWAccel](doc//TranscodeHWAccel.md)
- [TranscodePolicy](doc//TranscodePolicy.md) - [TranscodePolicy](doc//TranscodePolicy.md)
- [UnassignedFacesResponseDto](doc//UnassignedFacesResponseDto.md)
- [UpdateAlbumDto](doc//UpdateAlbumDto.md) - [UpdateAlbumDto](doc//UpdateAlbumDto.md)
- [UpdateAssetDto](doc//UpdateAssetDto.md) - [UpdateAssetDto](doc//UpdateAssetDto.md)
- [UpdateLibraryDto](doc//UpdateLibraryDto.md) - [UpdateLibraryDto](doc//UpdateLibraryDto.md)

20
mobile/openapi/doc/AssetFaceBoxDto.md generated Normal file
View file

@ -0,0 +1,20 @@
# openapi.model.AssetFaceBoxDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**boundingBoxX1** | **int** | |
**boundingBoxX2** | **int** | |
**boundingBoxY1** | **int** | |
**boundingBoxY2** | **int** | |
**imageHeight** | **int** | |
**imageWidth** | **int** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

15
mobile/openapi/doc/AssetFaceUpdateDto.md generated Normal file
View file

@ -0,0 +1,15 @@
# openapi.model.AssetFaceUpdateDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**data** | [**List<AssetFaceUpdateItem>**](AssetFaceUpdateItem.md) | | [default to const []]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -0,0 +1,16 @@
# openapi.model.AssetFaceUpdateItem
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetId** | **String** | |
**personId** | **String** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -30,7 +30,7 @@ Name | Type | Description | Notes
**originalPath** | **String** | | **originalPath** | **String** | |
**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional] **owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional]
**ownerId** | **String** | | **ownerId** | **String** | |
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []] **people** | [**List<PeopleAssetResponseDto>**](PeopleAssetResponseDto.md) | | [optional] [default to const []]
**resized** | **bool** | | **resized** | **bool** | |
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional] **smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
**stack** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [optional] [default to const []] **stack** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [optional] [default to const []]
@ -39,6 +39,7 @@ Name | Type | Description | Notes
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) | | [optional] [default to const []] **tags** | [**List<TagResponseDto>**](TagResponseDto.md) | | [optional] [default to const []]
**thumbhash** | **String** | | **thumbhash** | **String** | |
**type** | [**AssetTypeEnum**](AssetTypeEnum.md) | | **type** | [**AssetTypeEnum**](AssetTypeEnum.md) | |
**unassignedPeople** | [**List<UnassignedFacesResponseDto>**](UnassignedFacesResponseDto.md) | | [optional] [default to const []]
**updatedAt** | [**DateTime**](DateTime.md) | | **updatedAt** | [**DateTime**](DateTime.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -0,0 +1,16 @@
# openapi.model.PeopleAssetResponseDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetFaceId** | **String** | |
**person** | [**PersonResponseDto**](PersonResponseDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -9,16 +9,75 @@ All URIs are relative to */api*
Method | HTTP request | Description Method | HTTP request | Description
------------- | ------------- | ------------- ------------- | ------------- | -------------
[**createPerson**](PersonApi.md#createperson) | **POST** /person |
[**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person | [**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person |
[**getAssetFace**](PersonApi.md#getassetface) | **GET** /person/{id}/{assetId}/faceasset |
[**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} | [**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} |
[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | [**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
[**getPersonStatistics**](PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics | [**getPersonStatistics**](PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | [**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge | [**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
[**reassignFaces**](PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
[**unassignFaces**](PersonApi.md#unassignfaces) | **DELETE** /person |
[**updatePeople**](PersonApi.md#updatepeople) | **PUT** /person | [**updatePeople**](PersonApi.md#updatepeople) | **PUT** /person |
[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} | [**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} |
# **createPerson**
> PersonResponseDto createPerson(assetFaceUpdateDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = PersonApi();
final assetFaceUpdateDto = AssetFaceUpdateDto(); // AssetFaceUpdateDto |
try {
final result = api_instance.createPerson(assetFaceUpdateDto);
print(result);
} catch (e) {
print('Exception when calling PersonApi->createPerson: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetFaceUpdateDto** | [**AssetFaceUpdateDto**](AssetFaceUpdateDto.md)| |
### Return type
[**PersonResponseDto**](PersonResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAllPeople** # **getAllPeople**
> PeopleResponseDto getAllPeople(withHidden) > PeopleResponseDto getAllPeople(withHidden)
@ -74,6 +133,63 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAssetFace**
> AssetFaceBoxDto getAssetFace(id, assetId)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = PersonApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final assetId = assetId_example; // String |
try {
final result = api_instance.getAssetFace(id, assetId);
print(result);
} catch (e) {
print('Exception when calling PersonApi->getAssetFace: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**assetId** | **String**| |
### Return type
[**AssetFaceBoxDto**](AssetFaceBoxDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getPerson** # **getPerson**
> PersonResponseDto getPerson(id) > PersonResponseDto getPerson(id)
@ -351,6 +467,118 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **reassignFaces**
> List<PersonResponseDto> reassignFaces(id, assetFaceUpdateDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = PersonApi();
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final assetFaceUpdateDto = AssetFaceUpdateDto(); // AssetFaceUpdateDto |
try {
final result = api_instance.reassignFaces(id, assetFaceUpdateDto);
print(result);
} catch (e) {
print('Exception when calling PersonApi->reassignFaces: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**id** | **String**| |
**assetFaceUpdateDto** | [**AssetFaceUpdateDto**](AssetFaceUpdateDto.md)| |
### Return type
[**List<PersonResponseDto>**](PersonResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **unassignFaces**
> List<BulkIdResponseDto> unassignFaces(assetFaceUpdateDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = PersonApi();
final assetFaceUpdateDto = AssetFaceUpdateDto(); // AssetFaceUpdateDto |
try {
final result = api_instance.unassignFaces(assetFaceUpdateDto);
print(result);
} catch (e) {
print('Exception when calling PersonApi->unassignFaces: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetFaceUpdateDto** | [**AssetFaceUpdateDto**](AssetFaceUpdateDto.md)| |
### Return type
[**List<BulkIdResponseDto>**](BulkIdResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **updatePeople** # **updatePeople**
> List<BulkIdResponseDto> updatePeople(peopleUpdateDto) > List<BulkIdResponseDto> updatePeople(peopleUpdateDto)

View file

@ -0,0 +1,17 @@
# openapi.model.UnassignedFacesResponseDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetFaceId** | **String** | |
**assetId** | **String** | |
**boudinxBox** | [**AssetFaceBoxDto**](AssetFaceBoxDto.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -60,6 +60,9 @@ part 'model/asset_bulk_upload_check_dto.dart';
part 'model/asset_bulk_upload_check_item.dart'; part 'model/asset_bulk_upload_check_item.dart';
part 'model/asset_bulk_upload_check_response_dto.dart'; part 'model/asset_bulk_upload_check_response_dto.dart';
part 'model/asset_bulk_upload_check_result.dart'; part 'model/asset_bulk_upload_check_result.dart';
part 'model/asset_face_box_dto.dart';
part 'model/asset_face_update_dto.dart';
part 'model/asset_face_update_item.dart';
part 'model/asset_file_upload_response_dto.dart'; part 'model/asset_file_upload_response_dto.dart';
part 'model/asset_ids_dto.dart'; part 'model/asset_ids_dto.dart';
part 'model/asset_ids_response_dto.dart'; part 'model/asset_ids_response_dto.dart';
@ -122,6 +125,7 @@ part 'model/o_auth_config_dto.dart';
part 'model/o_auth_config_response_dto.dart'; part 'model/o_auth_config_response_dto.dart';
part 'model/path_entity_type.dart'; part 'model/path_entity_type.dart';
part 'model/path_type.dart'; part 'model/path_type.dart';
part 'model/people_asset_response_dto.dart';
part 'model/people_response_dto.dart'; part 'model/people_response_dto.dart';
part 'model/people_update_dto.dart'; part 'model/people_update_dto.dart';
part 'model/people_update_item.dart'; part 'model/people_update_item.dart';
@ -174,6 +178,7 @@ part 'model/time_bucket_size.dart';
part 'model/tone_mapping.dart'; part 'model/tone_mapping.dart';
part 'model/transcode_hw_accel.dart'; part 'model/transcode_hw_accel.dart';
part 'model/transcode_policy.dart'; part 'model/transcode_policy.dart';
part 'model/unassigned_faces_response_dto.dart';
part 'model/update_album_dto.dart'; part 'model/update_album_dto.dart';
part 'model/update_asset_dto.dart'; part 'model/update_asset_dto.dart';
part 'model/update_library_dto.dart'; part 'model/update_library_dto.dart';

View file

@ -16,6 +16,53 @@ class PersonApi {
final ApiClient apiClient; final ApiClient apiClient;
/// Performs an HTTP 'POST /person' operation and returns the [Response].
/// Parameters:
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<Response> createPersonWithHttpInfo(AssetFaceUpdateDto assetFaceUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person';
// ignore: prefer_final_locals
Object? postBody = assetFaceUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<PersonResponseDto?> createPerson(AssetFaceUpdateDto assetFaceUpdateDto,) async {
final response = await createPersonWithHttpInfo(assetFaceUpdateDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto;
}
return null;
}
/// Performs an HTTP 'GET /person' operation and returns the [Response]. /// Performs an HTTP 'GET /person' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
@ -67,6 +114,59 @@ class PersonApi {
return null; return null;
} }
/// Performs an HTTP 'GET /person/{id}/{assetId}/faceasset' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [String] assetId (required):
Future<Response> getAssetFaceWithHttpInfo(String id, String assetId,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/{assetId}/faceasset'
.replaceAll('{id}', id)
.replaceAll('{assetId}', assetId);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [String] assetId (required):
Future<AssetFaceBoxDto?> getAssetFace(String id, String assetId,) async {
final response = await getAssetFaceWithHttpInfo(id, assetId,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetFaceBoxDto',) as AssetFaceBoxDto;
}
return null;
}
/// Performs an HTTP 'GET /person/{id}' operation and returns the [Response]. /// Performs an HTTP 'GET /person/{id}' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
@ -317,6 +417,111 @@ class PersonApi {
return null; return null;
} }
/// Performs an HTTP 'PUT /person/{id}/reassign' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<Response> reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person/{id}/reassign'
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody = assetFaceUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<List<PersonResponseDto>?> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async {
final response = await reassignFacesWithHttpInfo(id, assetFaceUpdateDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<PersonResponseDto>') as List)
.cast<PersonResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'DELETE /person' operation and returns the [Response].
/// Parameters:
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<Response> unassignFacesWithHttpInfo(AssetFaceUpdateDto assetFaceUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person';
// ignore: prefer_final_locals
Object? postBody = assetFaceUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<List<BulkIdResponseDto>?> unassignFaces(AssetFaceUpdateDto assetFaceUpdateDto,) async {
final response = await unassignFacesWithHttpInfo(assetFaceUpdateDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<BulkIdResponseDto>') as List)
.cast<BulkIdResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'PUT /person' operation and returns the [Response]. /// Performs an HTTP 'PUT /person' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///

View file

@ -211,6 +211,12 @@ class ApiClient {
return AssetBulkUploadCheckResponseDto.fromJson(value); return AssetBulkUploadCheckResponseDto.fromJson(value);
case 'AssetBulkUploadCheckResult': case 'AssetBulkUploadCheckResult':
return AssetBulkUploadCheckResult.fromJson(value); return AssetBulkUploadCheckResult.fromJson(value);
case 'AssetFaceBoxDto':
return AssetFaceBoxDto.fromJson(value);
case 'AssetFaceUpdateDto':
return AssetFaceUpdateDto.fromJson(value);
case 'AssetFaceUpdateItem':
return AssetFaceUpdateItem.fromJson(value);
case 'AssetFileUploadResponseDto': case 'AssetFileUploadResponseDto':
return AssetFileUploadResponseDto.fromJson(value); return AssetFileUploadResponseDto.fromJson(value);
case 'AssetIdsDto': case 'AssetIdsDto':
@ -335,6 +341,8 @@ class ApiClient {
return PathEntityTypeTypeTransformer().decode(value); return PathEntityTypeTypeTransformer().decode(value);
case 'PathType': case 'PathType':
return PathTypeTypeTransformer().decode(value); return PathTypeTypeTransformer().decode(value);
case 'PeopleAssetResponseDto':
return PeopleAssetResponseDto.fromJson(value);
case 'PeopleResponseDto': case 'PeopleResponseDto':
return PeopleResponseDto.fromJson(value); return PeopleResponseDto.fromJson(value);
case 'PeopleUpdateDto': case 'PeopleUpdateDto':
@ -439,6 +447,8 @@ class ApiClient {
return TranscodeHWAccelTypeTransformer().decode(value); return TranscodeHWAccelTypeTransformer().decode(value);
case 'TranscodePolicy': case 'TranscodePolicy':
return TranscodePolicyTypeTransformer().decode(value); return TranscodePolicyTypeTransformer().decode(value);
case 'UnassignedFacesResponseDto':
return UnassignedFacesResponseDto.fromJson(value);
case 'UpdateAlbumDto': case 'UpdateAlbumDto':
return UpdateAlbumDto.fromJson(value); return UpdateAlbumDto.fromJson(value);
case 'UpdateAssetDto': case 'UpdateAssetDto':

View file

@ -0,0 +1,138 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetFaceBoxDto {
/// Returns a new [AssetFaceBoxDto] instance.
AssetFaceBoxDto({
required this.boundingBoxX1,
required this.boundingBoxX2,
required this.boundingBoxY1,
required this.boundingBoxY2,
required this.imageHeight,
required this.imageWidth,
});
int boundingBoxX1;
int boundingBoxX2;
int boundingBoxY1;
int boundingBoxY2;
int imageHeight;
int imageWidth;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetFaceBoxDto &&
other.boundingBoxX1 == boundingBoxX1 &&
other.boundingBoxX2 == boundingBoxX2 &&
other.boundingBoxY1 == boundingBoxY1 &&
other.boundingBoxY2 == boundingBoxY2 &&
other.imageHeight == imageHeight &&
other.imageWidth == imageWidth;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(boundingBoxX1.hashCode) +
(boundingBoxX2.hashCode) +
(boundingBoxY1.hashCode) +
(boundingBoxY2.hashCode) +
(imageHeight.hashCode) +
(imageWidth.hashCode);
@override
String toString() => 'AssetFaceBoxDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, imageHeight=$imageHeight, imageWidth=$imageWidth]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'boundingBoxX1'] = this.boundingBoxX1;
json[r'boundingBoxX2'] = this.boundingBoxX2;
json[r'boundingBoxY1'] = this.boundingBoxY1;
json[r'boundingBoxY2'] = this.boundingBoxY2;
json[r'imageHeight'] = this.imageHeight;
json[r'imageWidth'] = this.imageWidth;
return json;
}
/// Returns a new [AssetFaceBoxDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetFaceBoxDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetFaceBoxDto(
boundingBoxX1: mapValueOfType<int>(json, r'boundingBoxX1')!,
boundingBoxX2: mapValueOfType<int>(json, r'boundingBoxX2')!,
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
);
}
return null;
}
static List<AssetFaceBoxDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetFaceBoxDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetFaceBoxDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetFaceBoxDto> mapFromJson(dynamic json) {
final map = <String, AssetFaceBoxDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetFaceBoxDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetFaceBoxDto-objects as value to a dart map
static Map<String, List<AssetFaceBoxDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetFaceBoxDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetFaceBoxDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'boundingBoxX1',
'boundingBoxX2',
'boundingBoxY1',
'boundingBoxY2',
'imageHeight',
'imageWidth',
};
}

View file

@ -0,0 +1,98 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetFaceUpdateDto {
/// Returns a new [AssetFaceUpdateDto] instance.
AssetFaceUpdateDto({
this.data = const [],
});
List<AssetFaceUpdateItem> data;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetFaceUpdateDto &&
other.data == data;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(data.hashCode);
@override
String toString() => 'AssetFaceUpdateDto[data=$data]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'data'] = this.data;
return json;
}
/// Returns a new [AssetFaceUpdateDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetFaceUpdateDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetFaceUpdateDto(
data: AssetFaceUpdateItem.listFromJson(json[r'data']),
);
}
return null;
}
static List<AssetFaceUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetFaceUpdateDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetFaceUpdateDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetFaceUpdateDto> mapFromJson(dynamic json) {
final map = <String, AssetFaceUpdateDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetFaceUpdateDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetFaceUpdateDto-objects as value to a dart map
static Map<String, List<AssetFaceUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetFaceUpdateDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetFaceUpdateDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'data',
};
}

View file

@ -0,0 +1,106 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class AssetFaceUpdateItem {
/// Returns a new [AssetFaceUpdateItem] instance.
AssetFaceUpdateItem({
required this.assetId,
required this.personId,
});
String assetId;
String personId;
@override
bool operator ==(Object other) => identical(this, other) || other is AssetFaceUpdateItem &&
other.assetId == assetId &&
other.personId == personId;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetId.hashCode) +
(personId.hashCode);
@override
String toString() => 'AssetFaceUpdateItem[assetId=$assetId, personId=$personId]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetId'] = this.assetId;
json[r'personId'] = this.personId;
return json;
}
/// Returns a new [AssetFaceUpdateItem] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static AssetFaceUpdateItem? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return AssetFaceUpdateItem(
assetId: mapValueOfType<String>(json, r'assetId')!,
personId: mapValueOfType<String>(json, r'personId')!,
);
}
return null;
}
static List<AssetFaceUpdateItem> listFromJson(dynamic json, {bool growable = false,}) {
final result = <AssetFaceUpdateItem>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = AssetFaceUpdateItem.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, AssetFaceUpdateItem> mapFromJson(dynamic json) {
final map = <String, AssetFaceUpdateItem>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = AssetFaceUpdateItem.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of AssetFaceUpdateItem-objects as value to a dart map
static Map<String, List<AssetFaceUpdateItem>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<AssetFaceUpdateItem>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = AssetFaceUpdateItem.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetId',
'personId',
};
}

View file

@ -44,6 +44,7 @@ class AssetResponseDto {
this.tags = const [], this.tags = const [],
required this.thumbhash, required this.thumbhash,
required this.type, required this.type,
this.unassignedPeople = const [],
required this.updatedAt, required this.updatedAt,
}); });
@ -104,7 +105,7 @@ class AssetResponseDto {
String ownerId; String ownerId;
List<PersonResponseDto> people; List<PeopleAssetResponseDto> people;
bool resized; bool resized;
@ -128,6 +129,8 @@ class AssetResponseDto {
AssetTypeEnum type; AssetTypeEnum type;
List<UnassignedFacesResponseDto> unassignedPeople;
DateTime updatedAt; DateTime updatedAt;
@override @override
@ -163,6 +166,7 @@ class AssetResponseDto {
other.tags == tags && other.tags == tags &&
other.thumbhash == thumbhash && other.thumbhash == thumbhash &&
other.type == type && other.type == type &&
other.unassignedPeople == unassignedPeople &&
other.updatedAt == updatedAt; other.updatedAt == updatedAt;
@override @override
@ -199,10 +203,11 @@ class AssetResponseDto {
(tags.hashCode) + (tags.hashCode) +
(thumbhash == null ? 0 : thumbhash!.hashCode) + (thumbhash == null ? 0 : thumbhash!.hashCode) +
(type.hashCode) + (type.hashCode) +
(unassignedPeople.hashCode) +
(updatedAt.hashCode); (updatedAt.hashCode);
@override @override
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, stackCount=$stackCount, stackParentId=$stackParentId, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]'; String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, stackCount=$stackCount, stackParentId=$stackParentId, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedPeople=$unassignedPeople, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -261,6 +266,7 @@ class AssetResponseDto {
// json[r'thumbhash'] = null; // json[r'thumbhash'] = null;
} }
json[r'type'] = this.type; json[r'type'] = this.type;
json[r'unassignedPeople'] = this.unassignedPeople;
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
return json; return json;
} }
@ -295,7 +301,7 @@ class AssetResponseDto {
originalPath: mapValueOfType<String>(json, r'originalPath')!, originalPath: mapValueOfType<String>(json, r'originalPath')!,
owner: UserResponseDto.fromJson(json[r'owner']), owner: UserResponseDto.fromJson(json[r'owner']),
ownerId: mapValueOfType<String>(json, r'ownerId')!, ownerId: mapValueOfType<String>(json, r'ownerId')!,
people: PersonResponseDto.listFromJson(json[r'people']), people: PeopleAssetResponseDto.listFromJson(json[r'people']),
resized: mapValueOfType<bool>(json, r'resized')!, resized: mapValueOfType<bool>(json, r'resized')!,
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']), smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
stack: AssetResponseDto.listFromJson(json[r'stack']), stack: AssetResponseDto.listFromJson(json[r'stack']),
@ -304,6 +310,7 @@ class AssetResponseDto {
tags: TagResponseDto.listFromJson(json[r'tags']), tags: TagResponseDto.listFromJson(json[r'tags']),
thumbhash: mapValueOfType<String>(json, r'thumbhash'), thumbhash: mapValueOfType<String>(json, r'thumbhash'),
type: AssetTypeEnum.fromJson(json[r'type'])!, type: AssetTypeEnum.fromJson(json[r'type'])!,
unassignedPeople: UnassignedFacesResponseDto.listFromJson(json[r'unassignedPeople']),
updatedAt: mapDateTime(json, r'updatedAt', '')!, updatedAt: mapDateTime(json, r'updatedAt', '')!,
); );
} }

View file

@ -0,0 +1,106 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class PeopleAssetResponseDto {
/// Returns a new [PeopleAssetResponseDto] instance.
PeopleAssetResponseDto({
required this.assetFaceId,
required this.person,
});
String assetFaceId;
PersonResponseDto person;
@override
bool operator ==(Object other) => identical(this, other) || other is PeopleAssetResponseDto &&
other.assetFaceId == assetFaceId &&
other.person == person;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetFaceId.hashCode) +
(person.hashCode);
@override
String toString() => 'PeopleAssetResponseDto[assetFaceId=$assetFaceId, person=$person]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetFaceId'] = this.assetFaceId;
json[r'person'] = this.person;
return json;
}
/// Returns a new [PeopleAssetResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static PeopleAssetResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return PeopleAssetResponseDto(
assetFaceId: mapValueOfType<String>(json, r'assetFaceId')!,
person: PersonResponseDto.fromJson(json[r'person'])!,
);
}
return null;
}
static List<PeopleAssetResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <PeopleAssetResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = PeopleAssetResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, PeopleAssetResponseDto> mapFromJson(dynamic json) {
final map = <String, PeopleAssetResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = PeopleAssetResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of PeopleAssetResponseDto-objects as value to a dart map
static Map<String, List<PeopleAssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<PeopleAssetResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = PeopleAssetResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetFaceId',
'person',
};
}

View file

@ -0,0 +1,114 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class UnassignedFacesResponseDto {
/// Returns a new [UnassignedFacesResponseDto] instance.
UnassignedFacesResponseDto({
required this.assetFaceId,
required this.assetId,
required this.boudinxBox,
});
String assetFaceId;
String assetId;
AssetFaceBoxDto boudinxBox;
@override
bool operator ==(Object other) => identical(this, other) || other is UnassignedFacesResponseDto &&
other.assetFaceId == assetFaceId &&
other.assetId == assetId &&
other.boudinxBox == boudinxBox;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetFaceId.hashCode) +
(assetId.hashCode) +
(boudinxBox.hashCode);
@override
String toString() => 'UnassignedFacesResponseDto[assetFaceId=$assetFaceId, assetId=$assetId, boudinxBox=$boudinxBox]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetFaceId'] = this.assetFaceId;
json[r'assetId'] = this.assetId;
json[r'boudinxBox'] = this.boudinxBox;
return json;
}
/// Returns a new [UnassignedFacesResponseDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static UnassignedFacesResponseDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return UnassignedFacesResponseDto(
assetFaceId: mapValueOfType<String>(json, r'assetFaceId')!,
assetId: mapValueOfType<String>(json, r'assetId')!,
boudinxBox: AssetFaceBoxDto.fromJson(json[r'boudinxBox'])!,
);
}
return null;
}
static List<UnassignedFacesResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <UnassignedFacesResponseDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = UnassignedFacesResponseDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, UnassignedFacesResponseDto> mapFromJson(dynamic json) {
final map = <String, UnassignedFacesResponseDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = UnassignedFacesResponseDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of UnassignedFacesResponseDto-objects as value to a dart map
static Map<String, List<UnassignedFacesResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<UnassignedFacesResponseDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = UnassignedFacesResponseDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetFaceId',
'assetId',
'boudinxBox',
};
}

View file

@ -0,0 +1,52 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for AssetFaceBoxDto
void main() {
// final instance = AssetFaceBoxDto();
group('test AssetFaceBoxDto', () {
// int boundingBoxX1
test('to test the property `boundingBoxX1`', () async {
// TODO
});
// int boundingBoxX2
test('to test the property `boundingBoxX2`', () async {
// TODO
});
// int boundingBoxY1
test('to test the property `boundingBoxY1`', () async {
// TODO
});
// int boundingBoxY2
test('to test the property `boundingBoxY2`', () async {
// TODO
});
// int imageHeight
test('to test the property `imageHeight`', () async {
// TODO
});
// int imageWidth
test('to test the property `imageWidth`', () async {
// TODO
});
});
}

View file

@ -0,0 +1,27 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for AssetFaceUpdateDto
void main() {
// final instance = AssetFaceUpdateDto();
group('test AssetFaceUpdateDto', () {
// List<AssetFaceUpdateItem> data (default value: const [])
test('to test the property `data`', () async {
// TODO
});
});
}

View file

@ -0,0 +1,32 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for AssetFaceUpdateItem
void main() {
// final instance = AssetFaceUpdateItem();
group('test AssetFaceUpdateItem', () {
// String assetId
test('to test the property `assetId`', () async {
// TODO
});
// String personId
test('to test the property `personId`', () async {
// TODO
});
});
}

View file

@ -127,7 +127,7 @@ void main() {
// TODO // TODO
}); });
// List<PersonResponseDto> people (default value: const []) // List<PeopleAssetResponseDto> people (default value: const [])
test('to test the property `people`', () async { test('to test the property `people`', () async {
// TODO // TODO
}); });
@ -172,6 +172,11 @@ void main() {
// TODO // TODO
}); });
// List<UnassignedFacesResponseDto> unassignedPeople (default value: const [])
test('to test the property `unassignedPeople`', () async {
// TODO
});
// DateTime updatedAt // DateTime updatedAt
test('to test the property `updatedAt`', () async { test('to test the property `updatedAt`', () async {
// TODO // TODO

View file

@ -0,0 +1,32 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for PeopleAssetResponseDto
void main() {
// final instance = PeopleAssetResponseDto();
group('test PeopleAssetResponseDto', () {
// String assetFaceId
test('to test the property `assetFaceId`', () async {
// TODO
});
// PersonResponseDto person
test('to test the property `person`', () async {
// TODO
});
});
}

View file

@ -17,11 +17,21 @@ void main() {
// final instance = PersonApi(); // final instance = PersonApi();
group('tests for PersonApi', () { group('tests for PersonApi', () {
//Future<PersonResponseDto> createPerson(AssetFaceUpdateDto assetFaceUpdateDto) async
test('test createPerson', () async {
// TODO
});
//Future<PeopleResponseDto> getAllPeople({ bool withHidden }) async //Future<PeopleResponseDto> getAllPeople({ bool withHidden }) async
test('test getAllPeople', () async { test('test getAllPeople', () async {
// TODO // TODO
}); });
//Future<AssetFaceBoxDto> getAssetFace(String id, String assetId) async
test('test getAssetFace', () async {
// TODO
});
//Future<PersonResponseDto> getPerson(String id) async //Future<PersonResponseDto> getPerson(String id) async
test('test getPerson', () async { test('test getPerson', () async {
// TODO // TODO
@ -47,6 +57,16 @@ void main() {
// TODO // TODO
}); });
//Future<List<PersonResponseDto>> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto) async
test('test reassignFaces', () async {
// TODO
});
//Future<List<BulkIdResponseDto>> unassignFaces(AssetFaceUpdateDto assetFaceUpdateDto) async
test('test unassignFaces', () async {
// TODO
});
//Future<List<BulkIdResponseDto>> updatePeople(PeopleUpdateDto peopleUpdateDto) async //Future<List<BulkIdResponseDto>> updatePeople(PeopleUpdateDto peopleUpdateDto) async
test('test updatePeople', () async { test('test updatePeople', () async {
// TODO // TODO

View file

@ -0,0 +1,37 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for UnassignedFacesResponseDto
void main() {
// final instance = UnassignedFacesResponseDto();
group('test UnassignedFacesResponseDto', () {
// String assetFaceId
test('to test the property `assetFaceId`', () async {
// TODO
});
// String assetId
test('to test the property `assetId`', () async {
// TODO
});
// AssetFaceBoxDto boudinxBox
test('to test the property `boudinxBox`', () async {
// TODO
});
});
}

View file

@ -3357,6 +3357,49 @@
} }
}, },
"/person": { "/person": {
"delete": {
"operationId": "unassignFaces",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetFaceUpdateDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/BulkIdResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Person"
]
},
"get": { "get": {
"operationId": "getAllPeople", "operationId": "getAllPeople",
"parameters": [ "parameters": [
@ -3397,6 +3440,46 @@
"Person" "Person"
] ]
}, },
"post": {
"operationId": "createPerson",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetFaceUpdateDto"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PersonResponseDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Person"
]
},
"put": { "put": {
"operationId": "updatePeople", "operationId": "updatePeople",
"parameters": [], "parameters": [],
@ -3633,6 +3716,61 @@
] ]
} }
}, },
"/person/{id}/reassign": {
"put": {
"operationId": "reassignFaces",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetFaceUpdateDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/PersonResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Person"
]
}
},
"/person/{id}/statistics": { "/person/{id}/statistics": {
"get": { "get": {
"operationId": "getPersonStatistics", "operationId": "getPersonStatistics",
@ -3718,6 +3856,56 @@
] ]
} }
}, },
"/person/{id}/{assetId}/faceasset": {
"get": {
"operationId": "getAssetFace",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "assetId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetFaceBoxDto"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Person"
]
}
},
"/search": { "/search": {
"get": { "get": {
"operationId": "search", "operationId": "search",
@ -5816,6 +6004,66 @@
], ],
"type": "object" "type": "object"
}, },
"AssetFaceBoxDto": {
"properties": {
"boundingBoxX1": {
"type": "integer"
},
"boundingBoxX2": {
"type": "integer"
},
"boundingBoxY1": {
"type": "integer"
},
"boundingBoxY2": {
"type": "integer"
},
"imageHeight": {
"type": "integer"
},
"imageWidth": {
"type": "integer"
}
},
"required": [
"imageWidth",
"imageHeight",
"boundingBoxX1",
"boundingBoxY1",
"boundingBoxX2",
"boundingBoxY2"
],
"type": "object"
},
"AssetFaceUpdateDto": {
"properties": {
"data": {
"items": {
"$ref": "#/components/schemas/AssetFaceUpdateItem"
},
"type": "array"
}
},
"required": [
"data"
],
"type": "object"
},
"AssetFaceUpdateItem": {
"properties": {
"assetId": {
"type": "string"
},
"personId": {
"type": "string"
}
},
"required": [
"personId",
"assetId"
],
"type": "object"
},
"AssetFileUploadResponseDto": { "AssetFileUploadResponseDto": {
"properties": { "properties": {
"duplicate": { "duplicate": {
@ -5971,7 +6219,7 @@
}, },
"people": { "people": {
"items": { "items": {
"$ref": "#/components/schemas/PersonResponseDto" "$ref": "#/components/schemas/PeopleAssetResponseDto"
}, },
"type": "array" "type": "array"
}, },
@ -6007,6 +6255,12 @@
"type": { "type": {
"$ref": "#/components/schemas/AssetTypeEnum" "$ref": "#/components/schemas/AssetTypeEnum"
}, },
"unassignedPeople": {
"items": {
"$ref": "#/components/schemas/UnassignedFacesResponseDto"
},
"type": "array"
},
"updatedAt": { "updatedAt": {
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
@ -7275,6 +7529,21 @@
], ],
"type": "string" "type": "string"
}, },
"PeopleAssetResponseDto": {
"properties": {
"assetFaceId": {
"type": "string"
},
"person": {
"$ref": "#/components/schemas/PersonResponseDto"
}
},
"required": [
"assetFaceId",
"person"
],
"type": "object"
},
"PeopleResponseDto": { "PeopleResponseDto": {
"properties": { "properties": {
"people": { "people": {
@ -8539,6 +8808,25 @@
], ],
"type": "string" "type": "string"
}, },
"UnassignedFacesResponseDto": {
"properties": {
"assetFaceId": {
"type": "string"
},
"assetId": {
"type": "string"
},
"boudinxBox": {
"$ref": "#/components/schemas/AssetFaceBoxDto"
}
},
"required": [
"assetFaceId",
"assetId",
"boudinxBox"
],
"type": "object"
},
"UpdateAlbumDto": { "UpdateAlbumDto": {
"properties": { "properties": {
"albumName": { "albumName": {

View file

@ -1,6 +1,11 @@
import { AssetEntity, AssetType } from '@app/infra/entities'; import { AssetEntity, AssetType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { PersonResponseDto, mapFace } from '../../person/person.dto'; import {
PeopleAssetResponseDto,
UnassignedFacesResponseDto,
mapFace,
mapUnassignedFace,
} from '../../person/person.dto';
import { TagResponseDto, mapTag } from '../../tag'; import { TagResponseDto, mapTag } from '../../tag';
import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto'; import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
import { ExifResponseDto, mapExif } from './exif-response.dto'; import { ExifResponseDto, mapExif } from './exif-response.dto';
@ -39,7 +44,8 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
exifInfo?: ExifResponseDto; exifInfo?: ExifResponseDto;
smartInfo?: SmartInfoResponseDto; smartInfo?: SmartInfoResponseDto;
tags?: TagResponseDto[]; tags?: TagResponseDto[];
people?: PersonResponseDto[]; unassignedPeople?: UnassignedFacesResponseDto[];
people?: PeopleAssetResponseDto[];
/**base64 encoded sha1 hash */ /**base64 encoded sha1 hash */
checksum!: string; checksum!: string;
stackParentId?: string | null; stackParentId?: string | null;
@ -96,9 +102,12 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
livePhotoVideoId: entity.livePhotoVideoId, livePhotoVideoId: entity.livePhotoVideoId,
tags: entity.tags?.map(mapTag), tags: entity.tags?.map(mapTag),
unassignedPeople: entity.faces
?.map(mapUnassignedFace)
.filter((assetFace) => assetFace) as UnassignedFacesResponseDto[],
people: entity.faces people: entity.faces
?.map(mapFace) ?.map(mapFace)
.filter((person): person is PersonResponseDto => person !== null && !person.isHidden), .filter((assetFace) => assetFace && !assetFace.person.isHidden) as PeopleAssetResponseDto[],
checksum: entity.checksum.toString('base64'), checksum: entity.checksum.toString('base64'),
stackParentId: entity.stackParentId, stackParentId: entity.stackParentId,
stack: withStack ? entity.stack?.map((a) => mapAsset(a, { stripMetadata })) ?? undefined : undefined, stack: withStack ? entity.stack?.map((a) => mapAsset(a, { stripMetadata })) ?? undefined : undefined,

View file

@ -44,6 +44,23 @@ export class PeopleUpdateDto {
people!: PeopleUpdateItem[]; people!: PeopleUpdateItem[];
} }
export class AssetFaceUpdateDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => AssetFaceUpdateItem)
data!: AssetFaceUpdateItem[];
}
export class AssetFaceUpdateItem {
@IsString()
@IsNotEmpty()
personId!: string;
@IsString()
@IsNotEmpty()
assetId!: string;
}
export class PeopleUpdateItem extends PersonUpdateDto { export class PeopleUpdateItem extends PersonUpdateDto {
/** /**
* Person id. * Person id.
@ -64,6 +81,26 @@ export class PersonSearchDto {
withHidden?: boolean = false; withHidden?: boolean = false;
} }
export class AssetFaceBoxDto {
@ApiProperty({ type: 'integer' })
imageWidth!: number;
@ApiProperty({ type: 'integer' })
imageHeight!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX2!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY2!: number;
}
export class PersonResponseDto { export class PersonResponseDto {
id!: string; id!: string;
name!: string; name!: string;
@ -73,6 +110,17 @@ export class PersonResponseDto {
isHidden!: boolean; isHidden!: boolean;
} }
export class PeopleAssetResponseDto {
assetFaceId!: string;
person!: PersonResponseDto;
}
export class UnassignedFacesResponseDto {
assetFaceId!: string;
assetId!: string;
boudinxBox!: AssetFaceBoxDto;
}
export class PersonStatisticsResponseDto { export class PersonStatisticsResponseDto {
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'integer' })
assets!: number; assets!: number;
@ -98,10 +146,32 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
}; };
} }
export function mapFace(face: AssetFaceEntity): PersonResponseDto | null { export function mapFace(face: AssetFaceEntity): PeopleAssetResponseDto | null {
if (face.person) { if (face.person) {
return mapPerson(face.person); return {
assetFaceId: face.id,
person: face.person,
};
} else {
return null;
}
}
export function mapUnassignedFace(face: AssetFaceEntity): UnassignedFacesResponseDto | null {
if (face.person !== null) {
return null;
} else {
return {
assetFaceId: face.id,
assetId: face.assetId,
boudinxBox: {
imageWidth: face.imageWidth,
imageHeight: face.imageHeight,
boundingBoxX1: face.boundingBoxX1,
boundingBoxY1: face.boundingBoxY1,
boundingBoxX2: face.boundingBoxX2,
boundingBoxY2: face.boundingBoxY2,
},
};
} }
return null;
} }

View file

@ -28,6 +28,8 @@ import {
import { StorageCore } from '../storage'; import { StorageCore } from '../storage';
import { SystemConfigCore } from '../system-config'; import { SystemConfigCore } from '../system-config';
import { import {
AssetFaceBoxDto,
AssetFaceUpdateDto,
MergePersonDto, MergePersonDto,
PeopleResponseDto, PeopleResponseDto,
PeopleUpdateDto, PeopleUpdateDto,
@ -210,6 +212,137 @@ export class PersonService {
return true; return true;
} }
async getFaceEntity(authUser: AuthUserDto, personId: string, assetId: string): Promise<AssetFaceBoxDto> {
await this.access.requirePermission(authUser, Permission.PERSON_READ, personId);
const [face] = await this.repository.getFacesByIds([{ personId, assetId }]);
if (!face) {
throw new BadRequestException('Invalid assetId for feature face');
}
return {
boundingBoxX1: face.boundingBoxX1,
boundingBoxX2: face.boundingBoxX2,
boundingBoxY1: face.boundingBoxY1,
boundingBoxY2: face.boundingBoxY2,
imageHeight: face.imageHeight,
imageWidth: face.imageWidth,
};
}
async createPerson(authUser: AuthUserDto, dto: AssetFaceUpdateDto): Promise<PersonResponseDto> {
const changeFeaturePhoto: string[] = [];
const newPerson = await this.repository.create({ ownerId: authUser.id });
for (const data of dto.data) {
try {
await this.repository.reassignFace(data.personId, newPerson.id, data.assetId);
await this.repository.update({
id: newPerson.id,
faceAssetId: data.assetId,
});
const [face] = await this.repository.getFacesByIds([{ personId: newPerson.id, assetId: data.assetId }]);
const oldPerson = await this.findOrFail(data.personId);
if (oldPerson.faceAssetId === face?.assetId) {
changeFeaturePhoto.push(oldPerson.id);
}
if (!face) {
throw new BadRequestException('Invalid assetId for feature face');
}
} catch (error: Error | any) {
this.logger.error(`Unable to create a new person`, error?.stack);
}
}
const newPersonFeaturePhoto = await this.repository.getRandomFace(newPerson.id);
if (newPersonFeaturePhoto) {
await this.repository.update({
id: newPerson.id,
faceAssetId: newPersonFeaturePhoto.assetId,
});
await this.jobRepository.queue({
name: JobName.GENERATE_PERSON_THUMBNAIL,
data: {
id: newPerson.id,
},
});
}
await this.createNewFeaturePhoto(changeFeaturePhoto);
return newPerson;
}
async reassignFaces(authUser: AuthUserDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
const result: PersonResponseDto[] = [];
const changeFeaturePhoto: string[] = [];
for (const data of dto.data) {
try {
const [face] = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
const oldPerson = await this.findOrFail(data.personId);
if (oldPerson.faceAssetId === face?.assetId) {
changeFeaturePhoto.push(oldPerson.id);
}
await this.repository.reassignFace(data.personId, personId, data.assetId);
result.push(await this.findOrFail(personId).then(mapPerson));
} catch (error: Error | any) {
this.logger.error(
`Unable to un-merge asset ${data.assetId} from ${data.personId} to ${personId}`,
error?.stack,
);
}
}
await this.createNewFeaturePhoto(changeFeaturePhoto);
return result;
}
async unassignFaces(authUser: AuthUserDto, dto: AssetFaceUpdateDto): Promise<BulkIdResponseDto[]> {
const results: BulkIdResponseDto[] = [];
for (const data of dto.data) {
try {
const [face] = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
await this.access.requirePermission(authUser, Permission.ASSET_VIEW, face.assetId);
await this.repository.unassignFace(face.id);
results.push({id:face.id, success:true});
} catch (error: Error | any) {
this.logger.error(
`Unable to un-merge asset ${data.assetId} from ${data.personId}`,
error?.stack,
);
results.push({ id: 'face.id', success: false, error: BulkIdErrorReason.UNKNOWN });
}
}
return results;
}
async createNewFeaturePhoto(changeFeaturePhoto: string[]) {
for (const personId of changeFeaturePhoto) {
const assetFace = await this.repository.getRandomFace(personId);
if (assetFace !== null) {
await this.repository.update({
id: personId,
faceAssetId: assetFace.assetId,
});
await this.jobRepository.queue({
name: JobName.GENERATE_PERSON_THUMBNAIL,
data: {
id: personId,
},
});
}
}
}
async handleRecognizeFaces({ id }: IEntityJob) { async handleRecognizeFaces({ id }: IEntityJob) {
const { machineLearning } = await this.configCore.getConfig(); const { machineLearning } = await this.configCore.getConfig();
if (!machineLearning.enabled || !machineLearning.facialRecognition.enabled) { if (!machineLearning.enabled || !machineLearning.facialRecognition.enabled) {

View file

@ -36,6 +36,8 @@ export interface IPersonRepository {
getAssets(personId: string): Promise<AssetEntity[]>; getAssets(personId: string): Promise<AssetEntity[]>;
prepareReassignFaces(data: UpdateFacesData): Promise<string[]>; prepareReassignFaces(data: UpdateFacesData): Promise<string[]>;
reassignFaces(data: UpdateFacesData): Promise<number>; reassignFaces(data: UpdateFacesData): Promise<number>;
reassignFace(oldPersonId: string, newPersonId: string, assetId: string): Promise<number>;
unassignFace(id: string): Promise<number>;
create(entity: Partial<PersonEntity>): Promise<PersonEntity>; create(entity: Partial<PersonEntity>): Promise<PersonEntity>;
update(entity: Partial<PersonEntity>): Promise<PersonEntity>; update(entity: Partial<PersonEntity>): Promise<PersonEntity>;

View file

@ -1,4 +1,6 @@
import { import {
AssetFaceBoxDto,
AssetFaceUpdateDto,
AssetResponseDto, AssetResponseDto,
AuthUserDto, AuthUserDto,
BulkIdResponseDto, BulkIdResponseDto,
@ -12,8 +14,9 @@ import {
PersonStatisticsResponseDto, PersonStatisticsResponseDto,
PersonUpdateDto, PersonUpdateDto,
} from '@app/domain'; } from '@app/domain';
import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common'; import { Body, Controller, Delete, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { ParseMeUUIDPipe } from '../api-v1/validation/parse-me-uuid-pipe';
import { AuthUser, Authenticated } from '../app.guard'; import { AuthUser, Authenticated } from '../app.guard';
import { UseValidation } from '../app.utils'; import { UseValidation } from '../app.utils';
import { UUIDParamDto } from './dto/uuid-param.dto'; import { UUIDParamDto } from './dto/uuid-param.dto';
@ -29,6 +32,37 @@ function asStreamableFile({ stream, type, length }: ImmichReadStream) {
export class PersonController { export class PersonController {
constructor(private service: PersonService) {} constructor(private service: PersonService) {}
@Put(':id/reassign')
reassignFaces(
@AuthUser() authUser: AuthUserDto,
@Param() { id }: UUIDParamDto,
@Body() dto: AssetFaceUpdateDto,
): Promise<PersonResponseDto[]> {
return this.service.reassignFaces(authUser, id, dto);
}
@Delete('')
unassignFaces(
@AuthUser() authUser: AuthUserDto,
@Body() dto: AssetFaceUpdateDto,
): Promise<BulkIdResponseDto[]> {
return this.service.unassignFaces(authUser, dto);
}
@Get(':id/:assetId/faceasset')
getAssetFace(
@AuthUser() authUser: AuthUserDto,
@Param() { id }: UUIDParamDto,
@Param('assetId', new ParseMeUUIDPipe({ version: '4' })) assetId: string,
): Promise<AssetFaceBoxDto> {
return this.service.getFaceEntity(authUser, id, assetId);
}
@Post('')
createPerson(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetFaceUpdateDto): Promise<PersonResponseDto> {
return this.service.createPerson(authUser, dto);
}
@Get() @Get()
getAllPeople(@AuthUser() authUser: AuthUserDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> { getAllPeople(@AuthUser() authUser: AuthUserDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
return this.service.getAll(authUser, withHidden); return this.service.getAll(authUser, withHidden);

View file

@ -47,6 +47,29 @@ export class PersonRepository implements IPersonRepository {
return result.affected ?? 0; return result.affected ?? 0;
} }
async reassignFace(oldPersonId: string, newPersonId: string, assetId: string): Promise<number> {
const result = await this.assetFaceRepository
.createQueryBuilder()
.update()
.set({ personId: newPersonId })
.where({ personId: oldPersonId })
.andWhere({ assetId })
.execute();
return result.affected ?? 0;
}
async unassignFace(id: string): Promise<number> {
const result = await this.assetFaceRepository
.createQueryBuilder()
.update()
.set({ personId: null })
.where({ id })
.execute();
return result.affected ?? 0;
}
delete(entity: PersonEntity): Promise<PersonEntity | null> { delete(entity: PersonEntity): Promise<PersonEntity | null> {
return this.personRepository.remove(entity); return this.personRepository.remove(entity);
} }

View file

@ -9,6 +9,7 @@ export const newPersonRepositoryMock = (): jest.Mocked<IPersonRepository> => {
getAssets: jest.fn(), getAssets: jest.fn(),
getAllWithoutFaces: jest.fn(), getAllWithoutFaces: jest.fn(),
reassignFace: jest.fn(),
getByName: jest.fn(), getByName: jest.fn(),
create: jest.fn(), create: jest.fn(),

View file

@ -502,6 +502,81 @@ export const AssetBulkUploadCheckResultReasonEnum = {
export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum]; export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum];
/**
*
* @export
* @interface AssetFaceBoxDto
*/
export interface AssetFaceBoxDto {
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxX1': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxX2': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxY1': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'boundingBoxY2': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'imageHeight': number;
/**
*
* @type {number}
* @memberof AssetFaceBoxDto
*/
'imageWidth': number;
}
/**
*
* @export
* @interface AssetFaceUpdateDto
*/
export interface AssetFaceUpdateDto {
/**
*
* @type {Array<AssetFaceUpdateItem>}
* @memberof AssetFaceUpdateDto
*/
'data': Array<AssetFaceUpdateItem>;
}
/**
*
* @export
* @interface AssetFaceUpdateItem
*/
export interface AssetFaceUpdateItem {
/**
*
* @type {string}
* @memberof AssetFaceUpdateItem
*/
'assetId': string;
/**
*
* @type {string}
* @memberof AssetFaceUpdateItem
*/
'personId': string;
}
/** /**
* *
* @export * @export
@ -744,10 +819,10 @@ export interface AssetResponseDto {
'ownerId': string; 'ownerId': string;
/** /**
* *
* @type {Array<PersonResponseDto>} * @type {Array<PeopleAssetResponseDto>}
* @memberof AssetResponseDto * @memberof AssetResponseDto
*/ */
'people'?: Array<PersonResponseDto>; 'people'?: Array<PeopleAssetResponseDto>;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -796,6 +871,12 @@ export interface AssetResponseDto {
* @memberof AssetResponseDto * @memberof AssetResponseDto
*/ */
'type': AssetTypeEnum; 'type': AssetTypeEnum;
/**
*
* @type {Array<UnassignedFacesResponseDto>}
* @memberof AssetResponseDto
*/
'unassignedPeople'?: Array<UnassignedFacesResponseDto>;
/** /**
* *
* @type {string} * @type {string}
@ -2315,6 +2396,25 @@ export const PathType = {
export type PathType = typeof PathType[keyof typeof PathType]; export type PathType = typeof PathType[keyof typeof PathType];
/**
*
* @export
* @interface PeopleAssetResponseDto
*/
export interface PeopleAssetResponseDto {
/**
*
* @type {string}
* @memberof PeopleAssetResponseDto
*/
'assetFaceId': string;
/**
*
* @type {PersonResponseDto}
* @memberof PeopleAssetResponseDto
*/
'person': PersonResponseDto;
}
/** /**
* *
* @export * @export
@ -3949,6 +4049,31 @@ export const TranscodePolicy = {
export type TranscodePolicy = typeof TranscodePolicy[keyof typeof TranscodePolicy]; export type TranscodePolicy = typeof TranscodePolicy[keyof typeof TranscodePolicy];
/**
*
* @export
* @interface UnassignedFacesResponseDto
*/
export interface UnassignedFacesResponseDto {
/**
*
* @type {string}
* @memberof UnassignedFacesResponseDto
*/
'assetFaceId': string;
/**
*
* @type {string}
* @memberof UnassignedFacesResponseDto
*/
'assetId': string;
/**
*
* @type {AssetFaceBoxDto}
* @memberof UnassignedFacesResponseDto
*/
'boudinxBox': AssetFaceBoxDto;
}
/** /**
* *
* @export * @export
@ -11766,6 +11891,50 @@ export class PartnerApi extends BaseAPI {
*/ */
export const PersonApiAxiosParamCreator = function (configuration?: Configuration) { export const PersonApiAxiosParamCreator = function (configuration?: Configuration) {
return { return {
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createPerson: async (assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('createPerson', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person`;
// 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(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {boolean} [withHidden] * @param {boolean} [withHidden]
@ -11800,6 +11969,52 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} id
* @param {string} assetId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAssetFace: async (id: string, assetId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('getAssetFace', 'id', id)
// verify required parameter 'assetId' is not null or undefined
assertParamExists('getAssetFace', 'assetId', assetId)
const localVarPath = `/person/{id}/{assetId}/faceasset`
.replace(`{${"id"}}`, encodeURIComponent(String(id)))
.replace(`{${"assetId"}}`, encodeURIComponent(String(assetId)));
// 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)
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@ -12025,6 +12240,98 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
options: localVarRequestOptions, options: localVarRequestOptions,
}; };
}, },
/**
*
* @param {string} id
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFaces: async (id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('reassignFaces', 'id', id)
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('reassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person/{id}/reassign`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// 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: 'PUT', ...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(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces: async (assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('unassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person`;
// 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: 'DELETE', ...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(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {PeopleUpdateDto} peopleUpdateDto * @param {PeopleUpdateDto} peopleUpdateDto
@ -12127,6 +12434,16 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
export const PersonApiFp = function(configuration?: Configuration) { export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration) const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration)
return { return {
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createPerson(assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createPerson(assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {boolean} [withHidden] * @param {boolean} [withHidden]
@ -12137,6 +12454,17 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @param {string} id
* @param {string} assetId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getAssetFace(id: string, assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetFaceBoxDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetFace(id, assetId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {string} id * @param {string} id
@ -12188,6 +12516,27 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options); const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @param {string} id
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async reassignFaces(id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<PersonResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFaces(id, assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async unassignFaces(assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BulkIdResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.unassignFaces(assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {PeopleUpdateDto} peopleUpdateDto * @param {PeopleUpdateDto} peopleUpdateDto
@ -12219,6 +12568,15 @@ export const PersonApiFp = function(configuration?: Configuration) {
export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = PersonApiFp(configuration) const localVarFp = PersonApiFp(configuration)
return { return {
/**
*
* @param {PersonApiCreatePersonRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createPerson(requestParameters: PersonApiCreatePersonRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.createPerson(requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
@ -12228,6 +12586,15 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig): AxiosPromise<PeopleResponseDto> { getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig): AxiosPromise<PeopleResponseDto> {
return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath)); return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath));
}, },
/**
*
* @param {PersonApiGetAssetFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAssetFace(requestParameters: PersonApiGetAssetFaceRequest, options?: AxiosRequestConfig): AxiosPromise<AssetFaceBoxDto> {
return localVarFp.getAssetFace(requestParameters.id, requestParameters.assetId, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiGetPersonRequest} requestParameters Request parameters. * @param {PersonApiGetPersonRequest} requestParameters Request parameters.
@ -12273,6 +12640,24 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> { mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath)); return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath));
}, },
/**
*
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
return localVarFp.reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters. * @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
@ -12294,6 +12679,20 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
}; };
}; };
/**
* Request parameters for createPerson operation in PersonApi.
* @export
* @interface PersonApiCreatePersonRequest
*/
export interface PersonApiCreatePersonRequest {
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiCreatePerson
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/** /**
* Request parameters for getAllPeople operation in PersonApi. * Request parameters for getAllPeople operation in PersonApi.
* @export * @export
@ -12308,6 +12707,27 @@ export interface PersonApiGetAllPeopleRequest {
readonly withHidden?: boolean readonly withHidden?: boolean
} }
/**
* Request parameters for getAssetFace operation in PersonApi.
* @export
* @interface PersonApiGetAssetFaceRequest
*/
export interface PersonApiGetAssetFaceRequest {
/**
*
* @type {string}
* @memberof PersonApiGetAssetFace
*/
readonly id: string
/**
*
* @type {string}
* @memberof PersonApiGetAssetFace
*/
readonly assetId: string
}
/** /**
* Request parameters for getPerson operation in PersonApi. * Request parameters for getPerson operation in PersonApi.
* @export * @export
@ -12385,6 +12805,41 @@ export interface PersonApiMergePersonRequest {
readonly mergePersonDto: MergePersonDto readonly mergePersonDto: MergePersonDto
} }
/**
* Request parameters for reassignFaces operation in PersonApi.
* @export
* @interface PersonApiReassignFacesRequest
*/
export interface PersonApiReassignFacesRequest {
/**
*
* @type {string}
* @memberof PersonApiReassignFaces
*/
readonly id: string
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiReassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/**
* Request parameters for unassignFaces operation in PersonApi.
* @export
* @interface PersonApiUnassignFacesRequest
*/
export interface PersonApiUnassignFacesRequest {
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiUnassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/** /**
* Request parameters for updatePeople operation in PersonApi. * Request parameters for updatePeople operation in PersonApi.
* @export * @export
@ -12427,6 +12882,17 @@ export interface PersonApiUpdatePersonRequest {
* @extends {BaseAPI} * @extends {BaseAPI}
*/ */
export class PersonApi extends BaseAPI { export class PersonApi extends BaseAPI {
/**
*
* @param {PersonApiCreatePersonRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public createPerson(requestParameters: PersonApiCreatePersonRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).createPerson(requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
@ -12438,6 +12904,17 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @param {PersonApiGetAssetFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public getAssetFace(requestParameters: PersonApiGetAssetFaceRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).getAssetFace(requestParameters.id, requestParameters.assetId, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiGetPersonRequest} requestParameters Request parameters. * @param {PersonApiGetPersonRequest} requestParameters Request parameters.
@ -12493,6 +12970,28 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath)); return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters. * @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.

View file

@ -145,6 +145,9 @@
/> />
{/if} {/if}
{#if isOwner} {#if isOwner}
{#if !asset.isReadOnly && !asset.isExternal}
<CircleIconButton isOpacity={true} icon={mdiDeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
{/if}
<CircleIconButton <CircleIconButton
isOpacity={true} isOpacity={true}
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline} icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}

View file

@ -355,7 +355,7 @@
<section <section
id="immich-asset-viewer" id="immich-asset-viewer"
class="fixed left-0 top-0 z-[1001] grid h-screen w-screen grid-cols-4 grid-rows-[64px_1fr] overflow-y-hidden bg-black" class="fixed left-0 top-0 z-[1001] grid h-screen w-screen grid-cols-4 grid-rows-[64px_1fr] overflow-x-hidden overflow-y-hidden bg-black"
bind:this={assetViewerHtmlElement} bind:this={assetViewerHtmlElement}
> >
<div class="z-[1000] col-span-4 col-start-1 row-span-1 row-start-1 transition-transform"> <div class="z-[1000] col-span-4 col-start-1 row-span-1 row-start-1 transition-transform">

View file

@ -3,18 +3,44 @@
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
import { getAssetFilename } from '$lib/utils/asset-utils'; import { getAssetFilename } from '$lib/utils/asset-utils';
import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat, api } from '@api'; import {
AlbumResponseDto,
AssetResponseDto,
PersonResponseDto,
ThumbnailFormat,
UnassignedFacesResponseDto,
api,
} from '@api';
import type { LatLngTuple } from 'leaflet'; import type { LatLngTuple } from 'leaflet';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { asByteUnitString } from '../../utils/byte-units'; import { asByteUnitString } from '../../utils/byte-units';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte';
import { mdiCalendar, mdiCameraIris, mdiClose, mdiImageOutline, mdiMapMarkerOutline } from '@mdi/js'; import PersonSidePanel, { PersonToCreate } from '../faces-page/person-side-panel.svelte';
import { mdiCalendar, mdiCameraIris, mdiClose, mdiImageOutline, mdiMapMarkerOutline, mdiPencil } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
export let asset: AssetResponseDto; export let asset: AssetResponseDto;
export let albums: AlbumResponseDto[] = []; export let albums: AlbumResponseDto[] = [];
let unassignedFaces: UnassignedFacesResponseDto[] = [];
let people: PersonResponseDto[] = [];
let customFeaturePhoto = new Array<PersonToCreate | null>(asset.people?.length || 0);
let previousId: string;
$: {
if (!previousId) {
previousId = asset.id;
}
if (asset.id !== previousId) {
customFeaturePhoto = new Array<PersonToCreate | null>(asset.people?.length || 0);
showEditFaces = false;
previousId = asset.id;
}
}
let showEditFaces = false;
let textarea: HTMLTextAreaElement; let textarea: HTMLTextAreaElement;
let description: string; let description: string;
@ -23,9 +49,15 @@
$: { $: {
// Get latest description from server // Get latest description from server
if (asset.id && !api.isSharedLink) { if (asset.id && !api.isSharedLink && !showEditFaces) {
api.assetApi.getAssetById({ id: asset.id }).then((res) => { api.assetApi.getAssetById({ id: asset.id }).then((res) => {
people = res.data?.people || []; if (res.data && res.data.people) {
unassignedFaces = res.data?.unassignedPeople || [];
people = (res.data?.people || [])
.map((peopleAsset) => peopleAsset.person)
.filter((person): person is PersonResponseDto => person !== null);
}
textarea.value = res.data?.exifInfo?.description || ''; textarea.value = res.data?.exifInfo?.description || '';
}); });
} }
@ -43,8 +75,6 @@
$: lat = latlng ? latlng[0] : undefined; $: lat = latlng ? latlng[0] : undefined;
$: lng = latlng ? latlng[1] : undefined; $: lng = latlng ? latlng[1] : undefined;
$: people = asset.people || [];
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const getMegapixel = (width: number, height: number): number | undefined => { const getMegapixel = (width: number, height: number): number | undefined => {
@ -82,285 +112,307 @@
}; };
</script> </script>
<section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg"> <div class="relative overflow-x-hidden">
<div class="flex place-items-center gap-2"> <section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
<button <div class="flex place-items-center gap-2">
class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900" <button
on:click={() => dispatch('close')} class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
> on:click={() => dispatch('close')}
<Icon path={mdiClose} size="24" /> >
</button> <Icon path={mdiClose} size="24" />
</button>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p> <p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
</div> </div>
{#if asset.isOffline} {#if asset.isOffline}
<section class="px-4 py-4"> <section class="px-4 py-4">
<div role="alert"> <div role="alert">
<div class="rounded-t bg-red-500 px-4 py-2 font-bold text-white">Asset offline</div> <div class="rounded-t bg-red-500 px-4 py-2 font-bold text-white">Asset offline</div>
<div class="rounded-b border border-t-0 border-red-400 bg-red-100 px-4 py-3 text-red-700"> <div class="rounded-b border border-t-0 border-red-400 bg-red-100 px-4 py-3 text-red-700">
<p> <p>
This asset is offline. Immich can not access its file location. Please ensure the asset is available and This asset is offline. Immich can not access its file location. Please ensure the asset is available and
then rescan the library. then rescan the library.
</p> </p>
</div>
</div> </div>
</div> </section>
</section>
{/if}
<section class="mx-4 mt-10" style:display={!isOwner && textarea?.value == '' ? 'none' : 'block'}>
<textarea
bind:this={textarea}
class="max-h-[500px]
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
placeholder={!isOwner ? '' : 'Add a description'}
on:focusin={handleFocusIn}
on:focusout={handleFocusOut}
on:input={autoGrowHeight}
bind:value={description}
disabled={!isOwner}
/>
</section>
{#if !api.isSharedLink && people.length > 0}
<section class="px-4 py-4 text-sm">
<h2>PEOPLE</h2>
<div class="mt-4 flex flex-wrap gap-2">
{#each people as person (person.id)}
<a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
/>
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
{#if person.birthDate}
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
<p
class="font-light"
title={personBirthDate.toLocaleString(
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
{ locale: $locale },
)}
>
Age {Math.floor(DateTime.fromISO(asset.fileCreatedAt).diff(personBirthDate, 'years').years)}
</p>
{/if}
</a>
{/each}
</div>
</section>
{/if}
<div class="px-4 py-4">
{#if !asset.exifInfo && !asset.isExternal}
<p class="text-sm">NO EXIF INFO AVAILABLE</p>
{:else if !asset.exifInfo && asset.isExternal}
<div class="flex gap-4 py-4">
<div>
<p class="break-all">
Metadata not loaded for {asset.originalPath}
</p>
</div>
</div>
{:else}
<p class="text-sm">DETAILS</p>
{/if} {/if}
{#if asset.exifInfo?.dateTimeOriginal} <section class="mx-4 mt-10" style:display={!isOwner && textarea?.value == '' ? 'none' : 'block'}>
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, { <textarea
zone: asset.exifInfo.timeZone ?? undefined, bind:this={textarea}
})} class="max-h-[500px]
<div class="flex gap-4 py-4"> w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
<div> placeholder={!isOwner ? '' : 'Add a description'}
<Icon path={mdiCalendar} size="24" /> on:focusin={handleFocusIn}
</div> on:focusout={handleFocusOut}
on:input={autoGrowHeight}
bind:value={description}
disabled={!isOwner}
/>
</section>
<div> {#if !api.isSharedLink && people.length > 0}
<p> <section class="px-4 py-4 text-sm">
{assetDateTimeOriginal.toLocaleString( <div class="grid grid-cols-2 items-center">
{ <h2 class="justify-self-start uppercase">People</h2>
month: 'short', <button class="justify-self-end" on:click={() => (showEditFaces = true)}>
day: 'numeric', <Icon path={mdiPencil} size={18} />
year: 'numeric', </button>
}, {#if unassignedFaces.length > 0}
{ locale: $locale }, <p>{`${unassignedFaces.length} face${unassignedFaces.length > 1 ? 's' : ''} available to add`}</p>
)} {/if}
</p> </div>
<div class="flex gap-2 text-sm"> {#if people.length > 0}
<div class="mt-4 flex flex-wrap gap-2">
{#each people as person, index (person.id)}
<a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
<ImageThumbnail
curve
shadow
url={customFeaturePhoto[index]?.thumbnail || api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
/>
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
{#if person.birthDate}
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
<p
class="font-light"
title={personBirthDate.toLocaleString(
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
{ locale: $locale },
)}
>
Age {Math.floor(DateTime.fromISO(asset.fileCreatedAt).diff(personBirthDate, 'years').years)}
</p>
{/if}
</a>
{/each}
</div>
{/if}
</section>
{/if}
<div class="px-4 py-4">
{#if !asset.exifInfo && !asset.isExternal}
<p class="text-sm">NO EXIF INFO AVAILABLE</p>
{:else if !asset.exifInfo && asset.isExternal}
<div class="flex gap-4 py-4">
<div>
<p class="break-all">
Metadata not loaded for {asset.originalPath}
</p>
</div>
</div>
{:else}
<p class="text-sm">DETAILS</p>
{/if}
{#if asset.exifInfo?.dateTimeOriginal}
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
zone: asset.exifInfo.timeZone ?? undefined,
})}
<div class="flex gap-4 py-4">
<div>
<Icon path={mdiCalendar} size="24" />
</div>
<div>
<p> <p>
{assetDateTimeOriginal.toLocaleString( {assetDateTimeOriginal.toLocaleString(
{ {
weekday: 'short', month: 'short',
hour: 'numeric', day: 'numeric',
minute: '2-digit', year: 'numeric',
timeZoneName: 'longOffset',
}, },
{ locale: $locale }, { locale: $locale },
)} )}
</p> </p>
<div class="flex gap-2 text-sm">
<p>
{assetDateTimeOriginal.toLocaleString(
{
weekday: 'short',
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'longOffset',
},
{ locale: $locale },
)}
</p>
</div>
</div>
</div>{/if}
{#if asset.exifInfo?.fileSizeInByte}
<div class="flex gap-4 py-4">
<div><Icon path={mdiImageOutline} size="24" /></div>
<div>
<p class="break-all">
{getAssetFilename(asset)}
</p>
<div class="flex gap-2 text-sm">
{#if asset.exifInfo.exifImageHeight && asset.exifInfo.exifImageWidth}
{#if getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)}
<p>
{getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)} MP
</p>
{/if}
<p>{asset.exifInfo.exifImageHeight} x {asset.exifInfo.exifImageWidth}</p>
{/if}
<p>{asByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}</p>
</div>
</div> </div>
</div> </div>
</div>{/if} {/if}
{#if asset.exifInfo?.fileSizeInByte} {#if asset.exifInfo?.make || asset.exifInfo?.model || asset.exifInfo?.fNumber}
<div class="flex gap-4 py-4"> <div class="flex gap-4 py-4">
<div><Icon path={mdiImageOutline} size="24" /></div> <div><Icon path={mdiCameraIris} size="24" /></div>
<div> <div>
<p class="break-all"> <p>{asset.exifInfo.make || ''} {asset.exifInfo.model || ''}</p>
{getAssetFilename(asset)} <div class="flex gap-2 text-sm">
</p> {#if asset.exifInfo?.fNumber}
<div class="flex gap-2 text-sm"> <p>{`ƒ/${asset.exifInfo.fNumber.toLocaleString($locale)}` || ''}</p>
{#if asset.exifInfo.exifImageHeight && asset.exifInfo.exifImageWidth} {/if}
{#if getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)}
{#if asset.exifInfo.exposureTime}
<p>{`${asset.exifInfo.exposureTime}`}</p>
{/if}
{#if asset.exifInfo.focalLength}
<p>{`${asset.exifInfo.focalLength.toLocaleString($locale)} mm`}</p>
{/if}
{#if asset.exifInfo.iso}
<p> <p>
{getMegapixel(asset.exifInfo.exifImageHeight, asset.exifInfo.exifImageWidth)} MP {`ISO ${asset.exifInfo.iso}`}
</p> </p>
{/if} {/if}
</div>
<p>{asset.exifInfo.exifImageHeight} x {asset.exifInfo.exifImageWidth}</p>
{/if}
<p>{asByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}</p>
</div> </div>
</div> </div>
</div> {/if}
{/if}
{#if asset.exifInfo?.make || asset.exifInfo?.model || asset.exifInfo?.fNumber} {#if asset.exifInfo?.city}
<div class="flex gap-4 py-4"> <div class="flex gap-4 py-4">
<div><Icon path={mdiCameraIris} size="24" /></div> <div><Icon path={mdiMapMarkerOutline} size="24" /></div>
<div>
<p>{asset.exifInfo.make || ''} {asset.exifInfo.model || ''}</p>
<div class="flex gap-2 text-sm">
{#if asset.exifInfo?.fNumber}
<p>{`ƒ/${asset.exifInfo.fNumber.toLocaleString($locale)}` || ''}</p>
{/if}
{#if asset.exifInfo.exposureTime}
<p>{`${asset.exifInfo.exposureTime}`}</p>
{/if}
{#if asset.exifInfo.focalLength}
<p>{`${asset.exifInfo.focalLength.toLocaleString($locale)} mm`}</p>
{/if}
{#if asset.exifInfo.iso}
<p>
{`ISO ${asset.exifInfo.iso}`}
</p>
{/if}
</div>
</div>
</div>
{/if}
{#if asset.exifInfo?.city}
<div class="flex gap-4 py-4">
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
<div>
<p>{asset.exifInfo.city}</p>
{#if asset.exifInfo?.state}
<div class="flex gap-2 text-sm">
<p>{asset.exifInfo.state}</p>
</div>
{/if}
{#if asset.exifInfo?.country}
<div class="flex gap-2 text-sm">
<p>{asset.exifInfo.country}</p>
</div>
{/if}
</div>
</div>
{/if}
</div>
</section>
{#if latlng && $featureFlags.loaded && $featureFlags.map}
<div class="h-[360px]">
{#await import('../shared-components/leaflet') then { Map, TileLayer, Marker }}
<Map center={latlng} zoom={14}>
<TileLayer
urlTemplate={$serverConfig.mapTileUrl}
options={{
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}}
/>
<Marker {latlng}>
<p>
{lat}, {lng}
</p>
<a href="https://www.openstreetmap.org/?mlat={lat}&mlon={lng}&zoom=15#map=15/{lat}/{lng}">
Open in OpenStreetMap
</a>
</Marker>
</Map>
{/await}
</div>
{/if}
{#if asset.owner && !isOwner}
<section class="px-6 pt-6 dark:text-immich-dark-fg">
<p class="text-sm">SHARED BY</p>
<div class="flex gap-4 pt-4">
<div>
<UserAvatar user={asset.owner} size="md" autoColor />
</div>
<div class="mb-auto mt-auto">
<p>
{asset.owner.firstName}
{asset.owner.lastName}
</p>
</div>
</div>
</section>
{/if}
{#if albums.length > 0}
<section class="p-6 dark:text-immich-dark-fg">
<p class="pb-4 text-sm">APPEARS IN</p>
{#each albums as album}
<a data-sveltekit-preload-data="hover" href={`/albums/${album.id}`}>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="flex gap-4 py-2 hover:cursor-pointer"
on:click={() => dispatch('click', album)}
on:keydown={() => dispatch('click', album)}
>
<div> <div>
<img <div>
alt={album.albumName} <p>{asset.exifInfo.city}</p>
class="h-[50px] w-[50px] rounded object-cover" {#if asset.exifInfo?.state}
src={album.albumThumbnailAssetId && <div class="flex gap-2 text-sm">
api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)} <p>{asset.exifInfo.state}</p>
draggable="false" </div>
/> {/if}
</div> {#if asset.exifInfo?.country}
<div class="flex gap-2 text-sm">
<div class="mb-auto mt-auto"> <p>{asset.exifInfo.country}</p>
<p class="dark:text-immich-dark-primary">{album.albumName}</p> </div>
<div class="flex gap-2 text-sm">
<p>{album.assetCount} items</p>
{#if album.shared}
<p>· Shared</p>
{/if} {/if}
</div> </div>
</div> </div>
</div> </div>
</a> {/if}
{/each} </div>
</section> </section>
{#if latlng && $featureFlags.loaded && $featureFlags.map}
<div class="h-[360px]">
{#await import('../shared-components/leaflet') then { Map, TileLayer, Marker }}
<Map center={latlng} zoom={14}>
<TileLayer
urlTemplate={$serverConfig.mapTileUrl}
options={{
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}}
/>
<Marker {latlng}>
<p>
{lat}, {lng}
</p>
<a href="https://www.openstreetmap.org/?mlat={lat}&mlon={lng}&zoom=15#map=15/{lat}/{lng}">
Open in OpenStreetMap
</a>
</Marker>
</Map>
{/await}
</div>
{/if}
{#if asset.owner && !isOwner}
<section class="px-6 pt-6 dark:text-immich-dark-fg">
<p class="text-sm">SHARED BY</p>
<div class="flex gap-4 pt-4">
<div>
<UserAvatar user={asset.owner} size="md" autoColor />
</div>
<div class="mb-auto mt-auto">
<p>
{asset.owner.firstName}
{asset.owner.lastName}
</p>
</div>
</div>
</section>
{/if}
{#if albums.length > 0}
<section class="p-6 dark:text-immich-dark-fg">
<p class="pb-4 text-sm">APPEARS IN</p>
{#each albums as album}
<a data-sveltekit-preload-data="hover" href={`/albums/${album.id}`}>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="flex gap-4 py-2 hover:cursor-pointer"
on:click={() => dispatch('click', album)}
on:keydown={() => dispatch('click', album)}
>
<div>
<img
alt={album.albumName}
class="h-[50px] w-[50px] rounded object-cover"
src={album.albumThumbnailAssetId &&
api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
draggable="false"
/>
</div>
<div class="mb-auto mt-auto">
<p class="dark:text-immich-dark-primary">{album.albumName}</p>
<div class="flex gap-2 text-sm">
<p>{album.assetCount} items</p>
{#if album.shared}
<p>· Shared</p>
{/if}
</div>
</div>
</div>
</a>
{/each}
</section>
{/if}
</div>
{#if showEditFaces}
<PersonSidePanel
bind:people
bind:unassignedFaces
bind:selectedPersonToCreate={customFeaturePhoto}
assetId={asset.id}
on:close={() => (showEditFaces = false)}
/>
{/if} {/if}

View file

@ -17,8 +17,8 @@
<div <div
class="flex w-full h-14 place-items-center {suggestedPeople class="flex w-full h-14 place-items-center {suggestedPeople
? 'rounded-t-lg dark:border-immich-dark-gray' ? 'rounded-t-lg border-immich-primary dark:border-immich-dark-gray'
: 'rounded-lg'} bg-gray-100 p-2 dark:bg-gray-700" : 'rounded-lg'} bg-gray-200 p-2 dark:bg-gray-700"
> >
<ImageThumbnail <ImageThumbnail
circle circle
@ -36,7 +36,7 @@
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y-autofocus -->
<input <input
autofocus autofocus
class="w-full gap-2 bg-gray-100 dark:bg-gray-700 dark:text-white" class="w-full gap-2 bg-gray-200 dark:bg-gray-700 dark:text-white"
type="text" type="text"
placeholder="New name or nickname" placeholder="New name or nickname"
bind:value={name} bind:value={name}

View file

@ -12,6 +12,8 @@
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { goto, invalidateAll } from '$app/navigation'; import { goto, invalidateAll } from '$app/navigation';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import PeopleList from './people-list.svelte';
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js'; import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
@ -69,7 +71,6 @@
message: `Merged ${count} ${count === 1 ? 'person' : 'people'}`, message: `Merged ${count} ${count === 1 ? 'person' : 'people'}`,
type: NotificationType.Info, type: NotificationType.Info,
}); });
people = people.filter((person) => !results.some((result) => result.id === person.id && result.success === true));
await invalidateAll(); await invalidateAll();
dispatch('merge'); dispatch('merge');
} catch (error) { } catch (error) {
@ -133,16 +134,8 @@
<FaceThumbnail {person} border circle selectable={false} thumbnailSize={180} /> <FaceThumbnail {person} border circle selectable={false} thumbnailSize={180} />
</div> </div>
</div> </div>
<div
class="immich-scrollbar overflow-y-auto rounded-3xl bg-gray-200 p-10 dark:bg-immich-dark-gray" <PeopleList people={unselectedPeople} {screenHeight} on:select={({ detail }) => onSelect(detail)} />
style:max-height={screenHeight - 200 - 200 + 'px'}
>
<div class="grid-col-2 grid gap-8 md:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">
{#each unselectedPeople as person (person.id)}
<FaceThumbnail {person} on:click={() => onSelect(person)} circle border selectable />
{/each}
</div>
</div>
</section> </section>
{#if isShowConfirmation} {#if isShowConfirmation}

View file

@ -0,0 +1,31 @@
<script lang="ts">
import type { PersonResponseDto } from '@api';
import FaceThumbnail from './face-thumbnail.svelte';
import { createEventDispatcher } from 'svelte';
export let screenHeight: number;
export let people: PersonResponseDto[] = [];
let dispatch = createEventDispatcher<{
select: PersonResponseDto;
}>();
</script>
<div
class="immich-scrollbar overflow-y-auto rounded-3xl bg-gray-200 p-10 dark:bg-immich-dark-gray"
style:max-height={screenHeight - 400 + 'px'}
>
<div class="grid-col-2 grid gap-8 md:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">
{#each people as person (person.id)}
<FaceThumbnail
{person}
on:click={() => {
dispatch('select', person);
}}
circle
border
selectable
/>
{/each}
</div>
</div>

View file

@ -0,0 +1,453 @@
<script lang="ts" context="module">
export type PersonToCreate = {
thumbnail: string;
canEdit: boolean;
};
</script>
<script lang="ts">
import { blur, fly } from 'svelte/transition';
import { linear } from 'svelte/easing';
import { api, ThumbnailFormat, type PersonResponseDto, UnassignedFacesResponseDto } from '@api';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import { cloneDeep } from 'lodash-es';
import { handleError } from '$lib/utils/handle-error';
import { createEventDispatcher, onMount } from 'svelte';
import { mdiRestart, mdiAccount, mdiMinus, mdiClose, mdiPlus, mdiMagnify, mdiArrowLeftThin } from '@mdi/js';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import Icon from '$lib/components/elements/icon.svelte';
export let people: PersonResponseDto[];
export let unassignedFaces: UnassignedFacesResponseDto[];
export let assetId: string;
let peopleToAdd: (PersonResponseDto | string)[] = [];
let searchedPeople: PersonResponseDto[] = [];
let searchWord: string;
const dispatch = createEventDispatcher();
let searchFaces = false;
let searchName = '';
let isSearchingPeople = false;
let allPeople: PersonResponseDto[] = [];
let editedPerson: number;
let selectedPersonToReassign: (PersonResponseDto | null)[] = new Array<PersonResponseDto | null>(people.length);
export let selectedPersonToCreate: (PersonToCreate | null)[] = new Array<PersonToCreate | null>(people.length);
let showSeletecFaces = false;
let showLoadingSpinner = false;
let editedPeople = cloneDeep(people);
onMount(async () => {
const { data } = await api.personApi.getAllPeople({ withHidden: false });
allPeople = data.people;
peopleToAdd = await initUnassignedFaces();
});
const searchPeople = async () => {
if ((people.length < 20 && searchName.startsWith(searchWord)) || searchName === '') {
return;
}
const timeout = setTimeout(() => (isSearchingPeople = true), 100);
try {
const { data } = await api.searchApi.searchPerson({ name: searchName });
searchedPeople = data.filter((item) => item.id !== people[editedPerson].id);
searchWord = searchName;
} catch (error) {
handleError(error, "Can't search people");
} finally {
clearTimeout(timeout);
}
isSearchingPeople = false;
};
$: {
searchedPeople = !searchName
? allPeople
: allPeople
.filter((person: PersonResponseDto) => {
const nameParts = person.name.split(' ');
return nameParts.some((splitName) => splitName.toLowerCase().startsWith(searchName.toLowerCase()));
})
.slice(0, 5);
}
const initInput = (element: HTMLInputElement) => {
element.focus();
};
const initUnassignedFaces = async (): Promise<string[]> => {
const results: string[] = [];
for (let i = 0; i < unassignedFaces.length; i++) {
const data = await api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg);
const newFeaturePhoto = await zoomImageToBase64(
data,
unassignedFaces[i].boudinxBox.boundingBoxX1,
unassignedFaces[i].boudinxBox.boundingBoxX2,
unassignedFaces[i].boudinxBox.boundingBoxY1,
unassignedFaces[i].boudinxBox.boundingBoxY2,
);
if (newFeaturePhoto) {
results.push(newFeaturePhoto);
}
}
return results;
};
const handleBackButton = () => {
searchName = '';
searchFaces = false;
selectedPersonToCreate = new Array<PersonToCreate | null>(people.length);
if (showSeletecFaces) {
showSeletecFaces = false;
} else {
dispatch('close');
}
};
async function zoomImageToBase64(
imageSrc: string,
x1: number,
x2: number,
y1: number,
y2: number,
): Promise<string | null> {
// Calculate width and height from the coordinates
const width = x2 - x1;
const height = y2 - y1;
// Create an image element and load the image source
const img = new Image();
img.src = imageSrc;
// Wait for the image to load
await new Promise((resolve) => {
img.onload = resolve;
img.onerror = () => resolve(null); // Handle image load errors
});
// Calculate the new width and height after zooming out
const newWidth = width * 1.5;
const newHeight = height * 1.5;
// Create a canvas element to draw the zoomed-out image
const canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the zoomed-out portion of the image onto the canvas
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(
img,
x1 - (newWidth - width) / 2,
y1 - (newHeight - height) / 2,
newWidth,
newHeight,
0,
0,
newWidth,
newHeight,
);
// Convert the canvas content to base64
const base64Image = canvas.toDataURL('image/webp');
return base64Image;
} else {
return null;
}
}
const handleReset = (index: number) => {
editedPeople[index] = people[index];
if (selectedPersonToReassign[index]) {
selectedPersonToReassign[index] = null;
}
if (selectedPersonToCreate[index]) {
selectedPersonToCreate[index] = null;
}
};
const handleEditFaces = async () => {
const numberOfChanges =
selectedPersonToCreate.filter((person) => person !== null && person.canEdit !== false).length +
selectedPersonToReassign.filter((person) => person !== null).length;
if (numberOfChanges > 0) {
showLoadingSpinner = true;
try {
for (let i = 0; i < selectedPersonToReassign.length; i++) {
const personId = selectedPersonToReassign[i]?.id;
if (personId) {
await api.personApi.reassignFaces({
id: personId,
assetFaceUpdateDto: { data: [{ assetId: assetId, personId: people[i].id }] },
});
people[i] = selectedPersonToReassign[i] as PersonResponseDto;
}
}
for (let i = 0; i < selectedPersonToCreate.length; i++) {
const personToCreate = selectedPersonToCreate[i];
if (personToCreate && personToCreate.canEdit !== false) {
const { data } = await api.personApi.createPerson({
assetFaceUpdateDto: { data: [{ assetId: assetId, personId: people[i].id }] },
});
people[i] = data;
selectedPersonToCreate[i] = personToCreate;
}
}
notificationController.show({
message: `Edited ${numberOfChanges} ${numberOfChanges > 1 ? 'people' : 'person'}`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, "Can't apply changes");
}
showLoadingSpinner = false;
}
dispatch('close');
};
const handleCreatePerson = async () => {
const { data } = await api.personApi.getAssetFace({ id: people[editedPerson].id, assetId });
const assetFace = data;
for (let i = 0; i < people.length; i++) {
if (people[i].id === people[editedPerson].id) {
const data = await api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg);
const newFeaturePhoto = await zoomImageToBase64(
data,
assetFace.boundingBoxX1,
assetFace.boundingBoxX2,
assetFace.boundingBoxY1,
assetFace.boundingBoxY2,
);
if (newFeaturePhoto) {
selectedPersonToCreate[i] = { canEdit: true, thumbnail: data };
}
break;
}
}
showSeletecFaces = false;
};
const handleReassignFace = (person: PersonResponseDto) => {
selectedPersonToReassign[editedPerson] = person;
editedPeople[editedPerson] = person;
console.log(selectedPersonToReassign);
showSeletecFaces = false;
};
const handlePersonPicker = async (index: number) => {
editedPerson = index;
searchedPeople = allPeople.filter((item) => item.id !== people[index].id);
showSeletecFaces = true;
};
</script>
<section
transition:fly={{ x: 360, duration: 100, easing: linear }}
class="absolute top-0 z-[2000] h-full w-[360px] overflow-x-hidden p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg"
>
<div class="flex place-items-center justify-between gap-2">
<div class="flex items-center gap-2">
<button
class="flex place-content-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
on:click={handleBackButton}
>
<Icon path={mdiArrowLeftThin} size="24" />
</button>
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Edit faces</p>
</div>
{#if !showLoadingSpinner}
<button
class="justify-self-end rounded-lg p-2 hover:bg-immich-dark-primary hover:dark:bg-immich-dark-primary/50"
on:click={() => handleEditFaces()}
>
Done
</button>
{:else}
<LoadingSpinner />
{/if}
</div>
<div class="px-4 py-4 text-sm">
<div class="mt-4 flex flex-wrap gap-2">
{#each editedPeople as person, index}
<div class="relative h-[115px] w-[95px]">
<a href="/people/{person.id}">
<div class="absolute top-0 left-1/2 transform -translate-x-1/2 h-[90px] w-[90px]">
<ImageThumbnail
curve
shadow
url={selectedPersonToCreate[index]?.thumbnail || api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
/>
<p class="relative mt-1 truncate font-medium" title={person.name}>
{person.name}
</p>
</div>
</a>
<div
transition:blur={{ amount: 10, duration: 50 }}
class="absolute -left-[5px] -top-[5px] h-[20px] w-[20px] rounded-full bg-red-700"
>
<button on:click={() => handleReset(index)} class="flex h-full w-full">
<div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
<Icon path={mdiMinus} size={18} />
</div>
</button>
</div>
<div
transition:blur={{ amount: 10, duration: 50 }}
class="absolute -right-[5px] -top-[5px] h-[20px] w-[20px] rounded-full bg-blue-700"
>
{#if (selectedPersonToCreate[index] && selectedPersonToCreate[index]?.canEdit !== false) || selectedPersonToReassign[index]}
<button on:click={() => handleReset(index)} class="flex h-full w-full">
<div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
<Icon path={mdiRestart} size={18} />
</div>
</button>
{:else}
<button on:click={() => handlePersonPicker(index)} class="flex h-full w-full">
<div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
<Icon path={mdiAccount} size={18} />
</div>
</button>
{/if}
</div>
</div>
{/each}
{#each peopleToAdd as face, index}
<div class="relative h-[115px] w-[95px]">
<div class="absolute top-0 left-1/2 transform -translate-x-1/2 h-[90px] w-[90px]">
<ImageThumbnail
curve
shadow
url={typeof face === 'string' ? face : api.getPeopleThumbnailUrl(face.id)}
altText="Unassigned face"
title="TO DO"
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
/>
</div>
<div
transition:blur={{ amount: 10, duration: 50 }}
class="absolute -right-[5px] -top-[5px] h-[20px] w-[20px] rounded-full bg-blue-700"
>
<button on:click={() => handlePersonPicker(index)} class="flex h-full w-full">
<div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
<Icon path={mdiMinus} size={18} />
</div>
</button>
</div>
</div>
{/each}
</div>
</div>
</section>
{#if showSeletecFaces}
<section
transition:fly={{ x: 360, duration: 100, easing: linear }}
class="absolute top-0 z-[2001] h-full overflow-x-hidden p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg"
>
<div class="flex place-items-center justify-between gap-2">
{#if !searchFaces}
<div class="flex items-center gap-2">
<button
class="flex place-content-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
on:click={handleBackButton}
>
<Icon path={mdiArrowLeftThin} size="24" />
</button>
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Select face</p>
</div>
<div class="flex justify-end gap-2">
{#if isSearchingPeople}
<button
class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
title="Search existing person"
on:click={() => {
searchFaces = true;
}}
>
<Icon path={mdiMagnify} size="24" />
</button>
{:else}
<div
class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
>
<LoadingSpinner />
</div>
{/if}
<button
class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
on:click={() => handleCreatePerson()}
title="Create new person"
>
<Icon path={mdiPlus} size="24" />
</button>
</div>
{:else}
<button
class="flex place-content-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
on:click={handleBackButton}
>
<Icon path={mdiArrowLeftThin} size="24" />
</button>
<input
class="w-full gap-2 bg-immich-bg dark:bg-immich-dark-bg"
type="text"
placeholder="Name or nickname"
bind:value={searchName}
on:input={() => searchPeople()}
use:initInput
/>
<button
class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
on:click={() => (searchFaces = false)}
>
<Icon path={mdiClose} size="24" />
</button>
{/if}
</div>
<div class="px-4 py-4 text-sm">
<h2 class="mb-8 mt-4 uppercase">All people</h2>
<div class="immich-scrollbar mt-4 flex flex-wrap gap-2 overflow-y-auto">
{#each searchName == '' ? allPeople : searchedPeople as person (person.id)}
<div class="w-fit">
<button class="w-[90px]" on:click={() => handleReassignFace(person)}>
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
heightStyle="90px"
thumbhash={null}
/>
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
</button>
</div>
{/each}
</div>
</div>
</section>
{/if}

View file

@ -0,0 +1,200 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import FaceThumbnail from './face-thumbnail.svelte';
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import { api, AssetFaceUpdateItem, type PersonResponseDto } from '@api';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import Button from '../elements/buttons/button.svelte';
import { mdiPlus, mdiCloseThick, mdiMerge } from '@mdi/js';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { handleError } from '$lib/utils/handle-error';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
import PeopleList from './people-list.svelte';
import Icon from '$lib/components/elements/icon.svelte';
let people: PersonResponseDto[] = [];
export let assetIds: string[];
export let personId: string;
const data: AssetFaceUpdateItem[] = [];
onMount(async () => {
const { data } = await api.personApi.getAllPeople({ withHidden: false });
people = data.people;
});
for (const assetId of assetIds) {
data.push({ assetId, personId });
}
let selectedPerson: PersonResponseDto | null = null;
let disableButtons = false;
let showLoadingSpinnerCreate = false;
let showLoadingSpinnerReassign = false;
let showLoadingSpinnerUnassign = false;
let hasSelection = false;
let screenHeight: number;
let dispatch = createEventDispatcher();
const onClose = () => {
dispatch('close');
};
const handleSelectedPerson = (person: PersonResponseDto) => {
if (selectedPerson && selectedPerson.id === person.id) {
handleRemoveSelectedPerson();
return;
}
selectedPerson = person;
hasSelection = true;
};
const handleRemoveSelectedPerson = () => {
selectedPerson = null;
hasSelection = false;
};
const handleUnassign = async () => {
try {
showLoadingSpinnerCreate = true;
disableButtons = true;
await api.personApi.unassignFaces({
assetFaceUpdateDto: { data },
});
notificationController.show({
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to a new person`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to reassign assets to a new person');
}
dispatch('confirm');
};
const handleCreate = async () => {
try {
showLoadingSpinnerCreate = true;
disableButtons = true;
await api.personApi.createPerson({
assetFaceUpdateDto: { data },
});
notificationController.show({
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to a new person`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to reassign assets to a new person');
}
dispatch('confirm');
};
const handleReassign = async () => {
try {
showLoadingSpinnerReassign = true;
disableButtons = true;
if (selectedPerson) {
await api.personApi.reassignFaces({
id: selectedPerson.id,
assetFaceUpdateDto: { data },
});
notificationController.show({
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to ${
selectedPerson.name || 'an existing person'
}`,
type: NotificationType.Info,
});
}
} catch (error) {
handleError(error, `Unable to reassign assets to ${selectedPerson?.name || 'an existing person'}`);
}
dispatch('confirm');
};
</script>
<svelte:window bind:innerHeight={screenHeight} />
<section
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
>
<ControlAppBar on:close-button-click={onClose}>
<svelte:fragment slot="leading">
<slot name="header" />
<div />
</svelte:fragment>
<svelte:fragment slot="trailing">
<div class="flex gap-4">
<!-- TODO: Implement actions -->
<Button
title={'Unassign selected assets'}
size={'sm'}
disabled={disableButtons || hasSelection}
on:click={() => {
handleUnassign();
}}
>
{#if !showLoadingSpinnerUnassign}
<Icon path={mdiCloseThick} size={18} />
{:else}
<LoadingSpinner />
{/if}
<span class="ml-2"> Unassign assets</span></Button
>
<Button
title={'Assign selected assets to a new person'}
size={'sm'}
disabled={disableButtons || hasSelection}
on:click={() => {
handleCreate();
}}
>
{#if !showLoadingSpinnerCreate}
<Icon path={mdiPlus} size={18} />
{:else}
<LoadingSpinner />
{/if}
<span class="ml-2"> Create new Person</span></Button
>
<Button
size={'sm'}
title={'Assign selected assets to an existing person'}
disabled={disableButtons || !hasSelection}
on:click={() => {
handleReassign();
}}
>
{#if !showLoadingSpinnerReassign}
<Icon path={mdiMerge} size={18} class="rotate-180" />
{:else}
<LoadingSpinner />
{/if}
<span class="ml-2"> Reassign</span></Button
>
</div>
</svelte:fragment>
</ControlAppBar>
<slot name="merge" />
<section class="bg-immich-bg px-[70px] pt-[100px] dark:bg-immich-dark-bg">
<section id="merge-face-selector relative">
{#if selectedPerson !== null}
<div class="mb-10 h-[200px] place-content-center place-items-center">
<p class="mb-4 text-center uppercase dark:text-white">Choose matching faces to re assign</p>
<div class="grid grid-flow-col-dense place-content-center place-items-center gap-4">
<FaceThumbnail
person={selectedPerson}
border
circle
selectable
thumbnailSize={180}
on:click={handleRemoveSelectedPerson}
/>
</div>
</div>
{/if}
<PeopleList {people} {screenHeight} on:select={({ detail }) => handleSelectedPerson(detail)} />
</section>
</section>
</section>

View file

@ -22,7 +22,9 @@
OBJECTS = 'smartInfo.objects', OBJECTS = 'smartInfo.objects',
} }
const MAX_ITEMS = 12; let MAX_ITEMS = 12;
let innerWidth: number | null = null;
const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => { const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => {
const targetField = items.find((item) => item.fieldName === field); const targetField = items.find((item) => item.fieldName === field);
return targetField?.items || []; return targetField?.items || [];
@ -32,6 +34,7 @@
$: places = getFieldItems(data.items, Field.CITY); $: places = getFieldItems(data.items, Field.CITY);
$: people = data.response.people.slice(0, MAX_ITEMS); $: people = data.response.people.slice(0, MAX_ITEMS);
$: hasPeople = data.response.total > 0; $: hasPeople = data.response.total > 0;
$: MAX_ITEMS = innerWidth ? Math.floor(innerWidth / 112) : MAX_ITEMS;
</script> </script>
<UserPageLayout user={data.user} title={data.meta.title}> <UserPageLayout user={data.user} title={data.meta.title}>
@ -45,19 +48,21 @@
draggable="false">View All</a draggable="false">View All</a
> >
</div> </div>
<div class="flex flex-row flex-wrap gap-4"> <div class="flex flex-row {MAX_ITEMS < 5 ? 'justify-center' : ''} flex-wrap gap-4" bind:offsetWidth={innerWidth}>
{#each people as person (person.id)} {#if innerWidth}
<a href="/people/{person.id}" class="w-24 text-center"> {#each people as person (person.id)}
<ImageThumbnail <a href="/people/{person.id}" class="w-24 text-center">
circle <ImageThumbnail
shadow circle
url={api.getPeopleThumbnailUrl(person.id)} shadow
altText={person.name} url={api.getPeopleThumbnailUrl(person.id)}
widthStyle="100%" altText={person.name}
/> widthStyle="100%"
<p class="mt-2 text-ellipsis text-sm font-medium dark:text-white">{person.name}</p> />
</a> <p class="mt-2 text-ellipsis text-sm font-medium dark:text-white">{person.name}</p>
{/each} </a>
{/each}
{/if}
</div> </div>
</div> </div>
{/if} {/if}

View file

@ -30,6 +30,7 @@
import { AssetResponseDto, PersonResponseDto, TimeBucketSize, api } from '@api'; import { AssetResponseDto, PersonResponseDto, TimeBucketSize, api } from '@api';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import UnmergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte';
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
@ -46,6 +47,7 @@
MERGE_FACES = 'merge-faces', MERGE_FACES = 'merge-faces',
SUGGEST_MERGE = 'suggest-merge', SUGGEST_MERGE = 'suggest-merge',
BIRTH_DATE = 'birth-date', BIRTH_DATE = 'birth-date',
UNASSIGN_ASSETS = 'unassign-faces',
} }
let assetStore = new AssetStore({ let assetStore = new AssetStore({
@ -88,7 +90,7 @@
if ((people.length < 20 && name.startsWith(searchWord)) || name === '') { if ((people.length < 20 && name.startsWith(searchWord)) || name === '') {
return; return;
} }
const timeout = setTimeout(() => (isSearchingPeople = true), 300); const timeout = setTimeout(() => (isSearchingPeople = true), 100);
try { try {
const { data } = await api.searchApi.searchPerson({ name }); const { data } = await api.searchApi.searchPerson({ name });
people = data; people = data;
@ -158,6 +160,7 @@
personId: data.person.id, personId: data.person.id,
}); });
previousPersonId = data.person.id; previousPersonId = data.person.id;
name = data.person.name;
refreshAssetGrid = !refreshAssetGrid; refreshAssetGrid = !refreshAssetGrid;
} }
}); });
@ -198,6 +201,10 @@
viewMode = ViewMode.VIEW_ASSETS; viewMode = ViewMode.VIEW_ASSETS;
}; };
const handleReassignAssets = () => {
viewMode = ViewMode.UNASSIGN_ASSETS;
};
const updateAssetCount = async () => { const updateAssetCount = async () => {
try { try {
const { data: statistics } = await api.personApi.getPersonStatistics({ const { data: statistics } = await api.personApi.getPersonStatistics({
@ -209,6 +216,12 @@
} }
}; };
const handleUnmerge = () => {
$assetStore.removeAssets(Array.from($selectedAssets).map((a) => a.id));
assetInteractionStore.clearMultiselect();
viewMode = ViewMode.VIEW_ASSETS;
};
const handleMergeSameFace = async (response: [PersonResponseDto, PersonResponseDto]) => { const handleMergeSameFace = async (response: [PersonResponseDto, PersonResponseDto]) => {
const [personToMerge, personToBeMergedIn] = response; const [personToMerge, personToBeMergedIn] = response;
viewMode = ViewMode.VIEW_ASSETS; viewMode = ViewMode.VIEW_ASSETS;
@ -267,7 +280,7 @@
if (viewMode === ViewMode.SUGGEST_MERGE) { if (viewMode === ViewMode.SUGGEST_MERGE) {
return; return;
} }
isSearchingPeople = false;
isEditingName = false; isEditingName = false;
}; };
@ -351,6 +364,15 @@
/> />
{/if} {/if}
{#if viewMode === ViewMode.UNASSIGN_ASSETS}
<UnmergeFaceSelector
assetIds={Array.from($selectedAssets).map((a) => a.id)}
personId={data.person.id}
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
on:confirm={handleUnmerge}
/>
{/if}
{#if viewMode === ViewMode.BIRTH_DATE} {#if viewMode === ViewMode.BIRTH_DATE}
<SetBirthDateModal <SetBirthDateModal
birthDate={data.person.birthDate ?? ''} birthDate={data.person.birthDate ?? ''}
@ -377,6 +399,7 @@
<DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" /> <DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" />
<FavoriteAction menuItem removeFavorite={isAllFavorite} /> <FavoriteAction menuItem removeFavorite={isAllFavorite} />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(ids) => $assetStore.removeAssets(ids)} /> <ArchiveAction menuItem unarchive={isAllArchive} onArchive={(ids) => $assetStore.removeAssets(ids)} />
<MenuOption text="Unmerge assets" on:click={handleReassignAssets} />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
@ -465,7 +488,7 @@
<div class="absolute z-[999] w-64 sm:w-96"> <div class="absolute z-[999] w-64 sm:w-96">
{#if isSearchingPeople} {#if isSearchingPeople}
<div <div
class="flex rounded-b-lg dark:border-immich-dark-gray place-items-center bg-gray-100 p-2 dark:bg-gray-700" class="flex border h-14 rounded-b-lg border-gray-400 dark:border-immich-dark-gray place-items-center bg-gray-200 p-2 dark:bg-gray-700"
> >
<div class="flex w-full place-items-center"> <div class="flex w-full place-items-center">
<LoadingSpinner /> <LoadingSpinner />
@ -474,8 +497,10 @@
{:else} {:else}
{#each suggestedPeople as person, index (person.id)} {#each suggestedPeople as person, index (person.id)}
<div <div
class="flex border-t dark:border-immich-dark-gray place-items-center bg-gray-100 p-2 dark:bg-gray-700 {index === class="flex border-t border-x border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] {index ===
suggestedPeople.length - 1 && 'rounded-b-lg'}" suggestedPeople.length - 1
? 'rounded-b-lg border-b'
: ''}"
> >
<button class="flex w-full place-items-center" on:click={() => handleSuggestPeople(person)}> <button class="flex w-full place-items-center" on:click={() => handleSuggestPeople(person)}>
<ImageThumbnail <ImageThumbnail