mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 05:34:32 -04:00
feat(web): support searching by EXIF rating (#16208)
* Add rating to search DTO * Add search by EXIF rating in search query builder * Generate OpenAPI spec * Add rating filter on web * Add rating filter to search docs * Format / lint * Hide rating filter if ratings are disabled * chore: component order in form --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
f6ba071569
commit
34b88bb47a
@ -31,6 +31,7 @@ The filters smart search allows you to search by include:
|
|||||||
- Not in any album
|
- Not in any album
|
||||||
- Archived
|
- Archived
|
||||||
- Favorited
|
- Favorited
|
||||||
|
- Rating
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabItem value="Computer" label="Computer" default>
|
<TabItem value="Computer" label="Computer" default>
|
||||||
|
@ -1134,6 +1134,7 @@
|
|||||||
"search_timezone": "Search timezone...",
|
"search_timezone": "Search timezone...",
|
||||||
"search_type": "Search type",
|
"search_type": "Search type",
|
||||||
"search_your_photos": "Search your photos",
|
"search_your_photos": "Search your photos",
|
||||||
|
"search_rating": "Search by rating...",
|
||||||
"searching_locales": "Searching locales...",
|
"searching_locales": "Searching locales...",
|
||||||
"second": "Second",
|
"second": "Second",
|
||||||
"see_all_people": "See all people",
|
"see_all_people": "See all people",
|
||||||
|
21
mobile/openapi/lib/model/metadata_search_dto.dart
generated
21
mobile/openapi/lib/model/metadata_search_dto.dart
generated
@ -40,6 +40,7 @@ class MetadataSearchDto {
|
|||||||
this.page,
|
this.page,
|
||||||
this.personIds = const [],
|
this.personIds = const [],
|
||||||
this.previewPath,
|
this.previewPath,
|
||||||
|
this.rating,
|
||||||
this.size,
|
this.size,
|
||||||
this.state,
|
this.state,
|
||||||
this.tagIds = const [],
|
this.tagIds = const [],
|
||||||
@ -233,6 +234,16 @@ class MetadataSearchDto {
|
|||||||
///
|
///
|
||||||
String? previewPath;
|
String? previewPath;
|
||||||
|
|
||||||
|
/// Minimum value: -1
|
||||||
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
num? rating;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
/// Maximum value: 1000
|
/// Maximum value: 1000
|
||||||
///
|
///
|
||||||
@ -374,6 +385,7 @@ class MetadataSearchDto {
|
|||||||
other.page == page &&
|
other.page == page &&
|
||||||
_deepEquality.equals(other.personIds, personIds) &&
|
_deepEquality.equals(other.personIds, personIds) &&
|
||||||
other.previewPath == previewPath &&
|
other.previewPath == previewPath &&
|
||||||
|
other.rating == rating &&
|
||||||
other.size == size &&
|
other.size == size &&
|
||||||
other.state == state &&
|
other.state == state &&
|
||||||
_deepEquality.equals(other.tagIds, tagIds) &&
|
_deepEquality.equals(other.tagIds, tagIds) &&
|
||||||
@ -421,6 +433,7 @@ class MetadataSearchDto {
|
|||||||
(page == null ? 0 : page!.hashCode) +
|
(page == null ? 0 : page!.hashCode) +
|
||||||
(personIds.hashCode) +
|
(personIds.hashCode) +
|
||||||
(previewPath == null ? 0 : previewPath!.hashCode) +
|
(previewPath == null ? 0 : previewPath!.hashCode) +
|
||||||
|
(rating == null ? 0 : rating!.hashCode) +
|
||||||
(size == null ? 0 : size!.hashCode) +
|
(size == null ? 0 : size!.hashCode) +
|
||||||
(state == null ? 0 : state!.hashCode) +
|
(state == null ? 0 : state!.hashCode) +
|
||||||
(tagIds.hashCode) +
|
(tagIds.hashCode) +
|
||||||
@ -439,7 +452,7 @@ class MetadataSearchDto {
|
|||||||
(withStacked == null ? 0 : withStacked!.hashCode);
|
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -570,6 +583,11 @@ class MetadataSearchDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'previewPath'] = null;
|
// json[r'previewPath'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.rating != null) {
|
||||||
|
json[r'rating'] = this.rating;
|
||||||
|
} else {
|
||||||
|
// json[r'rating'] = null;
|
||||||
|
}
|
||||||
if (this.size != null) {
|
if (this.size != null) {
|
||||||
json[r'size'] = this.size;
|
json[r'size'] = this.size;
|
||||||
} else {
|
} else {
|
||||||
@ -683,6 +701,7 @@ class MetadataSearchDto {
|
|||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
previewPath: mapValueOfType<String>(json, r'previewPath'),
|
||||||
|
rating: num.parse('${json[r'rating']}'),
|
||||||
size: num.parse('${json[r'size']}'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
|
21
mobile/openapi/lib/model/random_search_dto.dart
generated
21
mobile/openapi/lib/model/random_search_dto.dart
generated
@ -30,6 +30,7 @@ class RandomSearchDto {
|
|||||||
this.make,
|
this.make,
|
||||||
this.model,
|
this.model,
|
||||||
this.personIds = const [],
|
this.personIds = const [],
|
||||||
|
this.rating,
|
||||||
this.size,
|
this.size,
|
||||||
this.state,
|
this.state,
|
||||||
this.tagIds = const [],
|
this.tagIds = const [],
|
||||||
@ -147,6 +148,16 @@ class RandomSearchDto {
|
|||||||
|
|
||||||
List<String> personIds;
|
List<String> personIds;
|
||||||
|
|
||||||
|
/// Minimum value: -1
|
||||||
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
num? rating;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
/// Maximum value: 1000
|
/// Maximum value: 1000
|
||||||
///
|
///
|
||||||
@ -270,6 +281,7 @@ class RandomSearchDto {
|
|||||||
other.make == make &&
|
other.make == make &&
|
||||||
other.model == model &&
|
other.model == model &&
|
||||||
_deepEquality.equals(other.personIds, personIds) &&
|
_deepEquality.equals(other.personIds, personIds) &&
|
||||||
|
other.rating == rating &&
|
||||||
other.size == size &&
|
other.size == size &&
|
||||||
other.state == state &&
|
other.state == state &&
|
||||||
_deepEquality.equals(other.tagIds, tagIds) &&
|
_deepEquality.equals(other.tagIds, tagIds) &&
|
||||||
@ -306,6 +318,7 @@ class RandomSearchDto {
|
|||||||
(make == null ? 0 : make!.hashCode) +
|
(make == null ? 0 : make!.hashCode) +
|
||||||
(model == null ? 0 : model!.hashCode) +
|
(model == null ? 0 : model!.hashCode) +
|
||||||
(personIds.hashCode) +
|
(personIds.hashCode) +
|
||||||
|
(rating == null ? 0 : rating!.hashCode) +
|
||||||
(size == null ? 0 : size!.hashCode) +
|
(size == null ? 0 : size!.hashCode) +
|
||||||
(state == null ? 0 : state!.hashCode) +
|
(state == null ? 0 : state!.hashCode) +
|
||||||
(tagIds.hashCode) +
|
(tagIds.hashCode) +
|
||||||
@ -323,7 +336,7 @@ class RandomSearchDto {
|
|||||||
(withStacked == null ? 0 : withStacked!.hashCode);
|
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'RandomSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
String toString() => 'RandomSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -408,6 +421,11 @@ class RandomSearchDto {
|
|||||||
// json[r'model'] = null;
|
// json[r'model'] = null;
|
||||||
}
|
}
|
||||||
json[r'personIds'] = this.personIds;
|
json[r'personIds'] = this.personIds;
|
||||||
|
if (this.rating != null) {
|
||||||
|
json[r'rating'] = this.rating;
|
||||||
|
} else {
|
||||||
|
// json[r'rating'] = null;
|
||||||
|
}
|
||||||
if (this.size != null) {
|
if (this.size != null) {
|
||||||
json[r'size'] = this.size;
|
json[r'size'] = this.size;
|
||||||
} else {
|
} else {
|
||||||
@ -506,6 +524,7 @@ class RandomSearchDto {
|
|||||||
personIds: json[r'personIds'] is Iterable
|
personIds: json[r'personIds'] is Iterable
|
||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
|
rating: num.parse('${json[r'rating']}'),
|
||||||
size: num.parse('${json[r'size']}'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
|
21
mobile/openapi/lib/model/smart_search_dto.dart
generated
21
mobile/openapi/lib/model/smart_search_dto.dart
generated
@ -32,6 +32,7 @@ class SmartSearchDto {
|
|||||||
this.page,
|
this.page,
|
||||||
this.personIds = const [],
|
this.personIds = const [],
|
||||||
required this.query,
|
required this.query,
|
||||||
|
this.rating,
|
||||||
this.size,
|
this.size,
|
||||||
this.state,
|
this.state,
|
||||||
this.tagIds = const [],
|
this.tagIds = const [],
|
||||||
@ -158,6 +159,16 @@ class SmartSearchDto {
|
|||||||
|
|
||||||
String query;
|
String query;
|
||||||
|
|
||||||
|
/// Minimum value: -1
|
||||||
|
/// Maximum value: 5
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
num? rating;
|
||||||
|
|
||||||
/// Minimum value: 1
|
/// Minimum value: 1
|
||||||
/// Maximum value: 1000
|
/// Maximum value: 1000
|
||||||
///
|
///
|
||||||
@ -267,6 +278,7 @@ class SmartSearchDto {
|
|||||||
other.page == page &&
|
other.page == page &&
|
||||||
_deepEquality.equals(other.personIds, personIds) &&
|
_deepEquality.equals(other.personIds, personIds) &&
|
||||||
other.query == query &&
|
other.query == query &&
|
||||||
|
other.rating == rating &&
|
||||||
other.size == size &&
|
other.size == size &&
|
||||||
other.state == state &&
|
other.state == state &&
|
||||||
_deepEquality.equals(other.tagIds, tagIds) &&
|
_deepEquality.equals(other.tagIds, tagIds) &&
|
||||||
@ -303,6 +315,7 @@ class SmartSearchDto {
|
|||||||
(page == null ? 0 : page!.hashCode) +
|
(page == null ? 0 : page!.hashCode) +
|
||||||
(personIds.hashCode) +
|
(personIds.hashCode) +
|
||||||
(query.hashCode) +
|
(query.hashCode) +
|
||||||
|
(rating == null ? 0 : rating!.hashCode) +
|
||||||
(size == null ? 0 : size!.hashCode) +
|
(size == null ? 0 : size!.hashCode) +
|
||||||
(state == null ? 0 : state!.hashCode) +
|
(state == null ? 0 : state!.hashCode) +
|
||||||
(tagIds.hashCode) +
|
(tagIds.hashCode) +
|
||||||
@ -318,7 +331,7 @@ class SmartSearchDto {
|
|||||||
(withExif == null ? 0 : withExif!.hashCode);
|
(withExif == null ? 0 : withExif!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]';
|
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -409,6 +422,11 @@ class SmartSearchDto {
|
|||||||
}
|
}
|
||||||
json[r'personIds'] = this.personIds;
|
json[r'personIds'] = this.personIds;
|
||||||
json[r'query'] = this.query;
|
json[r'query'] = this.query;
|
||||||
|
if (this.rating != null) {
|
||||||
|
json[r'rating'] = this.rating;
|
||||||
|
} else {
|
||||||
|
// json[r'rating'] = null;
|
||||||
|
}
|
||||||
if (this.size != null) {
|
if (this.size != null) {
|
||||||
json[r'size'] = this.size;
|
json[r'size'] = this.size;
|
||||||
} else {
|
} else {
|
||||||
@ -499,6 +517,7 @@ class SmartSearchDto {
|
|||||||
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
query: mapValueOfType<String>(json, r'query')!,
|
query: mapValueOfType<String>(json, r'query')!,
|
||||||
|
rating: num.parse('${json[r'rating']}'),
|
||||||
size: num.parse('${json[r'size']}'),
|
size: num.parse('${json[r'size']}'),
|
||||||
state: mapValueOfType<String>(json, r'state'),
|
state: mapValueOfType<String>(json, r'state'),
|
||||||
tagIds: json[r'tagIds'] is Iterable
|
tagIds: json[r'tagIds'] is Iterable
|
||||||
|
@ -9956,6 +9956,11 @@
|
|||||||
"previewPath": {
|
"previewPath": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"rating": {
|
||||||
|
"maximum": 5,
|
||||||
|
"minimum": -1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
@ -10613,6 +10618,11 @@
|
|||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
"rating": {
|
||||||
|
"maximum": 5,
|
||||||
|
"minimum": -1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
@ -11563,6 +11573,11 @@
|
|||||||
"query": {
|
"query": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"rating": {
|
||||||
|
"maximum": 5,
|
||||||
|
"minimum": -1,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"maximum": 1000,
|
"maximum": 1000,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
|
@ -811,6 +811,7 @@ export type MetadataSearchDto = {
|
|||||||
page?: number;
|
page?: number;
|
||||||
personIds?: string[];
|
personIds?: string[];
|
||||||
previewPath?: string;
|
previewPath?: string;
|
||||||
|
rating?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
state?: string | null;
|
state?: string | null;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
@ -878,6 +879,7 @@ export type RandomSearchDto = {
|
|||||||
make?: string;
|
make?: string;
|
||||||
model?: string | null;
|
model?: string | null;
|
||||||
personIds?: string[];
|
personIds?: string[];
|
||||||
|
rating?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
state?: string | null;
|
state?: string | null;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
@ -914,6 +916,7 @@ export type SmartSearchDto = {
|
|||||||
page?: number;
|
page?: number;
|
||||||
personIds?: string[];
|
personIds?: string[];
|
||||||
query: string;
|
query: string;
|
||||||
|
rating?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
state?: string | null;
|
state?: string | null;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
|
@ -114,6 +114,12 @@ class BaseSearchDto {
|
|||||||
|
|
||||||
@ValidateUUID({ each: true, optional: true })
|
@ValidateUUID({ each: true, optional: true })
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@IsInt()
|
||||||
|
@Max(5)
|
||||||
|
@Min(-1)
|
||||||
|
rating?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RandomSearchDto extends BaseSearchDto {
|
export class RandomSearchDto extends BaseSearchDto {
|
||||||
|
@ -387,6 +387,11 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
|
|||||||
.innerJoin('exif', 'assets.id', 'exif.assetId')
|
.innerJoin('exif', 'assets.id', 'exif.assetId')
|
||||||
.where('exif.lensModel', options.lensModel === null ? 'is' : '=', options.lensModel!),
|
.where('exif.lensModel', options.lensModel === null ? 'is' : '=', options.lensModel!),
|
||||||
)
|
)
|
||||||
|
.$if(options.rating !== undefined, (qb) =>
|
||||||
|
qb
|
||||||
|
.innerJoin('exif', 'assets.id', 'exif.assetId')
|
||||||
|
.where('exif.rating', options.rating === null ? 'is' : '=', options.rating!),
|
||||||
|
)
|
||||||
.$if(!!options.checksum, (qb) => qb.where('assets.checksum', '=', options.checksum!))
|
.$if(!!options.checksum, (qb) => qb.where('assets.checksum', '=', options.checksum!))
|
||||||
.$if(!!options.deviceAssetId, (qb) => qb.where('assets.deviceAssetId', '=', options.deviceAssetId!))
|
.$if(!!options.deviceAssetId, (qb) => qb.where('assets.deviceAssetId', '=', options.deviceAssetId!))
|
||||||
.$if(!!options.deviceId, (qb) => qb.where('assets.deviceId', '=', options.deviceId!))
|
.$if(!!options.deviceId, (qb) => qb.where('assets.deviceId', '=', options.deviceId!))
|
||||||
|
@ -109,6 +109,7 @@ export interface SearchExifOptions {
|
|||||||
model?: string | null;
|
model?: string | null;
|
||||||
state?: string | null;
|
state?: string | null;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
|
rating?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchEmbeddingOptions {
|
export interface SearchEmbeddingOptions {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
date: SearchDateFilter;
|
date: SearchDateFilter;
|
||||||
display: SearchDisplayFilters;
|
display: SearchDisplayFilters;
|
||||||
mediaType: MediaType;
|
mediaType: MediaType;
|
||||||
|
rating?: number;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -26,6 +27,7 @@
|
|||||||
import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte';
|
import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte';
|
||||||
import SearchDateSection from './search-date-section.svelte';
|
import SearchDateSection from './search-date-section.svelte';
|
||||||
import SearchMediaSection from './search-media-section.svelte';
|
import SearchMediaSection from './search-media-section.svelte';
|
||||||
|
import SearchRatingsSection from './search-ratings-section.svelte';
|
||||||
import { parseUtcDate } from '$lib/utils/date-time';
|
import { parseUtcDate } from '$lib/utils/date-time';
|
||||||
import SearchDisplaySection from './search-display-section.svelte';
|
import SearchDisplaySection from './search-display-section.svelte';
|
||||||
import SearchTextSection from './search-text-section.svelte';
|
import SearchTextSection from './search-text-section.svelte';
|
||||||
@ -34,6 +36,7 @@
|
|||||||
import { mdiTune } from '@mdi/js';
|
import { mdiTune } from '@mdi/js';
|
||||||
import { generateId } from '$lib/utils/generate-id';
|
import { generateId } from '$lib/utils/generate-id';
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchQuery: MetadataSearchDto | SmartSearchDto;
|
searchQuery: MetadataSearchDto | SmartSearchDto;
|
||||||
@ -81,6 +84,7 @@
|
|||||||
: searchQuery.type === AssetTypeEnum.Video
|
: searchQuery.type === AssetTypeEnum.Video
|
||||||
? MediaType.Video
|
? MediaType.Video
|
||||||
: MediaType.All,
|
: MediaType.All,
|
||||||
|
rating: searchQuery.rating,
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
@ -94,6 +98,7 @@
|
|||||||
date: {},
|
date: {},
|
||||||
display: {},
|
display: {},
|
||||||
mediaType: MediaType.All,
|
mediaType: MediaType.All,
|
||||||
|
rating: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,6 +129,7 @@
|
|||||||
personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined,
|
personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined,
|
||||||
tagIds: filter.tagIds.size > 0 ? [...filter.tagIds] : undefined,
|
tagIds: filter.tagIds.size > 0 ? [...filter.tagIds] : undefined,
|
||||||
type,
|
type,
|
||||||
|
rating: filter.rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearch(payload);
|
onSearch(payload);
|
||||||
@ -161,6 +167,11 @@
|
|||||||
<!-- DATE RANGE -->
|
<!-- DATE RANGE -->
|
||||||
<SearchDateSection bind:filters={filter.date} />
|
<SearchDateSection bind:filters={filter.date} />
|
||||||
|
|
||||||
|
<!-- RATING -->
|
||||||
|
{#if $preferences?.ratings.enabled}
|
||||||
|
<SearchRatingsSection bind:rating={filter.rating} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="grid md:grid-cols-2 gap-x-5 gap-y-10">
|
<div class="grid md:grid-cols-2 gap-x-5 gap-y-10">
|
||||||
<!-- MEDIA TYPE -->
|
<!-- MEDIA TYPE -->
|
||||||
<SearchMediaSection bind:filteredMedia={filter.mediaType} />
|
<SearchMediaSection bind:filteredMedia={filter.mediaType} />
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import Combobox from '../combobox.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
rating?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { rating = $bindable() }: Props = $props();
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ value: '0', label: $t('rating_count', { values: { count: 0 } }) },
|
||||||
|
{ value: '1', label: $t('rating_count', { values: { count: 1 } }) },
|
||||||
|
{ value: '2', label: $t('rating_count', { values: { count: 2 } }) },
|
||||||
|
{ value: '3', label: $t('rating_count', { values: { count: 3 } }) },
|
||||||
|
{ value: '4', label: $t('rating_count', { values: { count: 4 } }) },
|
||||||
|
{ value: '5', label: $t('rating_count', { values: { count: 5 } }) },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="grid grid-auto-fit-40 gap-5">
|
||||||
|
<label class="immich-form-label" for="start-date">
|
||||||
|
<Combobox
|
||||||
|
label={$t('rating').toUpperCase()}
|
||||||
|
placeholder={$t('search_rating')}
|
||||||
|
{options}
|
||||||
|
selectedOption={rating === undefined ? undefined : options[rating]}
|
||||||
|
onSelect={(r) => (rating = r === undefined ? undefined : Number.parseInt(r.value))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
Loading…
x
Reference in New Issue
Block a user