7702560b12
* 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>
159 lines
3.5 KiB
TypeScript
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,
|
|
};
|
|
}
|