feat: show archived assets for a person

This commit is contained in:
martabal 2024-10-15 11:25:28 +02:00
parent e8015dc7d7
commit 882d9bee04
No known key found for this signature in database
GPG Key ID: C00196E3148A52BD
20 changed files with 289 additions and 42 deletions

View File

@ -184,7 +184,9 @@ class PeopleApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
Future<Response> getPersonStatisticsWithHttpInfo(String id,) async { ///
/// * [bool] withArchived:
Future<Response> getPersonStatisticsWithHttpInfo(String id, { bool? withArchived, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final path = r'/people/{id}/statistics' final path = r'/people/{id}/statistics'
.replaceAll('{id}', id); .replaceAll('{id}', id);
@ -196,6 +198,10 @@ class PeopleApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (withArchived != null) {
queryParams.addAll(_queryParams('', 'withArchived', withArchived));
}
const contentTypes = <String>[]; const contentTypes = <String>[];
@ -213,8 +219,10 @@ class PeopleApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
Future<PersonStatisticsResponseDto?> getPersonStatistics(String id,) async { ///
final response = await getPersonStatisticsWithHttpInfo(id,); /// * [bool] withArchived:
Future<PersonStatisticsResponseDto?> getPersonStatistics(String id, { bool? withArchived, }) async {
final response = await getPersonStatisticsWithHttpInfo(id, withArchived: withArchived, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }

View File

@ -18,6 +18,7 @@ class PeopleUpdateItem {
required this.id, required this.id,
this.isHidden, this.isHidden,
this.name, this.name,
this.withArchived,
}); });
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null. /// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
@ -53,13 +54,23 @@ class PeopleUpdateItem {
/// ///
String? name; String? name;
/// This property was added in v1.118.0
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withArchived;
@override @override
bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem && bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.featureFaceAssetId == featureFaceAssetId && other.featureFaceAssetId == featureFaceAssetId &&
other.id == id && other.id == id &&
other.isHidden == isHidden && other.isHidden == isHidden &&
other.name == name; other.name == name &&
other.withArchived == withArchived;
@override @override
int get hashCode => int get hashCode =>
@ -68,10 +79,11 @@ class PeopleUpdateItem {
(featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) +
(id.hashCode) + (id.hashCode) +
(isHidden == null ? 0 : isHidden!.hashCode) + (isHidden == null ? 0 : isHidden!.hashCode) +
(name == null ? 0 : name!.hashCode); (name == null ? 0 : name!.hashCode) +
(withArchived == null ? 0 : withArchived!.hashCode);
@override @override
String toString() => 'PeopleUpdateItem[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, id=$id, isHidden=$isHidden, name=$name]'; String toString() => 'PeopleUpdateItem[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, id=$id, isHidden=$isHidden, name=$name, withArchived=$withArchived]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -96,6 +108,11 @@ class PeopleUpdateItem {
} else { } else {
// json[r'name'] = null; // json[r'name'] = null;
} }
if (this.withArchived != null) {
json[r'withArchived'] = this.withArchived;
} else {
// json[r'withArchived'] = null;
}
return json; return json;
} }
@ -113,6 +130,7 @@ class PeopleUpdateItem {
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isHidden: mapValueOfType<bool>(json, r'isHidden'), isHidden: mapValueOfType<bool>(json, r'isHidden'),
name: mapValueOfType<String>(json, r'name'), name: mapValueOfType<String>(json, r'name'),
withArchived: mapValueOfType<bool>(json, r'withArchived'),
); );
} }
return null; return null;

View File

