1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-01 15:11:21 +01:00

pr feedback

This commit is contained in:
martabal 2024-05-12 17:25:49 +02:00
parent 7ced61e67d
commit 0f6e665d99
No known key found for this signature in database
GPG key ID: C00196E3148A52BD
13 changed files with 74 additions and 32 deletions

View file

@ -214,7 +214,7 @@ describe('/asset', () => {
id: user1Assets[0].id, id: user1Assets[0].id,
isFavorite: false, isFavorite: false,
people: { people: {
faces: [ visiblePeople: [
{ {
birthDate: null, birthDate: null,
id: expect.any(String), id: expect.any(String),
@ -484,7 +484,7 @@ describe('/asset', () => {
id: user1Assets[0].id, id: user1Assets[0].id,
isFavorite: true, isFavorite: true,
people: { people: {
faces: [ visiblePeople: [
{ {
birthDate: null, birthDate: null,
id: expect.any(String), id: expect.any(String),

View file

@ -72,7 +72,7 @@ class AssetService {
final AssetResponseDto? dto = final AssetResponseDto? dto =
await _apiService.assetApi.getAssetInfo(remoteId); await _apiService.assetApi.getAssetInfo(remoteId);
return dto?.people?.faces; return dto?.people?.visiblePeople;
} catch (error, stack) { } catch (error, stack) {
log.severe( log.severe(
'Error while getting remote asset info: ${error.toString()}', 'Error while getting remote asset info: ${error.toString()}',

View file

@ -8,8 +8,8 @@ import 'package:openapi/api.dart';
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**faces** | [**List<PersonWithFacesResponseDto>**](PersonWithFacesResponseDto.md) | | [default to const []]
**numberOfFaces** | **int** | | **numberOfFaces** | **int** | |
**visiblePeople** | [**List<PersonWithFacesResponseDto>**](PersonWithFacesResponseDto.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) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View file

@ -13,32 +13,32 @@ part of openapi.api;
class PeopleWithFacesResponseDto { class PeopleWithFacesResponseDto {
/// Returns a new [PeopleWithFacesResponseDto] instance. /// Returns a new [PeopleWithFacesResponseDto] instance.
PeopleWithFacesResponseDto({ PeopleWithFacesResponseDto({
this.faces = const [],
required this.numberOfFaces, required this.numberOfFaces,
this.visiblePeople = const [],
}); });
List<PersonWithFacesResponseDto> faces;
int numberOfFaces; int numberOfFaces;
List<PersonWithFacesResponseDto> visiblePeople;
@override @override
bool operator ==(Object other) => identical(this, other) || other is PeopleWithFacesResponseDto && bool operator ==(Object other) => identical(this, other) || other is PeopleWithFacesResponseDto &&
_deepEquality.equals(other.faces, faces) && other.numberOfFaces == numberOfFaces &&
other.numberOfFaces == numberOfFaces; _deepEquality.equals(other.visiblePeople, visiblePeople);
@override @override
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(faces.hashCode) + (numberOfFaces.hashCode) +
(numberOfFaces.hashCode); (visiblePeople.hashCode);
@override @override
String toString() => 'PeopleWithFacesResponseDto[faces=$faces, numberOfFaces=$numberOfFaces]'; String toString() => 'PeopleWithFacesResponseDto[numberOfFaces=$numberOfFaces, visiblePeople=$visiblePeople]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'faces'] = this.faces;
json[r'numberOfFaces'] = this.numberOfFaces; json[r'numberOfFaces'] = this.numberOfFaces;
json[r'visiblePeople'] = this.visiblePeople;
return json; return json;
} }
@ -50,8 +50,8 @@ class PeopleWithFacesResponseDto {
final json = value.cast<String, dynamic>(); final json = value.cast<String, dynamic>();
return PeopleWithFacesResponseDto( return PeopleWithFacesResponseDto(
faces: PersonWithFacesResponseDto.listFromJson(json[r'faces']),
numberOfFaces: mapValueOfType<int>(json, r'numberOfFaces')!, numberOfFaces: mapValueOfType<int>(json, r'numberOfFaces')!,
visiblePeople: PersonWithFacesResponseDto.listFromJson(json[r'visiblePeople']),
); );
} }
return null; return null;
@ -99,8 +99,8 @@ class PeopleWithFacesResponseDto {
/// The list of required keys that must be present in a JSON. /// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{ static const requiredKeys = <String>{
'faces',
'numberOfFaces', 'numberOfFaces',
'visiblePeople',
}; };
} }

View file

@ -16,13 +16,13 @@ void main() {
// final instance = PeopleWithFacesResponseDto(); // final instance = PeopleWithFacesResponseDto();
group('test PeopleWithFacesResponseDto', () { group('test PeopleWithFacesResponseDto', () {
// List<PersonWithFacesResponseDto> faces (default value: const []) // int numberOfFaces
test('to test the property `faces`', () async { test('to test the property `numberOfFaces`', () async {
// TODO // TODO
}); });
// int numberOfFaces // List<PersonWithFacesResponseDto> visiblePeople (default value: const [])
test('to test the property `numberOfFaces`', () async { test('to test the property `visiblePeople`', () async {
// TODO // TODO
}); });

View file

@ -9014,19 +9014,19 @@
}, },
"PeopleWithFacesResponseDto": { "PeopleWithFacesResponseDto": {
"properties": { "properties": {
"faces": { "numberOfFaces": {
"type": "integer"
},
"visiblePeople": {
"items": { "items": {
"$ref": "#/components/schemas/PersonWithFacesResponseDto" "$ref": "#/components/schemas/PersonWithFacesResponseDto"
}, },
"type": "array" "type": "array"
},
"numberOfFaces": {
"type": "integer"
} }
}, },
"required": [ "required": [
"faces", "numberOfFaces",
"numberOfFaces" "visiblePeople"
], ],
"type": "object" "type": "object"
}, },

View file

@ -101,8 +101,8 @@ export type PersonWithFacesResponseDto = {
thumbnailPath: string; thumbnailPath: string;
}; };
export type PeopleWithFacesResponseDto = { export type PeopleWithFacesResponseDto = {
faces: PersonWithFacesResponseDto[];
numberOfFaces: number; numberOfFaces: number;
visiblePeople: PersonWithFacesResponseDto[];
}; };
export type SmartInfoResponseDto = { export type SmartInfoResponseDto = {
objects?: string[] | null; objects?: string[] | null;

View file

@ -78,7 +78,7 @@ const peopleWithFaces = (faces: AssetFaceEntity[]): PeopleWithFacesResponseDto =
} }
} }
return { faces: result, numberOfFaces: faces.length }; return { visiblePeople: result, numberOfFaces: faces.length };
}; };
export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto { export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {

View file

@ -78,7 +78,7 @@ export class PersonWithFacesResponseDto extends PersonResponseDto {
} }
export class PeopleWithFacesResponseDto { export class PeopleWithFacesResponseDto {
faces!: PersonWithFacesResponseDto[]; visiblePeople!: PersonWithFacesResponseDto[];
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'integer' })
numberOfFaces!: number; numberOfFaces!: number;
} }

View file

@ -449,6 +449,18 @@ describe(PersonService.name, () => {
await expect(sut.unassignFace(authStub.admin, faceStub.face1.id)).resolves.toStrictEqual( await expect(sut.unassignFace(authStub.admin, faceStub.face1.id)).resolves.toStrictEqual(
mapFaces(faceStub.unassignedFace, authStub.admin), mapFaces(faceStub.unassignedFace, authStub.admin),
); );
expect(mediaMock.generateThumbnail).not.toHaveBeenCalled();
});
it('should not unassign a face if user has no create access', async () => {
personMock.getFaceById.mockResolvedValueOnce(faceStub.face1);
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
personMock.reassignFace.mockResolvedValue(1);
personMock.getRandomFace.mockResolvedValue(null);
personMock.getFaceById.mockResolvedValueOnce(faceStub.unassignedFace);
await expect(sut.unassignFace(authStub.admin, faceStub.face1.id)).rejects.toBeInstanceOf(BadRequestException);
}); });
}); });
@ -465,6 +477,18 @@ describe(PersonService.name, () => {
sut.unassignFaces(authStub.admin, { data: [{ assetId: faceStub.face1.id, personId: 'person-1' }] }), sut.unassignFaces(authStub.admin, { data: [{ assetId: faceStub.face1.id, personId: 'person-1' }] }),
).resolves.toStrictEqual([{ id: 'assetFaceId1', success: true }]); ).resolves.toStrictEqual([{ id: 'assetFaceId1', success: true }]);
}); });
it('should not unassign a face if the user has no create access', async () => {
personMock.getFacesByIds.mockResolvedValueOnce([faceStub.face1]);
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
personMock.reassignFace.mockResolvedValue(1);
personMock.getRandomFace.mockResolvedValue(null);
personMock.getFaceById.mockResolvedValueOnce(faceStub.unassignedFace);
await expect(
sut.unassignFaces(authStub.admin, { data: [{ assetId: faceStub.face1.id, personId: 'person-1' }] }),
).rejects.toBeInstanceOf(BadRequestException);
});
}); });
describe('handlePersonCleanup', () => { describe('handlePersonCleanup', () => {

View file

@ -218,7 +218,7 @@
<div class="flex h-10 w-full items-center justify-between"> <div class="flex h-10 w-full items-center justify-between">
<h2>PEOPLE</h2> <h2>PEOPLE</h2>
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
{#if people.faces.some((person) => person.isHidden)} {#if people.visiblePeople.some((person) => person.isHidden)}
<CircleIconButton <CircleIconButton
title="Show hidden people" title="Show hidden people"
icon={showingHiddenPeople ? mdiEyeOff : mdiEye} icon={showingHiddenPeople ? mdiEyeOff : mdiEye}
@ -239,7 +239,7 @@
</div> </div>
<div class="mt-2 flex flex-wrap gap-2"> <div class="mt-2 flex flex-wrap gap-2">
{#each people.faces as person (person.id)} {#each people.visiblePeople as person (person.id)}
{#if showingHiddenPeople || !person.isHidden} {#if showingHiddenPeople || !person.isHidden}
<a <a
class="w-[90px]" class="w-[90px]"

View file

@ -64,6 +64,10 @@
let loaderLoadingDoneTimeout: ReturnType<typeof setTimeout>; let loaderLoadingDoneTimeout: ReturnType<typeof setTimeout>;
let automaticRefreshTimeout: ReturnType<typeof setTimeout>; let automaticRefreshTimeout: ReturnType<typeof setTimeout>;
$: mapFacesToBeCreated = Object.entries(selectedPersonToAdd)
.filter(([_, value]) => value.person === null)
.map(([key, _]) => key);
const thumbnailWidth = '90px'; const thumbnailWidth = '90px';
const generatePeopleWithoutFaces = async () => { const generatePeopleWithoutFaces = async () => {
@ -94,9 +98,23 @@
isShowLoadingPeople = false; isShowLoadingPeople = false;
} }
/*
* we wait for the server to create the feature photo for:
* - people which has been reassigned to a new person
* - faces removed assigned to a new person
*
* if after 15 seconds the server has not generated the feature photos,
* we go back to the detail-panel
*/
const onPersonThumbnail = (personId: string) => { const onPersonThumbnail = (personId: string) => {
assetFaceGenerated.push(personId); assetFaceGenerated.push(personId);
if (isEqual(assetFaceGenerated, peopleToCreate) && loaderLoadingDoneTimeout && automaticRefreshTimeout) {
if (
isEqual(assetFaceGenerated, peopleToCreate) &&
isEqual(assetFaceGenerated, mapFacesToBeCreated) &&
loaderLoadingDoneTimeout &&
automaticRefreshTimeout
) {
clearTimeout(loaderLoadingDoneTimeout); clearTimeout(loaderLoadingDoneTimeout);
clearTimeout(automaticRefreshTimeout); clearTimeout(automaticRefreshTimeout);
onRefresh(); onRefresh();

View file

@ -45,7 +45,7 @@ export function getAltText(asset: AssetResponseDto) {
altText += ` in ${asset.exifInfo.city}, ${asset.exifInfo.country}`; altText += ` in ${asset.exifInfo.city}, ${asset.exifInfo.country}`;
} }
const names = asset.people?.faces.filter((p) => p.name).map((p) => p.name) ?? []; const names = asset.people?.visiblePeople.filter((p) => p.name).map((p) => p.name) ?? [];
if (names.length == 1) { if (names.length == 1) {
altText += ` with ${names[0]}`; altText += ` with ${names[0]}`;
} }