Compare commits

...

1 commit

Author SHA1 Message Date
Alex
8cc6993926
feat(web): Show face box 2023-08-19 14:44:26 -04:00
16 changed files with 424 additions and 11 deletions

View file

@ -1376,6 +1376,49 @@ export interface ExifResponseDto {
*/
'timeZone'?: string | null;
}
/**
*
* @export
* @interface FaceGeometryDto
*/
export interface FaceGeometryDto {
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxX1': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxX2': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxY1': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxY2': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'imageHeight': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'imageWidth': number;
}
/**
*
* @export
@ -1883,6 +1926,12 @@ export interface PersonResponseDto {
* @memberof PersonResponseDto
*/
'birthDate': string | null;
/**
*
* @type {FaceGeometryDto}
* @memberof PersonResponseDto
*/
'geometry'?: FaceGeometryDto;
/**
*
* @type {string}

View file

@ -51,6 +51,7 @@ doc/DownloadArchiveInfo.md
doc/DownloadInfoDto.md
doc/DownloadResponseDto.md
doc/ExifResponseDto.md
doc/FaceGeometryDto.md
doc/ImportAssetDto.md
doc/JobApi.md
doc/JobCommand.md
@ -197,6 +198,7 @@ lib/model/download_archive_info.dart
lib/model/download_info_dto.dart
lib/model/download_response_dto.dart
lib/model/exif_response_dto.dart
lib/model/face_geometry_dto.dart
lib/model/import_asset_dto.dart
lib/model/job_command.dart
lib/model/job_command_dto.dart
@ -314,6 +316,7 @@ test/download_archive_info_test.dart
test/download_info_dto_test.dart
test/download_response_dto_test.dart
test/exif_response_dto_test.dart
test/face_geometry_dto_test.dart
test/import_asset_dto_test.dart
test/job_api_test.dart
test/job_command_dto_test.dart

View file

@ -225,6 +225,7 @@ Class | Method | HTTP request | Description
- [DownloadInfoDto](doc//DownloadInfoDto.md)
- [DownloadResponseDto](doc//DownloadResponseDto.md)
- [ExifResponseDto](doc//ExifResponseDto.md)
- [FaceGeometryDto](doc//FaceGeometryDto.md)
- [ImportAssetDto](doc//ImportAssetDto.md)
- [JobCommand](doc//JobCommand.md)
- [JobCommandDto](doc//JobCommandDto.md)

20
mobile/openapi/doc/FaceGeometryDto.md generated Normal file
View file

@ -0,0 +1,20 @@
# openapi.model.FaceGeometryDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**boundingBoxX1** | **int** | |
**boundingBoxX2** | **int** | |
**boundingBoxY1** | **int** | |
**boundingBoxY2** | **int** | |
**imageHeight** | **int** | |
**imageWidth** | **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)

View file

@ -9,6 +9,7 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**birthDate** | [**DateTime**](DateTime.md) | |
**geometry** | [**FaceGeometryDto**](FaceGeometryDto.md) | | [optional]
**id** | **String** | |
**isHidden** | **bool** | |
**name** | **String** | |

View file

@ -87,6 +87,7 @@ part 'model/download_archive_info.dart';
part 'model/download_info_dto.dart';
part 'model/download_response_dto.dart';
part 'model/exif_response_dto.dart';
part 'model/face_geometry_dto.dart';
part 'model/import_asset_dto.dart';
part 'model/job_command.dart';
part 'model/job_command_dto.dart';

View file

@ -269,6 +269,8 @@ class ApiClient {
return DownloadResponseDto.fromJson(value);
case 'ExifResponseDto':
return ExifResponseDto.fromJson(value);
case 'FaceGeometryDto':
return FaceGeometryDto.fromJson(value);
case 'ImportAssetDto':
return ImportAssetDto.fromJson(value);
case 'JobCommand':

View file

@ -0,0 +1,138 @@
//
// 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 FaceGeometryDto {
/// Returns a new [FaceGeometryDto] instance.
FaceGeometryDto({
required this.boundingBoxX1,
required this.boundingBoxX2,
required this.boundingBoxY1,
required this.boundingBoxY2,
required this.imageHeight,
required this.imageWidth,
});
int boundingBoxX1;
int boundingBoxX2;
int boundingBoxY1;
int boundingBoxY2;
int imageHeight;
int imageWidth;
@override
bool operator ==(Object other) => identical(this, other) || other is FaceGeometryDto &&
other.boundingBoxX1 == boundingBoxX1 &&
other.boundingBoxX2 == boundingBoxX2 &&
other.boundingBoxY1 == boundingBoxY1 &&
other.boundingBoxY2 == boundingBoxY2 &&
other.imageHeight == imageHeight &&
other.imageWidth == imageWidth;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(boundingBoxX1.hashCode) +
(boundingBoxX2.hashCode) +
(boundingBoxY1.hashCode) +
(boundingBoxY2.hashCode) +
(imageHeight.hashCode) +
(imageWidth.hashCode);
@override
String toString() => 'FaceGeometryDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, imageHeight=$imageHeight, imageWidth=$imageWidth]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'boundingBoxX1'] = this.boundingBoxX1;
json[r'boundingBoxX2'] = this.boundingBoxX2;
json[r'boundingBoxY1'] = this.boundingBoxY1;
json[r'boundingBoxY2'] = this.boundingBoxY2;
json[r'imageHeight'] = this.imageHeight;
json[r'imageWidth'] = this.imageWidth;
return json;
}
/// Returns a new [FaceGeometryDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static FaceGeometryDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return FaceGeometryDto(
boundingBoxX1: mapValueOfType<int>(json, r'boundingBoxX1')!,
boundingBoxX2: mapValueOfType<int>(json, r'boundingBoxX2')!,
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
);
}
return null;
}
static List<FaceGeometryDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <FaceGeometryDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = FaceGeometryDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, FaceGeometryDto> mapFromJson(dynamic json) {
final map = <String, FaceGeometryDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = FaceGeometryDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of FaceGeometryDto-objects as value to a dart map
static Map<String, List<FaceGeometryDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<FaceGeometryDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = FaceGeometryDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'boundingBoxX1',
'boundingBoxX2',
'boundingBoxY1',
'boundingBoxY2',
'imageHeight',
'imageWidth',
};
}

View file

@ -14,6 +14,7 @@ class PersonResponseDto {
/// Returns a new [PersonResponseDto] instance.
PersonResponseDto({
required this.birthDate,
this.geometry,
required this.id,
required this.isHidden,
required this.name,
@ -22,6 +23,14 @@ class PersonResponseDto {
DateTime? birthDate;
///
/// 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.
///
FaceGeometryDto? geometry;
String id;
bool isHidden;
@ -33,6 +42,7 @@ class PersonResponseDto {
@override
bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto &&
other.birthDate == birthDate &&
other.geometry == geometry &&
other.id == id &&
other.isHidden == isHidden &&
other.name == name &&
@ -42,13 +52,14 @@ class PersonResponseDto {
int get hashCode =>
// ignore: unnecessary_parenthesis
(birthDate == null ? 0 : birthDate!.hashCode) +
(geometry == null ? 0 : geometry!.hashCode) +
(id.hashCode) +
(isHidden.hashCode) +
(name.hashCode) +
(thumbnailPath.hashCode);
@override
String toString() => 'PersonResponseDto[birthDate=$birthDate, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]';
String toString() => 'PersonResponseDto[birthDate=$birthDate, geometry=$geometry, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -56,6 +67,11 @@ class PersonResponseDto {
json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc());
} else {
// json[r'birthDate'] = null;
}
if (this.geometry != null) {
json[r'geometry'] = this.geometry;
} else {
// json[r'geometry'] = null;
}
json[r'id'] = this.id;
json[r'isHidden'] = this.isHidden;
@ -73,6 +89,7 @@ class PersonResponseDto {
return PersonResponseDto(
birthDate: mapDateTime(json, r'birthDate', ''),
geometry: FaceGeometryDto.fromJson(json[r'geometry']),
id: mapValueOfType<String>(json, r'id')!,
isHidden: mapValueOfType<bool>(json, r'isHidden')!,
name: mapValueOfType<String>(json, r'name')!,

View file

@ -10,7 +10,7 @@ environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
http: '>=0.13.0 <0.14.0'
intl: '^0.18.0'
intl: '^0.17.0'
meta: '^1.1.8'
dev_dependencies:
test: '>=1.16.0 <1.18.0'

View file

@ -0,0 +1,52 @@
//
// 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 FaceGeometryDto
void main() {
// final instance = FaceGeometryDto();
group('test FaceGeometryDto', () {
// int boundingBoxX1
test('to test the property `boundingBoxX1`', () async {
// TODO
});
// int boundingBoxX2
test('to test the property `boundingBoxX2`', () async {
// TODO
});
// int boundingBoxY1
test('to test the property `boundingBoxY1`', () async {
// TODO
});
// int boundingBoxY2
test('to test the property `boundingBoxY2`', () async {
// TODO
});
// int imageHeight
test('to test the property `imageHeight`', () async {
// TODO
});
// int imageWidth
test('to test the property `imageWidth`', () async {
// TODO
});
});
}

View file

@ -21,6 +21,11 @@ void main() {
// TODO
});
// FaceGeometryDto geometry
test('to test the property `geometry`', () async {
// TODO
});
// String id
test('to test the property `id`', () async {
// TODO

View file

@ -5814,6 +5814,37 @@
},
"type": "object"
},
"FaceGeometryDto": {
"properties": {
"boundingBoxX1": {
"type": "integer"
},
"boundingBoxX2": {
"type": "integer"
},
"boundingBoxY1": {
"type": "integer"
},
"boundingBoxY2": {
"type": "integer"
},
"imageHeight": {
"type": "integer"
},
"imageWidth": {
"type": "integer"
}
},
"required": [
"imageWidth",
"imageHeight",
"boundingBoxX1",
"boundingBoxY1",
"boundingBoxX2",
"boundingBoxY2"
],
"type": "object"
},
"ImportAssetDto": {
"properties": {
"assetPath": {
@ -6211,6 +6242,9 @@
"nullable": true,
"type": "string"
},
"geometry": {
"$ref": "#/components/schemas/FaceGeometryDto"
},
"id": {
"type": "string"
},

View file

@ -103,6 +103,21 @@ export class PersonSearchDto {
withHidden?: boolean = false;
}
export class FaceGeometryDto {
@ApiProperty({ type: 'integer' })
imageWidth!: number;
@ApiProperty({ type: 'integer' })
imageHeight!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX2!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY2!: number;
}
export class PersonResponseDto {
id!: string;
name!: string;
@ -110,6 +125,7 @@ export class PersonResponseDto {
birthDate!: Date | null;
thumbnailPath!: string;
isHidden!: boolean;
geometry?: FaceGeometryDto;
}
export class PeopleResponseDto {
@ -132,6 +148,23 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
};
}
export function mapFace(face: AssetFaceEntity): PersonResponseDto {
return mapPerson(face.person);
export function mapGeometry(entity: AssetFaceEntity) {
return {
imageWidth: entity.imageWidth,
imageHeight: entity.imageHeight,
boundingBoxX1: entity.boundingBoxX1,
boundingBoxY1: entity.boundingBoxY1,
boundingBoxX2: entity.boundingBoxX2,
boundingBoxY2: entity.boundingBoxY2,
};
}
export function mapFace(entity: AssetFaceEntity): PersonResponseDto {
return {
id: entity.person.id,
name: entity.person.name,
birthDate: entity.person.birthDate,
thumbnailPath: entity.person.thumbnailPath,
isHidden: entity.person.isHidden,
geometry: mapGeometry(entity),
};
}

View file

@ -1376,6 +1376,49 @@ export interface ExifResponseDto {
*/
'timeZone'?: string | null;
}
/**
*
* @export
* @interface FaceGeometryDto
*/
export interface FaceGeometryDto {
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxX1': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxX2': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxY1': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'boundingBoxY2': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'imageHeight': number;
/**
*
* @type {number}
* @memberof FaceGeometryDto
*/
'imageWidth': number;
}
/**
*
* @export
@ -1883,6 +1926,12 @@ export interface PersonResponseDto {
* @memberof PersonResponseDto
*/
'birthDate': string | null;
/**
*
* @type {FaceGeometryDto}
* @memberof PersonResponseDto
*/
'geometry'?: FaceGeometryDto;
/**
*
* @type {string}

View file

@ -77,23 +77,23 @@
};
</script>
<section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
<section class="dark:bg-immich-dark-bg dark:text-immich-dark-fg p-2">
<div class="flex place-items-center gap-2">
<button
class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
class="dark:text-immich-dark-fg flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:hover:bg-gray-900"
on:click={() => dispatch('close')}
>
<Close size="24" />
</button>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
<p class="text-immich-fg dark:text-immich-dark-fg text-lg">Info</p>
</div>
<section class="mx-4 mt-10">
<textarea
bind:this={textarea}
class="max-h-[500px]
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
class="focus:border-immich-primary
dark:focus:border-immich-dark-primary max-h-[500px] w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 disabled:border-none dark:text-white"
placeholder={$page?.data?.user?.id !== asset.ownerId ? '' : 'Add a description'}
style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == '' ? 'none' : 'block'}
on:focusin={handleFocusIn}
@ -110,7 +110,15 @@
<div class="mt-4 flex flex-wrap gap-2">
{#each people as person (person.id)}
<a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
<a
href="/people/{person.id}"
class="w-[90px]"
on:click={() => dispatch('close-viewer')}
on:mouseover={() => {
console.log(asset);
}}
on:focus={() => console.log('focus')}
>
<ImageThumbnail
curve
shadow
@ -262,7 +270,7 @@
</div>
{/if}
<section class="p-2 dark:text-immich-dark-fg">
<section class="dark:text-immich-dark-fg p-2">
<div class="px-4 py-4">
{#if albums.length > 0}
<p class="pb-4 text-sm">APPEARS IN</p>