@ -19,6 +19,7 @@ class PersonResponseDto {
required this.name, required this.name,
required this.thumbnailPath, required this.thumbnailPath,
this.updatedAt, this.updatedAt,
this.withArchived,
}); });
DateTime? birthDate; DateTime? birthDate;
@ -40,6 +41,15 @@ class PersonResponseDto {
/// ///
DateTime? updatedAt; DateTime? updatedAt;
/// This property was added in v1.118.0
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withArchived;
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
@ -47,7 +57,8 @@ class PersonResponseDto {
other.isHidden == isHidden && other.isHidden == isHidden &&
other.name == name && other.name == name &&
other.thumbnailPath == thumbnailPath && other.thumbnailPath == thumbnailPath &&
other.updatedAt == updatedAt; other.updatedAt == updatedAt &&
other.withArchived == withArchived;
@override @override
int get hashCode => int get hashCode =>
@ -57,10 +68,11 @@ class PersonResponseDto {
(isHidden.hashCode) + (isHidden.hashCode) +
(name.hashCode) + (name.hashCode) +
(thumbnailPath.hashCode) + (thumbnailPath.hashCode) +
(updatedAt == null ? 0 : updatedAt!.hashCode); (updatedAt == null ? 0 : updatedAt!.hashCode) +
(withArchived == null ? 0 : withArchived!.hashCode);
@override @override
String toString() => 'PersonResponseDto[birthDate=$birthDate, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; String toString() => 'PersonResponseDto[birthDate=$birthDate, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt, withArchived=$withArchived]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -78,6 +90,11 @@ class PersonResponseDto {
} else { } else {
// json[r'updatedAt'] = null; // json[r'updatedAt'] = null;
} }
if (this.withArchived != null) {
json[r'withArchived'] = this.withArchived;
} else {
// json[r'withArchived'] = null;
}
return json; return json;
} }
@ -96,6 +113,7 @@ class PersonResponseDto {
name: mapValueOfType<String>(json, r'name')!, name: mapValueOfType<String>(json, r'name')!,
thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath')!, thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath')!,
updatedAt: mapDateTime(json, r'updatedAt', r''), updatedAt: mapDateTime(json, r'updatedAt', r''),
withArchived: mapValueOfType<bool>(json, r'withArchived'),
); );
} }
return null; return null;

View File

@ -17,6 +17,7 @@ class PersonUpdateDto {
this.featureFaceAssetId, this.featureFaceAssetId,
this.isHidden, this.isHidden,
this.name, this.name,
this.withArchived,
}); });
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null. /// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
@ -49,12 +50,22 @@ class PersonUpdateDto {
/// ///
String? name; String? name;
/// This property was added in v1.118.0
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withArchived;
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.featureFaceAssetId == featureFaceAssetId && other.featureFaceAssetId == featureFaceAssetId &&
other.isHidden == isHidden && other.isHidden == isHidden &&
other.name == name; other.name == name &&
other.withArchived == withArchived;
@override @override
int get hashCode => int get hashCode =>
@ -62,10 +73,11 @@ class PersonUpdateDto {
(birthDate == null ? 0 : birthDate!.hashCode) + (birthDate == null ? 0 : birthDate!.hashCode) +
(featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) +
(isHidden == null ? 0 : isHidden!.hashCode) + (isHidden == null ? 0 : isHidden!.hashCode) +
(name == null ? 0 : name!.hashCode); (name == null ? 0 : name!.hashCode) +
(withArchived == null ? 0 : withArchived!.hashCode);
@override @override
String toString() => 'PersonUpdateDto[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, isHidden=$isHidden, name=$name]'; String toString() => 'PersonUpdateDto[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, isHidden=$isHidden, name=$name, withArchived=$withArchived]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -89,6 +101,11 @@ class PersonUpdateDto {
} else { } else {
// json[r'name'] = null; // json[r'name'] = null;
} }
if (this.withArchived != null) {
json[r'withArchived'] = this.withArchived;
} else {
// json[r'withArchived'] = null;
}
return json; return json;
} }
@ -105,6 +122,7 @@ class PersonUpdateDto {
featureFaceAssetId: mapValueOfType<String>(json, r'featureFaceAssetId'), featureFaceAssetId: mapValueOfType<String>(json, r'featureFaceAssetId'),
isHidden: mapValueOfType<bool>(json, r'isHidden'), isHidden: mapValueOfType<bool>(json, r'isHidden'),
name: mapValueOfType<String>(json, r'name'), name: mapValueOfType<String>(json, r'name'),
withArchived: mapValueOfType<bool>(json, r'withArchived'),
); );
} }
return null; return null;

View File

@ -20,6 +20,7 @@ class PersonWithFacesResponseDto {
required this.name, required this.name,
required this.thumbnailPath, required this.thumbnailPath,
this.updatedAt, this.updatedAt,
this.withArchived,
}); });
DateTime? birthDate; DateTime? birthDate;
@ -43,6 +44,15 @@ class PersonWithFacesResponseDto {
/// ///
DateTime? updatedAt; DateTime? updatedAt;
/// This property was added in v1.118.0
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withArchived;
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto && bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
@ -51,7 +61,8 @@ class PersonWithFacesResponseDto {
other.isHidden == isHidden && other.isHidden == isHidden &&
other.name == name && other.name == name &&
other.thumbnailPath == thumbnailPath && other.thumbnailPath == thumbnailPath &&
other.updatedAt == updatedAt; other.updatedAt == updatedAt &&
other.withArchived == withArchived;
@override @override
int get hashCode => int get hashCode =>
@ -62,10 +73,11 @@ class PersonWithFacesResponseDto {
(isHidden.hashCode) + (isHidden.hashCode) +
(name.hashCode) + (name.hashCode) +
(thumbnailPath.hashCode) + (thumbnailPath.hashCode) +
(updatedAt == null ? 0 : updatedAt!.hashCode); (updatedAt == null ? 0 : updatedAt!.hashCode) +
(withArchived == null ? 0 : withArchived!.hashCode);
@override @override
String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, faces=$faces, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, faces=$faces, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt, withArchived=$withArchived]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -84,6 +96,11 @@ class PersonWithFacesResponseDto {
} else { } else {
// json[r'updatedAt'] = null; // json[r'updatedAt'] = null;
} }
if (this.withArchived != null) {
json[r'withArchived'] = this.withArchived;
} else {
// json[r'withArchived'] = null;
}
return json; return json;
} }
@ -103,6 +120,7 @@ class PersonWithFacesResponseDto {
name: mapValueOfType<String>(json, r'name')!, name: mapValueOfType<String>(json, r'name')!,
thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath')!, thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath')!,
updatedAt: mapDateTime(json, r'updatedAt', r''), updatedAt: mapDateTime(json, r'updatedAt', r''),
withArchived: mapValueOfType<bool>(json, r'withArchived'),
); );
} }
return null; return null;

