mirror of
https://github.com/immich-app/immich.git
synced 2025-12-16 09:13:13 +03:00
Compare commits
3 Commits
feat/locat
...
feature/th
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4568e55f3f | ||
|
|
35eda735c8 | ||
|
|
8f7a71d1cf |
@@ -1113,8 +1113,6 @@
|
||||
"failed_to_load_folder": "Failed to load folder",
|
||||
"favorite": "Favorite",
|
||||
"favorite_action_prompt": "{count} added to Favorites",
|
||||
"favorite_locations": "Favorite Locations",
|
||||
"favorite_locations_not_found": "No favorite locations saved",
|
||||
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
||||
"favorites": "Favorites",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
|
||||
@@ -47,7 +47,7 @@ class LikeActivityActionButton extends ConsumerWidget {
|
||||
|
||||
return BaseActionButton(
|
||||
maxWidth: 60,
|
||||
iconData: liked != null ? Icons.favorite : Icons.favorite_border,
|
||||
iconData: liked != null ? Icons.thumb_up : Icons.thumb_up_off_alt,
|
||||
label: "like".t(context: context),
|
||||
onPressed: () => onTap(liked),
|
||||
iconOnly: iconOnly,
|
||||
@@ -57,7 +57,7 @@ class LikeActivityActionButton extends ConsumerWidget {
|
||||
|
||||
// default to empty heart during loading
|
||||
loading: () => BaseActionButton(
|
||||
iconData: Icons.favorite_border,
|
||||
iconData: Icons.thumb_up_off_alt,
|
||||
label: "like".t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
|
||||
@@ -68,11 +68,11 @@ class ActivityTextField extends HookConsumerWidget {
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: IconButton(
|
||||
icon: Icon(liked ? Icons.favorite_rounded : Icons.favorite_border_rounded),
|
||||
icon: Icon(liked ? Icons.thumb_up : Icons.thumb_up_off_alt),
|
||||
onPressed: liked ? removeLike : addLike,
|
||||
),
|
||||
),
|
||||
suffixIconColor: liked ? Colors.red[700] : null,
|
||||
suffixIconColor: liked ? Colors.blue[700] : null,
|
||||
hintText: !isEnabled ? 'shared_album_activities_input_disable'.tr() : 'say_something'.tr(),
|
||||
hintStyle: TextStyle(fontWeight: FontWeight.normal, fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
|
||||
@@ -37,7 +37,7 @@ class ActivityTile extends HookConsumerWidget {
|
||||
? Container(
|
||||
width: isBottomSheet ? 30 : 44,
|
||||
alignment: Alignment.center,
|
||||
child: Icon(Icons.favorite_rounded, color: Colors.red[700]),
|
||||
child: Icon(Icons.thumb_up, color: Colors.blue[700]),
|
||||
)
|
||||
: isBottomSheet
|
||||
? UserCircleAvatar(user: activity.user, size: 30, radius: 15)
|
||||
|
||||
@@ -68,7 +68,7 @@ class CommentBubble extends ConsumerWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
|
||||
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
|
||||
child: Icon(Icons.thumb_up, color: Colors.blue[600], size: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -82,7 +82,7 @@ class CommentBubble extends ConsumerWidget {
|
||||
likes = Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.7), shape: BoxShape.circle),
|
||||
child: Icon(Icons.favorite, color: Colors.red[600], size: 18),
|
||||
child: Icon(Icons.thumb_up, color: Colors.blue[600], size: 18),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
7
mobile/openapi/README.md
generated
7
mobile/openapi/README.md
generated
@@ -163,12 +163,8 @@ Class | Method | HTTP request | Description
|
||||
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings
|
||||
*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode
|
||||
*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode
|
||||
*MapApi* | [**createFavoriteLocation**](doc//MapApi.md#createfavoritelocation) | **POST** /map/favorite-locations | Create favorite location
|
||||
*MapApi* | [**deleteFavoriteLocation**](doc//MapApi.md#deletefavoritelocation) | **DELETE** /map/favorite-locations/{id} | Delete favorite location
|
||||
*MapApi* | [**getFavoriteLocations**](doc//MapApi.md#getfavoritelocations) | **GET** /map/favorite-locations | Get favorite locations
|
||||
*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers
|
||||
*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | Reverse geocode coordinates
|
||||
*MapApi* | [**updateFavoriteLocation**](doc//MapApi.md#updatefavoritelocation) | **PUT** /map/favorite-locations/{id} | Update favorite location
|
||||
*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | Add assets to a memory
|
||||
*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories | Create a memory
|
||||
*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} | Delete a memory
|
||||
@@ -388,7 +384,6 @@ Class | Method | HTTP request | Description
|
||||
- [Colorspace](doc//Colorspace.md)
|
||||
- [ContributorCountResponseDto](doc//ContributorCountResponseDto.md)
|
||||
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
||||
- [CreateFavoriteLocationDto](doc//CreateFavoriteLocationDto.md)
|
||||
- [CreateLibraryDto](doc//CreateLibraryDto.md)
|
||||
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
||||
- [DatabaseBackupConfig](doc//DatabaseBackupConfig.md)
|
||||
@@ -404,7 +399,6 @@ Class | Method | HTTP request | Description
|
||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||
- [FaceDto](doc//FaceDto.md)
|
||||
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
||||
- [FavoriteLocationResponseDto](doc//FavoriteLocationResponseDto.md)
|
||||
- [FoldersResponse](doc//FoldersResponse.md)
|
||||
- [FoldersUpdate](doc//FoldersUpdate.md)
|
||||
- [ImageFormat](doc//ImageFormat.md)
|
||||
@@ -619,7 +613,6 @@ Class | Method | HTTP request | Description
|
||||
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
||||
- [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md)
|
||||
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
||||
- [UpdateFavoriteLocationDto](doc//UpdateFavoriteLocationDto.md)
|
||||
- [UpdateLibraryDto](doc//UpdateLibraryDto.md)
|
||||
- [UsageByUserDto](doc//UsageByUserDto.md)
|
||||
- [UserAdminCreateDto](doc//UserAdminCreateDto.md)
|
||||
|
||||
3
mobile/openapi/lib/api.dart
generated
3
mobile/openapi/lib/api.dart
generated
@@ -136,7 +136,6 @@ part 'model/check_existing_assets_response_dto.dart';
|
||||
part 'model/colorspace.dart';
|
||||
part 'model/contributor_count_response_dto.dart';
|
||||
part 'model/create_album_dto.dart';
|
||||
part 'model/create_favorite_location_dto.dart';
|
||||
part 'model/create_library_dto.dart';
|
||||
part 'model/create_profile_image_response_dto.dart';
|
||||
part 'model/database_backup_config.dart';
|
||||
@@ -152,7 +151,6 @@ part 'model/email_notifications_update.dart';
|
||||
part 'model/exif_response_dto.dart';
|
||||
part 'model/face_dto.dart';
|
||||
part 'model/facial_recognition_config.dart';
|
||||
part 'model/favorite_location_response_dto.dart';
|
||||
part 'model/folders_response.dart';
|
||||
part 'model/folders_update.dart';
|
||||
part 'model/image_format.dart';
|
||||
@@ -367,7 +365,6 @@ part 'model/trash_response_dto.dart';
|
||||
part 'model/update_album_dto.dart';
|
||||
part 'model/update_album_user_dto.dart';
|
||||
part 'model/update_asset_dto.dart';
|
||||
part 'model/update_favorite_location_dto.dart';
|
||||
part 'model/update_library_dto.dart';
|
||||
part 'model/usage_by_user_dto.dart';
|
||||
part 'model/user_admin_create_dto.dart';
|
||||
|
||||
217
mobile/openapi/lib/api/map_api.dart
generated
217
mobile/openapi/lib/api/map_api.dart
generated
@@ -16,162 +16,6 @@ class MapApi {
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Create favorite location
|
||||
///
|
||||
/// Create a new favorite location for the user.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [CreateFavoriteLocationDto] createFavoriteLocationDto (required):
|
||||
Future<Response> createFavoriteLocationWithHttpInfo(CreateFavoriteLocationDto createFavoriteLocationDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/map/favorite-locations';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = createFavoriteLocationDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create favorite location
|
||||
///
|
||||
/// Create a new favorite location for the user.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [CreateFavoriteLocationDto] createFavoriteLocationDto (required):
|
||||
Future<FavoriteLocationResponseDto?> createFavoriteLocation(CreateFavoriteLocationDto createFavoriteLocationDto,) async {
|
||||
final response = await createFavoriteLocationWithHttpInfo(createFavoriteLocationDto,);
|
||||
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), 'FavoriteLocationResponseDto',) as FavoriteLocationResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Delete favorite location
|
||||
///
|
||||
/// Delete a favorite location by its ID.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<Response> deleteFavoriteLocationWithHttpInfo(String id,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/map/favorite-locations/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'DELETE',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete favorite location
|
||||
///
|
||||
/// Delete a favorite location by its ID.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
Future<void> deleteFavoriteLocation(String id,) async {
|
||||
final response = await deleteFavoriteLocationWithHttpInfo(id,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get favorite locations
|
||||
///
|
||||
/// Retrieve a list of user's favorite locations.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
Future<Response> getFavoriteLocationsWithHttpInfo() async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/map/favorite-locations';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get favorite locations
|
||||
///
|
||||
/// Retrieve a list of user's favorite locations.
|
||||
Future<List<FavoriteLocationResponseDto>?> getFavoriteLocations() async {
|
||||
final response = await getFavoriteLocationsWithHttpInfo();
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<FavoriteLocationResponseDto>') as List)
|
||||
.cast<FavoriteLocationResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Retrieve map markers
|
||||
///
|
||||
/// Retrieve a list of latitude and longitude coordinates for every asset with location data.
|
||||
@@ -335,65 +179,4 @@ class MapApi {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update favorite location
|
||||
///
|
||||
/// Update an existing favorite location.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateFavoriteLocationDto] updateFavoriteLocationDto (required):
|
||||
Future<Response> updateFavoriteLocationWithHttpInfo(String id, UpdateFavoriteLocationDto updateFavoriteLocationDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/map/favorite-locations/{id}'
|
||||
.replaceAll('{id}', id);
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = updateFavoriteLocationDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'PUT',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update favorite location
|
||||
///
|
||||
/// Update an existing favorite location.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
///
|
||||
/// * [UpdateFavoriteLocationDto] updateFavoriteLocationDto (required):
|
||||
Future<FavoriteLocationResponseDto?> updateFavoriteLocation(String id, UpdateFavoriteLocationDto updateFavoriteLocationDto,) async {
|
||||
final response = await updateFavoriteLocationWithHttpInfo(id, updateFavoriteLocationDto,);
|
||||
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), 'FavoriteLocationResponseDto',) as FavoriteLocationResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
6
mobile/openapi/lib/api_client.dart
generated
6
mobile/openapi/lib/api_client.dart
generated
@@ -320,8 +320,6 @@ class ApiClient {
|
||||
return ContributorCountResponseDto.fromJson(value);
|
||||
case 'CreateAlbumDto':
|
||||
return CreateAlbumDto.fromJson(value);
|
||||
case 'CreateFavoriteLocationDto':
|
||||
return CreateFavoriteLocationDto.fromJson(value);
|
||||
case 'CreateLibraryDto':
|
||||
return CreateLibraryDto.fromJson(value);
|
||||
case 'CreateProfileImageResponseDto':
|
||||
@@ -352,8 +350,6 @@ class ApiClient {
|
||||
return FaceDto.fromJson(value);
|
||||
case 'FacialRecognitionConfig':
|
||||
return FacialRecognitionConfig.fromJson(value);
|
||||
case 'FavoriteLocationResponseDto':
|
||||
return FavoriteLocationResponseDto.fromJson(value);
|
||||
case 'FoldersResponse':
|
||||
return FoldersResponse.fromJson(value);
|
||||
case 'FoldersUpdate':
|
||||
@@ -782,8 +778,6 @@ class ApiClient {
|
||||
return UpdateAlbumUserDto.fromJson(value);
|
||||
case 'UpdateAssetDto':
|
||||
return UpdateAssetDto.fromJson(value);
|
||||
case 'UpdateFavoriteLocationDto':
|
||||
return UpdateFavoriteLocationDto.fromJson(value);
|
||||
case 'UpdateLibraryDto':
|
||||
return UpdateLibraryDto.fromJson(value);
|
||||
case 'UsageByUserDto':
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 CreateFavoriteLocationDto {
|
||||
/// Returns a new [CreateFavoriteLocationDto] instance.
|
||||
CreateFavoriteLocationDto({
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
num latitude;
|
||||
|
||||
num longitude;
|
||||
|
||||
String name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is CreateFavoriteLocationDto &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(latitude.hashCode) +
|
||||
(longitude.hashCode) +
|
||||
(name.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'CreateFavoriteLocationDto[latitude=$latitude, longitude=$longitude, name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'latitude'] = this.latitude;
|
||||
json[r'longitude'] = this.longitude;
|
||||
json[r'name'] = this.name;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [CreateFavoriteLocationDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static CreateFavoriteLocationDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "CreateFavoriteLocationDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return CreateFavoriteLocationDto(
|
||||
latitude: num.parse('${json[r'latitude']}'),
|
||||
longitude: num.parse('${json[r'longitude']}'),
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<CreateFavoriteLocationDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <CreateFavoriteLocationDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = CreateFavoriteLocationDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, CreateFavoriteLocationDto> mapFromJson(dynamic json) {
|
||||
final map = <String, CreateFavoriteLocationDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = CreateFavoriteLocationDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of CreateFavoriteLocationDto-objects as value to a dart map
|
||||
static Map<String, List<CreateFavoriteLocationDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<CreateFavoriteLocationDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = CreateFavoriteLocationDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'latitude',
|
||||
'longitude',
|
||||
'name',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 FavoriteLocationResponseDto {
|
||||
/// Returns a new [FavoriteLocationResponseDto] instance.
|
||||
FavoriteLocationResponseDto({
|
||||
required this.id,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
num? latitude;
|
||||
|
||||
num? longitude;
|
||||
|
||||
String name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is FavoriteLocationResponseDto &&
|
||||
other.id == id &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(id.hashCode) +
|
||||
(latitude == null ? 0 : latitude!.hashCode) +
|
||||
(longitude == null ? 0 : longitude!.hashCode) +
|
||||
(name.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'FavoriteLocationResponseDto[id=$id, latitude=$latitude, longitude=$longitude, name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'id'] = this.id;
|
||||
if (this.latitude != null) {
|
||||
json[r'latitude'] = this.latitude;
|
||||
} else {
|
||||
// json[r'latitude'] = null;
|
||||
}
|
||||
if (this.longitude != null) {
|
||||
json[r'longitude'] = this.longitude;
|
||||
} else {
|
||||
// json[r'longitude'] = null;
|
||||
}
|
||||
json[r'name'] = this.name;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [FavoriteLocationResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static FavoriteLocationResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "FavoriteLocationResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return FavoriteLocationResponseDto(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
latitude: json[r'latitude'] == null
|
||||
? null
|
||||
: num.parse('${json[r'latitude']}'),
|
||||
longitude: json[r'longitude'] == null
|
||||
? null
|
||||
: num.parse('${json[r'longitude']}'),
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<FavoriteLocationResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <FavoriteLocationResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = FavoriteLocationResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, FavoriteLocationResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, FavoriteLocationResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = FavoriteLocationResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of FavoriteLocationResponseDto-objects as value to a dart map
|
||||
static Map<String, List<FavoriteLocationResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<FavoriteLocationResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = FavoriteLocationResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'id',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'name',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// 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 UpdateFavoriteLocationDto {
|
||||
/// Returns a new [UpdateFavoriteLocationDto] instance.
|
||||
UpdateFavoriteLocationDto({
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.name,
|
||||
});
|
||||
|
||||
///
|
||||
/// 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? latitude;
|
||||
|
||||
///
|
||||
/// 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? longitude;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
String? name;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is UpdateFavoriteLocationDto &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.name == name;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(latitude == null ? 0 : latitude!.hashCode) +
|
||||
(longitude == null ? 0 : longitude!.hashCode) +
|
||||
(name == null ? 0 : name!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UpdateFavoriteLocationDto[latitude=$latitude, longitude=$longitude, name=$name]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.latitude != null) {
|
||||
json[r'latitude'] = this.latitude;
|
||||
} else {
|
||||
// json[r'latitude'] = null;
|
||||
}
|
||||
if (this.longitude != null) {
|
||||
json[r'longitude'] = this.longitude;
|
||||
} else {
|
||||
// json[r'longitude'] = null;
|
||||
}
|
||||
if (this.name != null) {
|
||||
json[r'name'] = this.name;
|
||||
} else {
|
||||
// json[r'name'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [UpdateFavoriteLocationDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static UpdateFavoriteLocationDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "UpdateFavoriteLocationDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return UpdateFavoriteLocationDto(
|
||||
latitude: num.parse('${json[r'latitude']}'),
|
||||
longitude: num.parse('${json[r'longitude']}'),
|
||||
name: mapValueOfType<String>(json, r'name'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<UpdateFavoriteLocationDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <UpdateFavoriteLocationDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = UpdateFavoriteLocationDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, UpdateFavoriteLocationDto> mapFromJson(dynamic json) {
|
||||
final map = <String, UpdateFavoriteLocationDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = UpdateFavoriteLocationDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of UpdateFavoriteLocationDto-objects as value to a dart map
|
||||
static Map<String, List<UpdateFavoriteLocationDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<UpdateFavoriteLocationDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = UpdateFavoriteLocationDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,15 +77,15 @@ void main() {
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsNothing);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Bordered icon if likedId == null', (tester) async {
|
||||
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
|
||||
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsNothing);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Adds new like', (tester) async {
|
||||
|
||||
@@ -91,17 +91,17 @@ void main() {
|
||||
group('Like Activity', () {
|
||||
final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin);
|
||||
|
||||
testWidgets('Like contains filled heart as leading', (tester) async {
|
||||
testWidgets('Like contains filled thumbs-up as leading', (tester) async {
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
// Leading widget should not be null
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
expect(listTile.leading, isNotNull);
|
||||
|
||||
// And should have a favorite icon
|
||||
final favoIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.favorite_rounded);
|
||||
// And should have a thumb_up icon
|
||||
final thumbUpIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.thumb_up);
|
||||
|
||||
expect(favoIconFinder, findsOneWidget);
|
||||
expect(thumbUpIconFinder, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Like title is center aligned', (tester) async {
|
||||
|
||||
@@ -5592,216 +5592,6 @@
|
||||
"x-immich-state": "Stable"
|
||||
}
|
||||
},
|
||||
"/map/favorite-locations": {
|
||||
"get": {
|
||||
"description": "Retrieve a list of user's favorite locations.",
|
||||
"operationId": "getFavoriteLocations",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/FavoriteLocationResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Get favorite locations",
|
||||
"tags": [
|
||||
"Map"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"post": {
|
||||
"description": "Create a new favorite location for the user.",
|
||||
"operationId": "createFavoriteLocation",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateFavoriteLocationDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FavoriteLocationResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Create favorite location",
|
||||
"tags": [
|
||||
"Map"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
}
|
||||
},
|
||||
"/map/favorite-locations/{id}": {
|
||||
"delete": {
|
||||
"description": "Delete a favorite location by its ID.",
|
||||
"operationId": "deleteFavoriteLocation",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Delete favorite location",
|
||||
"tags": [
|
||||
"Map"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
},
|
||||
"put": {
|
||||
"description": "Update an existing favorite location.",
|
||||
"operationId": "updateFavoriteLocation",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateFavoriteLocationDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FavoriteLocationResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"summary": "Update favorite location",
|
||||
"tags": [
|
||||
"Map"
|
||||
],
|
||||
"x-immich-history": [
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Added"
|
||||
},
|
||||
{
|
||||
"version": "v2",
|
||||
"state": "Stable"
|
||||
}
|
||||
],
|
||||
"x-immich-state": "Stable"
|
||||
}
|
||||
},
|
||||
"/map/markers": {
|
||||
"get": {
|
||||
"description": "Retrieve a list of latitude and longitude coordinates for every asset with location data.",
|
||||
@@ -16360,25 +16150,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CreateFavoriteLocationDto": {
|
||||
"properties": {
|
||||
"latitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"latitude",
|
||||
"longitude",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CreateLibraryDto": {
|
||||
"properties": {
|
||||
"exclusionPatterns": {
|
||||
@@ -16783,31 +16554,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FavoriteLocationResponseDto": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"latitude": {
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"nullable": true,
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FoldersResponse": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
@@ -22847,20 +22593,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateFavoriteLocationDto": {
|
||||
"properties": {
|
||||
"latitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateLibraryDto": {
|
||||
"properties": {
|
||||
"exclusionPatterns": {
|
||||
|
||||
@@ -790,22 +790,6 @@ export type ValidateLibraryImportPathResponseDto = {
|
||||
export type ValidateLibraryResponseDto = {
|
||||
importPaths?: ValidateLibraryImportPathResponseDto[];
|
||||
};
|
||||
export type FavoriteLocationResponseDto = {
|
||||
id: string;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
name: string;
|
||||
};
|
||||
export type CreateFavoriteLocationDto = {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name: string;
|
||||
};
|
||||
export type UpdateFavoriteLocationDto = {
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
name?: string;
|
||||
};
|
||||
export type MapMarkerResponseDto = {
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
@@ -3098,59 +3082,6 @@ export function validate({ id, validateLibraryDto }: {
|
||||
body: validateLibraryDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Get favorite locations
|
||||
*/
|
||||
export function getFavoriteLocations(opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: FavoriteLocationResponseDto[];
|
||||
}>("/map/favorite-locations", {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Create favorite location
|
||||
*/
|
||||
export function createFavoriteLocation({ createFavoriteLocationDto }: {
|
||||
createFavoriteLocationDto: CreateFavoriteLocationDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 201;
|
||||
data: FavoriteLocationResponseDto;
|
||||
}>("/map/favorite-locations", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: createFavoriteLocationDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Delete favorite location
|
||||
*/
|
||||
export function deleteFavoriteLocation({ id }: {
|
||||
id: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText(`/map/favorite-locations/${encodeURIComponent(id)}`, {
|
||||
...opts,
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Update favorite location
|
||||
*/
|
||||
export function updateFavoriteLocation({ id, updateFavoriteLocationDto }: {
|
||||
id: string;
|
||||
updateFavoriteLocationDto: UpdateFavoriteLocationDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: FavoriteLocationResponseDto;
|
||||
}>(`/map/favorite-locations/${encodeURIComponent(id)}`, oazapfts.json({
|
||||
...opts,
|
||||
method: "PUT",
|
||||
body: updateFavoriteLocationDto
|
||||
})));
|
||||
}
|
||||
/**
|
||||
* Retrieve map markers
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||
import { Controller, Get, HttpCode, HttpStatus, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Endpoint, HistoryBuilder } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import {
|
||||
CreateFavoriteLocationDto,
|
||||
FavoriteLocationResponseDto,
|
||||
UpdateFavoriteLocationDto,
|
||||
} from 'src/dtos/favorite-location.dto';
|
||||
import {
|
||||
MapMarkerDto,
|
||||
MapMarkerResponseDto,
|
||||
@@ -44,59 +39,4 @@ export class MapController {
|
||||
reverseGeocode(@Query() dto: MapReverseGeocodeDto): Promise<MapReverseGeocodeResponseDto[]> {
|
||||
return this.service.reverseGeocode(dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Get('favorite-locations')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Endpoint({
|
||||
summary: 'Get favorite locations',
|
||||
description: "Retrieve a list of user's favorite locations.",
|
||||
history: new HistoryBuilder().added('v2').stable('v2'),
|
||||
})
|
||||
getFavoriteLocations(@Auth() auth: AuthDto): Promise<FavoriteLocationResponseDto[]> {
|
||||
return this.service.getFavoriteLocations(auth);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Post('favorite-locations')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@Endpoint({
|
||||
summary: 'Create favorite location',
|
||||
description: 'Create a new favorite location for the user.',
|
||||
history: new HistoryBuilder().added('v2').stable('v2'),
|
||||
})
|
||||
createFavoriteLocation(
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: CreateFavoriteLocationDto,
|
||||
): Promise<FavoriteLocationResponseDto> {
|
||||
return this.service.createFavoriteLocation(auth, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Put('favorite-locations/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Endpoint({
|
||||
summary: 'Update favorite location',
|
||||
description: 'Update an existing favorite location.',
|
||||
history: new HistoryBuilder().added('v2').stable('v2'),
|
||||
})
|
||||
updateFavoriteLocation(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateFavoriteLocationDto,
|
||||
): Promise<FavoriteLocationResponseDto> {
|
||||
return this.service.updateFavoriteLocation(auth, id, dto);
|
||||
}
|
||||
|
||||
@Authenticated()
|
||||
@Delete('favorite-locations/:id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Endpoint({
|
||||
summary: 'Delete favorite location',
|
||||
description: 'Delete a favorite location by its ID.',
|
||||
history: new HistoryBuilder().added('v2').stable('v2'),
|
||||
})
|
||||
deleteFavoriteLocation(@Param('id') id: string) {
|
||||
return this.service.deleteFavoriteLocation(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,16 +274,6 @@ export type AssetFace = {
|
||||
updateId: string;
|
||||
};
|
||||
|
||||
export type FavoriteLocation = {
|
||||
id: string;
|
||||
name: string;
|
||||
userId: string;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export type Plugin = Selectable<PluginTable>;
|
||||
|
||||
export type PluginFilter = Selectable<PluginFilterTable> & {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { IsLatitude, IsLongitude, IsString } from 'class-validator';
|
||||
import { Optional } from 'src/validation';
|
||||
|
||||
export class CreateFavoriteLocationDto {
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@IsLatitude()
|
||||
latitude!: number;
|
||||
|
||||
@IsLongitude()
|
||||
longitude!: number;
|
||||
}
|
||||
|
||||
export class UpdateFavoriteLocationDto {
|
||||
@Optional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@Optional()
|
||||
@IsLatitude()
|
||||
latitude?: number;
|
||||
|
||||
@Optional()
|
||||
@IsLongitude()
|
||||
longitude?: number;
|
||||
}
|
||||
|
||||
export class FavoriteLocationResponseDto {
|
||||
id!: string;
|
||||
name!: string;
|
||||
latitude!: number | null;
|
||||
longitude!: number | null;
|
||||
}
|
||||
@@ -29,13 +29,3 @@ where
|
||||
)
|
||||
order by
|
||||
"fileCreatedAt" desc
|
||||
|
||||
-- MapRepository.getFavoriteLocations
|
||||
select
|
||||
*
|
||||
from
|
||||
"favorite_location"
|
||||
where
|
||||
"userId" = $1
|
||||
order by
|
||||
"name" asc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getName } from 'i18n-iso-countries';
|
||||
import { Expression, Insertable, Kysely, NotNull, sql, SqlBool, Updateable } from 'kysely';
|
||||
import { Expression, Insertable, Kysely, NotNull, sql, SqlBool } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { createReadStream, existsSync } from 'node:fs';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
@@ -12,7 +12,6 @@ import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { FavoriteLocationTable } from 'src/schema/tables/favorite-location.table';
|
||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||
import { NaturalEarthCountriesTable } from 'src/schema/tables/natural-earth-countries.table';
|
||||
|
||||
@@ -139,42 +138,6 @@ export class MapRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getFavoriteLocations(userId: string) {
|
||||
return this.db
|
||||
.selectFrom('favorite_location')
|
||||
.selectAll()
|
||||
.where('userId', '=', userId)
|
||||
.orderBy('name', 'asc')
|
||||
.execute();
|
||||
}
|
||||
|
||||
async createFavoriteLocation(entity: Insertable<FavoriteLocationTable>) {
|
||||
const inserted = await this.db
|
||||
.insertInto('favorite_location')
|
||||
.values(entity)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return inserted;
|
||||
}
|
||||
|
||||
async updateFavoriteLocation(id: string, userId: string, updates: Updateable<FavoriteLocationTable>) {
|
||||
const updated = await this.db
|
||||
.updateTable('favorite_location')
|
||||
.set(updates)
|
||||
.where('id', '=', id)
|
||||
.where('userId', '=', userId)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
async deleteFavoriteLocation(id: string) {
|
||||
await this.db.deleteFrom('favorite_location').where('id', '=', id).execute();
|
||||
}
|
||||
|
||||
async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult> {
|
||||
this.logger.debug(`Request: ${point.latitude},${point.longitude}`);
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { AuditTable } from 'src/schema/tables/audit.table';
|
||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||
import { FavoriteLocationTable } from 'src/schema/tables/favorite-location.table';
|
||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||
@@ -98,7 +97,6 @@ export class ImmichDatabase {
|
||||
AuditTable,
|
||||
AssetExifTable,
|
||||
FaceSearchTable,
|
||||
FavoriteLocationTable,
|
||||
GeodataPlacesTable,
|
||||
LibraryTable,
|
||||
MemoryTable,
|
||||
@@ -194,7 +192,6 @@ export interface DB {
|
||||
audit: AuditTable;
|
||||
|
||||
face_search: FaceSearchTable;
|
||||
favorite_location: FavoriteLocationTable;
|
||||
|
||||
geodata_places: GeodataPlacesTable;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE TABLE "favorite_location" (
|
||||
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"name" character varying NOT NULL,
|
||||
"userId" uuid NOT NULL,
|
||||
"latitude" double precision,
|
||||
"longitude" double precision,
|
||||
"createdAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "favorite_location_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT "favorite_location_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "favorite_location_userId_idx" ON "favorite_location" ("userId");`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TABLE "favorite_location";`.execute(db);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
ForeignKeyColumn,
|
||||
Generated,
|
||||
PrimaryGeneratedColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
|
||||
@Table('favorite_location')
|
||||
export class FavoriteLocationTable {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'character varying', nullable: false })
|
||||
name!: string;
|
||||
|
||||
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', index: true })
|
||||
userId!: string;
|
||||
|
||||
@Column({ type: 'double precision', nullable: true })
|
||||
latitude!: number | null;
|
||||
|
||||
@Column({ type: 'double precision', nullable: true })
|
||||
longitude!: number | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CreateFavoriteLocationDto, UpdateFavoriteLocationDto } from 'src/dtos/favorite-location.dto';
|
||||
import { MapService } from 'src/services/map.service';
|
||||
import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
@@ -94,91 +93,4 @@ describe(MapService.name, () => {
|
||||
expect(mocks.map.reverseGeocode).toHaveBeenCalledWith({ latitude: 42, longitude: 69 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFavoriteLocations', () => {
|
||||
it('should return favorite locations for the user', async () => {
|
||||
const favoriteLocation = {
|
||||
id: 'loc1',
|
||||
userId: authStub.user1.user.id,
|
||||
name: 'Home',
|
||||
latitude: 12.34,
|
||||
longitude: 56.78,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mocks.map.getFavoriteLocations.mockResolvedValue([favoriteLocation]);
|
||||
|
||||
const result = await sut.getFavoriteLocations(authStub.user1);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(favoriteLocation);
|
||||
expect(mocks.map.getFavoriteLocations).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFavoriteLocation', () => {
|
||||
it('should create a new favorite location', async () => {
|
||||
const dto: CreateFavoriteLocationDto = { name: 'Work', latitude: 1, longitude: 2 };
|
||||
|
||||
const created = {
|
||||
id: 'loc2',
|
||||
userId: authStub.user1.user.id,
|
||||
name: 'Work',
|
||||
latitude: 1,
|
||||
longitude: 2,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mocks.map.createFavoriteLocation.mockResolvedValue(created);
|
||||
|
||||
const result = await sut.createFavoriteLocation(authStub.user1, dto);
|
||||
|
||||
expect(result).toEqual(created);
|
||||
expect(mocks.map.createFavoriteLocation).toHaveBeenCalledWith({
|
||||
userId: authStub.user1.user.id,
|
||||
name: dto.name,
|
||||
latitude: dto.latitude,
|
||||
longitude: dto.longitude,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateFavoriteLocation', () => {
|
||||
it('should update an existing favorite location', async () => {
|
||||
const dto: UpdateFavoriteLocationDto = { name: 'Gym' };
|
||||
|
||||
const updated = {
|
||||
id: 'loc3',
|
||||
userId: authStub.user1.user.id,
|
||||
name: 'Gym',
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mocks.map.updateFavoriteLocation.mockResolvedValue(updated);
|
||||
|
||||
const result = await sut.updateFavoriteLocation(authStub.user1, 'loc3', dto);
|
||||
|
||||
expect(result).toEqual(updated);
|
||||
expect(mocks.map.updateFavoriteLocation).toHaveBeenCalledWith('loc3', authStub.user1.user.id, {
|
||||
id: 'loc3',
|
||||
userId: authStub.user1.user.id,
|
||||
name: 'Gym',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteFavoriteLocation', () => {
|
||||
it('should call repository to delete a location by id', async () => {
|
||||
mocks.map.deleteFavoriteLocation.mockResolvedValue();
|
||||
|
||||
await sut.deleteFavoriteLocation('loc4');
|
||||
|
||||
expect(mocks.map.deleteFavoriteLocation).toHaveBeenCalledWith('loc4');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import {
|
||||
CreateFavoriteLocationDto,
|
||||
FavoriteLocationResponseDto,
|
||||
UpdateFavoriteLocationDto,
|
||||
} from 'src/dtos/favorite-location.dto';
|
||||
import { MapMarkerDto, MapMarkerResponseDto, MapReverseGeocodeDto } from 'src/dtos/map.dto';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
@@ -37,38 +32,4 @@ export class MapService extends BaseService {
|
||||
const result = await this.mapRepository.reverseGeocode({ latitude, longitude });
|
||||
return result ? [result] : [];
|
||||
}
|
||||
|
||||
async getFavoriteLocations(auth: AuthDto): Promise<FavoriteLocationResponseDto[]> {
|
||||
return this.mapRepository.getFavoriteLocations(auth.user.id);
|
||||
}
|
||||
|
||||
async createFavoriteLocation(auth: AuthDto, dto: CreateFavoriteLocationDto): Promise<FavoriteLocationResponseDto> {
|
||||
const entity = {
|
||||
userId: auth.user.id,
|
||||
name: dto.name,
|
||||
latitude: dto.latitude,
|
||||
longitude: dto.longitude,
|
||||
};
|
||||
|
||||
return this.mapRepository.createFavoriteLocation(entity);
|
||||
}
|
||||
|
||||
async updateFavoriteLocation(
|
||||
auth: AuthDto,
|
||||
id: string,
|
||||
dto: UpdateFavoriteLocationDto,
|
||||
): Promise<FavoriteLocationResponseDto> {
|
||||
const entity = {
|
||||
userId: auth.user.id,
|
||||
id,
|
||||
...(dto.name !== undefined && { name: dto.name }),
|
||||
...(dto.latitude !== undefined && { latitude: dto.latitude }),
|
||||
...(dto.longitude !== undefined && { longitude: dto.longitude }),
|
||||
};
|
||||
return this.mapRepository.updateFavoriteLocation(id, auth.user.id, entity);
|
||||
}
|
||||
|
||||
async deleteFavoriteLocation(id: string) {
|
||||
await this.mapRepository.deleteFavoriteLocation(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import type { ActivityResponseDto } from '@immich/sdk';
|
||||
import { Icon } from '@immich/ui';
|
||||
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
|
||||
import { mdiCommentOutline, mdiThumbUp, mdiThumbUpOutline } from '@mdi/js';
|
||||
|
||||
interface Props {
|
||||
isLiked: ActivityResponseDto | null;
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="w-full flex p-4 items-center justify-center rounded-full gap-5 bg-subtle border bg-opacity-60">
|
||||
<button type="button" class={disabled ? 'cursor-not-allowed' : ''} onclick={onFavorite} {disabled}>
|
||||
<div class="flex gap-2 items-center justify-center">
|
||||
<Icon icon={isLiked ? mdiHeart : mdiHeartOutline} size="24" class={isLiked ? 'text-red-400' : 'text-fg'} />
|
||||
<Icon icon={isLiked ? mdiThumbUp : mdiThumbUpOutline} size="24" class={isLiked ? 'text-blue-400' : 'text-fg'} />
|
||||
{#if numberOfLikes}
|
||||
<div class="text-l">{numberOfLikes.toLocaleString($locale)}</div>
|
||||
{/if}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||
import { ReactionType, type ActivityResponseDto, type AssetTypeEnum, type UserResponseDto } from '@immich/sdk';
|
||||
import { Icon, IconButton, LoadingSpinner, toastManager } from '@immich/ui';
|
||||
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
|
||||
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiSend, mdiThumbUp } from '@mdi/js';
|
||||
import * as luxon from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
@@ -181,7 +181,7 @@
|
||||
{:else if reaction.type === ReactionType.Like}
|
||||
<div class="relative">
|
||||
<div class="flex py-3 ps-3 mt-3 gap-4 items-center text-sm">
|
||||
<div class="text-red-600"><Icon icon={mdiHeart} size="20" /></div>
|
||||
<div class="text-blue-600"><Icon icon={mdiThumbUp} size="20" /></div>
|
||||
|
||||
<div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
|
||||
{$t('user_liked', {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{#if downloadManager.isDownloading}
|
||||
<div
|
||||
transition:fly={{ x: -100, duration: 350 }}
|
||||
class="fixed bottom-10 start-2 max-h-67.5 w-79 rounded-2xl border dark:border-white/10 p-4 shadow-lg bg-subtle"
|
||||
class="fixed bottom-10 start-2 max-h-67.5 w-79 z-60 rounded-2xl border dark:border-white/10 p-4 shadow-lg bg-subtle"
|
||||
>
|
||||
<Heading size="tiny">{$t('downloading')}</Heading>
|
||||
<div class="my-2 mb-2 flex max-h-50 flex-col overflow-y-auto text-sm">
|
||||
|
||||
@@ -8,18 +8,9 @@
|
||||
import { lastChosenLocation } from '$lib/stores/asset-editor.store';
|
||||
import { delay } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
createFavoriteLocation,
|
||||
deleteFavoriteLocation,
|
||||
getFavoriteLocations,
|
||||
searchPlaces,
|
||||
type AssetResponseDto,
|
||||
type FavoriteLocationResponseDto,
|
||||
type PlacesResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { Button, ConfirmModal, IconButton, Input, LoadingSpinner } from '@immich/ui';
|
||||
import { mdiDelete, mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
|
||||
import { ConfirmModal, LoadingSpinner } from '@immich/ui';
|
||||
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
interface Point {
|
||||
@@ -54,22 +45,6 @@
|
||||
|
||||
let zoom = $derived(mapLat && mapLng ? 12.5 : 1);
|
||||
|
||||
let favoriteLocations: FavoriteLocationResponseDto[] = $state([]);
|
||||
let newFavoriteName = $state('');
|
||||
let savingFavorite = $state(false);
|
||||
|
||||
const loadFavoriteLocations = async () => {
|
||||
try {
|
||||
favoriteLocations = await getFavoriteLocations();
|
||||
} catch (error) {
|
||||
handleError(error, 'Failed to load favorite locations');
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
await loadFavoriteLocations();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (mapElement && initialPoint) {
|
||||
mapElement.addClipMapMarker(initialPoint.lng, initialPoint.lat);
|
||||
@@ -93,39 +68,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveFavorite = async () => {
|
||||
if (newFavoriteName.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
savingFavorite = true;
|
||||
try {
|
||||
const newLocation: FavoriteLocationResponseDto = await createFavoriteLocation({
|
||||
createFavoriteLocationDto: {
|
||||
name: newFavoriteName,
|
||||
latitude: point!.lat,
|
||||
longitude: point!.lng,
|
||||
},
|
||||
});
|
||||
favoriteLocations = [...favoriteLocations, newLocation];
|
||||
favoriteLocations = favoriteLocations.sort((a, b) => a.name.localeCompare(b.name));
|
||||
newFavoriteName = '';
|
||||
} catch (error) {
|
||||
handleError(error, 'Failed to save favorite location');
|
||||
} finally {
|
||||
savingFavorite = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteFavorite = async (locationId: string) => {
|
||||
try {
|
||||
await deleteFavoriteLocation({ id: locationId });
|
||||
favoriteLocations = favoriteLocations.filter((loc) => loc.id !== locationId);
|
||||
} catch (error) {
|
||||
handleError(error, 'Failed to delete favorite location');
|
||||
}
|
||||
};
|
||||
|
||||
const getLocation = (name: string, admin1Name?: string, admin2Name?: string): string => {
|
||||
return `${name}${admin1Name ? ', ' + admin1Name : ''}${admin2Name ? ', ' + admin2Name : ''}`;
|
||||
};
|
||||
@@ -164,13 +106,10 @@
|
||||
latestSearchTimeout = searchTimeout;
|
||||
};
|
||||
|
||||
const handleUseSuggested = (latitude: number, longitude: number, setZoom?: number) => {
|
||||
const handleUseSuggested = (latitude: number, longitude: number) => {
|
||||
hideSuggestion = true;
|
||||
point = { lng: longitude, lat: latitude };
|
||||
mapElement?.addClipMapMarker(longitude, latitude);
|
||||
if (setZoom) {
|
||||
zoom = setZoom;
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdate = (lat: number, lng: number) => {
|
||||
@@ -267,58 +206,6 @@
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm text-start mt-4">
|
||||
<CoordinatesInput lat={point ? point.lat : assetLat} lng={point ? point.lng : assetLng} {onUpdate} />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="flex justify-between items-center gap-2 mb-2">
|
||||
<p>{$t('favorite_locations')}</p>
|
||||
<div class="flex gap-2 items-center justify-end">
|
||||
<Input placeholder={$t('name')} size="tiny" bind:value={newFavoriteName} />
|
||||
<Button
|
||||
onclick={handleSaveFavorite}
|
||||
disabled={newFavoriteName.trim() === '' || savingFavorite || point === null}
|
||||
variant="outline"
|
||||
size="tiny"
|
||||
class="shrink-0">{$t('save')}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-h-40 overflow-y-auto border border-gray-300 dark:border-immich-dark-gray rounded-md p-2">
|
||||
{#if favoriteLocations.length === 0}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{$t('favorite_locations_not_found')}</p>
|
||||
{:else}
|
||||
<ul class="space-y-2">
|
||||
{#each favoriteLocations as location (location.id)}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full"
|
||||
onclick={() => handleUseSuggested(location.latitude!, location.longitude!, 14)}
|
||||
>
|
||||
<div
|
||||
class="flex justify-between items-center p-2 bg-gray-100 dark:bg-gray-800 rounded hover:bg-gray-200 hover:dark:bg-gray-700"
|
||||
>
|
||||
{location.name}
|
||||
<IconButton
|
||||
icon={mdiDelete}
|
||||
shape="round"
|
||||
variant="outline"
|
||||
size="medium"
|
||||
color="danger"
|
||||
aria-label={$t('delete')}
|
||||
onclick={async (e: Event) => {
|
||||
e.stopPropagation();
|
||||
await handleDeleteFavorite(location.id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ConfirmModal>
|
||||
|
||||
@@ -79,10 +79,30 @@
|
||||
searchStore.isSearchEnabled = false;
|
||||
};
|
||||
|
||||
const buildSearchPayload = (term: string): SmartSearchDto | MetadataSearchDto => {
|
||||
const searchType = getSearchType();
|
||||
switch (searchType) {
|
||||
case 'smart': {
|
||||
return { query: term };
|
||||
}
|
||||
case 'metadata': {
|
||||
return { originalFileName: term };
|
||||
}
|
||||
case 'description': {
|
||||
return { description: term };
|
||||
}
|
||||
case 'ocr': {
|
||||
return { ocr: term };
|
||||
}
|
||||
default: {
|
||||
return { query: term };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onHistoryTermClick = async (searchTerm: string) => {
|
||||
value = searchTerm;
|
||||
const searchPayload = { query: searchTerm };
|
||||
await handleSearch(searchPayload);
|
||||
await handleSearch(buildSearchPayload(searchTerm));
|
||||
};
|
||||
|
||||
const onFilterClick = async () => {
|
||||
@@ -112,29 +132,7 @@
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
const searchType = getSearchType();
|
||||
let payload = {} as SmartSearchDto | MetadataSearchDto;
|
||||
|
||||
switch (searchType) {
|
||||
case 'smart': {
|
||||
payload = { query: value } as SmartSearchDto;
|
||||
break;
|
||||
}
|
||||
case 'metadata': {
|
||||
payload = { originalFileName: value } as MetadataSearchDto;
|
||||
break;
|
||||
}
|
||||
case 'description': {
|
||||
payload = { description: value } as MetadataSearchDto;
|
||||
break;
|
||||
}
|
||||
case 'ocr': {
|
||||
payload = { ocr: value } as MetadataSearchDto;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handlePromiseError(handleSearch(payload));
|
||||
handlePromiseError(handleSearch(buildSearchPayload(value)));
|
||||
saveSearchTerm(value);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user