immich/server/src/domain/person/person.dto.ts
martin 7702560b12
feat(web): re-assign person faces (2) (#4949)
* feat: unassign person faces

* multiple improvements

* chore: regenerate api

* feat: improve face interactions in photos

* fix: tests

* fix: tests

* optimize

* fix: wrong assignment on complex-multiple re-assignments

* fix: thumbnails with large photos

* fix: complex reassign

* fix: don't send people with faces

* fix: person thumbnail generation

* chore: regenerate api

* add tess

* feat: face box even when zoomed

* fix: change feature photo

* feat: make the blue icon hoverable

* chore: regenerate api

* feat: use websocket

* fix: loading spinner when clicking on the done button

* fix: use the svelte way

* fix: tests

* simplify

* fix: unused vars

* fix: remove unused code

* fix: add migration

* chore: regenerate api

* ci: add unit tests

* chore: regenerate api

* feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it

* reorganize

* chore: regenerate api

* feat: global edit

* pr feedback

* pr feedback

* simplify

* revert test

* fix: face generation

* fix: tests

* fix: face generation

* fix merge

* feat: search names in unmerge face selector modal

* fix: merge face selector

* simplify feature photo generation

* fix: change endpoint

* pr feedback

* chore: fix merge

* chore: fix merge

* fix: tests

* fix: edit & hide buttons

* fix: tests

* feat: show if person is hidden

* feat: rename face to person

* feat: split in new panel

* copy-paste-error

* pr feedback

* fix: feature photo

* do not leak faces

* fix: unmerge modal

* fix: merge modal event

* feat(server): remove duplicates

* fix: title for image thumbnails

* fix: disable side panel when there's no face until next PR

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 09:43:15 -06:00

159 lines
3.5 KiB
TypeScript

import { AssetFaceEntity, PersonEntity } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
import { AuthUserDto } from '../auth';
import { Optional, ValidateUUID, toBoolean } from '../domain.util';
export class PersonUpdateDto {
/**
* Person name.
*/
@Optional()
@IsString()
name?: string;
/**
* Person date of birth.
* Note: the mobile app cannot currently set the birth date to null.
*/
@Optional({ nullable: true })
@IsDate()
@Type(() => Date)
@ApiProperty({ format: 'date' })
birthDate?: Date | null;
/**
* Asset is used to get the feature face thumbnail.
*/
@Optional()
@IsString()
featureFaceAssetId?: string;
/**
* Person visibility
*/
@Optional()
@IsBoolean()
isHidden?: boolean;
}
export class PeopleUpdateDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => PeopleUpdateItem)
people!: PeopleUpdateItem[];
}
export class PeopleUpdateItem extends PersonUpdateDto {
/**
* Person id.
*/
@IsString()
@IsNotEmpty()
id!: string;
}
export class MergePersonDto {
@ValidateUUID({ each: true })
ids!: string[];
}
export class PersonSearchDto {
@IsBoolean()
@Transform(toBoolean)
withHidden?: boolean = false;
}
export class PersonResponseDto {
id!: string;
name!: string;
@ApiProperty({ format: 'date' })
birthDate!: Date | null;
thumbnailPath!: string;
isHidden!: boolean;
}
export class PersonWithFacesResponseDto extends PersonResponseDto {
faces!: AssetFaceWithoutPersonResponseDto[];
}
export class AssetFaceWithoutPersonResponseDto {
@ValidateUUID()
id!: string;
@ApiProperty({ type: 'integer' })
imageHeight!: number;
@ApiProperty({ type: 'integer' })
imageWidth!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX2!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY2!: number;
}
export class AssetFaceResponseDto extends AssetFaceWithoutPersonResponseDto {
person!: PersonResponseDto | null;
}
export class AssetFaceUpdateDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => AssetFaceUpdateItem)
data!: AssetFaceUpdateItem[];
}
export class FaceDto {
@ValidateUUID()
id!: string;
}
export class AssetFaceUpdateItem {
@ValidateUUID()
personId!: string;
@ValidateUUID()
assetId!: string;
}
export class PersonStatisticsResponseDto {
@ApiProperty({ type: 'integer' })
assets!: number;
}
export class PeopleResponseDto {
@ApiProperty({ type: 'integer' })
total!: number;
@ApiProperty({ type: 'integer' })
visible!: number;
people!: PersonResponseDto[];
}
export function mapPerson(person: PersonEntity): PersonResponseDto {
return {
id: person.id,
name: person.name,
birthDate: person.birthDate,
thumbnailPath: person.thumbnailPath,
isHidden: person.isHidden,
};
}
export function mapFaces(face: AssetFaceEntity, authUser: AuthUserDto): AssetFaceResponseDto {
return {
id: face.id,
imageHeight: face.imageHeight,
imageWidth: face.imageWidth,
boundingBoxX1: face.boundingBoxX1,
boundingBoxX2: face.boundingBoxX2,
boundingBoxY1: face.boundingBoxY1,
boundingBoxY2: face.boundingBoxY2,
person: face.person?.ownerId === authUser.id ? mapPerson(face.person) : null,
};
}