View File

@ -4152,6 +4152,14 @@
"format": "uuid", "format": "uuid",
"type": "string" "type": "string"
} }
},
{
"name": "withArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
} }
], ],
"responses": { "responses": {
@ -10097,6 +10105,10 @@
"name": { "name": {
"description": "Person name.", "description": "Person name.",
"type": "string" "type": "string"
},
"withArchived": {
"description": "This property was added in v1.118.0",
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -10229,6 +10241,10 @@
"description": "This property was added in v1.107.0", "description": "This property was added in v1.107.0",
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
},
"withArchived": {
"description": "This property was added in v1.118.0",
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -10270,6 +10286,10 @@
"name": { "name": {
"description": "Person name.", "description": "Person name.",
"type": "string" "type": "string"
},
"withArchived": {
"description": "This property was added in v1.118.0",
"type": "boolean"
} }
}, },
"type": "object" "type": "object"
@ -10303,6 +10323,10 @@
"description": "This property was added in v1.107.0", "description": "This property was added in v1.107.0",
"format": "date-time", "format": "date-time",
"type": "string" "type": "string"
},
"withArchived": {
"description": "This property was added in v1.118.0",
"type": "boolean"
} }
}, },
"required": [ "required": [

View File

@ -220,6 +220,8 @@ export type PersonWithFacesResponseDto = {
thumbnailPath: string; thumbnailPath: string;
/** This property was added in v1.107.0 */ /** This property was added in v1.107.0 */
updatedAt?: string; updatedAt?: string;
/** This property was added in v1.118.0 */
withArchived?: boolean;
}; };
export type SmartInfoResponseDto = { export type SmartInfoResponseDto = {
objects?: string[] | null; objects?: string[] | null;
@ -502,6 +504,8 @@ export type PersonResponseDto = {
thumbnailPath: string; thumbnailPath: string;
/** This property was added in v1.107.0 */ /** This property was added in v1.107.0 */
updatedAt?: string; updatedAt?: string;
/** This property was added in v1.118.0 */
withArchived?: boolean;
}; };
export type AssetFaceResponseDto = { export type AssetFaceResponseDto = {
boundingBoxX1: number; boundingBoxX1: number;
@ -703,6 +707,8 @@ export type PeopleUpdateItem = {
isHidden?: boolean; isHidden?: boolean;
/** Person name. */ /** Person name. */
name?: string; name?: string;
/** This property was added in v1.118.0 */
withArchived?: boolean;
}; };
export type PeopleUpdateDto = { export type PeopleUpdateDto = {
people: PeopleUpdateItem[]; people: PeopleUpdateItem[];
@ -717,6 +723,8 @@ export type PersonUpdateDto = {
isHidden?: boolean; isHidden?: boolean;
/** Person name. */ /** Person name. */
name?: string; name?: string;
/** This property was added in v1.118.0 */
withArchived?: boolean;
}; };
export type MergePersonDto = { export type MergePersonDto = {
ids: string[]; ids: string[];
@ -2410,13 +2418,16 @@ export function reassignFaces({ id, assetFaceUpdateDto }: {
body: assetFaceUpdateDto body: assetFaceUpdateDto
}))); })));
} }
export function getPersonStatistics({ id }: { export function getPersonStatistics({ id, withArchived }: {
id: string; id: string;
withArchived?: boolean;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: PersonStatisticsResponseDto; data: PersonStatisticsResponseDto;
}>(`/people/${encodeURIComponent(id)}/statistics`, { }>(`/people/${encodeURIComponent(id)}/statistics${QS.query(QS.explode({
withArchived
}))}`, {
...opts ...opts
})); }));
} }

