fix: hide faces (#3352)

* fix: hide faces

* remove unused variable

* fix: work even if one fails

* better style for hidden people

* add hide face in the menu dropdown

* add buttons to toggle visibility for all faces

* add server test

* close modal with escape key

* fix: explore page

* improve show & hide faces modal

* keep name on people card

* simplify layout

* sticky app bar in show-hide page

* fix format

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
martin
2023-07-23 05:00:43 +02:00
committed by GitHub
parent c40aa4399b
commit ed64c91da6
25 changed files with 1097 additions and 72 deletions

View File

@@ -1,6 +1,6 @@
import { AssetFaceEntity, PersonEntity } from '@app/infra/entities';
import { Transform } from 'class-transformer';
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
import { toBoolean, ValidateUUID } from '../domain.util';
export class PersonUpdateDto {
@@ -26,6 +26,43 @@ export class PersonUpdateDto {
isHidden?: boolean;
}
export class PeopleUpdateDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => PeopleUpdateItem)
people!: PeopleUpdateItem[];
}
export class PeopleUpdateItem {
/**
* Person id.
*/
@IsString()
@IsNotEmpty()
id!: string;
/**
* Person name.
*/
@IsOptional()
@IsString()
name?: string;
/**
* Asset is used to get the feature face thumbnail.
*/
@IsOptional()
@IsString()
featureFaceAssetId?: string;
/**
* Person visibility
*/
@IsOptional()
@IsBoolean()
isHidden?: boolean;
}
export class MergePersonDto {
@ValidateUUID({ each: true })
ids!: string[];

View File

@@ -188,6 +188,16 @@ describe(PersonService.name, () => {
});
});
describe('updateAll', () => {
it('should throw an error when personId is invalid', async () => {
personMock.getById.mockResolvedValue(null);
await expect(
sut.updatePeople(authStub.admin, { people: [{ id: 'person-1', name: 'Person 1' }] }),
).resolves.toEqual([{ error: BulkIdErrorReason.UNKNOWN, id: 'person-1', success: false }]);
expect(personMock.update).not.toHaveBeenCalled();
});
});
describe('handlePersonCleanup', () => {
it('should delete people without faces', async () => {
personMock.getAllWithoutFaces.mockResolvedValue([personStub.noName]);

View File

@@ -8,6 +8,7 @@ import {
mapPerson,
MergePersonDto,
PeopleResponseDto,
PeopleUpdateDto,
PersonResponseDto,
PersonSearchDto,
PersonUpdateDto,
@@ -96,6 +97,24 @@ export class PersonService {
return mapPerson(person);
}
async updatePeople(authUser: AuthUserDto, dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
const results: BulkIdResponseDto[] = [];
for (const person of dto.people) {
try {
await this.update(authUser, person.id, {
isHidden: person.isHidden,
name: person.name,
featureFaceAssetId: person.featureFaceAssetId,
}),
results.push({ id: person.id, success: true });
} catch (error: Error | any) {
this.logger.error(`Unable to update ${person.id} : ${error}`, error?.stack);
results.push({ id: person.id, success: false, error: BulkIdErrorReason.UNKNOWN });
}
}
return results;
}
async handlePersonCleanup() {
const people = await this.repository.getAllWithoutFaces();
for (const person of people) {