From 208fe69ea3aa81cf41ece498715d922b4c4e5b66 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:31:10 +0200 Subject: [PATCH 01/28] feat: hide faces --- cli/src/api/open-api/api.ts | 12 ++ mobile/openapi/doc/PersonResponseDto.md | 1 + mobile/openapi/doc/PersonUpdateDto.md | 1 + .../lib/model/person_response_dto.dart | 14 ++- .../openapi/lib/model/person_update_dto.dart | 24 +++- .../test/person_response_dto_test.dart | 5 + .../openapi/test/person_update_dto_test.dart | 6 + server/immich-openapi-specs.json | 10 +- server/src/domain/person/person.dto.ts | 15 ++- .../src/domain/person/person.service.spec.ts | 1 + server/src/domain/person/person.service.ts | 8 ++ .../immich/controllers/person.controller.ts | 2 +- server/src/infra/entities/person.entity.ts | 3 + .../1689281196844-AddHiddenFaces.ts | 14 +++ server/test/fixtures.ts | 28 +++++ web/src/api/open-api/api.ts | 12 ++ .../asset-viewer/detail-panel.svelte | 32 ++--- .../assets/thumbnail/image-thumbnail.svelte | 11 +- .../layouts/user-page-layout-people.svelte | 84 ++++++++++++++ web/src/routes/(user)/explore/+page.svelte | 35 +++--- web/src/routes/(user)/people/+page.svelte | 109 +++++++++++++++--- 21 files changed, 366 insertions(+), 61 deletions(-) create mode 100644 server/src/infra/migrations/1689281196844-AddHiddenFaces.ts create mode 100644 web/src/lib/components/layouts/user-page-layout-people.svelte diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index da189929ca082..5d289b9d7ca7a 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -1813,6 +1813,12 @@ export interface PersonResponseDto { * @memberof PersonResponseDto */ 'thumbnailPath': string; + /** + * + * @type {boolean} + * @memberof PersonResponseDto + */ + 'hidden': boolean; } /** * @@ -1832,6 +1838,12 @@ export interface PersonUpdateDto { * @memberof PersonUpdateDto */ 'featureFaceAssetId'?: string; + /** + * Person visibility + * @type {boolean} + * @memberof PersonUpdateDto + */ + 'hidden'?: boolean; } /** * diff --git a/mobile/openapi/doc/PersonResponseDto.md b/mobile/openapi/doc/PersonResponseDto.md index 05927762a94f7..6ce8749f35935 100644 --- a/mobile/openapi/doc/PersonResponseDto.md +++ b/mobile/openapi/doc/PersonResponseDto.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **id** | **String** | | **name** | **String** | | **thumbnailPath** | **String** | | +**hidden** | **bool** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/PersonUpdateDto.md b/mobile/openapi/doc/PersonUpdateDto.md index 7496b2af62d42..96c47fe12f9f4 100644 --- a/mobile/openapi/doc/PersonUpdateDto.md +++ b/mobile/openapi/doc/PersonUpdateDto.md @@ -10,6 +10,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **name** | **String** | Person name. | [optional] **featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional] +**hidden** | **bool** | Person visibility | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart index ddaa733853d19..ff1e9f7a2b5ac 100644 --- a/mobile/openapi/lib/model/person_response_dto.dart +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -16,6 +16,7 @@ class PersonResponseDto { required this.id, required this.name, required this.thumbnailPath, + required this.hidden, }); String id; @@ -24,27 +25,32 @@ class PersonResponseDto { String thumbnailPath; + bool hidden; + @override bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && other.id == id && other.name == name && - other.thumbnailPath == thumbnailPath; + other.thumbnailPath == thumbnailPath && + other.hidden == hidden; @override int get hashCode => // ignore: unnecessary_parenthesis (id.hashCode) + (name.hashCode) + - (thumbnailPath.hashCode); + (thumbnailPath.hashCode) + + (hidden.hashCode); @override - String toString() => 'PersonResponseDto[id=$id, name=$name, thumbnailPath=$thumbnailPath]'; + String toString() => 'PersonResponseDto[id=$id, name=$name, thumbnailPath=$thumbnailPath, hidden=$hidden]'; Map toJson() { final json = {}; json[r'id'] = this.id; json[r'name'] = this.name; json[r'thumbnailPath'] = this.thumbnailPath; + json[r'hidden'] = this.hidden; return json; } @@ -59,6 +65,7 @@ class PersonResponseDto { id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, thumbnailPath: mapValueOfType(json, r'thumbnailPath')!, + hidden: mapValueOfType(json, r'hidden')!, ); } return null; @@ -109,6 +116,7 @@ class PersonResponseDto { 'id', 'name', 'thumbnailPath', + 'hidden', }; } diff --git a/mobile/openapi/lib/model/person_update_dto.dart b/mobile/openapi/lib/model/person_update_dto.dart index 3d15c71565b73..910b8c22d62fc 100644 --- a/mobile/openapi/lib/model/person_update_dto.dart +++ b/mobile/openapi/lib/model/person_update_dto.dart @@ -15,6 +15,7 @@ class PersonUpdateDto { PersonUpdateDto({ this.name, this.featureFaceAssetId, + this.hidden, }); /// Person name. @@ -35,19 +36,30 @@ class PersonUpdateDto { /// String? featureFaceAssetId; + /// Person visibility + /// + /// 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? hidden; + @override bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && other.name == name && - other.featureFaceAssetId == featureFaceAssetId; + other.featureFaceAssetId == featureFaceAssetId && + other.hidden == hidden; @override int get hashCode => // ignore: unnecessary_parenthesis (name == null ? 0 : name!.hashCode) + - (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode); + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + + (hidden == null ? 0 : hidden!.hashCode); @override - String toString() => 'PersonUpdateDto[name=$name, featureFaceAssetId=$featureFaceAssetId]'; + String toString() => 'PersonUpdateDto[name=$name, featureFaceAssetId=$featureFaceAssetId, hidden=$hidden]'; Map toJson() { final json = {}; @@ -61,6 +73,11 @@ class PersonUpdateDto { } else { // json[r'featureFaceAssetId'] = null; } + if (this.hidden != null) { + json[r'hidden'] = this.hidden; + } else { + // json[r'hidden'] = null; + } return json; } @@ -74,6 +91,7 @@ class PersonUpdateDto { return PersonUpdateDto( name: mapValueOfType(json, r'name'), featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), + hidden: mapValueOfType(json, r'hidden'), ); } return null; diff --git a/mobile/openapi/test/person_response_dto_test.dart b/mobile/openapi/test/person_response_dto_test.dart index 9adcbe1546210..f7c46ae1093c9 100644 --- a/mobile/openapi/test/person_response_dto_test.dart +++ b/mobile/openapi/test/person_response_dto_test.dart @@ -31,6 +31,11 @@ void main() { // TODO }); + // bool hidden + test('to test the property `hidden`', () async { + // TODO + }); + }); diff --git a/mobile/openapi/test/person_update_dto_test.dart b/mobile/openapi/test/person_update_dto_test.dart index be3b8fb741ca3..bde657c8a61f2 100644 --- a/mobile/openapi/test/person_update_dto_test.dart +++ b/mobile/openapi/test/person_update_dto_test.dart @@ -28,6 +28,12 @@ void main() { // TODO }); + // Person visibility + // bool hidden + test('to test the property `hidden`', () async { + // TODO + }); + }); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index f285899b12f13..2d8825a8912e2 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -5895,12 +5895,16 @@ }, "thumbnailPath": { "type": "string" + }, + "hidden": { + "type": "boolean" } }, "required": [ "id", "name", - "thumbnailPath" + "thumbnailPath", + "hidden" ] }, "PersonUpdateDto": { @@ -5913,6 +5917,10 @@ "featureFaceAssetId": { "type": "string", "description": "Asset is used to get the feature face thumbnail." + }, + "hidden": { + "type": "boolean", + "description": "Person visibility" } } }, diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index b8efa65c99f81..cc30b7ebc97c4 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -1,6 +1,7 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities'; -import { IsOptional, IsString } from 'class-validator'; -import { ValidateUUID } from '../domain.util'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; +import { toBoolean, ValidateUUID } from '../domain.util'; export class PersonUpdateDto { /** @@ -16,6 +17,14 @@ export class PersonUpdateDto { @IsOptional() @IsString() featureFaceAssetId?: string; + + /** + * Person visibility + */ + @IsOptional() + @IsBoolean() + @Transform(toBoolean) + hidden?: boolean; } export class MergePersonDto { @@ -27,6 +36,7 @@ export class PersonResponseDto { id!: string; name!: string; thumbnailPath!: string; + hidden!: boolean; } export function mapPerson(person: PersonEntity): PersonResponseDto { @@ -34,6 +44,7 @@ export function mapPerson(person: PersonEntity): PersonResponseDto { id: person.id, name: person.name, thumbnailPath: person.thumbnailPath, + hidden: person.hidden, }; } diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index c7eb08bfddf25..3a25e11f55c85 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -19,6 +19,7 @@ const responseDto: PersonResponseDto = { id: 'person-1', name: 'Person 1', thumbnailPath: '/path/to/thumbnail.jpg', + hidden: false, }; describe(PersonService.name, () => { diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 87046c50f85c3..d38aaab87e4cb 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -21,6 +21,7 @@ export class PersonService { const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); const named = people.filter((person) => !!person.name); const unnamed = people.filter((person) => !person.name); + return ( [...named, ...unnamed] // with thumbnails @@ -57,6 +58,13 @@ export class PersonService { await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); } + if (dto.hidden !== undefined) { + person = await this.repository.update({ id, hidden: dto.hidden }); + const assets = await this.repository.getAssets(authUser.id, id); + const ids = assets.map((asset) => asset.id); + await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); + } + if (dto.featureFaceAssetId) { const assetId = dto.featureFaceAssetId; const face = await this.repository.getFaceById({ personId: id, assetId }); diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 106961aa897d5..7c2c97592ed37 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -8,7 +8,7 @@ import { PersonService, PersonUpdateDto, } from '@app/domain'; -import { Body, Controller, Get, Param, Post, Put, StreamableFile } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser } from '../app.guard'; import { UseValidation } from '../app.utils'; diff --git a/server/src/infra/entities/person.entity.ts b/server/src/infra/entities/person.entity.ts index 40ad593e93552..b2d2d7625da3b 100644 --- a/server/src/infra/entities/person.entity.ts +++ b/server/src/infra/entities/person.entity.ts @@ -35,4 +35,7 @@ export class PersonEntity { @OneToMany(() => AssetFaceEntity, (assetFace) => assetFace.person) faces!: AssetFaceEntity[]; + + @Column({ default: false }) + hidden!: boolean; } diff --git a/server/src/infra/migrations/1689281196844-AddHiddenFaces.ts b/server/src/infra/migrations/1689281196844-AddHiddenFaces.ts new file mode 100644 index 0000000000000..c6c57bcd30f04 --- /dev/null +++ b/server/src/infra/migrations/1689281196844-AddHiddenFaces.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Infra1689281196844 implements MigrationInterface { + name = 'Infra1689281196844' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "person" ADD "hidden" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "person" DROP COLUMN "hidden"`); + } + +} diff --git a/server/test/fixtures.ts b/server/test/fixtures.ts index 145229407306a..63337c05b83fa 100644 --- a/server/test/fixtures.ts +++ b/server/test/fixtures.ts @@ -1160,6 +1160,7 @@ export const personStub = { name: '', thumbnailPath: '/path/to/thumbnail.jpg', faces: [], + hidden: false, }), withName: Object.freeze({ id: 'person-1', @@ -1170,6 +1171,29 @@ export const personStub = { name: 'Person 1', thumbnailPath: '/path/to/thumbnail.jpg', faces: [], + hidden: false, + }), + isHidden: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userEntityStub.admin.id, + owner: userEntityStub.admin, + name: 'Person 1', + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + hidden: false, + }), + isnotHidden: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userEntityStub.admin.id, + owner: userEntityStub.admin, + name: 'Person 1', + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + hidden: true, }), noThumbnail: Object.freeze({ id: 'person-1', @@ -1180,6 +1204,7 @@ export const personStub = { name: '', thumbnailPath: '', faces: [], + hidden: false, }), newThumbnail: Object.freeze({ id: 'person-1', @@ -1190,6 +1215,7 @@ export const personStub = { name: '', thumbnailPath: '/new/path/to/thumbnail.jpg', faces: [], + hidden: false, }), primaryPerson: Object.freeze({ id: 'person-1', @@ -1200,6 +1226,7 @@ export const personStub = { name: 'Person 1', thumbnailPath: '/path/to/thumbnail', faces: [], + hidden: false, }), mergePerson: Object.freeze({ id: 'person-2', @@ -1210,6 +1237,7 @@ export const personStub = { name: 'Person 2', thumbnailPath: '/path/to/thumbnail', faces: [], + hidden: false, }), }; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 89633647cf86b..1f5270e04fc58 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1813,6 +1813,12 @@ export interface PersonResponseDto { * @memberof PersonResponseDto */ 'thumbnailPath': string; + /** + * + * @type {boolean} + * @memberof PersonResponseDto + */ + 'hidden': boolean; } /** * @@ -1832,6 +1838,12 @@ export interface PersonUpdateDto { * @memberof PersonUpdateDto */ 'featureFaceAssetId'?: string; + /** + * Person visibility + * @type {boolean} + * @memberof PersonUpdateDto + */ + 'hidden'?: boolean; } /** * diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 42bfb6de759cd..566dfd769019d 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -75,6 +75,10 @@ console.error(error); } }; + + const countHidden = (): number => { + return people.reduce((count, obj) => count + (!obj.hidden ? 1 : 0), 0); + };
@@ -104,24 +108,26 @@ />
- {#if people.length > 0} + {#if people.length > 0 && countHidden() != 0}

PEOPLE

{#each people as person (person.id)} - dispatch('close-viewer')}> - -

{person.name}

-
+ {#if !person.hidden} + dispatch('close-viewer')}> + +

{person.name}

+
+ {/if} {/each}
diff --git a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte index 4b1eea5e52390..e24bf2166c528 100644 --- a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte @@ -3,6 +3,7 @@ import { fade } from 'svelte/transition'; import { thumbHashToDataURL } from 'thumbhash'; import { Buffer } from 'buffer'; + import EyeOffOutline from 'svelte-material-icons/EyeOffOutline.svelte'; export let url: string; export let altText: string; @@ -12,16 +13,17 @@ export let curve = false; export let shadow = false; export let circle = false; - + export let hidden = false; let complete = false; {altText} (complete = true)} /> +{#if hidden} +
+ +
+{/if} {#if thumbhash && !complete} + import { openFileUploadDialog } from '$lib/utils/file-uploader'; + import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte'; + import SideBar from '../shared-components/side-bar/side-bar.svelte'; + import EyeOutline from 'svelte-material-icons/EyeOutline.svelte'; + import IconButton from '../elements/buttons/icon-button.svelte'; + import { fly } from 'svelte/transition'; + import type { UserResponseDto } from '@api'; + import { createEventDispatcher } from 'svelte'; + + export let user: UserResponseDto; + export let hideNavbar = false; + export let showUploadButton = false; + export let title: string | undefined = undefined; + export let hidden: boolean; + + const done = async () => { + hidden = !hidden; + }; + const dispatch = createEventDispatcher(); + const handleDoneClick = () => { + dispatch('doneClick'); + }; + + +{#if !hidden} +
+ {#if !hideNavbar} + openFileUploadDialog()} /> + {/if} + + +
+
+ + + + + {#if title} +
+
+

{title}

+ done()}> +
+ +

Show & hide faces

+
+
+ +
+ +
+ +
+
+ {/if} +
+
+{:else} +
+
+

Show & hide faces

+ (hidden = !hidden)}> +
+ +
+
+
+ +
+
+
+{/if} diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index 09b2f0710f0a2..f85516f333ab9 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -19,7 +19,6 @@ } const MAX_ITEMS = 12; - const getFieldItems = (items: SearchExploreResponseDto[], field: Field) => { const targetField = items.find((item) => item.fieldName === field); return targetField?.items || []; @@ -35,26 +34,26 @@

People

- {#if data.people.length > MAX_ITEMS} - View All - {/if} + View All
{#each people as person (person.id)} - - -

{person.name}

-
+ {#if !person.hidden} + + +

{person.name}

+
+ {/if} {/each}
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 745197adfa874..87e5d010e0d5b 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -1,27 +1,100 @@ - + {#if data.people.length > 0} -
-
- {#each data.people as person (person.id)} -
- -
- + {#if !hidden} + + {:else} +
+
+ {#each data.people as person (person.id)} +
+
+
{#if person.name} {/if} - -
- {/each} +
+ {/each} +
-
+ {/if} {:else}
From 01cdd710c7c13defafa8465b1166bcdb9e92ae75 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:50:47 +0200 Subject: [PATCH 02/28] fix: types --- server/src/immich/controllers/person.controller.ts | 2 +- web/src/routes/(user)/people/+page.svelte | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 7c2c97592ed37..106961aa897d5 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -8,7 +8,7 @@ import { PersonService, PersonUpdateDto, } from '@app/domain'; -import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Put, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser } from '../app.guard'; import { UseValidation } from '../app.utils'; diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 87e5d010e0d5b..636f65d8a5722 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -1,7 +1,7 @@
@@ -108,26 +104,24 @@ />
- {#if people.length > 0 && countHidden() != 0} + {#if people.length > 0}

PEOPLE

{#each people as person (person.id)} - {#if !person.hidden} - dispatch('close-viewer')}> - -

{person.name}

-
- {/if} + dispatch('close-viewer')}> + +

{person.name}

+
{/each}
diff --git a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte index e24bf2166c528..5599359ede740 100644 --- a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte @@ -20,7 +20,7 @@ {altText} (complete = true)} /> {#if hidden} -
+
{/if} diff --git a/web/src/lib/components/layouts/user-page-layout-people.svelte b/web/src/lib/components/layouts/user-page-layout-people.svelte deleted file mode 100644 index 536be167d119e..0000000000000 --- a/web/src/lib/components/layouts/user-page-layout-people.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - -{#if !hidden} -
- {#if !hideNavbar} - openFileUploadDialog()} /> - {/if} - - -
-
- - - - - {#if title} -
-
-

{title}

- done()}> -
- -

Show & hide faces

-
-
- -
- -
- -
-
- {/if} -
-
-{:else} -
-
-

Show & hide faces

- (hidden = !hidden)}> -
- -
-
-
- -
-
-
-{/if} diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index d625707a8c54b..bbc9ed42cb164 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -1,42 +1,88 @@ + export let hidden = false; + export let fullscreen = false; -
- {#if !hideNavbar} - openFileUploadDialog()} /> - {/if} + const done = async () => { + hidden = !hidden; + console.log(hidden); + }; + const dispatch = createEventDispatcher(); + const handleDoneClick = () => { + dispatch('doneClick'); + }; + - -
+{#if !hidden} +
+ {#if !hideNavbar} + openFileUploadDialog()} /> + {/if} -
- - - - - {#if title} -
-
-

{title}

- -
+ +
+
+ + + + + {#if title} +
+
+

{title}

+ {#if fullscreen} + done()}> +
+ +

Show & hide faces

+
+
+ {/if} + +
-
- +
+ +
+
+ {/if} +
+
+{:else} +
+
+

Show & hide faces

+ (hidden = !hidden)}> +
+
- - {/if} - -
+ +
+ +
+
+ +{/if} diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index f85516f333ab9..b9004c54cf3b5 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -34,15 +34,17 @@

People

- View All + {#if data.people.length > 0} + View All + {/if}
{#each people as person (person.id)} - {#if !person.hidden} + {#if !person.isHidden} import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; - import UserPageLayout from '$lib/components/layouts/user-page-layout-people.svelte'; + import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import { PersonResponseDto, api } from '@api'; import AccountOff from 'svelte-material-icons/AccountOff.svelte'; import type { PageData } from './$types'; @@ -18,26 +18,30 @@ onMount(() => { // Save the initial number of "hidden" faces - initialHiddenValues = data.people.map((person: PersonResponseDto) => person.hidden); + initialHiddenValues = data.people.map((person: PersonResponseDto) => person.isHidden); }); + const countHidden = (): number => { + return data.people.reduce((count: number, obj) => count + (!obj.isHidden ? 1 : 0), 0); + }; + const handleDoneClick = async () => { - // Reset the counter before checking changes - changeCounter = 0; + try { + // Reset the counter before checking changes + changeCounter = 0; - data.people.forEach(async (person: PersonResponseDto, index: number) => { - if (person.hidden !== initialHiddenValues[index]) { - changeCounter++; - await api.personApi.updatePerson({ - id: person.id, - personUpdateDto: { hidden: person.hidden }, - }); + data.people.forEach(async (person: PersonResponseDto, index: number) => { + if (person.isHidden !== initialHiddenValues[index]) { + changeCounter++; + await api.personApi.updatePerson({ + id: person.id, + personUpdateDto: { isHidden: person.isHidden }, + }); - // Update the initial hidden value for the person - initialHiddenValues[index] = person.hidden; - } - }); - try { + // Update the initial hidden value for the person + initialHiddenValues[index] = person.isHidden; + } + }); if (changeCounter > 0) notificationController.show({ type: NotificationType.Info, @@ -50,13 +54,20 @@ }; - - {#if data.people.length > 0} + + {#if (data.people.length > 0 && countHidden() > 0) || hidden} {#if !hidden}
{#each data.people as person (person.id)} - {#if !person.hidden} + {#if !person.isHidden}
@@ -86,9 +97,9 @@ {#each data.people as person (person.id)}
-
{/if} - {:else} + {:else if data.people.length == 0 || countHidden() == 0}
From d71b45ecbe6bd63d16d18cfc070786da9b349c72 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:37:43 +0200 Subject: [PATCH 04/28] fix: svelte checks --- web/src/lib/components/layouts/user-page-layout.svelte | 6 +----- web/src/routes/(user)/people/+page.svelte | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index bbc9ed42cb164..33bb0dc135ded 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -15,10 +15,6 @@ export let hidden = false; export let fullscreen = false; - const done = async () => { - hidden = !hidden; - console.log(hidden); - }; const dispatch = createEventDispatcher(); const handleDoneClick = () => { dispatch('doneClick'); @@ -47,7 +43,7 @@ >

{title}

{#if fullscreen} - done()}> + hidden != hidden}>

Show & hide faces

diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index bb27748a040ac..518cfea18034d 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -22,7 +22,7 @@ }); const countHidden = (): number => { - return data.people.reduce((count: number, obj) => count + (!obj.isHidden ? 1 : 0), 0); + return data.people.reduce((count: number, obj: PersonResponseDto) => count + (!obj.isHidden ? 1 : 0), 0); }; const handleDoneClick = async () => { @@ -119,7 +119,7 @@
{/if} - {:else if data.people.length == 0 || countHidden() == 0} + {:else}
From c7626f454983285715c4ec2d03823b069f215289 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sat, 15 Jul 2023 17:37:00 +0200 Subject: [PATCH 05/28] feat: new server endpoint --- cli/src/api/open-api/api.ts | 138 ++++++++++++++++-- .../search/services/person.service.dart | 2 +- mobile/openapi/.openapi-generator/FILES | 3 + mobile/openapi/README.md | 12 +- mobile/openapi/doc/PersonApi.md | 72 ++++++++- mobile/openapi/doc/StatResponseDto.md | 17 +++ mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api/person_api.dart | 75 ++++++++-- mobile/openapi/lib/api_client.dart | 2 + .../openapi/lib/model/stat_response_dto.dart | 114 +++++++++++++++ mobile/openapi/test/person_api_test.dart | 7 +- .../openapi/test/stat_response_dto_test.dart | 37 +++++ server/immich-openapi-specs.json | 70 ++++++++- .../src/domain/person/person.service.spec.ts | 2 +- server/src/domain/person/person.service.ts | 20 ++- .../user/response-dto/user-response.dto.ts | 10 ++ .../immich/controllers/person.controller.ts | 25 +++- web/src/api/api.ts | 2 +- web/src/api/open-api/api.ts | 138 ++++++++++++++++-- .../faces-page/merge-face-selector.svelte | 2 +- .../layouts/user-page-layout.svelte | 8 +- web/src/routes/(user)/explore/+page.server.ts | 4 +- web/src/routes/(user)/explore/+page.svelte | 26 ++-- web/src/routes/(user)/people/+page.server.ts | 5 +- web/src/routes/(user)/people/+page.svelte | 35 +++-- 25 files changed, 719 insertions(+), 108 deletions(-) create mode 100644 mobile/openapi/doc/StatResponseDto.md create mode 100644 mobile/openapi/lib/model/stat_response_dto.dart create mode 100644 mobile/openapi/test/stat_response_dto_test.dart diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 61f68052231b3..0082fe9d9c4f5 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2417,6 +2417,31 @@ export interface SmartInfoResponseDto { */ 'objects'?: Array | null; } +/** + * + * @export + * @interface StatResponseDto + */ +export interface StatResponseDto { + /** + * + * @type {number} + * @memberof StatResponseDto + */ + 'hidden': number; + /** + * + * @type {number} + * @memberof StatResponseDto + */ + 'visible': number; + /** + * + * @type {number} + * @memberof StatResponseDto + */ + 'total': number; +} /** * * @export @@ -8667,10 +8692,13 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio return { /** * + * @param {boolean} areHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople: async (options: AxiosRequestConfig = {}): Promise => { + getAllPeople: async (areHidden: boolean, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'areHidden' is not null or undefined + assertParamExists('getAllPeople', 'areHidden', areHidden) const localVarPath = `/person`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8692,6 +8720,10 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) + if (areHidden !== undefined) { + localVarQueryParameter['areHidden'] = areHidden; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -8712,7 +8744,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPerson: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPerson', 'id', id) - const localVarPath = `/person/{id}` + const localVarPath = `/person/personById/{id}` .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); @@ -8754,7 +8786,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonAssets: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonAssets', 'id', id) - const localVarPath = `/person/{id}/assets` + const localVarPath = `/person/personById/{id}/assets` .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); @@ -8778,6 +8810,44 @@ 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 {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPersonCount: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/person/count`; + // 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); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -8796,7 +8866,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonThumbnail: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonThumbnail', 'id', id) - const localVarPath = `/person/{id}/thumbnail` + const localVarPath = `/person/personById/{id}/thumbnail` .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); @@ -8841,7 +8911,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('mergePerson', 'id', id) // verify required parameter 'mergePersonDto' is not null or undefined assertParamExists('mergePerson', 'mergePersonDto', mergePersonDto) - const localVarPath = `/person/{id}/merge` + const localVarPath = `/person/personById/{id}/merge` .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); @@ -8889,7 +8959,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('updatePerson', 'id', id) // verify required parameter 'personUpdateDto' is not null or undefined assertParamExists('updatePerson', 'personUpdateDto', personUpdateDto) - const localVarPath = `/person/{id}` + const localVarPath = `/person/personById/{id}` .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); @@ -8937,11 +9007,12 @@ export const PersonApiFp = function(configuration?: Configuration) { return { /** * + * @param {boolean} areHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(options); + async getAllPeople(areHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(areHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -8964,6 +9035,15 @@ export const PersonApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonAssets(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getPersonCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonCount(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id @@ -9008,11 +9088,12 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat return { /** * + * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAllPeople(options).then((request) => request(axios, basePath)); + getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig): AxiosPromise> { + return localVarFp.getAllPeople(requestParameters.areHidden, options).then((request) => request(axios, basePath)); }, /** * @@ -9032,6 +9113,14 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat getPersonAssets(requestParameters: PersonApiGetPersonAssetsRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getPersonAssets(requestParameters.id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPersonCount(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getPersonCount(options).then((request) => request(axios, basePath)); + }, /** * * @param {PersonApiGetPersonThumbnailRequest} requestParameters Request parameters. @@ -9062,6 +9151,20 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat }; }; +/** + * Request parameters for getAllPeople operation in PersonApi. + * @export + * @interface PersonApiGetAllPeopleRequest + */ +export interface PersonApiGetAllPeopleRequest { + /** + * + * @type {boolean} + * @memberof PersonApiGetAllPeople + */ + readonly areHidden: boolean +} + /** * Request parameters for getPerson operation in PersonApi. * @export @@ -9155,12 +9258,13 @@ export interface PersonApiUpdatePersonRequest { export class PersonApi extends BaseAPI { /** * + * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof PersonApi */ - public getAllPeople(options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getAllPeople(options).then((request) => request(this.axios, this.basePath)); + public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig) { + return PersonApiFp(this.configuration).getAllPeople(requestParameters.areHidden, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9185,6 +9289,16 @@ export class PersonApi extends BaseAPI { return PersonApiFp(this.configuration).getPersonAssets(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PersonApi + */ + public getPersonCount(options?: AxiosRequestConfig) { + return PersonApiFp(this.configuration).getPersonCount(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {PersonApiGetPersonThumbnailRequest} requestParameters Request parameters. diff --git a/mobile/lib/modules/search/services/person.service.dart b/mobile/lib/modules/search/services/person.service.dart index 5813653cc7af6..f3f5690502dfd 100644 --- a/mobile/lib/modules/search/services/person.service.dart +++ b/mobile/lib/modules/search/services/person.service.dart @@ -18,7 +18,7 @@ class PersonService { Future?> getCuratedPeople() async { try { - return await _apiService.personApi.getAllPeople(); + return await _apiService.personApi.getAllPeople(true); } catch (e) { debugPrint("Error [getCuratedPeople] ${e.toString()}"); return null; diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 86742e468c4b6..cf8c29a961136 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -98,6 +98,7 @@ doc/SharedLinkResponseDto.md doc/SharedLinkType.md doc/SignUpDto.md doc/SmartInfoResponseDto.md +doc/StatResponseDto.md doc/SystemConfigApi.md doc/SystemConfigDto.md doc/SystemConfigFFmpegDto.md @@ -230,6 +231,7 @@ lib/model/shared_link_response_dto.dart lib/model/shared_link_type.dart lib/model/sign_up_dto.dart lib/model/smart_info_response_dto.dart +lib/model/stat_response_dto.dart lib/model/system_config_dto.dart lib/model/system_config_f_fmpeg_dto.dart lib/model/system_config_job_dto.dart @@ -347,6 +349,7 @@ test/shared_link_response_dto_test.dart test/shared_link_type_test.dart test/sign_up_dto_test.dart test/smart_info_response_dto_test.dart +test/stat_response_dto_test.dart test/system_config_api_test.dart test/system_config_dto_test.dart test/system_config_f_fmpeg_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 98dc3fac7e7af..df0dc72b98460 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -131,11 +131,12 @@ Class | Method | HTTP request | Description *PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner | *PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} | *PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person | -*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} | -*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | -*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | -*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge | -*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} | +*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/personById/{id} | +*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/personById/{id}/assets | +*PersonApi* | [**getPersonCount**](doc//PersonApi.md#getpersoncount) | **GET** /person/count | +*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/personById/{id}/thumbnail | +*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/personById/{id}/merge | +*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/personById/{id} | *SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | *SearchApi* | [**getSearchConfig**](doc//SearchApi.md#getsearchconfig) | **GET** /search/config | *SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search | @@ -261,6 +262,7 @@ Class | Method | HTTP request | Description - [SharedLinkType](doc//SharedLinkType.md) - [SignUpDto](doc//SignUpDto.md) - [SmartInfoResponseDto](doc//SmartInfoResponseDto.md) + - [StatResponseDto](doc//StatResponseDto.md) - [SystemConfigDto](doc//SystemConfigDto.md) - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) - [SystemConfigJobDto](doc//SystemConfigJobDto.md) diff --git a/mobile/openapi/doc/PersonApi.md b/mobile/openapi/doc/PersonApi.md index ee57d0c5064b6..2e5ff539da811 100644 --- a/mobile/openapi/doc/PersonApi.md +++ b/mobile/openapi/doc/PersonApi.md @@ -10,15 +10,16 @@ All URIs are relative to */api* Method | HTTP request | Description ------------- | ------------- | ------------- [**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person | -[**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} | -[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | -[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | -[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge | -[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} | +[**getPerson**](PersonApi.md#getperson) | **GET** /person/personById/{id} | +[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/personById/{id}/assets | +[**getPersonCount**](PersonApi.md#getpersoncount) | **GET** /person/count | +[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/personById/{id}/thumbnail | +[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/personById/{id}/merge | +[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/personById/{id} | # **getAllPeople** -> List getAllPeople() +> List getAllPeople(areHidden) @@ -41,9 +42,10 @@ import 'package:openapi/api.dart'; //defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); final api_instance = PersonApi(); +final areHidden = true; // bool | try { - final result = api_instance.getAllPeople(); + final result = api_instance.getAllPeople(areHidden); print(result); } catch (e) { print('Exception when calling PersonApi->getAllPeople: $e\n'); @@ -51,7 +53,10 @@ try { ``` ### Parameters -This endpoint does not need any parameter. + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **areHidden** | **bool**| | ### Return type @@ -178,6 +183,57 @@ 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) +# **getPersonCount** +> StatResponseDto getPersonCount() + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = PersonApi(); + +try { + final result = api_instance.getPersonCount(); + print(result); +} catch (e) { + print('Exception when calling PersonApi->getPersonCount: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**StatResponseDto**](StatResponseDto.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) + # **getPersonThumbnail** > MultipartFile getPersonThumbnail(id) diff --git a/mobile/openapi/doc/StatResponseDto.md b/mobile/openapi/doc/StatResponseDto.md new file mode 100644 index 0000000000000..485740fc29980 --- /dev/null +++ b/mobile/openapi/doc/StatResponseDto.md @@ -0,0 +1,17 @@ +# openapi.model.StatResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**hidden** | **int** | | +**visible** | **int** | | +**total** | **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) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 099f5615c5eae..dc6fb2f52c317 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -127,6 +127,7 @@ part 'model/shared_link_response_dto.dart'; part 'model/shared_link_type.dart'; part 'model/sign_up_dto.dart'; part 'model/smart_info_response_dto.dart'; +part 'model/stat_response_dto.dart'; part 'model/system_config_dto.dart'; part 'model/system_config_f_fmpeg_dto.dart'; part 'model/system_config_job_dto.dart'; diff --git a/mobile/openapi/lib/api/person_api.dart b/mobile/openapi/lib/api/person_api.dart index 3a53bd5ebfa8e..3cf23bc3b0930 100644 --- a/mobile/openapi/lib/api/person_api.dart +++ b/mobile/openapi/lib/api/person_api.dart @@ -17,7 +17,10 @@ class PersonApi { final ApiClient apiClient; /// Performs an HTTP 'GET /person' operation and returns the [Response]. - Future getAllPeopleWithHttpInfo() async { + /// Parameters: + /// + /// * [bool] areHidden (required): + Future getAllPeopleWithHttpInfo(bool areHidden,) async { // ignore: prefer_const_declarations final path = r'/person'; @@ -28,6 +31,8 @@ class PersonApi { final headerParams = {}; final formParams = {}; + queryParams.addAll(_queryParams('', 'areHidden', areHidden)); + const contentTypes = []; @@ -42,8 +47,11 @@ class PersonApi { ); } - Future?> getAllPeople() async { - final response = await getAllPeopleWithHttpInfo(); + /// Parameters: + /// + /// * [bool] areHidden (required): + Future?> getAllPeople(bool areHidden,) async { + final response = await getAllPeopleWithHttpInfo(areHidden,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -60,13 +68,13 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/{id}' operation and returns the [Response]. + /// Performs an HTTP 'GET /person/personById/{id}' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): Future getPersonWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/person/{id}' + final path = r'/person/personById/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -108,13 +116,13 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/{id}/assets' operation and returns the [Response]. + /// Performs an HTTP 'GET /person/personById/{id}/assets' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): Future getPersonAssetsWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/person/{id}/assets' + final path = r'/person/personById/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -159,13 +167,54 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/{id}/thumbnail' operation and returns the [Response]. + /// Performs an HTTP 'GET /person/count' operation and returns the [Response]. + Future getPersonCountWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/person/count'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + Future getPersonCount() async { + final response = await getPersonCountWithHttpInfo(); + 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), 'StatResponseDto',) as StatResponseDto; + + } + return null; + } + + /// Performs an HTTP 'GET /person/personById/{id}/thumbnail' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): Future getPersonThumbnailWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/person/{id}/thumbnail' + final path = r'/person/personById/{id}/thumbnail' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -207,7 +256,7 @@ class PersonApi { return null; } - /// Performs an HTTP 'POST /person/{id}/merge' operation and returns the [Response]. + /// Performs an HTTP 'POST /person/personById/{id}/merge' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): @@ -215,7 +264,7 @@ class PersonApi { /// * [MergePersonDto] mergePersonDto (required): Future mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async { // ignore: prefer_const_declarations - final path = r'/person/{id}/merge' + final path = r'/person/personById/{id}/merge' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -262,7 +311,7 @@ class PersonApi { return null; } - /// Performs an HTTP 'PUT /person/{id}' operation and returns the [Response]. + /// Performs an HTTP 'PUT /person/personById/{id}' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): @@ -270,7 +319,7 @@ class PersonApi { /// * [PersonUpdateDto] personUpdateDto (required): Future updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/person/{id}' + final path = r'/person/personById/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 5855da8e82cf7..f3e2daa425337 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -349,6 +349,8 @@ class ApiClient { return SignUpDto.fromJson(value); case 'SmartInfoResponseDto': return SmartInfoResponseDto.fromJson(value); + case 'StatResponseDto': + return StatResponseDto.fromJson(value); case 'SystemConfigDto': return SystemConfigDto.fromJson(value); case 'SystemConfigFFmpegDto': diff --git a/mobile/openapi/lib/model/stat_response_dto.dart b/mobile/openapi/lib/model/stat_response_dto.dart new file mode 100644 index 0000000000000..2ff174fb07286 --- /dev/null +++ b/mobile/openapi/lib/model/stat_response_dto.dart @@ -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 StatResponseDto { + /// Returns a new [StatResponseDto] instance. + StatResponseDto({ + required this.hidden, + required this.visible, + required this.total, + }); + + int hidden; + + int visible; + + int total; + + @override + bool operator ==(Object other) => identical(this, other) || other is StatResponseDto && + other.hidden == hidden && + other.visible == visible && + other.total == total; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (hidden.hashCode) + + (visible.hashCode) + + (total.hashCode); + + @override + String toString() => 'StatResponseDto[hidden=$hidden, visible=$visible, total=$total]'; + + Map toJson() { + final json = {}; + json[r'hidden'] = this.hidden; + json[r'visible'] = this.visible; + json[r'total'] = this.total; + return json; + } + + /// Returns a new [StatResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static StatResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return StatResponseDto( + hidden: mapValueOfType(json, r'hidden')!, + visible: mapValueOfType(json, r'visible')!, + total: mapValueOfType(json, r'total')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = StatResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = StatResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of StatResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = StatResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'hidden', + 'visible', + 'total', + }; +} + diff --git a/mobile/openapi/test/person_api_test.dart b/mobile/openapi/test/person_api_test.dart index 95482f63d34b6..191e83197c2a5 100644 --- a/mobile/openapi/test/person_api_test.dart +++ b/mobile/openapi/test/person_api_test.dart @@ -17,7 +17,7 @@ void main() { // final instance = PersonApi(); group('tests for PersonApi', () { - //Future> getAllPeople() async + //Future> getAllPeople(bool areHidden) async test('test getAllPeople', () async { // TODO }); @@ -32,6 +32,11 @@ void main() { // TODO }); + //Future getPersonCount() async + test('test getPersonCount', () async { + // TODO + }); + //Future getPersonThumbnail(String id) async test('test getPersonThumbnail', () async { // TODO diff --git a/mobile/openapi/test/stat_response_dto_test.dart b/mobile/openapi/test/stat_response_dto_test.dart new file mode 100644 index 0000000000000..30e88ee1fafcf --- /dev/null +++ b/mobile/openapi/test/stat_response_dto_test.dart @@ -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 StatResponseDto +void main() { + // final instance = StatResponseDto(); + + group('test StatResponseDto', () { + // int hidden + test('to test the property `hidden`', () async { + // TODO + }); + + // int visible + test('to test the property `visible`', () async { + // TODO + }); + + // int total + test('to test the property `total`', () async { + // TODO + }); + + + }); + +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 2c92f32fb18c0..97dfd6e71a499 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2524,7 +2524,16 @@ "/person": { "get": { "operationId": "getAllPeople", - "parameters": [], + "parameters": [ + { + "name": "areHidden", + "required": true, + "in": "query", + "schema": { + "type": "boolean" + } + } + ], "responses": { "200": { "description": "", @@ -2556,7 +2565,39 @@ ] } }, - "/person/{id}": { + "/person/count": { + "get": { + "operationId": "getPersonCount", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatResponseDto" + } + } + } + } + }, + "tags": [ + "Person" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + } + }, + "/person/personById/{id}": { "get": { "operationId": "getPerson", "parameters": [ @@ -2648,7 +2689,7 @@ ] } }, - "/person/{id}/assets": { + "/person/personById/{id}/assets": { "get": { "operationId": "getPersonAssets", "parameters": [ @@ -2693,7 +2734,7 @@ ] } }, - "/person/{id}/merge": { + "/person/personById/{id}/merge": { "post": { "operationId": "mergePerson", "parameters": [ @@ -2748,7 +2789,7 @@ ] } }, - "/person/{id}/thumbnail": { + "/person/personById/{id}/thumbnail": { "get": { "operationId": "getPersonThumbnail", "parameters": [ @@ -6404,6 +6445,25 @@ } } }, + "StatResponseDto": { + "type": "object", + "properties": { + "hidden": { + "type": "integer" + }, + "visible": { + "type": "integer" + }, + "total": { + "type": "integer" + } + }, + "required": [ + "hidden", + "visible", + "total" + ] + }, "SystemConfigDto": { "type": "object", "properties": { diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 5dd9ce14e12c6..b6b9d6b98fce6 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -42,7 +42,7 @@ describe(PersonService.name, () => { describe('getAll', () => { it('should get all people with thumbnails', async () => { personMock.getAll.mockResolvedValue([personStub.withName, personStub.noThumbnail]); - await expect(sut.getAll(authStub.admin)).resolves.toEqual([responseDto]); + await expect(sut.getAll(authStub.admin, false)).resolves.toEqual([responseDto]); expect(personMock.getAll).toHaveBeenCalledWith(authStub.admin.id, { minimumFaceCount: 1 }); }); }); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 741f509a5bf65..38d48d1792969 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { StatResponseDto } from '..'; import { AssetResponseDto, BulkIdErrorReason, BulkIdResponseDto, mapAsset } from '../asset'; import { AuthUserDto } from '../auth'; import { mimeTypes } from '../domain.constant'; @@ -17,10 +18,18 @@ export class PersonService { @Inject(IJobRepository) private jobRepository: IJobRepository, ) {} - async getAll(authUser: AuthUserDto): Promise { + async getAll(authUser: AuthUserDto, areHidden: boolean): Promise { const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); const named = people.filter((person) => !!person.name); const unnamed = people.filter((person) => !person.name); + if (areHidden) + return ( + [...named, ...unnamed] + // with thumbnails + .filter((person) => !!person.thumbnailPath) + .filter((person) => !person.isHidden) + .map((person) => mapPerson(person)) + ); return ( [...named, ...unnamed] @@ -34,6 +43,15 @@ export class PersonService { return this.findOrFail(authUser, id).then(mapPerson); } + async getPersonCount(authUser: AuthUserDto): Promise { + const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); + return { + hidden: people.filter((obj) => obj.isHidden === true).length, + visible: people.filter((obj) => obj.isHidden === false).length, + total: people.length, + }; + } + async getThumbnail(authUser: AuthUserDto, id: string): Promise { const person = await this.repository.getById(authUser.id, id); if (!person || !person.thumbnailPath) { diff --git a/server/src/domain/user/response-dto/user-response.dto.ts b/server/src/domain/user/response-dto/user-response.dto.ts index a2bd508837bc4..d4c579aae9b2e 100644 --- a/server/src/domain/user/response-dto/user-response.dto.ts +++ b/server/src/domain/user/response-dto/user-response.dto.ts @@ -1,4 +1,5 @@ import { UserEntity } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; export class UserResponseDto { id!: string; @@ -16,6 +17,15 @@ export class UserResponseDto { oauthId!: string; } +export class StatResponseDto { + @ApiProperty({ type: 'integer' }) + hidden!: number; + @ApiProperty({ type: 'integer' }) + visible!: number; + @ApiProperty({ type: 'integer' }) + total!: number; +} + export function mapUser(entity: UserEntity): UserResponseDto { return { id: entity.id, diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 106961aa897d5..1d8fd882baad5 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -7,8 +7,9 @@ import { PersonResponseDto, PersonService, PersonUpdateDto, + StatResponseDto, } from '@app/domain'; -import { Body, Controller, Get, Param, Post, Put, StreamableFile } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -26,16 +27,19 @@ export class PersonController { constructor(private service: PersonService) {} @Get() - getAllPeople(@AuthUser() authUser: AuthUserDto): Promise { - return this.service.getAll(authUser); + getAllPeople( + @AuthUser() authUser: AuthUserDto, + @Query('areHidden') areHidden: boolean, + ): Promise { + return this.service.getAll(authUser, areHidden); } - @Get(':id') + @Get('/personById/:id') getPerson(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getById(authUser, id); } - @Put(':id') + @Put('/personById/:id') updatePerson( @AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @@ -44,7 +48,12 @@ export class PersonController { return this.service.update(authUser, id, dto); } - @Get(':id/thumbnail') + @Get('/count') + getPersonCount(@AuthUser() authUser: AuthUserDto): Promise { + return this.service.getPersonCount(authUser); + } + + @Get('/personById/:id/thumbnail') @ApiOkResponse({ content: { 'image/jpeg': { schema: { type: 'string', format: 'binary' } }, @@ -54,12 +63,12 @@ export class PersonController { return this.service.getThumbnail(authUser, id).then(asStreamableFile); } - @Get(':id/assets') + @Get('/personById/:id/assets') getPersonAssets(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getAssets(authUser, id); } - @Post(':id/merge') + @Post('/personById/:id/merge') mergePerson( @AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, diff --git a/web/src/api/api.ts b/web/src/api/api.ts index 22f998972143e..e6efa76ceb240 100644 --- a/web/src/api/api.ts +++ b/web/src/api/api.ts @@ -100,7 +100,7 @@ export class ImmichApi { } public getPeopleThumbnailUrl(personId: string) { - const path = `/person/${personId}/thumbnail`; + const path = `/person/personById/${personId}/thumbnail`; return this.createUrl(path); } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 7419b54d516e6..4ded22fcffe7b 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2417,6 +2417,31 @@ export interface SmartInfoResponseDto { */ 'objects'?: Array | null; } +/** + * + * @export + * @interface StatResponseDto + */ +export interface StatResponseDto { + /** + * + * @type {number} + * @memberof StatResponseDto + */ + 'hidden': number; + /** + * + * @type {number} + * @memberof StatResponseDto + */ + 'visible': number; + /** + * + * @type {number} + * @memberof StatResponseDto + */ + 'total': number; +} /** * * @export @@ -8710,10 +8735,13 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio return { /** * + * @param {boolean} areHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople: async (options: AxiosRequestConfig = {}): Promise => { + getAllPeople: async (areHidden: boolean, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'areHidden' is not null or undefined + assertParamExists('getAllPeople', 'areHidden', areHidden) const localVarPath = `/person`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8735,6 +8763,10 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) + if (areHidden !== undefined) { + localVarQueryParameter['areHidden'] = areHidden; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -8755,7 +8787,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPerson: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPerson', 'id', id) - const localVarPath = `/person/{id}` + const localVarPath = `/person/personById/{id}` .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); @@ -8797,7 +8829,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonAssets: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonAssets', 'id', id) - const localVarPath = `/person/{id}/assets` + const localVarPath = `/person/personById/{id}/assets` .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); @@ -8821,6 +8853,44 @@ 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 {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPersonCount: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/person/count`; + // 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); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -8839,7 +8909,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonThumbnail: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonThumbnail', 'id', id) - const localVarPath = `/person/{id}/thumbnail` + const localVarPath = `/person/personById/{id}/thumbnail` .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); @@ -8884,7 +8954,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('mergePerson', 'id', id) // verify required parameter 'mergePersonDto' is not null or undefined assertParamExists('mergePerson', 'mergePersonDto', mergePersonDto) - const localVarPath = `/person/{id}/merge` + const localVarPath = `/person/personById/{id}/merge` .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); @@ -8932,7 +9002,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('updatePerson', 'id', id) // verify required parameter 'personUpdateDto' is not null or undefined assertParamExists('updatePerson', 'personUpdateDto', personUpdateDto) - const localVarPath = `/person/{id}` + const localVarPath = `/person/personById/{id}` .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); @@ -8980,11 +9050,12 @@ export const PersonApiFp = function(configuration?: Configuration) { return { /** * + * @param {boolean} areHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(options); + async getAllPeople(areHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(areHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -9007,6 +9078,15 @@ export const PersonApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonAssets(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getPersonCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonCount(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id @@ -9051,11 +9131,12 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat return { /** * + * @param {boolean} areHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(options?: any): AxiosPromise> { - return localVarFp.getAllPeople(options).then((request) => request(axios, basePath)); + getAllPeople(areHidden: boolean, options?: any): AxiosPromise> { + return localVarFp.getAllPeople(areHidden, options).then((request) => request(axios, basePath)); }, /** * @@ -9075,6 +9156,14 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat getPersonAssets(id: string, options?: any): AxiosPromise> { return localVarFp.getPersonAssets(id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPersonCount(options?: any): AxiosPromise { + return localVarFp.getPersonCount(options).then((request) => request(axios, basePath)); + }, /** * * @param {string} id @@ -9107,6 +9196,20 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat }; }; +/** + * Request parameters for getAllPeople operation in PersonApi. + * @export + * @interface PersonApiGetAllPeopleRequest + */ +export interface PersonApiGetAllPeopleRequest { + /** + * + * @type {boolean} + * @memberof PersonApiGetAllPeople + */ + readonly areHidden: boolean +} + /** * Request parameters for getPerson operation in PersonApi. * @export @@ -9200,12 +9303,13 @@ export interface PersonApiUpdatePersonRequest { export class PersonApi extends BaseAPI { /** * + * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof PersonApi */ - public getAllPeople(options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getAllPeople(options).then((request) => request(this.axios, this.basePath)); + public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig) { + return PersonApiFp(this.configuration).getAllPeople(requestParameters.areHidden, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9230,6 +9334,16 @@ export class PersonApi extends BaseAPI { return PersonApiFp(this.configuration).getPersonAssets(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PersonApi + */ + public getPersonCount(options?: AxiosRequestConfig) { + return PersonApiFp(this.configuration).getPersonCount(options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {PersonApiGetPersonThumbnailRequest} requestParameters Request parameters. diff --git a/web/src/lib/components/faces-page/merge-face-selector.svelte b/web/src/lib/components/faces-page/merge-face-selector.svelte index 71c3f6bf8f5df..bef491757138a 100644 --- a/web/src/lib/components/faces-page/merge-face-selector.svelte +++ b/web/src/lib/components/faces-page/merge-face-selector.svelte @@ -25,7 +25,7 @@ $: unselectedPeople = people.filter((source) => !selectedPeople.includes(source) && source.id !== person.id); onMount(async () => { - const { data } = await api.personApi.getAllPeople(); + const { data } = await api.personApi.getAllPeople({ areHidden: true }); people = data; }); diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 33bb0dc135ded..ae8a69d6e142e 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -12,7 +12,7 @@ export let hideNavbar = false; export let showUploadButton = false; export let title: string | undefined = undefined; - export let hidden = false; + export let selecthidden = false; export let fullscreen = false; const dispatch = createEventDispatcher(); @@ -21,7 +21,7 @@ }; -{#if !hidden} +{#if !selecthidden}
{#if !hideNavbar} openFileUploadDialog()} /> @@ -43,7 +43,7 @@ >

{title}

{#if fullscreen} - hidden != hidden}> + (selecthidden = !selecthidden)}>

Show & hide faces

@@ -71,7 +71,7 @@ class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16" >

Show & hide faces

- (hidden = !hidden)}> + (selecthidden = !selecthidden)}>
diff --git a/web/src/routes/(user)/explore/+page.server.ts b/web/src/routes/(user)/explore/+page.server.ts index 97b674cb388b3..82de38fc21ada 100644 --- a/web/src/routes/(user)/explore/+page.server.ts +++ b/web/src/routes/(user)/explore/+page.server.ts @@ -9,11 +9,13 @@ export const load = (async ({ locals, parent }) => { } const { data: items } = await locals.api.searchApi.getExploreData(); - const { data: people } = await locals.api.personApi.getAllPeople(); + const { data: people } = await locals.api.personApi.getAllPeople({ areHidden: true }); + const { data: countpeople } = await locals.api.personApi.getPersonCount(); return { user, items, people, + countpeople, meta: { title: 'Explore', }, diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index b9004c54cf3b5..9cfc21364cc33 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -30,11 +30,11 @@ - {#if people.length > 0} + {#if data.countpeople.total > 0}
diff --git a/web/src/routes/(user)/people/+page.server.ts b/web/src/routes/(user)/people/+page.server.ts index bfcae137d92df..d4ebe4ee4c64b 100644 --- a/web/src/routes/(user)/people/+page.server.ts +++ b/web/src/routes/(user)/people/+page.server.ts @@ -8,11 +8,12 @@ export const load = (async ({ locals, parent }) => { throw redirect(302, AppRoute.AUTH_LOGIN); } - const { data: people } = await locals.api.personApi.getAllPeople(); - + const { data: people } = await locals.api.personApi.getAllPeople({ areHidden: false }); + const { data: countpeople } = await locals.api.personApi.getPersonCount(); return { user, people, + countpeople, meta: { title: 'People', }, diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 518cfea18034d..2b2c280be2a2d 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -1,10 +1,9 @@ @@ -72,10 +73,12 @@ class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16" >

Show & hide faces

- (selecthidden = !selecthidden)}> -
- -
+ { + handleDoneClick(); + }} + > + Done
From 4489476b134c3f39e0a665ec89e92d41d39c133b Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 00:27:55 +0200 Subject: [PATCH 09/28] docs: add comments --- web/src/routes/(user)/people/+page.svelte | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 245ce3ecc8839..0c027008bb37d 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -14,6 +14,8 @@ let selecthidden = false; let changeCounter = 0; let initialHiddenValues: boolean[] = data.people.map((person: PersonResponseDto) => person.isHidden); + + // Get number of hidden and visible people let countpeople: PersonCountResponseDto = data.countpeople; const handleDoneClick = async () => { @@ -29,13 +31,10 @@ id: person.id, personUpdateDto: { isHidden: person.isHidden }, }); - if (person.isHidden) { - countpeople.hidden++; - countpeople.visible--; - } else { - countpeople.hidden--; - countpeople.visible++; - } + + // Keeps track of the number of hidden/visible people, it helps to know whether to show "no people" or not + countpeople.hidden += person.isHidden ? 1 : -1; + countpeople.visible += person.isHidden ? -1 : 1; // Update the initial hidden values initialHiddenValues[index] = person.isHidden; From 6ec23439343fe8a293a0a68b24cf53bdf620fab8 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 14:38:49 +0200 Subject: [PATCH 10/28] pr feedback --- cli/src/api/open-api/api.ts | 50 ++++++++--------- .../search/services/person.service.dart | 2 +- mobile/openapi/README.md | 12 ++--- mobile/openapi/doc/PersonApi.md | 28 +++++----- mobile/openapi/lib/api/person_api.dart | 42 +++++++-------- mobile/openapi/test/person_api_test.dart | 6 +-- server/immich-openapi-specs.json | 14 ++--- server/src/domain/person/person.service.ts | 13 ++--- .../immich/controllers/person.controller.ts | 24 ++++----- web/src/api/api.ts | 2 +- web/src/api/open-api/api.ts | 54 +++++++++---------- .../faces-page/merge-face-selector.svelte | 2 +- web/src/routes/(user)/explore/+page.server.ts | 4 +- web/src/routes/(user)/explore/+page.svelte | 38 ++++++++----- web/src/routes/(user)/people/+page.server.ts | 4 +- web/src/routes/(user)/people/+page.svelte | 10 +++- 16 files changed, 158 insertions(+), 147 deletions(-) diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index e50811903d714..fcb6787bbbdb9 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -8692,13 +8692,13 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio return { /** * - * @param {boolean} areHidden + * @param {boolean} withHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople: async (areHidden: boolean, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'areHidden' is not null or undefined - assertParamExists('getAllPeople', 'areHidden', areHidden) + getAllPeople: async (withHidden: boolean, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'withHidden' is not null or undefined + assertParamExists('getAllPeople', 'withHidden', withHidden) const localVarPath = `/person`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8720,8 +8720,8 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) - if (areHidden !== undefined) { - localVarQueryParameter['areHidden'] = areHidden; + if (withHidden !== undefined) { + localVarQueryParameter['withHidden'] = withHidden; } @@ -8744,7 +8744,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPerson: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPerson', 'id', id) - const localVarPath = `/person/personById/{id}` + const localVarPath = `/person/{id}` .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); @@ -8786,7 +8786,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonAssets: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonAssets', 'id', id) - const localVarPath = `/person/personById/{id}/assets` + const localVarPath = `/person/{id}/assets` .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); @@ -8824,8 +8824,8 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPersonCount: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/person/count`; + getPersonStats: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/person/statistics`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -8866,7 +8866,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonThumbnail: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonThumbnail', 'id', id) - const localVarPath = `/person/personById/{id}/thumbnail` + const localVarPath = `/person/{id}/thumbnail` .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); @@ -8911,7 +8911,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('mergePerson', 'id', id) // verify required parameter 'mergePersonDto' is not null or undefined assertParamExists('mergePerson', 'mergePersonDto', mergePersonDto) - const localVarPath = `/person/personById/{id}/merge` + const localVarPath = `/person/{id}/merge` .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); @@ -8959,7 +8959,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('updatePerson', 'id', id) // verify required parameter 'personUpdateDto' is not null or undefined assertParamExists('updatePerson', 'personUpdateDto', personUpdateDto) - const localVarPath = `/person/personById/{id}` + const localVarPath = `/person/{id}` .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); @@ -9007,12 +9007,12 @@ export const PersonApiFp = function(configuration?: Configuration) { return { /** * - * @param {boolean} areHidden + * @param {boolean} withHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(areHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(areHidden, options); + async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -9040,8 +9040,8 @@ export const PersonApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getPersonCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonCount(options); + async getPersonStats(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonStats(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -9093,7 +9093,7 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat * @throws {RequiredError} */ getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAllPeople(requestParameters.areHidden, options).then((request) => request(axios, basePath)); + return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath)); }, /** * @@ -9118,8 +9118,8 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPersonCount(options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getPersonCount(options).then((request) => request(axios, basePath)); + getPersonStats(options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getPersonStats(options).then((request) => request(axios, basePath)); }, /** * @@ -9162,7 +9162,7 @@ export interface PersonApiGetAllPeopleRequest { * @type {boolean} * @memberof PersonApiGetAllPeople */ - readonly areHidden: boolean + readonly withHidden: boolean } /** @@ -9264,7 +9264,7 @@ export class PersonApi extends BaseAPI { * @memberof PersonApi */ public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getAllPeople(requestParameters.areHidden, options).then((request) => request(this.axios, this.basePath)); + return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9295,8 +9295,8 @@ export class PersonApi extends BaseAPI { * @throws {RequiredError} * @memberof PersonApi */ - public getPersonCount(options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getPersonCount(options).then((request) => request(this.axios, this.basePath)); + public getPersonStats(options?: AxiosRequestConfig) { + return PersonApiFp(this.configuration).getPersonStats(options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/mobile/lib/modules/search/services/person.service.dart b/mobile/lib/modules/search/services/person.service.dart index f3f5690502dfd..2ac7d090c118a 100644 --- a/mobile/lib/modules/search/services/person.service.dart +++ b/mobile/lib/modules/search/services/person.service.dart @@ -18,7 +18,7 @@ class PersonService { Future?> getCuratedPeople() async { try { - return await _apiService.personApi.getAllPeople(true); + return await _apiService.personApi.getAllPeople(false); } catch (e) { debugPrint("Error [getCuratedPeople] ${e.toString()}"); return null; diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 6432457510309..0b857d166f721 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -131,12 +131,12 @@ Class | Method | HTTP request | Description *PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner | *PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} | *PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person | -*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/personById/{id} | -*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/personById/{id}/assets | -*PersonApi* | [**getPersonCount**](doc//PersonApi.md#getpersoncount) | **GET** /person/count | -*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/personById/{id}/thumbnail | -*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/personById/{id}/merge | -*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/personById/{id} | +*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} | +*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | +*PersonApi* | [**getPersonStats**](doc//PersonApi.md#getpersonstats) | **GET** /person/statistics | +*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | +*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge | +*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} | *SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | *SearchApi* | [**getSearchConfig**](doc//SearchApi.md#getsearchconfig) | **GET** /search/config | *SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search | diff --git a/mobile/openapi/doc/PersonApi.md b/mobile/openapi/doc/PersonApi.md index f81fa08b3900c..c1954459a603d 100644 --- a/mobile/openapi/doc/PersonApi.md +++ b/mobile/openapi/doc/PersonApi.md @@ -10,16 +10,16 @@ All URIs are relative to */api* Method | HTTP request | Description ------------- | ------------- | ------------- [**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person | -[**getPerson**](PersonApi.md#getperson) | **GET** /person/personById/{id} | -[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/personById/{id}/assets | -[**getPersonCount**](PersonApi.md#getpersoncount) | **GET** /person/count | -[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/personById/{id}/thumbnail | -[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/personById/{id}/merge | -[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/personById/{id} | +[**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} | +[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | +[**getPersonStats**](PersonApi.md#getpersonstats) | **GET** /person/statistics | +[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | +[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge | +[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} | # **getAllPeople** -> List getAllPeople(areHidden) +> List getAllPeople(withHidden) @@ -42,10 +42,10 @@ import 'package:openapi/api.dart'; //defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); final api_instance = PersonApi(); -final areHidden = true; // bool | +final withHidden = true; // bool | try { - final result = api_instance.getAllPeople(areHidden); + final result = api_instance.getAllPeople(withHidden); print(result); } catch (e) { print('Exception when calling PersonApi->getAllPeople: $e\n'); @@ -56,7 +56,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **areHidden** | **bool**| | + **withHidden** | **bool**| | ### Return type @@ -183,8 +183,8 @@ 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) -# **getPersonCount** -> PersonCountResponseDto getPersonCount() +# **getPersonStats** +> PersonCountResponseDto getPersonStats() @@ -209,10 +209,10 @@ import 'package:openapi/api.dart'; final api_instance = PersonApi(); try { - final result = api_instance.getPersonCount(); + final result = api_instance.getPersonStats(); print(result); } catch (e) { - print('Exception when calling PersonApi->getPersonCount: $e\n'); + print('Exception when calling PersonApi->getPersonStats: $e\n'); } ``` diff --git a/mobile/openapi/lib/api/person_api.dart b/mobile/openapi/lib/api/person_api.dart index fdf3caa9c1207..62185eff467e4 100644 --- a/mobile/openapi/lib/api/person_api.dart +++ b/mobile/openapi/lib/api/person_api.dart @@ -19,8 +19,8 @@ class PersonApi { /// Performs an HTTP 'GET /person' operation and returns the [Response]. /// Parameters: /// - /// * [bool] areHidden (required): - Future getAllPeopleWithHttpInfo(bool areHidden,) async { + /// * [bool] withHidden (required): + Future getAllPeopleWithHttpInfo(bool withHidden,) async { // ignore: prefer_const_declarations final path = r'/person'; @@ -31,7 +31,7 @@ class PersonApi { final headerParams = {}; final formParams = {}; - queryParams.addAll(_queryParams('', 'areHidden', areHidden)); + queryParams.addAll(_queryParams('', 'withHidden', withHidden)); const contentTypes = []; @@ -49,9 +49,9 @@ class PersonApi { /// Parameters: /// - /// * [bool] areHidden (required): - Future?> getAllPeople(bool areHidden,) async { - final response = await getAllPeopleWithHttpInfo(areHidden,); + /// * [bool] withHidden (required): + Future?> getAllPeople(bool withHidden,) async { + final response = await getAllPeopleWithHttpInfo(withHidden,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -68,13 +68,13 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/personById/{id}' operation and returns the [Response]. + /// Performs an HTTP 'GET /person/{id}' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): Future getPersonWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/person/personById/{id}' + final path = r'/person/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -116,13 +116,13 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/personById/{id}/assets' operation and returns the [Response]. + /// Performs an HTTP 'GET /person/{id}/assets' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): Future getPersonAssetsWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/person/personById/{id}/assets' + final path = r'/person/{id}/assets' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -167,10 +167,10 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/count' operation and returns the [Response]. - Future getPersonCountWithHttpInfo() async { + /// Performs an HTTP 'GET /person/statistics' operation and returns the [Response]. + Future getPersonStatsWithHttpInfo() async { // ignore: prefer_const_declarations - final path = r'/person/count'; + final path = r'/person/statistics'; // ignore: prefer_final_locals Object? postBody; @@ -193,8 +193,8 @@ class PersonApi { ); } - Future getPersonCount() async { - final response = await getPersonCountWithHttpInfo(); + Future getPersonStats() async { + final response = await getPersonStatsWithHttpInfo(); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -208,13 +208,13 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/personById/{id}/thumbnail' operation and returns the [Response]. + /// Performs an HTTP 'GET /person/{id}/thumbnail' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): Future getPersonThumbnailWithHttpInfo(String id,) async { // ignore: prefer_const_declarations - final path = r'/person/personById/{id}/thumbnail' + final path = r'/person/{id}/thumbnail' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -256,7 +256,7 @@ class PersonApi { return null; } - /// Performs an HTTP 'POST /person/personById/{id}/merge' operation and returns the [Response]. + /// Performs an HTTP 'POST /person/{id}/merge' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): @@ -264,7 +264,7 @@ class PersonApi { /// * [MergePersonDto] mergePersonDto (required): Future mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async { // ignore: prefer_const_declarations - final path = r'/person/personById/{id}/merge' + final path = r'/person/{id}/merge' .replaceAll('{id}', id); // ignore: prefer_final_locals @@ -311,7 +311,7 @@ class PersonApi { return null; } - /// Performs an HTTP 'PUT /person/personById/{id}' operation and returns the [Response]. + /// Performs an HTTP 'PUT /person/{id}' operation and returns the [Response]. /// Parameters: /// /// * [String] id (required): @@ -319,7 +319,7 @@ class PersonApi { /// * [PersonUpdateDto] personUpdateDto (required): Future updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto,) async { // ignore: prefer_const_declarations - final path = r'/person/personById/{id}' + final path = r'/person/{id}' .replaceAll('{id}', id); // ignore: prefer_final_locals diff --git a/mobile/openapi/test/person_api_test.dart b/mobile/openapi/test/person_api_test.dart index 5a490843d9d05..20abc170fab5f 100644 --- a/mobile/openapi/test/person_api_test.dart +++ b/mobile/openapi/test/person_api_test.dart @@ -17,7 +17,7 @@ void main() { // final instance = PersonApi(); group('tests for PersonApi', () { - //Future> getAllPeople(bool areHidden) async + //Future> getAllPeople(bool withHidden) async test('test getAllPeople', () async { // TODO }); @@ -32,8 +32,8 @@ void main() { // TODO }); - //Future getPersonCount() async - test('test getPersonCount', () async { + //Future getPersonStats() async + test('test getPersonStats', () async { // TODO }); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 62cdb7b49951b..fa5879a87bec5 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2526,7 +2526,7 @@ "operationId": "getAllPeople", "parameters": [ { - "name": "areHidden", + "name": "withHidden", "required": true, "in": "query", "schema": { @@ -2565,9 +2565,9 @@ ] } }, - "/person/count": { + "/person/statistics": { "get": { - "operationId": "getPersonCount", + "operationId": "getPersonStats", "parameters": [], "responses": { "200": { @@ -2597,7 +2597,7 @@ ] } }, - "/person/personById/{id}": { + "/person/{id}": { "get": { "operationId": "getPerson", "parameters": [ @@ -2689,7 +2689,7 @@ ] } }, - "/person/personById/{id}/assets": { + "/person/{id}/assets": { "get": { "operationId": "getPersonAssets", "parameters": [ @@ -2734,7 +2734,7 @@ ] } }, - "/person/personById/{id}/merge": { + "/person/{id}/merge": { "post": { "operationId": "mergePerson", "parameters": [ @@ -2789,7 +2789,7 @@ ] } }, - "/person/personById/{id}/thumbnail": { + "/person/{id}/thumbnail": { "get": { "operationId": "getPersonThumbnail", "parameters": [ diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index d31f45e025087..2402e93229637 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -17,23 +17,18 @@ export class PersonService { @Inject(IJobRepository) private jobRepository: IJobRepository, ) {} - async getAll(authUser: AuthUserDto, areHidden: boolean): Promise { + async getAll(authUser: AuthUserDto, withHidden: boolean): Promise { const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); const named = people.filter((person) => !!person.name); const unnamed = people.filter((person) => !person.name); - if (areHidden) - return ( - [...named, ...unnamed] - // with thumbnails - .filter((person) => !!person.thumbnailPath) - .filter((person) => !person.isHidden) - .map((person) => mapPerson(person)) - ); + + console.log(withHidden); return ( [...named, ...unnamed] // with thumbnails .filter((person) => !!person.thumbnailPath) + .filter((person) => !withHidden || !person.isHidden) .map((person) => mapPerson(person)) ); } diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 2e942df4d426a..a1143a0074307 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -29,17 +29,22 @@ export class PersonController { @Get() getAllPeople( @AuthUser() authUser: AuthUserDto, - @Query('areHidden') areHidden: boolean, + @Query('withHidden') withHidden: boolean, ): Promise { - return this.service.getAll(authUser, areHidden); + return this.service.getAll(authUser, withHidden); } - @Get('/personById/:id') + @Get('statistics') + getPersonStats(@AuthUser() authUser: AuthUserDto): Promise { + return this.service.getPersonCount(authUser); + } + + @Get(':id') getPerson(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getById(authUser, id); } - @Put('/personById/:id') + @Put(':id') updatePerson( @AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @@ -48,12 +53,7 @@ export class PersonController { return this.service.update(authUser, id, dto); } - @Get('/count') - getPersonCount(@AuthUser() authUser: AuthUserDto): Promise { - return this.service.getPersonCount(authUser); - } - - @Get('/personById/:id/thumbnail') + @Get(':id/thumbnail') @ApiOkResponse({ content: { 'image/jpeg': { schema: { type: 'string', format: 'binary' } }, @@ -63,12 +63,12 @@ export class PersonController { return this.service.getThumbnail(authUser, id).then(asStreamableFile); } - @Get('/personById/:id/assets') + @Get(':id/assets') getPersonAssets(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getAssets(authUser, id); } - @Post('/personById/:id/merge') + @Post(':id/merge') mergePerson( @AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, diff --git a/web/src/api/api.ts b/web/src/api/api.ts index e6efa76ceb240..22f998972143e 100644 --- a/web/src/api/api.ts +++ b/web/src/api/api.ts @@ -100,7 +100,7 @@ export class ImmichApi { } public getPeopleThumbnailUrl(personId: string) { - const path = `/person/personById/${personId}/thumbnail`; + const path = `/person/${personId}/thumbnail`; return this.createUrl(path); } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index b6480318f89a3..c8e49463dcaa3 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -8735,13 +8735,13 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio return { /** * - * @param {boolean} areHidden + * @param {boolean} withHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople: async (areHidden: boolean, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'areHidden' is not null or undefined - assertParamExists('getAllPeople', 'areHidden', areHidden) + getAllPeople: async (withHidden: boolean, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'withHidden' is not null or undefined + assertParamExists('getAllPeople', 'withHidden', withHidden) const localVarPath = `/person`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -8763,8 +8763,8 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) - if (areHidden !== undefined) { - localVarQueryParameter['areHidden'] = areHidden; + if (withHidden !== undefined) { + localVarQueryParameter['withHidden'] = withHidden; } @@ -8787,7 +8787,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPerson: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPerson', 'id', id) - const localVarPath = `/person/personById/{id}` + const localVarPath = `/person/{id}` .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); @@ -8829,7 +8829,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonAssets: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonAssets', 'id', id) - const localVarPath = `/person/personById/{id}/assets` + const localVarPath = `/person/{id}/assets` .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); @@ -8867,8 +8867,8 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPersonCount: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/person/count`; + getPersonStats: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/person/statistics`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -8909,7 +8909,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio getPersonThumbnail: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined assertParamExists('getPersonThumbnail', 'id', id) - const localVarPath = `/person/personById/{id}/thumbnail` + const localVarPath = `/person/{id}/thumbnail` .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); @@ -8954,7 +8954,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('mergePerson', 'id', id) // verify required parameter 'mergePersonDto' is not null or undefined assertParamExists('mergePerson', 'mergePersonDto', mergePersonDto) - const localVarPath = `/person/personById/{id}/merge` + const localVarPath = `/person/{id}/merge` .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); @@ -9002,7 +9002,7 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio assertParamExists('updatePerson', 'id', id) // verify required parameter 'personUpdateDto' is not null or undefined assertParamExists('updatePerson', 'personUpdateDto', personUpdateDto) - const localVarPath = `/person/personById/{id}` + const localVarPath = `/person/{id}` .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); @@ -9050,12 +9050,12 @@ export const PersonApiFp = function(configuration?: Configuration) { return { /** * - * @param {boolean} areHidden + * @param {boolean} withHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(areHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(areHidden, options); + async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -9083,8 +9083,8 @@ export const PersonApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getPersonCount(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonCount(options); + async getPersonStats(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonStats(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -9131,12 +9131,12 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat return { /** * - * @param {boolean} areHidden + * @param {boolean} withHidden * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(areHidden: boolean, options?: any): AxiosPromise> { - return localVarFp.getAllPeople(areHidden, options).then((request) => request(axios, basePath)); + getAllPeople(withHidden: boolean, options?: any): AxiosPromise> { + return localVarFp.getAllPeople(withHidden, options).then((request) => request(axios, basePath)); }, /** * @@ -9161,8 +9161,8 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPersonCount(options?: any): AxiosPromise { - return localVarFp.getPersonCount(options).then((request) => request(axios, basePath)); + getPersonStats(options?: any): AxiosPromise { + return localVarFp.getPersonStats(options).then((request) => request(axios, basePath)); }, /** * @@ -9207,7 +9207,7 @@ export interface PersonApiGetAllPeopleRequest { * @type {boolean} * @memberof PersonApiGetAllPeople */ - readonly areHidden: boolean + readonly withHidden: boolean } /** @@ -9309,7 +9309,7 @@ export class PersonApi extends BaseAPI { * @memberof PersonApi */ public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getAllPeople(requestParameters.areHidden, options).then((request) => request(this.axios, this.basePath)); + return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); } /** @@ -9340,8 +9340,8 @@ export class PersonApi extends BaseAPI { * @throws {RequiredError} * @memberof PersonApi */ - public getPersonCount(options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getPersonCount(options).then((request) => request(this.axios, this.basePath)); + public getPersonStats(options?: AxiosRequestConfig) { + return PersonApiFp(this.configuration).getPersonStats(options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/lib/components/faces-page/merge-face-selector.svelte b/web/src/lib/components/faces-page/merge-face-selector.svelte index bef491757138a..47bee767a1b04 100644 --- a/web/src/lib/components/faces-page/merge-face-selector.svelte +++ b/web/src/lib/components/faces-page/merge-face-selector.svelte @@ -25,7 +25,7 @@ $: unselectedPeople = people.filter((source) => !selectedPeople.includes(source) && source.id !== person.id); onMount(async () => { - const { data } = await api.personApi.getAllPeople({ areHidden: true }); + const { data } = await api.personApi.getAllPeople({ withHidden: true }); people = data; }); diff --git a/web/src/routes/(user)/explore/+page.server.ts b/web/src/routes/(user)/explore/+page.server.ts index 82de38fc21ada..08f21ba8437ef 100644 --- a/web/src/routes/(user)/explore/+page.server.ts +++ b/web/src/routes/(user)/explore/+page.server.ts @@ -9,13 +9,11 @@ export const load = (async ({ locals, parent }) => { } const { data: items } = await locals.api.searchApi.getExploreData(); - const { data: people } = await locals.api.personApi.getAllPeople({ areHidden: true }); - const { data: countpeople } = await locals.api.personApi.getPersonCount(); + const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true }); return { user, items, people, - countpeople, meta: { title: 'Explore', }, diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index 9cfc21364cc33..bcf2f45ac5ae7 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -3,7 +3,7 @@ import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import { AppRoute } from '$lib/constants'; - import { AssetTypeEnum, SearchExploreResponseDto, api } from '@api'; + import { AssetTypeEnum, PersonCountResponseDto, PersonResponseDto, SearchExploreResponseDto, api } from '@api'; import ClockOutline from 'svelte-material-icons/ClockOutline.svelte'; import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte'; import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte'; @@ -27,14 +27,24 @@ $: things = getFieldItems(data.items, Field.OBJECTS); $: places = getFieldItems(data.items, Field.CITY); $: people = data.people.slice(0, MAX_ITEMS); + + let countpeople: PersonCountResponseDto = countIsHiddenStatus(data.people); + + function countIsHiddenStatus(persons: PersonResponseDto[]): PersonCountResponseDto { + return { + total: persons.length, + hidden: persons.filter((person) => person.isHidden === true).length, + visible: persons.filter((person) => person.isHidden === false).length, + }; + } - {#if data.countpeople.total > 0} + {#if countpeople.total > 0}

People

- {#if data.countpeople.total > 0} + {#if countpeople.total > 0}
diff --git a/web/src/routes/(user)/people/+page.server.ts b/web/src/routes/(user)/people/+page.server.ts index d4ebe4ee4c64b..de5354d065b6b 100644 --- a/web/src/routes/(user)/people/+page.server.ts +++ b/web/src/routes/(user)/people/+page.server.ts @@ -8,12 +8,10 @@ export const load = (async ({ locals, parent }) => { throw redirect(302, AppRoute.AUTH_LOGIN); } - const { data: people } = await locals.api.personApi.getAllPeople({ areHidden: false }); - const { data: countpeople } = await locals.api.personApi.getPersonCount(); + const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true }); return { user, people, - countpeople, meta: { title: 'People', }, diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 0c027008bb37d..bba46bc03aded 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -16,7 +16,15 @@ let initialHiddenValues: boolean[] = data.people.map((person: PersonResponseDto) => person.isHidden); // Get number of hidden and visible people - let countpeople: PersonCountResponseDto = data.countpeople; + let countpeople: PersonCountResponseDto = countIsHiddenStatus(data.people); + + function countIsHiddenStatus(persons: PersonResponseDto[]): PersonCountResponseDto { + return { + total: persons.length, + hidden: persons.filter((obj) => obj.isHidden === true).length, + visible: persons.filter((obj) => obj.isHidden === false).length, + }; + } const handleDoneClick = async () => { try { From edbe146600661d10a22af45d64eca46b07e31123 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:10:36 +0200 Subject: [PATCH 11/28] fix: get unhidden faces --- mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api_client.dart | 168 +++++------------- web/src/routes/(user)/explore/+page.server.ts | 2 +- web/src/routes/(user)/explore/+page.svelte | 16 +- web/src/routes/(user)/people/+page.server.ts | 2 +- web/src/routes/(user)/people/+page.svelte | 51 +++--- 6 files changed, 88 insertions(+), 152 deletions(-) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index a892f4b6dfd50..fa269028641a0 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -151,6 +151,7 @@ part 'model/user_response_dto.dart'; part 'model/validate_access_token_response_dto.dart'; part 'model/video_codec.dart'; + const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'}; const _dateEpochMarker = 'epoch'; final _dateFormatter = DateFormat('yyyy-MM-dd'); diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index eb8d399e901cf..e1430cfa0d37b 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -11,10 +11,7 @@ part of openapi.api; class ApiClient { - ApiClient({ - this.basePath = '/api', - this.authentication, - }); + ApiClient({this.basePath = '/api', this.authentication,}); final String basePath; final Authentication? authentication; @@ -35,7 +32,7 @@ class ApiClient { Map get defaultHeaderMap => _defaultHeaderMap; void addDefaultHeader(String key, String value) { - _defaultHeaderMap[key] = value; + _defaultHeaderMap[key] = value; } // We don't use a Map for queryParams. @@ -57,26 +54,25 @@ class ApiClient { } final urlEncodedQueryParams = queryParams.map((param) => '$param'); - final queryString = urlEncodedQueryParams.isNotEmpty - ? '?${urlEncodedQueryParams.join('&')}' - : ''; + final queryString = urlEncodedQueryParams.isNotEmpty ? '?${urlEncodedQueryParams.join('&')}' : ''; final uri = Uri.parse('$basePath$path$queryString'); try { // Special case for uploading a single file which isn't a 'multipart/form-data'. - if (body is MultipartFile && - (contentType == null || - !contentType.toLowerCase().startsWith('multipart/form-data'))) { + if ( + body is MultipartFile && (contentType == null || + !contentType.toLowerCase().startsWith('multipart/form-data')) + ) { final request = StreamedRequest(method, uri); request.headers.addAll(headerParams); request.contentLength = body.length; body.finalize().listen( - request.sink.add, - onDone: request.sink.close, - // ignore: avoid_types_on_closure_parameters - onError: (Object error, StackTrace trace) => request.sink.close(), - cancelOnError: true, - ); + request.sink.add, + onDone: request.sink.close, + // ignore: avoid_types_on_closure_parameters + onError: (Object error, StackTrace trace) => request.sink.close(), + cancelOnError: true, + ); final response = await _client.send(request); return Response.fromStream(response); } @@ -92,45 +88,17 @@ class ApiClient { } final msgBody = contentType == 'application/x-www-form-urlencoded' - ? formParams - : await serializeAsync(body); + ? formParams + : await serializeAsync(body); final nullableHeaderParams = headerParams.isEmpty ? null : headerParams; - switch (method) { - case 'POST': - return await _client.post( - uri, - headers: nullableHeaderParams, - body: msgBody, - ); - case 'PUT': - return await _client.put( - uri, - headers: nullableHeaderParams, - body: msgBody, - ); - case 'DELETE': - return await _client.delete( - uri, - headers: nullableHeaderParams, - body: msgBody, - ); - case 'PATCH': - return await _client.patch( - uri, - headers: nullableHeaderParams, - body: msgBody, - ); - case 'HEAD': - return await _client.head( - uri, - headers: nullableHeaderParams, - ); - case 'GET': - return await _client.get( - uri, - headers: nullableHeaderParams, - ); + switch(method) { + case 'POST': return await _client.post(uri, headers: nullableHeaderParams, body: msgBody,); + case 'PUT': return await _client.put(uri, headers: nullableHeaderParams, body: msgBody,); + case 'DELETE': return await _client.delete(uri, headers: nullableHeaderParams, body: msgBody,); + case 'PATCH': return await _client.patch(uri, headers: nullableHeaderParams, body: msgBody,); + case 'HEAD': return await _client.head(uri, headers: nullableHeaderParams,); + case 'GET': return await _client.get(uri, headers: nullableHeaderParams,); } } on SocketException catch (error, trace) { throw ApiException.withInner( @@ -175,42 +143,28 @@ class ApiClient { ); } - Future deserializeAsync( - String json, - String targetType, { - bool growable = false, - }) => - // ignore: deprecated_member_use_from_same_package - deserialize(json, targetType, growable: growable); + Future deserializeAsync(String json, String targetType, {bool growable = false,}) => + // ignore: deprecated_member_use_from_same_package + deserialize(json, targetType, growable: growable); - @Deprecated( - 'Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.') - Future deserialize( - String json, - String targetType, { - bool growable = false, - }) async { + @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.') + Future deserialize(String json, String targetType, {bool growable = false,}) async { // Remove all spaces. Necessary for regular expressions as well. - targetType = - targetType.replaceAll(' ', ''); // ignore: parameter_assignments + targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments // If the expected target type is String, nothing to do... return targetType == 'String' - ? json - : _deserialize( - await compute((String j) => jsonDecode(j), json), targetType, - growable: growable); + ? json + : _deserialize(await compute((String j) => jsonDecode(j), json), targetType, growable: growable); } // ignore: deprecated_member_use_from_same_package Future serializeAsync(Object? value) async => serialize(value); - @Deprecated( - 'Scheduled for removal in OpenAPI Generator 6.x. Use serializeAsync() instead.') + @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use serializeAsync() instead.') String serialize(Object? value) => value == null ? '' : json.encode(value); - static dynamic _deserialize(dynamic value, String targetType, - {bool growable = false}) { + static dynamic _deserialize(dynamic value, String targetType, {bool growable = false}) { try { switch (targetType) { case 'String': @@ -443,50 +397,27 @@ class ApiClient { return VideoCodecTypeTransformer().decode(value); default: dynamic match; - if (value is List && - (match = _regList.firstMatch(targetType)?.group(1)) != null) { + if (value is List && (match = _regList.firstMatch(targetType)?.group(1)) != null) { return value - .map((dynamic v) => _deserialize( - v, - match, - growable: growable, - )) - .toList(growable: growable); + .map((dynamic v) => _deserialize(v, match, growable: growable,)) + .toList(growable: growable); } - if (value is Set && - (match = _regSet.firstMatch(targetType)?.group(1)) != null) { + if (value is Set && (match = _regSet.firstMatch(targetType)?.group(1)) != null) { return value - .map((dynamic v) => _deserialize( - v, - match, - growable: growable, - )) - .toSet(); + .map((dynamic v) => _deserialize(v, match, growable: growable,)) + .toSet(); } - if (value is Map && - (match = _regMap.firstMatch(targetType)?.group(1)) != null) { + if (value is Map && (match = _regMap.firstMatch(targetType)?.group(1)) != null) { return Map.fromIterables( value.keys.cast(), - value.values.map((dynamic v) => _deserialize( - v, - match, - growable: growable, - )), + value.values.map((dynamic v) => _deserialize(v, match, growable: growable,)), ); } } } on Exception catch (error, trace) { - throw ApiException.withInner( - HttpStatus.internalServerError, - 'Exception during deserialization.', - error, - trace, - ); + throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', error, trace,); } - throw ApiException( - HttpStatus.internalServerError, - 'Could not find a suitable class for deserialization', - ); + throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization',); } } @@ -515,14 +446,13 @@ Future deserializeAsync(DeserializationMessage message) async { // If the expected target type is String, nothing to do... return targetType == 'String' - ? message.json - : ApiClient._deserialize( - jsonDecode(message.json), - targetType, - growable: message.growable, - ); + ? message.json + : ApiClient._deserialize( + jsonDecode(message.json), + targetType, + growable: message.growable, + ); } /// Primarily intended for use in an isolate. -Future serializeAsync(Object? value) async => - value == null ? '' : json.encode(value); +Future serializeAsync(Object? value) async => value == null ? '' : json.encode(value); diff --git a/web/src/routes/(user)/explore/+page.server.ts b/web/src/routes/(user)/explore/+page.server.ts index 08f21ba8437ef..809b5822e412a 100644 --- a/web/src/routes/(user)/explore/+page.server.ts +++ b/web/src/routes/(user)/explore/+page.server.ts @@ -9,7 +9,7 @@ export const load = (async ({ locals, parent }) => { } const { data: items } = await locals.api.searchApi.getExploreData(); - const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true }); + const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: false }); return { user, items, diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index bcf2f45ac5ae7..a720ce6a10449 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -28,15 +28,11 @@ $: places = getFieldItems(data.items, Field.CITY); $: people = data.people.slice(0, MAX_ITEMS); - let countpeople: PersonCountResponseDto = countIsHiddenStatus(data.people); - - function countIsHiddenStatus(persons: PersonResponseDto[]): PersonCountResponseDto { - return { - total: persons.length, - hidden: persons.filter((person) => person.isHidden === true).length, - visible: persons.filter((person) => person.isHidden === false).length, - }; - } + let countpeople: PersonCountResponseDto = { + total: data.people.length, + hidden: data.people.filter((obj) => obj.isHidden === true).length, + visible: data.people.filter((obj) => obj.isHidden === false).length, + }; @@ -54,7 +50,7 @@
{#each people as person (person.id)} - {#if person.isHidden} + {#if !person.isHidden} { throw redirect(302, AppRoute.AUTH_LOGIN); } - const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true }); + const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: false }); return { user, people, diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index bba46bc03aded..5b661ff7d7c9d 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -13,50 +13,59 @@ export let data: PageData; let selecthidden = false; let changeCounter = 0; - let initialHiddenValues: boolean[] = data.people.map((person: PersonResponseDto) => person.isHidden); + let initialHiddenValues: Record = {}; - // Get number of hidden and visible people - let countpeople: PersonCountResponseDto = countIsHiddenStatus(data.people); + data.people.forEach((person: PersonResponseDto) => { + initialHiddenValues[person.id] = person.isHidden; + }); - function countIsHiddenStatus(persons: PersonResponseDto[]): PersonCountResponseDto { - return { - total: persons.length, - hidden: persons.filter((obj) => obj.isHidden === true).length, - visible: persons.filter((obj) => obj.isHidden === false).length, - }; - } + // Get number of hidden and visible people + let countpeople: PersonCountResponseDto = { + total: data.people.length, + hidden: data.people.filter((obj) => obj.isHidden === true).length, + visible: data.people.filter((obj) => obj.isHidden === false).length, + }; const handleDoneClick = async () => { try { // Reset the counter before checking changes - changeCounter = 0; + let changeCounter = 0; + + // Check if the visibility for each person has been changed + for (const person of data.people) { + const index = person.id; + const initialHiddenValue = initialHiddenValues[index]; - // Check if the visibility for each persons has been changed - data.people.forEach(async (person: PersonResponseDto, index: number) => { - if (person.isHidden !== initialHiddenValues[index]) { + if (person.isHidden !== initialHiddenValue) { changeCounter++; await api.personApi.updatePerson({ id: person.id, personUpdateDto: { isHidden: person.isHidden }, }); - // Keeps track of the number of hidden/visible people, it helps to know whether to show "no people" or not - countpeople.hidden += person.isHidden ? 1 : -1; - countpeople.visible += person.isHidden ? -1 : 1; - // Update the initial hidden values initialHiddenValues[index] = person.isHidden; + + // Update the count of hidden/visible people + if (person.isHidden) { + countpeople.hidden++; + countpeople.visible--; + } else { + countpeople.hidden--; + countpeople.visible++; + } } - }); - if (changeCounter > 0) + } + + if (changeCounter > 0) { notificationController.show({ type: NotificationType.Info, message: `Visibility changed for ${changeCounter} person${changeCounter <= 1 ? '' : 's'}`, }); + } } catch (error) { handleError(error, `Unable to change the visibility for ${changeCounter} person${changeCounter <= 1 ? '' : 's'}`); } - changeCounter = 0; }; From 90a945be4f311f371e765e0bddaa7ec92ea7a24c Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 16:12:34 +0200 Subject: [PATCH 12/28] fix: do not use PersonCountResponseDto --- .../layouts/user-page-layout.svelte | 14 ++++----- web/src/routes/(user)/explore/+page.svelte | 12 ++------ web/src/routes/(user)/people/+page.svelte | 29 +++++++------------ 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 182d21c6f551f..260a96af7b4b8 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -5,25 +5,25 @@ import EyeOutline from 'svelte-material-icons/EyeOutline.svelte'; import IconButton from '../elements/buttons/icon-button.svelte'; import { fly } from 'svelte/transition'; - import type { PersonCountResponseDto, UserResponseDto } from '@api'; + import type { UserResponseDto } from '@api'; import { createEventDispatcher } from 'svelte'; export let user: UserResponseDto; export let hideNavbar = false; export let showUploadButton = false; export let title: string | undefined = undefined; - export let selecthidden = false; + export let selectHidden = false; export let fullscreen = false; - export let countpeople: PersonCountResponseDto | undefined = undefined; + export let countTotalPerson: number | undefined = undefined; const dispatch = createEventDispatcher(); const handleDoneClick = () => { - selecthidden = !selecthidden; + selectHidden = !selectHidden; dispatch('doneClick'); }; -{#if !selecthidden} +{#if !selectHidden}
{#if !hideNavbar} openFileUploadDialog()} /> @@ -44,8 +44,8 @@ class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16" >

{title}

- {#if fullscreen && countpeople && countpeople.total > 0} - (selecthidden = !selecthidden)}> + {#if fullscreen && countTotalPerson && countTotalPerson > 0} + (selectHidden = !selectHidden)}>

Show & hide faces

diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index a720ce6a10449..87343d50e1c40 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -3,7 +3,7 @@ import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import { AppRoute } from '$lib/constants'; - import { AssetTypeEnum, PersonCountResponseDto, PersonResponseDto, SearchExploreResponseDto, api } from '@api'; + import { AssetTypeEnum, SearchExploreResponseDto, api } from '@api'; import ClockOutline from 'svelte-material-icons/ClockOutline.svelte'; import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte'; import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte'; @@ -27,20 +27,14 @@ $: things = getFieldItems(data.items, Field.OBJECTS); $: places = getFieldItems(data.items, Field.CITY); $: people = data.people.slice(0, MAX_ITEMS); - - let countpeople: PersonCountResponseDto = { - total: data.people.length, - hidden: data.people.filter((obj) => obj.isHidden === true).length, - visible: data.people.filter((obj) => obj.isHidden === false).length, - }; - {#if countpeople.total > 0} + {#if data.people.length > 0}

People

- {#if countpeople.total > 0} + {#if data.people.length > 0}
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; - import { PersonResponseDto, PersonCountResponseDto, api } from '@api'; + import { PersonResponseDto, api } from '@api'; import AccountOff from 'svelte-material-icons/AccountOff.svelte'; import type { PageData } from './$types'; import { handleError } from '$lib/utils/handle-error'; @@ -11,7 +11,7 @@ } from '$lib/components/shared-components/notification/notification'; export let data: PageData; - let selecthidden = false; + let selectHidden = false; let changeCounter = 0; let initialHiddenValues: Record = {}; @@ -19,12 +19,9 @@ initialHiddenValues[person.id] = person.isHidden; }); - // Get number of hidden and visible people - let countpeople: PersonCountResponseDto = { - total: data.people.length, - hidden: data.people.filter((obj) => obj.isHidden === true).length, - visible: data.people.filter((obj) => obj.isHidden === false).length, - }; + // Get number of person and visible persons + let countTotalPerson = data.people.length; + let countVisiblePersons = data.people.filter((person: PersonResponseDto) => person.isHidden === true).length; const handleDoneClick = async () => { try { @@ -47,13 +44,7 @@ initialHiddenValues[index] = person.isHidden; // Update the count of hidden/visible people - if (person.isHidden) { - countpeople.hidden++; - countpeople.visible--; - } else { - countpeople.hidden--; - countpeople.visible++; - } + countVisiblePersons += person.isHidden ? -1 : 1; } } @@ -73,13 +64,13 @@ user={data.user} fullscreen={true} showUploadButton - bind:countpeople - bind:selecthidden + bind:countTotalPerson + bind:selectHidden on:doneClick={handleDoneClick} title="People" > - {#if countpeople.visible > 0 || selecthidden} - {#if !selecthidden} + {#if countVisiblePersons > 0 || selectHidden} + {#if !selectHidden}
{#each data.people as person (person.id)} From 388b995e342b534456da4ea336dca00e8e8d6a68 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 17:07:02 +0200 Subject: [PATCH 13/28] fix: transition --- .../layouts/user-page-layout.svelte | 86 +++++++++---------- web/src/routes/(user)/people/+page.svelte | 2 +- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 260a96af7b4b8..8b58341d17e51 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -7,6 +7,7 @@ import { fly } from 'svelte/transition'; import type { UserResponseDto } from '@api'; import { createEventDispatcher } from 'svelte'; + import { quintOut } from 'svelte/easing'; export let user: UserResponseDto; export let hideNavbar = false; @@ -23,51 +24,48 @@ }; -{#if !selectHidden} -
- {#if !hideNavbar} - openFileUploadDialog()} /> - {/if} +
+ {#if !hideNavbar} + openFileUploadDialog()} /> + {/if} - -
-
- - - - - {#if title} -
-
-

{title}

- {#if fullscreen && countTotalPerson && countTotalPerson > 0} - (selectHidden = !selectHidden)}> -
- -

Show & hide faces

-
-
- {/if} - -
+ +
+
+ + + + + {#if title} +
+
+

{title}

+ {#if fullscreen && countTotalPerson && countTotalPerson > 0} + (selectHidden = !selectHidden)}> +
+ +

Show & hide faces

+
+
+ {/if} + +
-
- -
-
- {/if} -
-
-{:else} -
+ +
+ + {/if} + + +{#if selectHidden} +
- + {/if} diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 1c30eff43a4ca..467d5f9637e62 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -21,7 +21,7 @@ // Get number of person and visible persons let countTotalPerson = data.people.length; - let countVisiblePersons = data.people.filter((person: PersonResponseDto) => person.isHidden === true).length; + let countVisiblePersons = data.people.filter((person: PersonResponseDto) => person.isHidden === false).length; const handleDoneClick = async () => { try { From 95a1f8a8d5cb5ece0c24b9eee23a502796f7e299 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 18:05:35 +0200 Subject: [PATCH 14/28] pr feedback --- cli/src/api/open-api/api.ts | 29 ++++- .../search/services/person.service.dart | 3 +- mobile/openapi/.openapi-generator/FILES | 3 + mobile/openapi/README.md | 1 + mobile/openapi/doc/PeopleResponseDto.md | 17 +++ mobile/openapi/doc/PersonApi.md | 4 +- mobile/openapi/lib/api.dart | 1 + mobile/openapi/lib/api/person_api.dart | 9 +- mobile/openapi/lib/api_client.dart | 2 + .../lib/model/people_response_dto.dart | 114 ++++++++++++++++++ .../test/people_response_dto_test.dart | 37 ++++++ mobile/openapi/test/person_api_test.dart | 2 +- server/immich-openapi-specs.json | 27 ++++- .../asset/response-dto/asset-response.dto.ts | 2 +- server/src/domain/person/person.dto.ts | 9 +- server/src/domain/person/person.service.ts | 28 +++-- .../src/immich/api-v1/asset/asset.service.ts | 8 +- .../immich/controllers/person.controller.ts | 3 +- web/src/api/open-api/api.ts | 29 ++++- web/src/routes/(user)/explore/+page.svelte | 6 +- web/src/routes/(user)/people/+page.server.ts | 2 +- web/src/routes/(user)/people/+page.svelte | 12 +- 22 files changed, 300 insertions(+), 48 deletions(-) create mode 100644 mobile/openapi/doc/PeopleResponseDto.md create mode 100644 mobile/openapi/lib/model/people_response_dto.dart create mode 100644 mobile/openapi/test/people_response_dto_test.dart diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index ca7a4359ac62e..d2a8f99171800 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -1777,6 +1777,31 @@ export interface OAuthConfigResponseDto { */ 'autoLaunch'?: boolean; } +/** + * + * @export + * @interface PeopleResponseDto + */ +export interface PeopleResponseDto { + /** + * + * @type {number} + * @memberof PeopleResponseDto + */ + 'total': number; + /** + * + * @type {number} + * @memberof PeopleResponseDto + */ + 'visible': number; + /** + * + * @type {Array} + * @memberof PeopleResponseDto + */ + 'people': Array; +} /** * * @export @@ -9000,7 +9025,7 @@ export const PersonApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9081,7 +9106,7 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig): AxiosPromise> { + getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath)); }, /** diff --git a/mobile/lib/modules/search/services/person.service.dart b/mobile/lib/modules/search/services/person.service.dart index 2ac7d090c118a..8faafa91411a9 100644 --- a/mobile/lib/modules/search/services/person.service.dart +++ b/mobile/lib/modules/search/services/person.service.dart @@ -18,7 +18,8 @@ class PersonService { Future?> getCuratedPeople() async { try { - return await _apiService.personApi.getAllPeople(false); + final peopleResponseDto = await _apiService.personApi.getAllPeople(false); + return peopleResponseDto?.people; } catch (e) { debugPrint("Error [getCuratedPeople] ${e.toString()}"); return null; diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 2848e5dd79240..5045f5728d511 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -71,6 +71,7 @@ doc/OAuthCallbackDto.md doc/OAuthConfigDto.md doc/OAuthConfigResponseDto.md doc/PartnerApi.md +doc/PeopleResponseDto.md doc/PersonApi.md doc/PersonCountResponseDto.md doc/PersonResponseDto.md @@ -209,6 +210,7 @@ lib/model/merge_person_dto.dart lib/model/o_auth_callback_dto.dart lib/model/o_auth_config_dto.dart lib/model/o_auth_config_response_dto.dart +lib/model/people_response_dto.dart lib/model/person_count_response_dto.dart lib/model/person_response_dto.dart lib/model/person_update_dto.dart @@ -324,6 +326,7 @@ test/o_auth_callback_dto_test.dart test/o_auth_config_dto_test.dart test/o_auth_config_response_dto_test.dart test/partner_api_test.dart +test/people_response_dto_test.dart test/person_api_test.dart test/person_count_response_dto_test.dart test/person_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 251f7fc839ee4..20c7bea5d743e 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -239,6 +239,7 @@ Class | Method | HTTP request | Description - [OAuthCallbackDto](doc//OAuthCallbackDto.md) - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) + - [PeopleResponseDto](doc//PeopleResponseDto.md) - [PersonCountResponseDto](doc//PersonCountResponseDto.md) - [PersonResponseDto](doc//PersonResponseDto.md) - [PersonUpdateDto](doc//PersonUpdateDto.md) diff --git a/mobile/openapi/doc/PeopleResponseDto.md b/mobile/openapi/doc/PeopleResponseDto.md new file mode 100644 index 0000000000000..9d00d8608cd13 --- /dev/null +++ b/mobile/openapi/doc/PeopleResponseDto.md @@ -0,0 +1,17 @@ +# openapi.model.PeopleResponseDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**total** | **num** | | +**visible** | **num** | | +**people** | [**List**](PersonResponseDto.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) + + diff --git a/mobile/openapi/doc/PersonApi.md b/mobile/openapi/doc/PersonApi.md index c1954459a603d..4833aaeae5804 100644 --- a/mobile/openapi/doc/PersonApi.md +++ b/mobile/openapi/doc/PersonApi.md @@ -19,7 +19,7 @@ Method | HTTP request | Description # **getAllPeople** -> List getAllPeople(withHidden) +> PeopleResponseDto getAllPeople(withHidden) @@ -60,7 +60,7 @@ Name | Type | Description | Notes ### Return type -[**List**](PersonResponseDto.md) +[**PeopleResponseDto**](PeopleResponseDto.md) ### Authorization diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index fa269028641a0..627232e29c36a 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -104,6 +104,7 @@ part 'model/merge_person_dto.dart'; part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/o_auth_config_response_dto.dart'; +part 'model/people_response_dto.dart'; part 'model/person_count_response_dto.dart'; part 'model/person_response_dto.dart'; part 'model/person_update_dto.dart'; diff --git a/mobile/openapi/lib/api/person_api.dart b/mobile/openapi/lib/api/person_api.dart index 62185eff467e4..e67c3bc139903 100644 --- a/mobile/openapi/lib/api/person_api.dart +++ b/mobile/openapi/lib/api/person_api.dart @@ -50,7 +50,7 @@ class PersonApi { /// Parameters: /// /// * [bool] withHidden (required): - Future?> getAllPeople(bool withHidden,) async { + Future getAllPeople(bool withHidden,) async { final response = await getAllPeopleWithHttpInfo(withHidden,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); @@ -59,11 +59,8 @@ class PersonApi { // 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') as List) - .cast() - .toList(); - + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PeopleResponseDto',) as PeopleResponseDto; + } return null; } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index e1430cfa0d37b..522e77f68a522 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -303,6 +303,8 @@ class ApiClient { return OAuthConfigDto.fromJson(value); case 'OAuthConfigResponseDto': return OAuthConfigResponseDto.fromJson(value); + case 'PeopleResponseDto': + return PeopleResponseDto.fromJson(value); case 'PersonCountResponseDto': return PersonCountResponseDto.fromJson(value); case 'PersonResponseDto': diff --git a/mobile/openapi/lib/model/people_response_dto.dart b/mobile/openapi/lib/model/people_response_dto.dart new file mode 100644 index 0000000000000..9470a827ebf5a --- /dev/null +++ b/mobile/openapi/lib/model/people_response_dto.dart @@ -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 PeopleResponseDto { + /// Returns a new [PeopleResponseDto] instance. + PeopleResponseDto({ + required this.total, + required this.visible, + this.people = const [], + }); + + num total; + + num visible; + + List people; + + @override + bool operator ==(Object other) => identical(this, other) || other is PeopleResponseDto && + other.total == total && + other.visible == visible && + other.people == people; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (total.hashCode) + + (visible.hashCode) + + (people.hashCode); + + @override + String toString() => 'PeopleResponseDto[total=$total, visible=$visible, people=$people]'; + + Map toJson() { + final json = {}; + json[r'total'] = this.total; + json[r'visible'] = this.visible; + json[r'people'] = this.people; + return json; + } + + /// Returns a new [PeopleResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PeopleResponseDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return PeopleResponseDto( + total: num.parse('${json[r'total']}'), + visible: num.parse('${json[r'visible']}'), + people: PersonResponseDto.listFromJson(json[r'people']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PeopleResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PeopleResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PeopleResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PeopleResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'total', + 'visible', + 'people', + }; +} + diff --git a/mobile/openapi/test/people_response_dto_test.dart b/mobile/openapi/test/people_response_dto_test.dart new file mode 100644 index 0000000000000..c48f0099adf2e --- /dev/null +++ b/mobile/openapi/test/people_response_dto_test.dart @@ -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 PeopleResponseDto +void main() { + // final instance = PeopleResponseDto(); + + group('test PeopleResponseDto', () { + // num total + test('to test the property `total`', () async { + // TODO + }); + + // num visible + test('to test the property `visible`', () async { + // TODO + }); + + // List people (default value: const []) + test('to test the property `people`', () async { + // TODO + }); + + + }); + +} diff --git a/mobile/openapi/test/person_api_test.dart b/mobile/openapi/test/person_api_test.dart index 20abc170fab5f..8671155e62c50 100644 --- a/mobile/openapi/test/person_api_test.dart +++ b/mobile/openapi/test/person_api_test.dart @@ -17,7 +17,7 @@ void main() { // final instance = PersonApi(); group('tests for PersonApi', () { - //Future> getAllPeople(bool withHidden) async + //Future getAllPeople(bool withHidden) async test('test getAllPeople', () async { // TODO }); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 7bd6df381fac5..0eea743b13836 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2525,10 +2525,7 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PersonResponseDto" - } + "$ref": "#/components/schemas/PeopleResponseDto" } } } @@ -5918,6 +5915,28 @@ "passwordLoginEnabled" ] }, + "PeopleResponseDto": { + "type": "object", + "properties": { + "total": { + "type": "number" + }, + "visible": { + "type": "number" + }, + "people": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PersonResponseDto" + } + } + }, + "required": [ + "total", + "visible", + "people" + ] + }, "PersonCountResponseDto": { "type": "object", "properties": { diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index f5284f3908ac1..226cc77a9e20d 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -54,7 +54,7 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, livePhotoVideoId: entity.livePhotoVideoId, tags: entity.tags?.map(mapTag), - people: entity.faces?.map(mapFace), + people: entity.faces?.map(mapFace).filter((person) => !person.isHidden), checksum: entity.checksum.toString('base64'), }; } diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index 11e3dad58e8bd..815cbdbf50e14 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -1,7 +1,7 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsBoolean, IsOptional, IsString } from 'class-validator'; import { toBoolean, ValidateUUID } from '../domain.util'; export class PersonUpdateDto { @@ -49,6 +49,13 @@ export class PersonResponseDto { isHidden!: boolean; } +export class PeopleResponseDto { + total!: number; + visible!: number; + @IsArray() + people!: PersonResponseDto[]; +} + export function mapPerson(person: PersonEntity): PersonResponseDto { return { id: person.id, diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 2402e93229637..cedbcb84150aa 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -4,7 +4,14 @@ import { AuthUserDto } from '../auth'; import { mimeTypes } from '../domain.constant'; import { IJobRepository, JobName } from '../job'; import { ImmichReadStream, IStorageRepository } from '../storage'; -import { mapPerson, MergePersonDto, PersonCountResponseDto, PersonResponseDto, PersonUpdateDto } from './person.dto'; +import { + mapPerson, + MergePersonDto, + PeopleResponseDto, + PersonCountResponseDto, + PersonResponseDto, + PersonUpdateDto, +} from './person.dto'; import { IPersonRepository, UpdateFacesData } from './person.repository'; @Injectable() @@ -17,20 +24,21 @@ export class PersonService { @Inject(IJobRepository) private jobRepository: IJobRepository, ) {} - async getAll(authUser: AuthUserDto, withHidden: boolean): Promise { + async getAll(authUser: AuthUserDto, withHidden: boolean): Promise { const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); const named = people.filter((person) => !!person.name); const unnamed = people.filter((person) => !person.name); - console.log(withHidden); + const persons: PersonResponseDto[] = [...named, ...unnamed] + // with thumbnails + .filter((person) => !!person.thumbnailPath) + .map((person) => mapPerson(person)); - return ( - [...named, ...unnamed] - // with thumbnails - .filter((person) => !!person.thumbnailPath) - .filter((person) => !withHidden || !person.isHidden) - .map((person) => mapPerson(person)) - ); + return { + people: persons.filter((person) => withHidden || !person.isHidden), + total: persons.length, + visible: persons.filter((person: PersonResponseDto) => person.isHidden === false).length, + }; } getById(authUser: AuthUserDto, id: string): Promise { diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index 7076f43666231..61520a9b06402 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -209,13 +209,7 @@ export class AssetService { const asset = await this._assetRepository.getById(assetId); if (allowExif) { - const data = mapAsset(asset); - - if (data.people) { - data.people = data.people.filter((person) => !person.isHidden); - } - - return data; + return mapAsset(asset); } else { return mapAssetWithoutExif(asset); } diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index a1143a0074307..98863beb8bde7 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -4,6 +4,7 @@ import { BulkIdResponseDto, ImmichReadStream, MergePersonDto, + PeopleResponseDto, PersonCountResponseDto, PersonResponseDto, PersonService, @@ -30,7 +31,7 @@ export class PersonController { getAllPeople( @AuthUser() authUser: AuthUserDto, @Query('withHidden') withHidden: boolean, - ): Promise { + ): Promise { return this.service.getAll(authUser, withHidden); } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index f605ec2f5c2f6..49bc48b2cf6ea 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1777,6 +1777,31 @@ export interface OAuthConfigResponseDto { */ 'autoLaunch'?: boolean; } +/** + * + * @export + * @interface PeopleResponseDto + */ +export interface PeopleResponseDto { + /** + * + * @type {number} + * @memberof PeopleResponseDto + */ + 'total': number; + /** + * + * @type {number} + * @memberof PeopleResponseDto + */ + 'visible': number; + /** + * + * @type {Array} + * @memberof PeopleResponseDto + */ + 'people': Array; +} /** * * @export @@ -9044,7 +9069,7 @@ export const PersonApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9125,7 +9150,7 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(withHidden: boolean, options?: any): AxiosPromise> { + getAllPeople(withHidden: boolean, options?: any): AxiosPromise { return localVarFp.getAllPeople(withHidden, options).then((request) => request(axios, basePath)); }, /** diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index 87343d50e1c40..f12f8b9cb5eb1 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -26,15 +26,15 @@ $: things = getFieldItems(data.items, Field.OBJECTS); $: places = getFieldItems(data.items, Field.CITY); - $: people = data.people.slice(0, MAX_ITEMS); + $: people = data.people.people.slice(0, MAX_ITEMS); - {#if data.people.length > 0} + {#if data.people.total > 0}

People

- {#if data.people.length > 0} + {#if data.people.total > 0}
{ throw redirect(302, AppRoute.AUTH_LOGIN); } - const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: false }); + const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true }); return { user, people, diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 467d5f9637e62..49fae5c09640b 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -15,13 +15,13 @@ let changeCounter = 0; let initialHiddenValues: Record = {}; - data.people.forEach((person: PersonResponseDto) => { + data.people.people.forEach((person: PersonResponseDto) => { initialHiddenValues[person.id] = person.isHidden; }); // Get number of person and visible persons - let countTotalPerson = data.people.length; - let countVisiblePersons = data.people.filter((person: PersonResponseDto) => person.isHidden === false).length; + let countTotalPerson = data.people.total; + let countVisiblePersons = data.people.visible; const handleDoneClick = async () => { try { @@ -29,7 +29,7 @@ let changeCounter = 0; // Check if the visibility for each person has been changed - for (const person of data.people) { + for (const person of data.people.people) { const index = person.id; const initialHiddenValue = initialHiddenValues[index]; @@ -73,7 +73,7 @@ {#if !selectHidden}
- {#each data.people as person (person.id)} + {#each data.people.people as person (person.id)} {#if !person.isHidden}
@@ -101,7 +101,7 @@ {:else}
- {#each data.people as person (person.id)} + {#each data.people.people as person (person.id)}
From 0adcb7adad1fb27641c8b97c5faf605954db256b Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 22:32:41 +0200 Subject: [PATCH 17/28] add server tests --- server/src/domain/person/person.service.spec.ts | 15 +++++++++++++++ server/test/fixtures.ts | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 4de9d0623fccd..e99cdaa276482 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -116,6 +116,21 @@ describe(PersonService.name, () => { }); }); + it('should update a person visibility', async () => { + personMock.getById.mockResolvedValue(personStub.hidden); + personMock.update.mockResolvedValue(personStub.withName); + personMock.getAssets.mockResolvedValue([assetEntityStub.image]); + + await expect(sut.update(authStub.admin, 'person-1', { isHidden: false })).resolves.toEqual(responseDto); + + expect(personMock.getById).toHaveBeenCalledWith('admin_id', 'person-1'); + expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false }); + expect(jobMock.queue).toHaveBeenCalledWith({ + name: JobName.SEARCH_INDEX_ASSET, + data: { ids: [assetEntityStub.image.id] }, + }); + }); + it("should update a person's thumbnailPath", async () => { personMock.getById.mockResolvedValue(personStub.withName); personMock.getFaceById.mockResolvedValue(faceStub.face1); diff --git a/server/test/fixtures.ts b/server/test/fixtures.ts index 64b073138ab8b..b4abca792ed63 100644 --- a/server/test/fixtures.ts +++ b/server/test/fixtures.ts @@ -1096,6 +1096,17 @@ export const personStub = { faces: [], isHidden: false, }), + hidden: Object.freeze({ + id: 'person-1', + createdAt: new Date('2021-01-01'), + updatedAt: new Date('2021-01-01'), + ownerId: userEntityStub.admin.id, + owner: userEntityStub.admin, + name: '', + thumbnailPath: '/path/to/thumbnail.jpg', + faces: [], + isHidden: true, + }), withName: Object.freeze({ id: 'person-1', createdAt: new Date('2021-01-01'), From d04fbfba2b6c38d9f59772a3b358afa0e50979cc Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Sun, 16 Jul 2023 22:41:03 +0200 Subject: [PATCH 18/28] rename persons to people --- .../layouts/user-page-layout.svelte | 4 ++-- web/src/routes/(user)/people/+page.svelte | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 8b58341d17e51..f98984adbdf21 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -15,7 +15,7 @@ export let title: string | undefined = undefined; export let selectHidden = false; export let fullscreen = false; - export let countTotalPerson: number | undefined = undefined; + export let countTotalPeople: number | undefined = undefined; const dispatch = createEventDispatcher(); const handleDoneClick = () => { @@ -44,7 +44,7 @@ class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16" >

{title}

- {#if fullscreen && countTotalPerson && countTotalPerson > 0} + {#if fullscreen && countTotalPeople && countTotalPeople > 0} (selectHidden = !selectHidden)}>
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 49fae5c09640b..0f6ad192cd366 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -19,9 +19,9 @@ initialHiddenValues[person.id] = person.isHidden; }); - // Get number of person and visible persons - let countTotalPerson = data.people.total; - let countVisiblePersons = data.people.visible; + // Get number of person and visible people + let countTotalPeople = data.people.total; + let countVisiblePeople = data.people.visible; const handleDoneClick = async () => { try { @@ -44,18 +44,21 @@ initialHiddenValues[index] = person.isHidden; // Update the count of hidden/visible people - countVisiblePersons += person.isHidden ? -1 : 1; + countVisiblePeople += person.isHidden ? -1 : 1; } } if (changeCounter > 0) { notificationController.show({ type: NotificationType.Info, - message: `Visibility changed for ${changeCounter} person${changeCounter <= 1 ? '' : 's'}`, + message: `Visibility changed for ${changeCounter} ${changeCounter <= 1 ? 'person' : 'people'}`, }); } } catch (error) { - handleError(error, `Unable to change the visibility for ${changeCounter} person${changeCounter <= 1 ? '' : 's'}`); + handleError( + error, + `Unable to change the visibility for ${changeCounter} ${changeCounter <= 1 ? 'person' : 'people'}`, + ); } }; @@ -64,12 +67,12 @@ user={data.user} fullscreen={true} showUploadButton - bind:countTotalPerson + bind:countTotalPeople bind:selectHidden on:doneClick={handleDoneClick} title="People" > - {#if countVisiblePersons > 0 || selectHidden} + {#if countVisiblePeople > 0 || selectHidden} {#if !selectHidden}
From 405a95be76386bbcbbe780e406de69f9129d0e61 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Mon, 17 Jul 2023 00:48:17 +0200 Subject: [PATCH 19/28] feat: add exit button --- .../layouts/user-page-layout.svelte | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index f98984adbdf21..3dda706c093a0 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -3,11 +3,13 @@ import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte'; import SideBar from '../shared-components/side-bar/side-bar.svelte'; import EyeOutline from 'svelte-material-icons/EyeOutline.svelte'; + import Close from 'svelte-material-icons/Close.svelte'; import IconButton from '../elements/buttons/icon-button.svelte'; import { fly } from 'svelte/transition'; import type { UserResponseDto } from '@api'; import { createEventDispatcher } from 'svelte'; import { quintOut } from 'svelte/easing'; + import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; export let user: UserResponseDto; export let hideNavbar = false; @@ -68,16 +70,27 @@ class="absolute top-0 left-0 w-full h-full bg-immich-bg dark:bg-immich-dark-bg z-[9999]" >
-

Show & hide faces

- { - handleDoneClick(); - }} - > - Done - +
+ { + selectHidden = !selectHidden; + }} + /> +

Show & hide faces

+
+ +
+ { + handleDoneClick(); + }} + > + Done + +
From b3e5646c4a45324403622cdc39608f810034d84f Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:29:37 +0200 Subject: [PATCH 20/28] pr feedback --- cli/src/api/open-api/api.ts | 16 ++++++------- .../search/services/person.service.dart | 2 +- mobile/openapi/doc/PersonApi.md | 2 +- mobile/openapi/lib/api/person_api.dart | 12 ++++++---- mobile/openapi/test/person_api_test.dart | 2 +- server/immich-openapi-specs.json | 3 ++- server/src/domain/person/person.dto.ts | 8 +++---- server/src/domain/person/person.service.ts | 4 ++-- .../immich/controllers/person.controller.ts | 4 ++-- .../repositories/typesense.repository.ts | 3 ++- web/src/api/open-api/api.ts | 18 +++++++------- .../layouts/user-page-layout.svelte | 24 +++++++++---------- web/src/routes/(user)/people/+page.svelte | 1 - 13 files changed, 47 insertions(+), 52 deletions(-) diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index d2a8f99171800..272ff460e1dac 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -8706,13 +8706,11 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio return { /** * - * @param {boolean} withHidden + * @param {boolean} [withHidden] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople: async (withHidden: boolean, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'withHidden' is not null or undefined - assertParamExists('getAllPeople', 'withHidden', withHidden) + getAllPeople: async (withHidden?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/person`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -9021,11 +9019,11 @@ export const PersonApiFp = function(configuration?: Configuration) { return { /** * - * @param {boolean} withHidden + * @param {boolean} [withHidden] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getAllPeople(withHidden?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9106,7 +9104,7 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig): AxiosPromise { + getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.getAllPeople(requestParameters.withHidden, options).then((request) => request(axios, basePath)); }, /** @@ -9176,7 +9174,7 @@ export interface PersonApiGetAllPeopleRequest { * @type {boolean} * @memberof PersonApiGetAllPeople */ - readonly withHidden: boolean + readonly withHidden?: boolean } /** @@ -9277,7 +9275,7 @@ export class PersonApi extends BaseAPI { * @throws {RequiredError} * @memberof PersonApi */ - public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig) { + public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig) { return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); } diff --git a/mobile/lib/modules/search/services/person.service.dart b/mobile/lib/modules/search/services/person.service.dart index 8faafa91411a9..8314ed10964ae 100644 --- a/mobile/lib/modules/search/services/person.service.dart +++ b/mobile/lib/modules/search/services/person.service.dart @@ -18,7 +18,7 @@ class PersonService { Future?> getCuratedPeople() async { try { - final peopleResponseDto = await _apiService.personApi.getAllPeople(false); + final peopleResponseDto = await _apiService.personApi.getAllPeople(); return peopleResponseDto?.people; } catch (e) { debugPrint("Error [getCuratedPeople] ${e.toString()}"); diff --git a/mobile/openapi/doc/PersonApi.md b/mobile/openapi/doc/PersonApi.md index 4833aaeae5804..8cce9a8ae9469 100644 --- a/mobile/openapi/doc/PersonApi.md +++ b/mobile/openapi/doc/PersonApi.md @@ -56,7 +56,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **withHidden** | **bool**| | + **withHidden** | **bool**| | [optional] [default to false] ### Return type diff --git a/mobile/openapi/lib/api/person_api.dart b/mobile/openapi/lib/api/person_api.dart index e67c3bc139903..fb8a3d7c6d030 100644 --- a/mobile/openapi/lib/api/person_api.dart +++ b/mobile/openapi/lib/api/person_api.dart @@ -19,8 +19,8 @@ class PersonApi { /// Performs an HTTP 'GET /person' operation and returns the [Response]. /// Parameters: /// - /// * [bool] withHidden (required): - Future getAllPeopleWithHttpInfo(bool withHidden,) async { + /// * [bool] withHidden: + Future getAllPeopleWithHttpInfo({ bool? withHidden, }) async { // ignore: prefer_const_declarations final path = r'/person'; @@ -31,7 +31,9 @@ class PersonApi { final headerParams = {}; final formParams = {}; + if (withHidden != null) { queryParams.addAll(_queryParams('', 'withHidden', withHidden)); + } const contentTypes = []; @@ -49,9 +51,9 @@ class PersonApi { /// Parameters: /// - /// * [bool] withHidden (required): - Future getAllPeople(bool withHidden,) async { - final response = await getAllPeopleWithHttpInfo(withHidden,); + /// * [bool] withHidden: + Future getAllPeople({ bool? withHidden, }) async { + final response = await getAllPeopleWithHttpInfo( withHidden: withHidden, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/test/person_api_test.dart b/mobile/openapi/test/person_api_test.dart index 8671155e62c50..fbe597bc02bda 100644 --- a/mobile/openapi/test/person_api_test.dart +++ b/mobile/openapi/test/person_api_test.dart @@ -17,7 +17,7 @@ void main() { // final instance = PersonApi(); group('tests for PersonApi', () { - //Future getAllPeople(bool withHidden) async + //Future getAllPeople({ bool withHidden }) async test('test getAllPeople', () async { // TODO }); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 0eea743b13836..5f3bb2cef6b57 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2512,9 +2512,10 @@ "parameters": [ { "name": "withHidden", - "required": true, + "required": false, "in": "query", "schema": { + "default": false, "type": "boolean" } } diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index c460055236a50..381312fea2ef9 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -1,7 +1,7 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsBoolean, IsOptional, IsString } from 'class-validator'; import { toBoolean, ValidateUUID } from '../domain.util'; export class PersonUpdateDto { @@ -24,7 +24,6 @@ export class PersonUpdateDto { */ @IsOptional() @IsBoolean() - @Transform(toBoolean) isHidden?: boolean; } @@ -42,11 +41,10 @@ export class MergePersonDto { ids!: string[]; } -export class HiddenPersonDto { - @IsNotEmpty() +export class PersonSearchDto { @IsBoolean() @Transform(toBoolean) - withHidden!: boolean; + withHidden?: boolean = false; } export class PersonResponseDto { diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 37ffcfc0b00f6..ec8fee8e5f710 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -5,12 +5,12 @@ import { mimeTypes } from '../domain.constant'; import { IJobRepository, JobName } from '../job'; import { ImmichReadStream, IStorageRepository } from '../storage'; import { - HiddenPersonDto, mapPerson, MergePersonDto, PeopleResponseDto, PersonCountResponseDto, PersonResponseDto, + PersonSearchDto, PersonUpdateDto, } from './person.dto'; import { IPersonRepository, UpdateFacesData } from './person.repository'; @@ -25,7 +25,7 @@ export class PersonService { @Inject(IJobRepository) private jobRepository: IJobRepository, ) {} - async getAll(authUser: AuthUserDto, dto: HiddenPersonDto): Promise { + async getAll(authUser: AuthUserDto, dto: PersonSearchDto): Promise { const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); const named = people.filter((person) => !!person.name); const unnamed = people.filter((person) => !person.name); diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index 7a72a96a5d1ae..df9227f0ffc4c 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -2,12 +2,12 @@ import { AssetResponseDto, AuthUserDto, BulkIdResponseDto, - HiddenPersonDto, ImmichReadStream, MergePersonDto, PeopleResponseDto, PersonCountResponseDto, PersonResponseDto, + PersonSearchDto, PersonService, PersonUpdateDto, } from '@app/domain'; @@ -31,7 +31,7 @@ export class PersonController { @Get() getAllPeople( @AuthUser() authUser: AuthUserDto, - @Query(new ValidationPipe({ transform: true })) withHidden: HiddenPersonDto, + @Query(new ValidationPipe({ transform: true })) withHidden: PersonSearchDto, ): Promise { return this.service.getAll(authUser, withHidden); } diff --git a/server/src/infra/repositories/typesense.repository.ts b/server/src/infra/repositories/typesense.repository.ts index 40bd71ddb3fcc..2ca81f19c4fbc 100644 --- a/server/src/infra/repositories/typesense.repository.ts +++ b/server/src/infra/repositories/typesense.repository.ts @@ -385,7 +385,8 @@ export class TypesenseRepository implements ISearchRepository { custom = { ...custom, geo: [lat, lng] }; } - const people = asset.faces?.map((face) => face.person.name).filter((name) => name) || []; + const people = + asset.faces?.filter((face) => !face.person.isHidden && face.person.name).map((face) => face.person.name) || []; if (people.length) { custom = { ...custom, people }; } diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 49bc48b2cf6ea..e7b90997678c9 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -8750,13 +8750,11 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio return { /** * - * @param {boolean} withHidden + * @param {boolean} [withHidden] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople: async (withHidden: boolean, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'withHidden' is not null or undefined - assertParamExists('getAllPeople', 'withHidden', withHidden) + getAllPeople: async (withHidden?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/person`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -9065,11 +9063,11 @@ export const PersonApiFp = function(configuration?: Configuration) { return { /** * - * @param {boolean} withHidden + * @param {boolean} [withHidden] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllPeople(withHidden: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getAllPeople(withHidden?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getAllPeople(withHidden, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9146,11 +9144,11 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat return { /** * - * @param {boolean} withHidden + * @param {boolean} [withHidden] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllPeople(withHidden: boolean, options?: any): AxiosPromise { + getAllPeople(withHidden?: boolean, options?: any): AxiosPromise { return localVarFp.getAllPeople(withHidden, options).then((request) => request(axios, basePath)); }, /** @@ -9222,7 +9220,7 @@ export interface PersonApiGetAllPeopleRequest { * @type {boolean} * @memberof PersonApiGetAllPeople */ - readonly withHidden: boolean + readonly withHidden?: boolean } /** @@ -9323,7 +9321,7 @@ export class PersonApi extends BaseAPI { * @throws {RequiredError} * @memberof PersonApi */ - public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest, options?: AxiosRequestConfig) { + public getAllPeople(requestParameters: PersonApiGetAllPeopleRequest = {}, options?: AxiosRequestConfig) { return PersonApiFp(this.configuration).getAllPeople(requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); } diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index 3dda706c093a0..b2af981a06fc1 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -16,7 +16,6 @@ export let showUploadButton = false; export let title: string | undefined = undefined; export let selectHidden = false; - export let fullscreen = false; export let countTotalPeople: number | undefined = undefined; const dispatch = createEventDispatcher(); @@ -46,7 +45,7 @@ class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16" >

{title}

- {#if fullscreen && countTotalPeople && countTotalPeople > 0} + {#if countTotalPeople && countTotalPeople > 0} (selectHidden = !selectHidden)}>
@@ -72,17 +71,16 @@
-
- { - selectHidden = !selectHidden; - }} - /> -

Show & hide faces

-
- -
+
+
+ { + selectHidden = !selectHidden; + }} + /> +

Show & hide faces

+
{ handleDoneClick(); diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 0f6ad192cd366..b6d9b9ff6f680 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -65,7 +65,6 @@ Date: Mon, 17 Jul 2023 22:22:59 +0200 Subject: [PATCH 21/28] add server tests --- .../src/domain/person/person.service.spec.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index e99cdaa276482..e31806d01da94 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -42,13 +42,51 @@ describe(PersonService.name, () => { describe('getAll', () => { it('should get all people with thumbnails', async () => { personMock.getAll.mockResolvedValue([personStub.withName, personStub.noThumbnail]); - await expect(sut.getAll(authStub.admin, { withHidden: false })).resolves.toEqual({ + await expect(sut.getAll(authStub.admin, { withHidden: undefined })).resolves.toEqual({ total: 1, visible: 1, people: [responseDto], }); expect(personMock.getAll).toHaveBeenCalledWith(authStub.admin.id, { minimumFaceCount: 1 }); }); + it('should get all visible people with thumbnails', async () => { + personMock.getAll.mockResolvedValue([personStub.withName, personStub.hidden]); + await expect(sut.getAll(authStub.admin, { withHidden: false })).resolves.toEqual({ + total: 2, + visible: 1, + people: [responseDto], + }); + expect(personMock.getAll).toHaveBeenCalledWith(authStub.admin.id, { minimumFaceCount: 1 }); + }); + it('should get all hidden and visible people with thumbnails', async () => { + personMock.getAll.mockResolvedValue([personStub.withName, personStub.hidden]); + await expect(sut.getAll(authStub.admin, { withHidden: true })).resolves.toEqual({ + total: 2, + visible: 1, + people: [ + responseDto, + { + id: 'person-1', + name: '', + thumbnailPath: '/path/to/thumbnail.jpg', + isHidden: true, + }, + ], + }); + expect(personMock.getAll).toHaveBeenCalledWith(authStub.admin.id, { minimumFaceCount: 1 }); + }); + }); + + describe('getPersonStats', () => { + it('should get the stats for all people', async () => { + personMock.getAll.mockResolvedValue([personStub.withName, personStub.hidden]); + await expect(sut.getPersonCount(authStub.admin)).resolves.toEqual({ + hidden: 1, + visible: 1, + total: 2, + }); + expect(personMock.getAll).toHaveBeenCalledWith(authStub.admin.id, { minimumFaceCount: 1 }); + }); }); describe('getById', () => { From 46f581ae54f05b3560d2a522c65b47166fd51334 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 00:52:40 +0200 Subject: [PATCH 22/28] pr feedback --- cli/src/api/open-api/api.ts | 90 -------------- mobile/openapi/.openapi-generator/FILES | 3 - mobile/openapi/README.md | 2 - mobile/openapi/doc/PersonApi.md | 52 -------- mobile/openapi/doc/PersonCountResponseDto.md | 17 --- mobile/openapi/lib/api.dart | 1 - mobile/openapi/lib/api/person_api.dart | 41 ------- mobile/openapi/lib/api_client.dart | 2 - .../lib/model/person_count_response_dto.dart | 114 ------------------ mobile/openapi/test/person_api_test.dart | 5 - .../test/person_count_response_dto_test.dart | 37 ------ server/immich-openapi-specs.json | 51 -------- server/src/domain/person/person.dto.ts | 13 +- .../src/domain/person/person.service.spec.ts | 12 -- server/src/domain/person/person.service.ts | 23 +--- .../immich/controllers/person.controller.ts | 13 +- web/src/api/open-api/api.ts | 90 -------------- 17 files changed, 6 insertions(+), 560 deletions(-) delete mode 100644 mobile/openapi/doc/PersonCountResponseDto.md delete mode 100644 mobile/openapi/lib/model/person_count_response_dto.dart delete mode 100644 mobile/openapi/test/person_count_response_dto_test.dart diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 272ff460e1dac..5201db9e12145 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -1802,31 +1802,6 @@ export interface PeopleResponseDto { */ 'people': Array; } -/** - * - * @export - * @interface PersonCountResponseDto - */ -export interface PersonCountResponseDto { - /** - * - * @type {number} - * @memberof PersonCountResponseDto - */ - 'hidden': number; - /** - * - * @type {number} - * @memberof PersonCountResponseDto - */ - 'visible': number; - /** - * - * @type {number} - * @memberof PersonCountResponseDto - */ - 'total': number; -} /** * * @export @@ -8822,44 +8797,6 @@ 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 {*} [options] Override http request option. - * @throws {RequiredError} - */ - getPersonStats: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/person/statistics`; - // 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); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -9047,15 +8984,6 @@ export const PersonApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonAssets(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getPersonStats(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonStats(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {string} id @@ -9125,14 +9053,6 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat getPersonAssets(requestParameters: PersonApiGetPersonAssetsRequest, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getPersonAssets(requestParameters.id, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getPersonStats(options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getPersonStats(options).then((request) => request(axios, basePath)); - }, /** * * @param {PersonApiGetPersonThumbnailRequest} requestParameters Request parameters. @@ -9301,16 +9221,6 @@ export class PersonApi extends BaseAPI { return PersonApiFp(this.configuration).getPersonAssets(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof PersonApi - */ - public getPersonStats(options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getPersonStats(options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {PersonApiGetPersonThumbnailRequest} requestParameters Request parameters. diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 5045f5728d511..5e8a7ee704790 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -73,7 +73,6 @@ doc/OAuthConfigResponseDto.md doc/PartnerApi.md doc/PeopleResponseDto.md doc/PersonApi.md -doc/PersonCountResponseDto.md doc/PersonResponseDto.md doc/PersonUpdateDto.md doc/QueueStatusDto.md @@ -211,7 +210,6 @@ lib/model/o_auth_callback_dto.dart lib/model/o_auth_config_dto.dart lib/model/o_auth_config_response_dto.dart lib/model/people_response_dto.dart -lib/model/person_count_response_dto.dart lib/model/person_response_dto.dart lib/model/person_update_dto.dart lib/model/queue_status_dto.dart @@ -328,7 +326,6 @@ test/o_auth_config_response_dto_test.dart test/partner_api_test.dart test/people_response_dto_test.dart test/person_api_test.dart -test/person_count_response_dto_test.dart test/person_response_dto_test.dart test/person_update_dto_test.dart test/queue_status_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 20c7bea5d743e..5bb42236eaa3d 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -132,7 +132,6 @@ Class | Method | HTTP request | Description *PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person | *PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} | *PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | -*PersonApi* | [**getPersonStats**](doc//PersonApi.md#getpersonstats) | **GET** /person/statistics | *PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | *PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge | *PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} | @@ -240,7 +239,6 @@ Class | Method | HTTP request | Description - [OAuthConfigDto](doc//OAuthConfigDto.md) - [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md) - [PeopleResponseDto](doc//PeopleResponseDto.md) - - [PersonCountResponseDto](doc//PersonCountResponseDto.md) - [PersonResponseDto](doc//PersonResponseDto.md) - [PersonUpdateDto](doc//PersonUpdateDto.md) - [QueueStatusDto](doc//QueueStatusDto.md) diff --git a/mobile/openapi/doc/PersonApi.md b/mobile/openapi/doc/PersonApi.md index 8cce9a8ae9469..609043526e766 100644 --- a/mobile/openapi/doc/PersonApi.md +++ b/mobile/openapi/doc/PersonApi.md @@ -12,7 +12,6 @@ Method | HTTP request | Description [**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person | [**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} | [**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets | -[**getPersonStats**](PersonApi.md#getpersonstats) | **GET** /person/statistics | [**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail | [**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge | [**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} | @@ -183,57 +182,6 @@ 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) -# **getPersonStats** -> PersonCountResponseDto getPersonStats() - - - -### Example -```dart -import 'package:openapi/api.dart'; -// TODO Configure API key authorization: cookie -//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; -// uncomment below to setup prefix (e.g. Bearer) for API key, if needed -//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; -// TODO Configure API key authorization: api_key -//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; -// uncomment below to setup prefix (e.g. Bearer) for API key, if needed -//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; -// TODO Configure HTTP Bearer authorization: bearer -// Case 1. Use String Token -//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); -// Case 2. Use Function which generate token. -// String yourTokenGeneratorFunction() { ... } -//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); - -final api_instance = PersonApi(); - -try { - final result = api_instance.getPersonStats(); - print(result); -} catch (e) { - print('Exception when calling PersonApi->getPersonStats: $e\n'); -} -``` - -### Parameters -This endpoint does not need any parameter. - -### Return type - -[**PersonCountResponseDto**](PersonCountResponseDto.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) - # **getPersonThumbnail** > MultipartFile getPersonThumbnail(id) diff --git a/mobile/openapi/doc/PersonCountResponseDto.md b/mobile/openapi/doc/PersonCountResponseDto.md deleted file mode 100644 index 1d9ba2051ecd8..0000000000000 --- a/mobile/openapi/doc/PersonCountResponseDto.md +++ /dev/null @@ -1,17 +0,0 @@ -# openapi.model.PersonCountResponseDto - -## Load the model package -```dart -import 'package:openapi/api.dart'; -``` - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**hidden** | **int** | | -**visible** | **int** | | -**total** | **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) - - diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 627232e29c36a..819f3092ad4c8 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -105,7 +105,6 @@ part 'model/o_auth_callback_dto.dart'; part 'model/o_auth_config_dto.dart'; part 'model/o_auth_config_response_dto.dart'; part 'model/people_response_dto.dart'; -part 'model/person_count_response_dto.dart'; part 'model/person_response_dto.dart'; part 'model/person_update_dto.dart'; part 'model/queue_status_dto.dart'; diff --git a/mobile/openapi/lib/api/person_api.dart b/mobile/openapi/lib/api/person_api.dart index fb8a3d7c6d030..7ced3bf7af7a1 100644 --- a/mobile/openapi/lib/api/person_api.dart +++ b/mobile/openapi/lib/api/person_api.dart @@ -166,47 +166,6 @@ class PersonApi { return null; } - /// Performs an HTTP 'GET /person/statistics' operation and returns the [Response]. - Future getPersonStatsWithHttpInfo() async { - // ignore: prefer_const_declarations - final path = r'/person/statistics'; - - // ignore: prefer_final_locals - Object? postBody; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - const contentTypes = []; - - - return apiClient.invokeAPI( - path, - 'GET', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - Future getPersonStats() async { - final response = await getPersonStatsWithHttpInfo(); - 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), 'PersonCountResponseDto',) as PersonCountResponseDto; - - } - return null; - } - /// Performs an HTTP 'GET /person/{id}/thumbnail' operation and returns the [Response]. /// Parameters: /// diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 522e77f68a522..b36572fb4acdf 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -305,8 +305,6 @@ class ApiClient { return OAuthConfigResponseDto.fromJson(value); case 'PeopleResponseDto': return PeopleResponseDto.fromJson(value); - case 'PersonCountResponseDto': - return PersonCountResponseDto.fromJson(value); case 'PersonResponseDto': return PersonResponseDto.fromJson(value); case 'PersonUpdateDto': diff --git a/mobile/openapi/lib/model/person_count_response_dto.dart b/mobile/openapi/lib/model/person_count_response_dto.dart deleted file mode 100644 index d11af067ce581..0000000000000 --- a/mobile/openapi/lib/model/person_count_response_dto.dart +++ /dev/null @@ -1,114 +0,0 @@ -// -// 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 PersonCountResponseDto { - /// Returns a new [PersonCountResponseDto] instance. - PersonCountResponseDto({ - required this.hidden, - required this.visible, - required this.total, - }); - - int hidden; - - int visible; - - int total; - - @override - bool operator ==(Object other) => identical(this, other) || other is PersonCountResponseDto && - other.hidden == hidden && - other.visible == visible && - other.total == total; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (hidden.hashCode) + - (visible.hashCode) + - (total.hashCode); - - @override - String toString() => 'PersonCountResponseDto[hidden=$hidden, visible=$visible, total=$total]'; - - Map toJson() { - final json = {}; - json[r'hidden'] = this.hidden; - json[r'visible'] = this.visible; - json[r'total'] = this.total; - return json; - } - - /// Returns a new [PersonCountResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static PersonCountResponseDto? fromJson(dynamic value) { - if (value is Map) { - final json = value.cast(); - - return PersonCountResponseDto( - hidden: mapValueOfType(json, r'hidden')!, - visible: mapValueOfType(json, r'visible')!, - total: mapValueOfType(json, r'total')!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = PersonCountResponseDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = PersonCountResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of PersonCountResponseDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = PersonCountResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'hidden', - 'visible', - 'total', - }; -} - diff --git a/mobile/openapi/test/person_api_test.dart b/mobile/openapi/test/person_api_test.dart index fbe597bc02bda..a49f40aa6b71a 100644 --- a/mobile/openapi/test/person_api_test.dart +++ b/mobile/openapi/test/person_api_test.dart @@ -32,11 +32,6 @@ void main() { // TODO }); - //Future getPersonStats() async - test('test getPersonStats', () async { - // TODO - }); - //Future getPersonThumbnail(String id) async test('test getPersonThumbnail', () async { // TODO diff --git a/mobile/openapi/test/person_count_response_dto_test.dart b/mobile/openapi/test/person_count_response_dto_test.dart deleted file mode 100644 index c7c5b7006ca7f..0000000000000 --- a/mobile/openapi/test/person_count_response_dto_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -// -// 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 PersonCountResponseDto -void main() { - // final instance = PersonCountResponseDto(); - - group('test PersonCountResponseDto', () { - // int hidden - test('to test the property `hidden`', () async { - // TODO - }); - - // int visible - test('to test the property `visible`', () async { - // TODO - }); - - // int total - test('to test the property `total`', () async { - // TODO - }); - - - }); - -} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 5f3bb2cef6b57..8865bbae25fbb 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2548,38 +2548,6 @@ ] } }, - "/person/statistics": { - "get": { - "operationId": "getPersonStats", - "parameters": [], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PersonCountResponseDto" - } - } - } - } - }, - "tags": [ - "Person" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - } - }, "/person/{id}": { "get": { "operationId": "getPerson", @@ -5938,25 +5906,6 @@ "people" ] }, - "PersonCountResponseDto": { - "type": "object", - "properties": { - "hidden": { - "type": "integer" - }, - "visible": { - "type": "integer" - }, - "total": { - "type": "integer" - } - }, - "required": [ - "hidden", - "visible", - "total" - ] - }, "PersonResponseDto": { "type": "object", "properties": { diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index 381312fea2ef9..41430afaf26f2 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -1,7 +1,6 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities'; -import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsArray, IsBoolean, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; import { toBoolean, ValidateUUID } from '../domain.util'; export class PersonUpdateDto { @@ -27,15 +26,6 @@ export class PersonUpdateDto { isHidden?: boolean; } -export class PersonCountResponseDto { - @ApiProperty({ type: 'integer' }) - hidden!: number; - @ApiProperty({ type: 'integer' }) - visible!: number; - @ApiProperty({ type: 'integer' }) - total!: number; -} - export class MergePersonDto { @ValidateUUID({ each: true }) ids!: string[]; @@ -57,7 +47,6 @@ export class PersonResponseDto { export class PeopleResponseDto { total!: number; visible!: number; - @IsArray() people!: PersonResponseDto[]; } diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index e31806d01da94..52a8d0f5dc5ea 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -77,18 +77,6 @@ describe(PersonService.name, () => { }); }); - describe('getPersonStats', () => { - it('should get the stats for all people', async () => { - personMock.getAll.mockResolvedValue([personStub.withName, personStub.hidden]); - await expect(sut.getPersonCount(authStub.admin)).resolves.toEqual({ - hidden: 1, - visible: 1, - total: 2, - }); - expect(personMock.getAll).toHaveBeenCalledWith(authStub.admin.id, { minimumFaceCount: 1 }); - }); - }); - describe('getById', () => { it('should throw a bad request when person is not found', async () => { personMock.getById.mockResolvedValue(null); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index ec8fee8e5f710..eb030f90c91b6 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -8,7 +8,6 @@ import { mapPerson, MergePersonDto, PeopleResponseDto, - PersonCountResponseDto, PersonResponseDto, PersonSearchDto, PersonUpdateDto, @@ -38,7 +37,7 @@ export class PersonService { return { people: persons.filter((person) => dto.withHidden || !person.isHidden), total: persons.length, - visible: persons.filter((person: PersonResponseDto) => person.isHidden === false).length, + visible: persons.filter((person: PersonResponseDto) => !person.isHidden).length, }; } @@ -46,15 +45,6 @@ export class PersonService { return this.findOrFail(authUser, id).then(mapPerson); } - async getPersonCount(authUser: AuthUserDto): Promise { - const people = await this.repository.getAll(authUser.id, { minimumFaceCount: 1 }); - return { - hidden: people.filter((obj) => obj.isHidden === true).length, - visible: people.filter((obj) => obj.isHidden === false).length, - total: people.length, - }; - } - async getThumbnail(authUser: AuthUserDto, id: string): Promise { const person = await this.repository.getById(authUser.id, id); if (!person || !person.thumbnailPath) { @@ -72,15 +62,8 @@ export class PersonService { async update(authUser: AuthUserDto, id: string, dto: PersonUpdateDto): Promise { let person = await this.findOrFail(authUser, id); - if (dto.name) { - person = await this.repository.update({ id, name: dto.name }); - const assets = await this.repository.getAssets(authUser.id, id); - const ids = assets.map((asset) => asset.id); - await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); - } - - if (dto.isHidden !== undefined) { - person = await this.repository.update({ id, isHidden: dto.isHidden }); + if (dto.name || dto.isHidden !== undefined) { + person = await this.repository.update({ id, name: dto.name, isHidden: dto.isHidden }); const assets = await this.repository.getAssets(authUser.id, id); const ids = assets.map((asset) => asset.id); await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } }); diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/immich/controllers/person.controller.ts index df9227f0ffc4c..7620145128188 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/immich/controllers/person.controller.ts @@ -5,13 +5,12 @@ import { ImmichReadStream, MergePersonDto, PeopleResponseDto, - PersonCountResponseDto, PersonResponseDto, PersonSearchDto, PersonService, PersonUpdateDto, } from '@app/domain'; -import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile, ValidationPipe } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { Authenticated, AuthUser } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -29,18 +28,10 @@ export class PersonController { constructor(private service: PersonService) {} @Get() - getAllPeople( - @AuthUser() authUser: AuthUserDto, - @Query(new ValidationPipe({ transform: true })) withHidden: PersonSearchDto, - ): Promise { + getAllPeople(@AuthUser() authUser: AuthUserDto, @Query() withHidden: PersonSearchDto): Promise { return this.service.getAll(authUser, withHidden); } - @Get('statistics') - getPersonStats(@AuthUser() authUser: AuthUserDto): Promise { - return this.service.getPersonCount(authUser); - } - @Get(':id') getPerson(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.getById(authUser, id); diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index e7b90997678c9..24f3b333798e0 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1802,31 +1802,6 @@ export interface PeopleResponseDto { */ 'people': Array; } -/** - * - * @export - * @interface PersonCountResponseDto - */ -export interface PersonCountResponseDto { - /** - * - * @type {number} - * @memberof PersonCountResponseDto - */ - 'hidden': number; - /** - * - * @type {number} - * @memberof PersonCountResponseDto - */ - 'visible': number; - /** - * - * @type {number} - * @memberof PersonCountResponseDto - */ - 'total': number; -} /** * * @export @@ -8866,44 +8841,6 @@ 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 {*} [options] Override http request option. - * @throws {RequiredError} - */ - getPersonStats: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/person/statistics`; - // 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); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -9091,15 +9028,6 @@ export const PersonApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonAssets(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getPersonStats(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getPersonStats(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {string} id @@ -9169,14 +9097,6 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat getPersonAssets(id: string, options?: any): AxiosPromise> { return localVarFp.getPersonAssets(id, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getPersonStats(options?: any): AxiosPromise { - return localVarFp.getPersonStats(options).then((request) => request(axios, basePath)); - }, /** * * @param {string} id @@ -9347,16 +9267,6 @@ export class PersonApi extends BaseAPI { return PersonApiFp(this.configuration).getPersonAssets(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof PersonApi - */ - public getPersonStats(options?: AxiosRequestConfig) { - return PersonApiFp(this.configuration).getPersonStats(options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {PersonApiGetPersonThumbnailRequest} requestParameters Request parameters. From 007d0b87fe63dbb17c38e31e14f4eea1ed707b95 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 02:03:55 +0200 Subject: [PATCH 23/28] pr feedback --- .../components/faces-page/show-hide.svelte | 49 ++++++++++++++++ .../layouts/user-page-layout.svelte | 58 +------------------ web/src/routes/(user)/people/+page.svelte | 50 +++++++++++++--- 3 files changed, 92 insertions(+), 65 deletions(-) create mode 100644 web/src/lib/components/faces-page/show-hide.svelte diff --git a/web/src/lib/components/faces-page/show-hide.svelte b/web/src/lib/components/faces-page/show-hide.svelte new file mode 100644 index 0000000000000..41782861cb60e --- /dev/null +++ b/web/src/lib/components/faces-page/show-hide.svelte @@ -0,0 +1,49 @@ + + +{#if selectHidden} +
+
+
+
+ { + selectHidden = !selectHidden; + }} + /> +

Show & hide faces

+
+ { + handleDoneClick(); + }} + > + Done + +
+
+ +
+
+
+{/if} diff --git a/web/src/lib/components/layouts/user-page-layout.svelte b/web/src/lib/components/layouts/user-page-layout.svelte index b2af981a06fc1..7fddb3f17c7c3 100644 --- a/web/src/lib/components/layouts/user-page-layout.svelte +++ b/web/src/lib/components/layouts/user-page-layout.svelte @@ -1,28 +1,12 @@
@@ -45,14 +29,6 @@ class="absolute border-b dark:border-immich-dark-gray flex justify-between place-items-center dark:text-immich-dark-fg w-full p-4 h-16" >

{title}

- {#if countTotalPeople && countTotalPeople > 0} - (selectHidden = !selectHidden)}> -
- -

Show & hide faces

-
-
- {/if}
@@ -63,35 +39,3 @@ {/if} -{#if selectHidden} -
-
-
-
- { - selectHidden = !selectHidden; - }} - /> -

Show & hide faces

-
- { - handleDoneClick(); - }} - > - Done - -
-
- -
-
-
-{/if} diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index b6d9b9ff6f680..a0fafb176b5b1 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -9,6 +9,9 @@ NotificationType, notificationController, } from '$lib/components/shared-components/notification/notification'; + import ShowHide from '$lib/components/faces-page/show-hide.svelte'; + import IconButton from '$lib/components/elements/buttons/icon-button.svelte'; + import EyeOutline from 'svelte-material-icons/EyeOutline.svelte'; export let data: PageData; let selectHidden = false; @@ -63,14 +66,16 @@ }; - + + {#if countTotalPeople && countTotalPeople > 0} + (selectHidden = !selectHidden)} slot="buttons"> +
+ +

Show & hide faces

+
+
+ {/if} + {#if countVisiblePeople > 0 || selectHidden} {#if !selectHidden}
@@ -137,3 +142,32 @@
{/if}
+ + +
+
+ {#each data.people.people as person (person.id)} +
+
+ +
+ {#if person.name} + + {person.name} + + {/if} +
+ {/each} +
+
+
From 422ca70e6b491ba486064082badabbcbb78edfa6 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 02:20:53 +0200 Subject: [PATCH 24/28] fix: show & hide faces --- web/src/routes/(user)/people/+page.svelte | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index a0fafb176b5b1..45080d4608e42 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -67,14 +67,16 @@ - {#if countTotalPeople && countTotalPeople > 0} - (selectHidden = !selectHidden)} slot="buttons"> -
- -

Show & hide faces

-
-
- {/if} + + {#if countTotalPeople > 0} + (selectHidden = !selectHidden)}> +
+ +

Show & hide faces

+
+
+ {/if} +
{#if countVisiblePeople > 0 || selectHidden} {#if !selectHidden} From 6b68e30afbb0cc1a921fdace6a5c2902465db35e Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 08:13:25 +0200 Subject: [PATCH 25/28] simplify --- .../components/faces-page/show-hide.svelte | 6 +- web/src/routes/(user)/people/+page.svelte | 84 +++++++------------ 2 files changed, 34 insertions(+), 56 deletions(-) diff --git a/web/src/lib/components/faces-page/show-hide.svelte b/web/src/lib/components/faces-page/show-hide.svelte index 41782861cb60e..f3623d35298f9 100644 --- a/web/src/lib/components/faces-page/show-hide.svelte +++ b/web/src/lib/components/faces-page/show-hide.svelte @@ -13,6 +13,10 @@ selectHidden = !selectHidden; dispatch('doneClick'); }; + const handleCloseClick = () => { + selectHidden = !selectHidden; + dispatch('closeClick'); + }; {#if selectHidden} @@ -28,7 +32,7 @@ { - selectHidden = !selectHidden; + handleCloseClick(); }} />

Show & hide faces

diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 45080d4608e42..cf752e39b6589 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -26,6 +26,12 @@ let countTotalPeople = data.people.total; let countVisiblePeople = data.people.visible; + const handleCloseClick = () => { + for (const person of data.people.people) { + person.isHidden = initialHiddenValues[person.id]; + } + }; + const handleDoneClick = async () => { try { // Reset the counter before checking changes @@ -33,10 +39,7 @@ // Check if the visibility for each person has been changed for (const person of data.people.people) { - const index = person.id; - const initialHiddenValue = initialHiddenValues[index]; - - if (person.isHidden !== initialHiddenValue) { + if (person.isHidden !== initialHiddenValues[person.id]) { changeCounter++; await api.personApi.updatePerson({ id: person.id, @@ -44,7 +47,7 @@ }); // Update the initial hidden values - initialHiddenValues[index] = person.isHidden; + initialHiddenValues[person.id] = person.isHidden; // Update the count of hidden/visible people countVisiblePeople += person.isHidden ? -1 : 1; @@ -66,7 +69,7 @@ }; - + {#if countTotalPeople > 0} (selectHidden = !selectHidden)}> @@ -78,63 +81,34 @@ {/if} - {#if countVisiblePeople > 0 || selectHidden} - {#if !selectHidden} -
-
- {#each data.people.people as person (person.id)} - {#if !person.isHidden} - - {/if} - {/each} -
-
- {:else} -
-
- {#each data.people.people as person (person.id)} + {#if countVisiblePeople > 0} +
+
+ {#each data.people.people as person (person.id)} + {#if !person.isHidden}
-
- -
- {#if person.name} - - {person.name} - - {/if} +
+ {#if person.name} + + {person.name} + + {/if} +
- {/each} -
+ {/if} + {/each}
- {/if} +
{:else}
@@ -145,7 +119,7 @@ {/if} - +
{#each data.people.people as person (person.id)} From af9f749e5f964313a09b012f4a2138e2de3e6985 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:28:45 +0200 Subject: [PATCH 26/28] fix: close button --- .../components/faces-page/show-hide.svelte | 62 ++++----- web/src/routes/(user)/explore/+page.svelte | 12 +- web/src/routes/(user)/people/+page.svelte | 123 +++++++++--------- 3 files changed, 94 insertions(+), 103 deletions(-) diff --git a/web/src/lib/components/faces-page/show-hide.svelte b/web/src/lib/components/faces-page/show-hide.svelte index f3623d35298f9..1953af3b8d4c9 100644 --- a/web/src/lib/components/faces-page/show-hide.svelte +++ b/web/src/lib/components/faces-page/show-hide.svelte @@ -6,48 +6,36 @@ import IconButton from '../elements/buttons/icon-button.svelte'; import { createEventDispatcher } from 'svelte'; - export let selectHidden = false; - const dispatch = createEventDispatcher(); - const handleDoneClick = () => { - selectHidden = !selectHidden; - dispatch('doneClick'); - }; - const handleCloseClick = () => { - selectHidden = !selectHidden; - dispatch('closeClick'); - }; -{#if selectHidden} -
+
-
-
-
- { - handleCloseClick(); - }} - /> -

Show & hide faces

-
- +
+ { - handleDoneClick(); + dispatch('closeClick'); }} - > - Done - -
-
- + /> +

Show & hide faces

+ { + dispatch('doneClick'); + }} + > + Done + +
+
+
-
-{/if} +
+ diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index 213c448871e77..a7c2f0285fe2e 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -34,13 +34,11 @@

People

- {#if data.people.total > 0} - View All - {/if} + View All
{#each people as person (person.id)} diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index cf752e39b6589..ed2ebd8c4c36a 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -18,27 +18,29 @@ let changeCounter = 0; let initialHiddenValues: Record = {}; - data.people.people.forEach((person: PersonResponseDto) => { - initialHiddenValues[person.id] = person.isHidden; - }); - - // Get number of person and visible people + let people = data.people.people; let countTotalPeople = data.people.total; let countVisiblePeople = data.people.visible; + people.forEach((person: PersonResponseDto) => { + initialHiddenValues[person.id] = person.isHidden; + }); + const handleCloseClick = () => { - for (const person of data.people.people) { + selectHidden = false; + people.forEach((person: PersonResponseDto) => { person.isHidden = initialHiddenValues[person.id]; - } + }); }; const handleDoneClick = async () => { + selectHidden = false; try { // Reset the counter before checking changes let changeCounter = 0; // Check if the visibility for each person has been changed - for (const person of data.people.people) { + for (const person of people) { if (person.isHidden !== initialHiddenValues[person.id]) { changeCounter++; await api.personApi.updatePerson({ @@ -69,7 +71,7 @@ }; - + {#if countTotalPeople > 0} (selectHidden = !selectHidden)}> @@ -84,29 +86,31 @@ {#if countVisiblePeople > 0}
- {#each data.people.people as person (person.id)} - {#if !person.isHidden} - - {/if} - {/each} + {#key selectHidden} + {#each people as person (person.id)} + {#if !person.isHidden} + + {/if} + {/each} + {/key}
{:else} @@ -118,32 +122,33 @@
{/if} - - -
-
- {#each data.people.people as person (person.id)} -
-
- +{#if selectHidden} + +
+
+ {#each people as person (person.id)} +
+
+ +
+ {#if person.name} + + {person.name} + + {/if}
- {#if person.name} - - {person.name} - - {/if} -
- {/each} + {/each} +
-
- + +{/if} From 70e94db1ea4ede998eb95dff60a80287d34111ec Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 14:32:13 +0200 Subject: [PATCH 27/28] pr feeback --- .../lib/components/faces-page/show-hide.svelte | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/web/src/lib/components/faces-page/show-hide.svelte b/web/src/lib/components/faces-page/show-hide.svelte index 1953af3b8d4c9..1bf395f53d8e0 100644 --- a/web/src/lib/components/faces-page/show-hide.svelte +++ b/web/src/lib/components/faces-page/show-hide.svelte @@ -18,21 +18,10 @@ >
- { - dispatch('closeClick'); - }} - /> + dispatch('closeClick')} />

Show & hide faces

- { - dispatch('doneClick'); - }} - > - Done - + dispatch('doneClick')}>Done
From 1272b2f23f4866abe2087b3ef5c48fb93899ab9f Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:59:38 +0200 Subject: [PATCH 28/28] pr feeback --- web/src/routes/(user)/explore/+page.server.ts | 4 ++-- web/src/routes/(user)/explore/+page.svelte | 5 +++-- web/src/routes/(user)/people/+page.svelte | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/src/routes/(user)/explore/+page.server.ts b/web/src/routes/(user)/explore/+page.server.ts index 809b5822e412a..5d0491ddd7239 100644 --- a/web/src/routes/(user)/explore/+page.server.ts +++ b/web/src/routes/(user)/explore/+page.server.ts @@ -9,11 +9,11 @@ export const load = (async ({ locals, parent }) => { } const { data: items } = await locals.api.searchApi.getExploreData(); - const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: false }); + const { data: response } = await locals.api.personApi.getAllPeople({ withHidden: false }); return { user, items, - people, + response, meta: { title: 'Explore', }, diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index a7c2f0285fe2e..fb5e2631cfc5b 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -26,11 +26,12 @@ $: things = getFieldItems(data.items, Field.OBJECTS); $: places = getFieldItems(data.items, Field.CITY); - $: people = data.people.people.slice(0, MAX_ITEMS); + $: people = data.response.people.slice(0, MAX_ITEMS); + $: hasPeople = data.response.total > 0; - {#if data.people.total > 0} + {#if hasPeople}

People

diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index ed2ebd8c4c36a..50632b5282f98 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -75,8 +75,8 @@ {#if countTotalPeople > 0} (selectHidden = !selectHidden)}> -
- +
+

Show & hide faces