View File

@ -12,6 +12,7 @@ import {
PersonResponseDto, PersonResponseDto,
PersonSearchDto, PersonSearchDto,
PersonStatisticsResponseDto, PersonStatisticsResponseDto,
PersonStatsDto,
PersonUpdateDto, PersonUpdateDto,
} from 'src/dtos/person.dto'; } from 'src/dtos/person.dto';
import { Permission } from 'src/enum'; import { Permission } from 'src/enum';
@ -65,8 +66,12 @@ export class PersonController {
@Get(':id/statistics') @Get(':id/statistics')
@Authenticated({ permission: Permission.PERSON_STATISTICS }) @Authenticated({ permission: Permission.PERSON_STATISTICS })
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> { getPersonStatistics(
return this.service.getStatistics(auth, id); @Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Query() dto: PersonStatsDto,
): Promise<PersonStatisticsResponseDto> {
return this.service.getStatistics(auth, id, dto);
} }
@Get(':id/thumbnail') @Get(':id/thumbnail')

View File

@ -1,6 +1,6 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsArray, IsInt, IsNotEmpty, IsString, Max, Min, ValidateNested } from 'class-validator'; import { IsArray, IsBoolean, IsInt, IsNotEmpty, IsString, Max, Min, ValidateNested } from 'class-validator';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { PropertyLifecycle } from 'src/decorators'; import { PropertyLifecycle } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
@ -41,6 +41,11 @@ export class PersonUpdateDto extends PersonCreateDto {
@Optional() @Optional()
@IsString() @IsString()
featureFaceAssetId?: string; featureFaceAssetId?: string;
@Optional()
@IsBoolean()
@PropertyLifecycle({ addedAt: 'v1.118.0' })
withArchived?: boolean;
} }
export class PeopleUpdateDto { export class PeopleUpdateDto {
@ -93,6 +98,8 @@ export class PersonResponseDto {
isHidden!: boolean; isHidden!: boolean;
@PropertyLifecycle({ addedAt: 'v1.107.0' }) @PropertyLifecycle({ addedAt: 'v1.107.0' })
updatedAt?: Date; updatedAt?: Date;
@PropertyLifecycle({ addedAt: 'v1.118.0' })
withArchived?: boolean;
} }
export class PersonWithFacesResponseDto extends PersonResponseDto { export class PersonWithFacesResponseDto extends PersonResponseDto {
@ -147,6 +154,11 @@ export class PersonStatisticsResponseDto {
assets!: number; assets!: number;
} }
export class PersonStatsDto {
@ValidateBoolean({ optional: true })
withArchived?: boolean;
}
export class PeopleResponseDto { export class PeopleResponseDto {
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'integer' })
total!: number; total!: number;
@ -167,6 +179,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
thumbnailPath: person.thumbnailPath, thumbnailPath: person.thumbnailPath,
isHidden: person.isHidden, isHidden: person.isHidden,
updatedAt: person.updatedAt, updatedAt: person.updatedAt,
withArchived: person.withArchived,
}; };
} }

View File

@ -49,4 +49,7 @@ export class PersonEntity {
@Column({ default: false }) @Column({ default: false })
isHidden!: boolean; isHidden!: boolean;
@Column({ default: false })
withArchived!: boolean;
} }

View File

@ -45,6 +45,10 @@ export interface DeleteFacesOptions {
sourceType: SourceType; sourceType: SourceType;
} }
export interface PersonStatsOptions {
withArchived?: boolean;
}
export type UnassignFacesOptions = DeleteFacesOptions; export type UnassignFacesOptions = DeleteFacesOptions;
export interface IPersonRepository { export interface IPersonRepository {
@ -74,7 +78,7 @@ export interface IPersonRepository {
getFaces(assetId: string): Promise<AssetFaceEntity[]>; getFaces(assetId: string): Promise<AssetFaceEntity[]>;
getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>; getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]>;
getRandomFace(personId: string): Promise<AssetFaceEntity | null>; getRandomFace(personId: string): Promise<AssetFaceEntity | null>;
getStatistics(personId: string): Promise<PersonStatistics>; getStatistics(personId: string, options: PersonStatsOptions): Promise<PersonStatistics>;
reassignFace(assetFaceId: string, newPersonId: string): Promise<number>; reassignFace(assetFaceId: string, newPersonId: string): Promise<number>;
getNumberOfPeople(userId: string): Promise<PeopleStatistics>; getNumberOfPeople(userId: string): Promise<PeopleStatistics>;
reassignFaces(data: UpdateFacesData): Promise<number>; reassignFaces(data: UpdateFacesData): Promise<number>;

View File

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddWithArchivedToPerson1728944141526 implements MigrationInterface {
name = 'AddWithArchivedToPerson1728944141526'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "person" ADD "withArchived" boolean NOT NULL DEFAULT false`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "person" DROP COLUMN "withArchived"`);
}
}

View File

@ -212,6 +212,7 @@ SELECT
"8258e303a73a72cf6abb13d73fb592dde0d68280"."thumbnailPath" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_thumbnailPath", "8258e303a73a72cf6abb13d73fb592dde0d68280"."thumbnailPath" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_thumbnailPath",
"8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId", "8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId",
"8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden", "8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden",
"8258e303a73a72cf6abb13d73fb592dde0d68280"."withArchived" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_withArchived",
"AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id", "AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id",
"AssetEntity__AssetEntity_stack"."ownerId" AS "AssetEntity__AssetEntity_stack_ownerId", "AssetEntity__AssetEntity_stack"."ownerId" AS "AssetEntity__AssetEntity_stack_ownerId",
"AssetEntity__AssetEntity_stack"."primaryAssetId" AS "AssetEntity__AssetEntity_stack_primaryAssetId", "AssetEntity__AssetEntity_stack"."primaryAssetId" AS "AssetEntity__AssetEntity_stack_primaryAssetId",

View File

@ -17,7 +17,8 @@ SELECT
"person"."birthDate" AS "person_birthDate", "person"."birthDate" AS "person_birthDate",
"person"."thumbnailPath" AS "person_thumbnailPath", "person"."thumbnailPath" AS "person_thumbnailPath",
"person"."faceAssetId" AS "person_faceAssetId", "person"."faceAssetId" AS "person_faceAssetId",
"person"."isHidden" AS "person_isHidden" "person"."isHidden" AS "person_isHidden",
"person"."withArchived" AS "person_withArchived"
FROM FROM
"person" "person" "person" "person"
LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id" LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
@ -54,7 +55,8 @@ SELECT
"person"."birthDate" AS "person_birthDate", "person"."birthDate" AS "person_birthDate",
"person"."thumbnailPath" AS "person_thumbnailPath", "person"."thumbnailPath" AS "person_thumbnailPath",
"person"."faceAssetId" AS "person_faceAssetId", "person"."faceAssetId" AS "person_faceAssetId",
"person"."isHidden" AS "person_isHidden" "person"."isHidden" AS "person_isHidden",
"person"."withArchived" AS "person_withArchived"
FROM FROM
"person" "person" "person" "person"
LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id" LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
@ -83,7 +85,8 @@ SELECT
"AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate", "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate",
"AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath", "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath",
"AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId", "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId",
"AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden" "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden",
"AssetFaceEntity__AssetFaceEntity_person"."withArchived" AS "AssetFaceEntity__AssetFaceEntity_person_withArchived"
FROM FROM
"asset_faces" "AssetFaceEntity" "asset_faces" "AssetFaceEntity"
LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId" LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
@ -116,7 +119,8 @@ FROM
"AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate", "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate",
"AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath", "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath",
"AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId", "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId",
"AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden" "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden",
"AssetFaceEntity__AssetFaceEntity_person"."withArchived" AS "AssetFaceEntity__AssetFaceEntity_person_withArchived"
FROM FROM
"asset_faces" "AssetFaceEntity" "asset_faces" "AssetFaceEntity"
LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId" LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
@ -153,6 +157,7 @@ FROM
"AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath", "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath",
"AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId", "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId",
"AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden", "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden",
"AssetFaceEntity__AssetFaceEntity_person"."withArchived" AS "AssetFaceEntity__AssetFaceEntity_person_withArchived",
"AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id", "AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id",
"AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId", "AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId",
"AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId", "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId",
@ -213,7 +218,8 @@ SELECT
"person"."birthDate" AS "person_birthDate", "person"."birthDate" AS "person_birthDate",
"person"."thumbnailPath" AS "person_thumbnailPath", "person"."thumbnailPath" AS "person_thumbnailPath",
"person"."faceAssetId" AS "person_faceAssetId", "person"."faceAssetId" AS "person_faceAssetId",
"person"."isHidden" AS "person_isHidden" "person"."isHidden" AS "person_isHidden",
"person"."withArchived" AS "person_withArchived"
FROM FROM
"person" "person" "person" "person"
WHERE WHERE
@ -242,11 +248,18 @@ FROM
"asset_faces" "face" "asset_faces" "face"
LEFT JOIN "assets" "asset" ON "asset"."id" = "face"."assetId" LEFT JOIN "assets" "asset" ON "asset"."id" = "face"."assetId"
AND ("asset"."deletedAt" IS NULL) AND ("asset"."deletedAt" IS NULL)
LEFT JOIN "person" "person" ON "person"."id" = "face"."personId"
WHERE WHERE
"face"."personId" = $1 "face"."personId" = $1
AND "asset"."isArchived" = false
AND "asset"."deletedAt" IS NULL AND "asset"."deletedAt" IS NULL
AND "asset"."livePhotoVideoId" IS NULL AND "asset"."livePhotoVideoId" IS NULL
AND (
(
"person"."withArchived" = false
AND "asset"."isArchived" = false
)
OR "person"."withArchived" = true
)
-- PersonRepository.getNumberOfPeople -- PersonRepository.getNumberOfPeople
SELECT SELECT

View File

@ -17,6 +17,7 @@ import {
PersonNameSearchOptions, PersonNameSearchOptions,
PersonSearchOptions, PersonSearchOptions,
PersonStatistics, PersonStatistics,
PersonStatsOptions,
UnassignFacesOptions, UnassignFacesOptions,
UpdateFacesData, UpdateFacesData,
} from 'src/interfaces/person.interface'; } from 'src/interfaces/person.interface';
@ -214,17 +215,33 @@ export class PersonRepository implements IPersonRepository {
return queryBuilder.getMany(); return queryBuilder.getMany();
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID, { withArchived: undefined }] })
async getStatistics(personId: string): Promise<PersonStatistics> { async getStatistics(personId: string, options: PersonStatsOptions): Promise<PersonStatistics> {
const items = await this.assetFaceRepository /*
* withArchived: true -> Return the count of all assets for a given person
* withArchived: false -> Return the count of all unarchived assets for a given person
* withArchived: undefiend ->
* - If person.withArchived = true -> Return the count of all assets for a given person
* - If person.withArchived = false -> Return the count of all unarchived assets for a given person
*/
let queryBuilder = this.assetFaceRepository
.createQueryBuilder('face') .createQueryBuilder('face')
.leftJoin('face.asset', 'asset') .leftJoin('face.asset', 'asset')
.where('face.personId = :personId', { personId }) .where('face.personId = :personId', { personId })
.andWhere('asset.isArchived = false')
.andWhere('asset.deletedAt IS NULL') .andWhere('asset.deletedAt IS NULL')
.andWhere('asset.livePhotoVideoId IS NULL') .andWhere('asset.livePhotoVideoId IS NULL')
.select('COUNT(DISTINCT(asset.id))', 'count') .select('COUNT(DISTINCT(asset.id))', 'count');
.getRawOne();
if (options.withArchived === false) {
queryBuilder = queryBuilder.andWhere('asset.isArchived = false');
} else if (options.withArchived === undefined) {
queryBuilder = queryBuilder
.leftJoin('face.person', 'person')
.andWhere('((person.withArchived = false AND asset.isArchived = false) OR person.withArchived = true)');
}
const items = await queryBuilder.getRawOne();
return { return {
assets: items.count ?? 0, assets: items.count ?? 0,
}; };

View File

@ -31,6 +31,7 @@ const responseDto: PersonResponseDto = {
thumbnailPath: '/path/to/thumbnail.jpg', thumbnailPath: '/path/to/thumbnail.jpg',
isHidden: false, isHidden: false,
updatedAt: expect.any(Date), updatedAt: expect.any(Date),
withArchived: false,
}; };
const statistics = { assets: 3 }; const statistics = { assets: 3 };
@ -118,6 +119,7 @@ describe(PersonService.name, () => {
thumbnailPath: '/path/to/thumbnail.jpg', thumbnailPath: '/path/to/thumbnail.jpg',
isHidden: true, isHidden: true,
updatedAt: expect.any(Date), updatedAt: expect.any(Date),
withArchived: false,
}, },
], ],
}); });
@ -218,6 +220,16 @@ describe(PersonService.name, () => {
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
}); });
it("should update a person's withArchived", async () => {
personMock.update.mockResolvedValue(personStub.withName);
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.update(authStub.admin, 'person-1', { withArchived: true })).resolves.toEqual(responseDto);
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', withArchived: true });
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
});
it("should update a person's date of birth", async () => { it("should update a person's date of birth", async () => {
personMock.update.mockResolvedValue(personStub.withBirthDate); personMock.update.mockResolvedValue(personStub.withBirthDate);
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
@ -229,6 +241,7 @@ describe(PersonService.name, () => {
thumbnailPath: '/path/to/thumbnail.jpg', thumbnailPath: '/path/to/thumbnail.jpg',
isHidden: false, isHidden: false,
updatedAt: expect.any(Date), updatedAt: expect.any(Date),
withArchived: false,
}); });
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: '1976-06-30' }); expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: '1976-06-30' });
expect(jobMock.queue).not.toHaveBeenCalled(); expect(jobMock.queue).not.toHaveBeenCalled();
@ -381,6 +394,7 @@ describe(PersonService.name, () => {
name: personStub.noName.name, name: personStub.noName.name,
thumbnailPath: personStub.noName.thumbnailPath, thumbnailPath: personStub.noName.thumbnailPath,
updatedAt: expect.any(Date), updatedAt: expect.any(Date),
withArchived: personStub.noName.withArchived,
}); });
expect(jobMock.queue).not.toHaveBeenCalledWith(); expect(jobMock.queue).not.toHaveBeenCalledWith();
@ -1171,13 +1185,13 @@ describe(PersonService.name, () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.primaryPerson);
personMock.getStatistics.mockResolvedValue(statistics); personMock.getStatistics.mockResolvedValue(statistics);
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
await expect(sut.getStatistics(authStub.admin, 'person-1')).resolves.toEqual({ assets: 3 }); await expect(sut.getStatistics(authStub.admin, 'person-1', {})).resolves.toEqual({ assets: 3 });
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
}); });
it('should require person.read permission', async () => { it('should require person.read permission', async () => {
personMock.getById.mockResolvedValue(personStub.primaryPerson); personMock.getById.mockResolvedValue(personStub.primaryPerson);
await expect(sut.getStatistics(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); await expect(sut.getStatistics(authStub.admin, 'person-1', {})).rejects.toBeInstanceOf(BadRequestException);
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1']));
}); });
}); });

View File

@ -14,6 +14,7 @@ import {
PersonResponseDto, PersonResponseDto,
PersonSearchDto, PersonSearchDto,
PersonStatisticsResponseDto, PersonStatisticsResponseDto,
PersonStatsDto,
PersonUpdateDto, PersonUpdateDto,
mapFaces, mapFaces,
mapPerson, mapPerson,
@ -153,9 +154,9 @@ export class PersonService extends BaseService {
return this.findOrFail(id).then(mapPerson); return this.findOrFail(id).then(mapPerson);
} }
async getStatistics(auth: AuthDto, id: string): Promise<PersonStatisticsResponseDto> { async getStatistics(auth: AuthDto, id: string, dto: PersonStatsDto): Promise<PersonStatisticsResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_READ, ids: [id] }); await this.requireAccess({ auth, permission: Permission.PERSON_READ, ids: [id] });
return this.personRepository.getStatistics(id); return this.personRepository.getStatistics(id, dto);
} }
async getThumbnail(auth: AuthDto, id: string): Promise<ImmichFileResponse> { async getThumbnail(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
@ -184,7 +185,7 @@ export class PersonService extends BaseService {
async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> { async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [id] }); await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [id] });
const { name, birthDate, isHidden, featureFaceAssetId: assetId } = dto; const { name, birthDate, isHidden, featureFaceAssetId: assetId, withArchived } = dto;
// TODO: set by faceId directly // TODO: set by faceId directly
let faceId: string | undefined = undefined; let faceId: string | undefined = undefined;
if (assetId) { if (assetId) {
@ -197,7 +198,14 @@ export class PersonService extends BaseService {
faceId = face.id; faceId = face.id;
} }
const person = await this.personRepository.update({ id, faceAssetId: faceId, name, birthDate, isHidden }); const person = await this.personRepository.update({
id,
faceAssetId: faceId,
name,
birthDate,
isHidden,
withArchived,
});
if (assetId) { if (assetId) {
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } }); await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } });

View File

@ -15,6 +15,7 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
hidden: Object.freeze<PersonEntity>({ hidden: Object.freeze<PersonEntity>({
id: 'person-1', id: 'person-1',
@ -29,6 +30,7 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: true, isHidden: true,
withArchived: false,
}), }),
withName: Object.freeze<PersonEntity>({ withName: Object.freeze<PersonEntity>({
id: 'person-1', id: 'person-1',
@ -43,6 +45,7 @@ export const personStub = {
faceAssetId: 'assetFaceId', faceAssetId: 'assetFaceId',
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
withBirthDate: Object.freeze<PersonEntity>({ withBirthDate: Object.freeze<PersonEntity>({
id: 'person-1', id: 'person-1',
@ -57,6 +60,7 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
noThumbnail: Object.freeze<PersonEntity>({ noThumbnail: Object.freeze<PersonEntity>({
id: 'person-1', id: 'person-1',
@ -71,6 +75,7 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
newThumbnail: Object.freeze<PersonEntity>({ newThumbnail: Object.freeze<PersonEntity>({
id: 'person-1', id: 'person-1',
@ -85,6 +90,7 @@ export const personStub = {
faceAssetId: 'asset-id', faceAssetId: 'asset-id',
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
primaryPerson: Object.freeze<PersonEntity>({ primaryPerson: Object.freeze<PersonEntity>({
id: 'person-1', id: 'person-1',
@ -99,6 +105,7 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
mergePerson: Object.freeze<PersonEntity>({ mergePerson: Object.freeze<PersonEntity>({
id: 'person-2', id: 'person-2',
@ -113,6 +120,7 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
randomPerson: Object.freeze<PersonEntity>({ randomPerson: Object.freeze<PersonEntity>({
id: 'person-3', id: 'person-3',
@ -127,5 +135,6 @@ export const personStub = {
faceAssetId: null, faceAssetId: null,
faceAsset: null, faceAsset: null,
isHidden: false, isHidden: false,
withArchived: false,
}), }),
}; };

View File

@ -1140,6 +1140,7 @@
"show_albums": "Show albums", "show_albums": "Show albums",
"show_all_people": "Show all people", "show_all_people": "Show all people",
"show_and_hide_people": "Show & hide people", "show_and_hide_people": "Show & hide people",
"show_archived_or_unarchived_assets": "{with, select, true {With} other {Without}} archived assets",
"show_file_location": "Show file location", "show_file_location": "Show file location",
"show_gallery": "Show gallery", "show_gallery": "Show gallery",
"show_hidden_people": "Show hidden people", "show_hidden_people": "Show hidden people",

View File

@ -45,6 +45,8 @@
import { import {
mdiAccountBoxOutline, mdiAccountBoxOutline,
mdiAccountMultipleCheckOutline, mdiAccountMultipleCheckOutline,
mdiArchiveArrowDown,
mdiArchiveArrowDownOutline,
mdiArrowLeft, mdiArrowLeft,
mdiCalendarEditOutline, mdiCalendarEditOutline,
mdiDotsVertical, mdiDotsVertical,
@ -72,8 +74,10 @@
UNASSIGN_ASSETS = 'unassign-faces', UNASSIGN_ASSETS = 'unassign-faces',
} }
$: isArchived = person.withArchived ? undefined : person.withArchived;
let assetStore = new AssetStore({ let assetStore = new AssetStore({
isArchived: false, isArchived,
personId: data.person.id, personId: data.person.id,
}); });
@ -81,7 +85,7 @@
$: thumbnailData = getPeopleThumbnailUrl(person); $: thumbnailData = getPeopleThumbnailUrl(person);
$: if (person) { $: if (person) {
handlePromiseError(updateAssetCount()); handlePromiseError(updateAssetCount());
handlePromiseError(assetStore.updateOptions({ personId: person.id })); handlePromiseError(assetStore.updateOptions({ personId: person.id, isArchived }));
} }
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
@ -338,6 +342,27 @@
} }
}; };
const handleToggleWithArhived = async () => {
const withArchived = !person.withArchived;
try {
await updatePerson({
id: person.id,
personUpdateDto: { withArchived },
});
data.person.withArchived = withArchived;
refreshAssetGrid = !refreshAssetGrid;
} catch (error) {
handleError(error, $t('errors.unable_to_archive_unarchive', { values: { archived: withArchived } }));
}
};
const handleDeleteAssets = async (assetIds: string[]) => {
$assetStore.removeAssets(assetIds);
await updateAssetCount();
};
onDestroy(() => { onDestroy(() => {
assetStore.destroy(); assetStore.destroy();
}); });
@ -395,7 +420,7 @@
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => $assetStore.removeAssets(assetIds)} /> <ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => $assetStore.removeAssets(assetIds)} />
<DeleteAssets menuItem onAssetDelete={(assetIds) => $assetStore.removeAssets(assetIds)} /> <DeleteAssets menuItem onAssetDelete={handleDeleteAssets} />
</ButtonContextMenu> </ButtonContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
@ -423,6 +448,11 @@
icon={mdiAccountMultipleCheckOutline} icon={mdiAccountMultipleCheckOutline}
onClick={() => (viewMode = ViewMode.MERGE_PEOPLE)} onClick={() => (viewMode = ViewMode.MERGE_PEOPLE)}
/> />
<MenuOption
text={$t('show_archived_or_unarchived_assets', { values: { with: !person.withArchived } })}
icon={person.withArchived ? mdiArchiveArrowDown : mdiArchiveArrowDownOutline}
onClick={handleToggleWithArhived}
/>
</ButtonContextMenu> </ButtonContextMenu>
</svelte:fragment> </svelte:fragment>
</ControlAppBar> </ControlAppBar>