mirror of
https://github.com/immich-app/immich.git
synced 2026-07-03 03:15:22 -07:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1139c96cbc | |||
| 2f5b78d163 | |||
| 58d1373a04 | |||
| 0b68e1376a | |||
| 7cbd9eada9 | |||
| 4b05d02769 | |||
| 18d0991b61 | |||
| fa29dc08f9 | |||
| fdcd6c7671 | |||
| 44fdaa47d3 | |||
| 604d100bb8 | |||
| ae022fd90a | |||
| afa433b3ad | |||
| 7cdc252384 | |||
| 3c43b7240b | |||
| b966940264 | |||
| 72cca30272 | |||
| bcd01a2464 | |||
| a5d8415c5f | |||
| 74e9ec4872 | |||
| 411487dfd7 | |||
| 524b191ccc | |||
| 075f7d507b | |||
| c4df4d7852 | |||
| 0eaa2c3419 | |||
| 554e7b28a2 | |||
| 167aad7ac2 |
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"label": "v3.0.1",
|
||||
"url": "https://docs.v3.0.1.archive.immich.app"
|
||||
"label": "v3.0.0",
|
||||
"url": "https://docs.v3.0.0.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v2.7.5",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -82,9 +82,6 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
||||
cast: {
|
||||
gCastEnabled: false,
|
||||
},
|
||||
recentlyAdded: {
|
||||
sidebarWeb: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,7 +110,6 @@ export async function enableTagsPreference(context: BrowserContext) {
|
||||
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
|
||||
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
|
||||
cast: { gCastEnabled: false },
|
||||
recentlyAdded: { sidebarWeb: false },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1718,7 +1718,6 @@
|
||||
"recent_searches": "Recent searches",
|
||||
"recently_added": "Recently added",
|
||||
"recently_added_body": "Jump straight to everything you've added lately on a dedicated page.",
|
||||
"recently_added_description": "Browse your assets sorted by when they were uploaded to Immich",
|
||||
"recently_added_page_title": "Recently Added",
|
||||
"recently_added_title": "Recently added",
|
||||
"recently_taken": "Recently taken",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "immich-ml"
|
||||
version = "3.0.1"
|
||||
version = "3.0.0"
|
||||
description = ""
|
||||
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
|
||||
requires-python = ">=3.11,<4.0"
|
||||
|
||||
Generated
+1
-1
@@ -974,7 +974,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "immich-ml"
|
||||
version = "3.0.1"
|
||||
version = "3.0.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiocache" },
|
||||
|
||||
@@ -22,8 +22,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 3054,
|
||||
"android.injected.version.name" => "3.0.1",
|
||||
"android.injected.version.code" => 3053,
|
||||
"android.injected.version.name" => "3.0.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab', track: 'beta')
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 3054,
|
||||
"android.injected.version.name" => "3.0.1",
|
||||
"android.injected.version.code" => 3053,
|
||||
"android.injected.version.name" => "3.0.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.1</string>
|
||||
<string>3.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -25,7 +25,6 @@ enum SyncMigrationTask {
|
||||
v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations.
|
||||
v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets.
|
||||
v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column.
|
||||
v20260701_ResetAlbumsV1, // Album user migration dropped the owner. Sync fresh albums from the server to re-populate them.
|
||||
}
|
||||
|
||||
class SyncStreamService {
|
||||
@@ -104,12 +103,6 @@ class SyncStreamService {
|
||||
}
|
||||
|
||||
Future<void> _runPreSyncTasks(List<String> migrations, SemVer semVer) async {
|
||||
if (!migrations.contains(SyncMigrationTask.v20260701_ResetAlbumsV1.name)) {
|
||||
_logger.info("Running pre-sync task: v20260701_ResetAlbumsV1");
|
||||
await _syncApiRepository.deleteSyncAck([SyncEntityType.albumV1]);
|
||||
migrations.add(SyncMigrationTask.v20260701_ResetAlbumsV1.name);
|
||||
}
|
||||
|
||||
if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) {
|
||||
_logger.info("Running pre-sync task: v20260128_ResetExifV1");
|
||||
await _syncApiRepository.deleteSyncAck([
|
||||
|
||||
@@ -26,7 +26,6 @@ final Map<String, Map<String, Object?>> openApiPatches = {
|
||||
'sharedLinks': SharedLinksResponse(enabled: true, sidebarWeb: false).toJson(),
|
||||
'cast': CastResponse(gCastEnabled: false).toJson(),
|
||||
'albums': {'defaultAssetOrder': 'desc'},
|
||||
'recentlyAdded': RecentlyAddedResponse(sidebarWeb: false).toJson(),
|
||||
},
|
||||
'ServerConfigDto': {
|
||||
'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json',
|
||||
|
||||
Generated
+1
-3
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 3.0.1
|
||||
- API version: 3.0.0
|
||||
- Generator version: 7.22.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
@@ -545,8 +545,6 @@ Class | Method | HTTP request | Description
|
||||
- [RatingsUpdate](doc//RatingsUpdate.md)
|
||||
- [ReactionLevel](doc//ReactionLevel.md)
|
||||
- [ReactionType](doc//ReactionType.md)
|
||||
- [RecentlyAddedResponse](doc//RecentlyAddedResponse.md)
|
||||
- [RecentlyAddedUpdate](doc//RecentlyAddedUpdate.md)
|
||||
- [ReleaseChannel](doc//ReleaseChannel.md)
|
||||
- [ReleaseEventV1](doc//ReleaseEventV1.md)
|
||||
- [ReleaseType](doc//ReleaseType.md)
|
||||
|
||||
Generated
-2
@@ -266,8 +266,6 @@ part 'model/ratings_response.dart';
|
||||
part 'model/ratings_update.dart';
|
||||
part 'model/reaction_level.dart';
|
||||
part 'model/reaction_type.dart';
|
||||
part 'model/recently_added_response.dart';
|
||||
part 'model/recently_added_update.dart';
|
||||
part 'model/release_channel.dart';
|
||||
part 'model/release_event_v1.dart';
|
||||
part 'model/release_type.dart';
|
||||
|
||||
Generated
-8
@@ -681,10 +681,8 @@ class AlbumsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
/// Album user ID, or \"me\" to reference the current user
|
||||
Future<Response> removeUserFromAlbumWithHttpInfo(String id, String userId, { Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/albums/{id}/user/{userId}'
|
||||
@@ -720,10 +718,8 @@ class AlbumsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
/// Album user ID, or \"me\" to reference the current user
|
||||
Future<void> removeUserFromAlbum(String id, String userId, { Future<void>? abortTrigger, }) async {
|
||||
final response = await removeUserFromAlbumWithHttpInfo(id, userId, abortTrigger: abortTrigger,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
@@ -802,10 +798,8 @@ class AlbumsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
/// Album user ID, or \"me\" to reference the current user
|
||||
///
|
||||
/// * [UpdateAlbumUserDto] updateAlbumUserDto (required):
|
||||
Future<Response> updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future<void>? abortTrigger, }) async {
|
||||
@@ -843,10 +837,8 @@ class AlbumsApi {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
/// Album ID
|
||||
///
|
||||
/// * [String] userId (required):
|
||||
/// Album user ID, or \"me\" to reference the current user
|
||||
///
|
||||
/// * [UpdateAlbumUserDto] updateAlbumUserDto (required):
|
||||
Future<void> updateAlbumUser(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future<void>? abortTrigger, }) async {
|
||||
|
||||
Generated
-4
@@ -577,10 +577,6 @@ class ApiClient {
|
||||
return ReactionLevelTypeTransformer().decode(value);
|
||||
case 'ReactionType':
|
||||
return ReactionTypeTypeTransformer().decode(value);
|
||||
case 'RecentlyAddedResponse':
|
||||
return RecentlyAddedResponse.fromJson(value);
|
||||
case 'RecentlyAddedUpdate':
|
||||
return RecentlyAddedUpdate.fromJson(value);
|
||||
case 'ReleaseChannel':
|
||||
return ReleaseChannelTypeTransformer().decode(value);
|
||||
case 'ReleaseEventV1':
|
||||
|
||||
-100
@@ -1,100 +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 RecentlyAddedResponse {
|
||||
/// Returns a new [RecentlyAddedResponse] instance.
|
||||
RecentlyAddedResponse({
|
||||
required this.sidebarWeb,
|
||||
});
|
||||
|
||||
/// Whether the recently added page appears in the web sidebar
|
||||
bool sidebarWeb;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is RecentlyAddedResponse &&
|
||||
other.sidebarWeb == sidebarWeb;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(sidebarWeb.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'RecentlyAddedResponse[sidebarWeb=$sidebarWeb]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'sidebarWeb'] = this.sidebarWeb;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [RecentlyAddedResponse] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static RecentlyAddedResponse? fromJson(dynamic value) {
|
||||
upgradeDto(value, "RecentlyAddedResponse");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return RecentlyAddedResponse(
|
||||
sidebarWeb: mapValueOfType<bool>(json, r'sidebarWeb')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<RecentlyAddedResponse> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <RecentlyAddedResponse>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = RecentlyAddedResponse.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, RecentlyAddedResponse> mapFromJson(dynamic json) {
|
||||
final map = <String, RecentlyAddedResponse>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = RecentlyAddedResponse.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of RecentlyAddedResponse-objects as value to a dart map
|
||||
static Map<String, List<RecentlyAddedResponse>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<RecentlyAddedResponse>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = RecentlyAddedResponse.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'sidebarWeb',
|
||||
};
|
||||
}
|
||||
|
||||
-108
@@ -1,108 +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 RecentlyAddedUpdate {
|
||||
/// Returns a new [RecentlyAddedUpdate] instance.
|
||||
RecentlyAddedUpdate({
|
||||
this.sidebarWeb = const Optional.absent(),
|
||||
});
|
||||
|
||||
/// Whether the recently added page appears in the web sidebar
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
Optional<bool?> sidebarWeb;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is RecentlyAddedUpdate &&
|
||||
other.sidebarWeb == sidebarWeb;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(sidebarWeb == null ? 0 : sidebarWeb!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'RecentlyAddedUpdate[sidebarWeb=$sidebarWeb]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.sidebarWeb.isPresent) {
|
||||
final value = this.sidebarWeb.value;
|
||||
json[r'sidebarWeb'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [RecentlyAddedUpdate] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static RecentlyAddedUpdate? fromJson(dynamic value) {
|
||||
upgradeDto(value, "RecentlyAddedUpdate");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return RecentlyAddedUpdate(
|
||||
sidebarWeb: json.containsKey(r'sidebarWeb') ? Optional.present(mapValueOfType<bool>(json, r'sidebarWeb')) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<RecentlyAddedUpdate> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <RecentlyAddedUpdate>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = RecentlyAddedUpdate.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, RecentlyAddedUpdate> mapFromJson(dynamic json) {
|
||||
final map = <String, RecentlyAddedUpdate>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = RecentlyAddedUpdate.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of RecentlyAddedUpdate-objects as value to a dart map
|
||||
static Map<String, List<RecentlyAddedUpdate>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<RecentlyAddedUpdate>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = RecentlyAddedUpdate.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
+1
-9
@@ -22,7 +22,6 @@ class UserPreferencesResponseDto {
|
||||
required this.people,
|
||||
required this.purchase,
|
||||
required this.ratings,
|
||||
required this.recentlyAdded,
|
||||
required this.sharedLinks,
|
||||
required this.tags,
|
||||
});
|
||||
@@ -45,8 +44,6 @@ class UserPreferencesResponseDto {
|
||||
|
||||
RatingsResponse ratings;
|
||||
|
||||
RecentlyAddedResponse recentlyAdded;
|
||||
|
||||
SharedLinksResponse sharedLinks;
|
||||
|
||||
TagsResponse tags;
|
||||
@@ -62,7 +59,6 @@ class UserPreferencesResponseDto {
|
||||
other.people == people &&
|
||||
other.purchase == purchase &&
|
||||
other.ratings == ratings &&
|
||||
other.recentlyAdded == recentlyAdded &&
|
||||
other.sharedLinks == sharedLinks &&
|
||||
other.tags == tags;
|
||||
|
||||
@@ -78,12 +74,11 @@ class UserPreferencesResponseDto {
|
||||
(people.hashCode) +
|
||||
(purchase.hashCode) +
|
||||
(ratings.hashCode) +
|
||||
(recentlyAdded.hashCode) +
|
||||
(sharedLinks.hashCode) +
|
||||
(tags.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UserPreferencesResponseDto[albums=$albums, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, recentlyAdded=$recentlyAdded, sharedLinks=$sharedLinks, tags=$tags]';
|
||||
String toString() => 'UserPreferencesResponseDto[albums=$albums, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -96,7 +91,6 @@ class UserPreferencesResponseDto {
|
||||
json[r'people'] = this.people;
|
||||
json[r'purchase'] = this.purchase;
|
||||
json[r'ratings'] = this.ratings;
|
||||
json[r'recentlyAdded'] = this.recentlyAdded;
|
||||
json[r'sharedLinks'] = this.sharedLinks;
|
||||
json[r'tags'] = this.tags;
|
||||
return json;
|
||||
@@ -120,7 +114,6 @@ class UserPreferencesResponseDto {
|
||||
people: PeopleResponse.fromJson(json[r'people'])!,
|
||||
purchase: PurchaseResponse.fromJson(json[r'purchase'])!,
|
||||
ratings: RatingsResponse.fromJson(json[r'ratings'])!,
|
||||
recentlyAdded: RecentlyAddedResponse.fromJson(json[r'recentlyAdded'])!,
|
||||
sharedLinks: SharedLinksResponse.fromJson(json[r'sharedLinks'])!,
|
||||
tags: TagsResponse.fromJson(json[r'tags'])!,
|
||||
);
|
||||
@@ -179,7 +172,6 @@ class UserPreferencesResponseDto {
|
||||
'people',
|
||||
'purchase',
|
||||
'ratings',
|
||||
'recentlyAdded',
|
||||
'sharedLinks',
|
||||
'tags',
|
||||
};
|
||||
|
||||
+1
-17
@@ -23,7 +23,6 @@ class UserPreferencesUpdateDto {
|
||||
this.people = const Optional.absent(),
|
||||
this.purchase = const Optional.absent(),
|
||||
this.ratings = const Optional.absent(),
|
||||
this.recentlyAdded = const Optional.absent(),
|
||||
this.sharedLinks = const Optional.absent(),
|
||||
this.tags = const Optional.absent(),
|
||||
});
|
||||
@@ -108,14 +107,6 @@ class UserPreferencesUpdateDto {
|
||||
///
|
||||
Optional<RatingsUpdate?> ratings;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
Optional<RecentlyAddedUpdate?> recentlyAdded;
|
||||
|
||||
///
|
||||
/// 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
|
||||
@@ -144,7 +135,6 @@ class UserPreferencesUpdateDto {
|
||||
other.people == people &&
|
||||
other.purchase == purchase &&
|
||||
other.ratings == ratings &&
|
||||
other.recentlyAdded == recentlyAdded &&
|
||||
other.sharedLinks == sharedLinks &&
|
||||
other.tags == tags;
|
||||
|
||||
@@ -161,12 +151,11 @@ class UserPreferencesUpdateDto {
|
||||
(people == null ? 0 : people!.hashCode) +
|
||||
(purchase == null ? 0 : purchase!.hashCode) +
|
||||
(ratings == null ? 0 : ratings!.hashCode) +
|
||||
(recentlyAdded == null ? 0 : recentlyAdded!.hashCode) +
|
||||
(sharedLinks == null ? 0 : sharedLinks!.hashCode) +
|
||||
(tags == null ? 0 : tags!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UserPreferencesUpdateDto[albums=$albums, avatar=$avatar, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, recentlyAdded=$recentlyAdded, sharedLinks=$sharedLinks, tags=$tags]';
|
||||
String toString() => 'UserPreferencesUpdateDto[albums=$albums, avatar=$avatar, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -210,10 +199,6 @@ class UserPreferencesUpdateDto {
|
||||
final value = this.ratings.value;
|
||||
json[r'ratings'] = value;
|
||||
}
|
||||
if (this.recentlyAdded.isPresent) {
|
||||
final value = this.recentlyAdded.value;
|
||||
json[r'recentlyAdded'] = value;
|
||||
}
|
||||
if (this.sharedLinks.isPresent) {
|
||||
final value = this.sharedLinks.value;
|
||||
json[r'sharedLinks'] = value;
|
||||
@@ -244,7 +229,6 @@ class UserPreferencesUpdateDto {
|
||||
people: json.containsKey(r'people') ? Optional.present(PeopleUpdate.fromJson(json[r'people'])) : const Optional.absent(),
|
||||
purchase: json.containsKey(r'purchase') ? Optional.present(PurchaseUpdate.fromJson(json[r'purchase'])) : const Optional.absent(),
|
||||
ratings: json.containsKey(r'ratings') ? Optional.present(RatingsUpdate.fromJson(json[r'ratings'])) : const Optional.absent(),
|
||||
recentlyAdded: json.containsKey(r'recentlyAdded') ? Optional.present(RecentlyAddedUpdate.fromJson(json[r'recentlyAdded'])) : const Optional.absent(),
|
||||
sharedLinks: json.containsKey(r'sharedLinks') ? Optional.present(SharedLinksUpdate.fromJson(json[r'sharedLinks'])) : const Optional.absent(),
|
||||
tags: json.containsKey(r'tags') ? Optional.present(TagsUpdate.fromJson(json[r'tags'])) : const Optional.absent(),
|
||||
);
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 3.0.1+3054
|
||||
version: 3.0.0+3053
|
||||
|
||||
environment:
|
||||
sdk: '>=3.12.0 <4.0.0'
|
||||
|
||||
@@ -2702,7 +2702,6 @@
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Album ID",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
@@ -2713,7 +2712,6 @@
|
||||
"name": "userId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Album user ID, or \"me\" to reference the current user",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -2764,7 +2762,6 @@
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Album ID",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
@@ -2775,7 +2772,6 @@
|
||||
"name": "userId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Album user ID, or \"me\" to reference the current user",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -16210,7 +16206,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [
|
||||
@@ -21995,27 +21991,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RecentlyAddedResponse": {
|
||||
"properties": {
|
||||
"sidebarWeb": {
|
||||
"description": "Whether the recently added page appears in the web sidebar",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sidebarWeb"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RecentlyAddedUpdate": {
|
||||
"properties": {
|
||||
"sidebarWeb": {
|
||||
"description": "Whether the recently added page appears in the web sidebar",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ReleaseChannel": {
|
||||
"description": "Release channel",
|
||||
"enum": [
|
||||
@@ -27550,9 +27525,6 @@
|
||||
"ratings": {
|
||||
"$ref": "#/components/schemas/RatingsResponse"
|
||||
},
|
||||
"recentlyAdded": {
|
||||
"$ref": "#/components/schemas/RecentlyAddedResponse"
|
||||
},
|
||||
"sharedLinks": {
|
||||
"$ref": "#/components/schemas/SharedLinksResponse"
|
||||
},
|
||||
@@ -27570,7 +27542,6 @@
|
||||
"people",
|
||||
"purchase",
|
||||
"ratings",
|
||||
"recentlyAdded",
|
||||
"sharedLinks",
|
||||
"tags"
|
||||
],
|
||||
@@ -27608,9 +27579,6 @@
|
||||
"ratings": {
|
||||
"$ref": "#/components/schemas/RatingsUpdate"
|
||||
},
|
||||
"recentlyAdded": {
|
||||
"$ref": "#/components/schemas/RecentlyAddedUpdate"
|
||||
},
|
||||
"sharedLinks": {
|
||||
"$ref": "#/components/schemas/SharedLinksUpdate"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-monorepo",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "Monorepo for Immich",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 3.0.1
|
||||
* 3.0.0
|
||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||
* See https://www.npmjs.com/package/oazapfts
|
||||
*/
|
||||
@@ -342,10 +342,6 @@ export type RatingsResponse = {
|
||||
/** Whether ratings are enabled */
|
||||
enabled: boolean;
|
||||
};
|
||||
export type RecentlyAddedResponse = {
|
||||
/** Whether the recently added page appears in the web sidebar */
|
||||
sidebarWeb: boolean;
|
||||
};
|
||||
export type SharedLinksResponse = {
|
||||
/** Whether shared links are enabled */
|
||||
enabled: boolean;
|
||||
@@ -368,7 +364,6 @@ export type UserPreferencesResponseDto = {
|
||||
people: PeopleResponse;
|
||||
purchase: PurchaseResponse;
|
||||
ratings: RatingsResponse;
|
||||
recentlyAdded: RecentlyAddedResponse;
|
||||
sharedLinks: SharedLinksResponse;
|
||||
tags: TagsResponse;
|
||||
};
|
||||
@@ -426,10 +421,6 @@ export type RatingsUpdate = {
|
||||
/** Whether ratings are enabled */
|
||||
enabled?: boolean;
|
||||
};
|
||||
export type RecentlyAddedUpdate = {
|
||||
/** Whether the recently added page appears in the web sidebar */
|
||||
sidebarWeb?: boolean;
|
||||
};
|
||||
export type SharedLinksUpdate = {
|
||||
/** Whether shared links are enabled */
|
||||
enabled?: boolean;
|
||||
@@ -453,7 +444,6 @@ export type UserPreferencesUpdateDto = {
|
||||
people?: PeopleUpdate;
|
||||
purchase?: PurchaseUpdate;
|
||||
ratings?: RatingsUpdate;
|
||||
recentlyAdded?: RecentlyAddedUpdate;
|
||||
sharedLinks?: SharedLinksUpdate;
|
||||
tags?: TagsUpdate;
|
||||
};
|
||||
|
||||
Generated
+10
-10
@@ -786,8 +786,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../packages/sdk
|
||||
'@immich/ui':
|
||||
specifier: ^0.81.1
|
||||
version: 0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))
|
||||
specifier: ^0.83.0
|
||||
version: 0.83.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))
|
||||
'@mapbox/mapbox-gl-rtl-text':
|
||||
specifier: 0.4.0
|
||||
version: 0.4.0
|
||||
@@ -3250,8 +3250,8 @@ packages:
|
||||
resolution: {integrity: sha512-O1SJ+BbeFVsUTF4af1MfagJZM+lPgLjI8lQ3SZNjpo8SGJReSbUl2ii03OKuGni/G0yp2GnRLpOTNSHYGtVrcg==}
|
||||
hasBin: true
|
||||
|
||||
'@immich/ui@0.81.1':
|
||||
resolution: {integrity: sha512-7g173hArs7OS5CfHUG+ZJVQp1iCvFZzBmm+f2uH1WChlFdcwty5DLilYrDBszWuPIvu8wTX2AXTneS/KYBCUxw==}
|
||||
'@immich/ui@0.83.0':
|
||||
resolution: {integrity: sha512-Xh3R3yhn8/Qyq8lGWIiVmturA+dHXtmW/h++AGb2UAwS/58qu7ssocO/HWIamaSpkgjPGotlWpPIpUJq1zh9FQ==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.13.0
|
||||
svelte: ^5.0.0
|
||||
@@ -16111,7 +16111,7 @@ snapshots:
|
||||
pg-connection-string: 2.13.0
|
||||
postgres: 3.4.9
|
||||
|
||||
'@immich/ui@0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))':
|
||||
'@immich/ui@0.83.0(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))':
|
||||
dependencies:
|
||||
'@internationalized/date': 3.12.2
|
||||
'@mdi/js': 7.4.47
|
||||
@@ -21092,21 +21092,21 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
exiftool-vendored.exe@13.59.0:
|
||||
optional: true
|
||||
exiftool-vendored.exe@13.59.0: {}
|
||||
|
||||
exiftool-vendored.pl@13.59.0: {}
|
||||
exiftool-vendored.pl@13.59.0:
|
||||
optional: true
|
||||
|
||||
exiftool-vendored@35.21.0:
|
||||
dependencies:
|
||||
'@photostructure/tz-lookup': 11.5.0
|
||||
'@types/luxon': 3.7.1
|
||||
batch-cluster: 17.3.1
|
||||
exiftool-vendored.pl: 13.59.0
|
||||
exiftool-vendored.exe: 13.59.0
|
||||
he: 1.2.0
|
||||
luxon: 3.7.2
|
||||
optionalDependencies:
|
||||
exiftool-vendored.exe: 13.59.0
|
||||
exiftool-vendored.pl: 13.59.0
|
||||
|
||||
expand-template@2.0.3:
|
||||
optional: true
|
||||
|
||||
+1
-1
@@ -66,4 +66,4 @@ injectWorkspacePackages: true
|
||||
shamefullyHoist: false
|
||||
verifyDepsBeforeRun: install
|
||||
minimumReleaseAgeExclude:
|
||||
- '@immich/ui@0.81.1'
|
||||
- '@immich/ui@0.83.0'
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
AlbumsAddAssetsDto,
|
||||
AlbumsAddAssetsResponseDto,
|
||||
AlbumStatisticsResponseDto,
|
||||
AlbumUserParamDto,
|
||||
CreateAlbumDto,
|
||||
GetAlbumsDto,
|
||||
UpdateAlbumDto,
|
||||
@@ -19,7 +18,7 @@ import { MapMarkerResponseDto } from 'src/dtos/map.dto';
|
||||
import { ApiTag, Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { AlbumService } from 'src/services/album.service';
|
||||
import { UUIDParamDto } from 'src/validation';
|
||||
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags(ApiTag.Albums)
|
||||
@Controller('albums')
|
||||
@@ -176,7 +175,8 @@ export class AlbumController {
|
||||
})
|
||||
updateAlbumUser(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id, userId }: AlbumUserParamDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
|
||||
@Body() dto: UpdateAlbumUserDto,
|
||||
): Promise<void> {
|
||||
return this.service.updateUser(auth, id, userId, dto);
|
||||
@@ -190,7 +190,11 @@ export class AlbumController {
|
||||
description: 'Remove a user from an album. Use an ID of "me" to leave a shared album.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
})
|
||||
removeUserFromAlbum(@Auth() auth: AuthDto, @Param() { id, userId }: AlbumUserParamDto): Promise<void> {
|
||||
removeUserFromAlbum(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
|
||||
): Promise<void> {
|
||||
return this.service.removeUser(auth, id, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,18 +140,6 @@ export const AlbumResponseSchema = z
|
||||
})
|
||||
.meta({ id: 'AlbumResponseDto' });
|
||||
|
||||
const AlbumUserParamSchema = z.object({
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
// TODO: disallow 'me' as a shortcut in v4 and type userId as uuidv4
|
||||
userId: z
|
||||
.string()
|
||||
.refine((value) => value === 'me' || z.uuidv4().safeParse(value).success, {
|
||||
error: 'Must be a UUID v4 or "me"',
|
||||
})
|
||||
.describe('Album user ID, or "me" to reference the current user'),
|
||||
});
|
||||
|
||||
export class AlbumUserParamDto extends createZodDto(AlbumUserParamSchema) {}
|
||||
export class AddUsersDto extends createZodDto(AddUsersSchema) {}
|
||||
export class AlbumUserCreateDto extends createZodDto(AlbumUserCreateSchema) {}
|
||||
export class CreateAlbumDto extends createZodDto(CreateAlbumSchema) {}
|
||||
|
||||
@@ -98,13 +98,6 @@ const CastUpdateSchema = z
|
||||
.optional()
|
||||
.meta({ id: 'CastUpdate' });
|
||||
|
||||
const RecentlyAddedUpdateSchema = z
|
||||
.object({
|
||||
sidebarWeb: z.boolean().optional().describe('Whether the recently added page appears in the web sidebar'),
|
||||
})
|
||||
.optional()
|
||||
.meta({ id: 'RecentlyAddedUpdate' });
|
||||
|
||||
const UserPreferencesUpdateSchema = z
|
||||
.object({
|
||||
albums: AlbumsUpdateSchema,
|
||||
@@ -119,7 +112,6 @@ const UserPreferencesUpdateSchema = z
|
||||
ratings: RatingsUpdateSchema,
|
||||
sharedLinks: SharedLinksUpdateSchema,
|
||||
tags: TagsUpdateSchema,
|
||||
recentlyAdded: RecentlyAddedUpdateSchema,
|
||||
})
|
||||
.meta({ id: 'UserPreferencesUpdateDto' });
|
||||
|
||||
@@ -199,12 +191,6 @@ const CastResponseSchema = z
|
||||
})
|
||||
.meta({ id: 'CastResponse' });
|
||||
|
||||
const RecentlyAddedResponseSchema = z
|
||||
.object({
|
||||
sidebarWeb: z.boolean().describe('Whether the recently added page appears in the web sidebar'),
|
||||
})
|
||||
.meta({ id: 'RecentlyAddedResponse' });
|
||||
|
||||
const UserPreferencesResponseSchema = z
|
||||
.object({
|
||||
albums: AlbumsResponseSchema,
|
||||
@@ -218,7 +204,6 @@ const UserPreferencesResponseSchema = z
|
||||
download: DownloadResponseSchema,
|
||||
purchase: PurchaseResponseSchema,
|
||||
cast: CastResponseSchema,
|
||||
recentlyAdded: RecentlyAddedResponseSchema,
|
||||
})
|
||||
.meta({ id: 'UserPreferencesResponseDto' });
|
||||
|
||||
|
||||
@@ -619,9 +619,6 @@ export type UserPreferences = {
|
||||
cast: {
|
||||
gCastEnabled: boolean;
|
||||
};
|
||||
recentlyAdded: {
|
||||
sidebarWeb: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type UserMetadataItem<T extends keyof UserMetadata = UserMetadataKey> = {
|
||||
|
||||
@@ -50,9 +50,6 @@ const getDefaultPreferences = (): UserPreferences => {
|
||||
cast: {
|
||||
gCastEnabled: false,
|
||||
},
|
||||
recentlyAdded: {
|
||||
sidebarWeb: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileValidator, Injectable } from '@nestjs/common';
|
||||
import { ArgumentMetadata, FileValidator, Injectable, ParseUUIDPipe } from '@nestjs/common';
|
||||
import { createZodDto } from 'nestjs-zod';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { isIP, isIPRange } from 'validator';
|
||||
@@ -74,6 +74,16 @@ export function IsNotSiblingOf<
|
||||
);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ParseMeUUIDPipe extends ParseUUIDPipe {
|
||||
async transform(value: string, metadata: ArgumentMetadata) {
|
||||
if (value == 'me') {
|
||||
return value;
|
||||
}
|
||||
return super.transform(value, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FileNotEmptyValidator extends FileValidator {
|
||||
constructor(private requiredFields: string[]) {
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"@formatjs/icu-messageformat-parser": "^3.0.0",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@immich/sdk": "workspace:*",
|
||||
"@immich/ui": "^0.81.1",
|
||||
"@immich/ui": "^0.83.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.4.0",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@noble/hashes": "^2.2.0",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { getAssetActions } from '$lib/services/asset.service';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
import { ocrManager } from '$lib/stores/ocr.svelte';
|
||||
@@ -23,6 +24,7 @@
|
||||
import type { OnUndoDelete } from '$lib/utils/actions';
|
||||
import { navigateToAsset } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import { InvocationTracker } from '$lib/utils/invocationTracker';
|
||||
import { SlideshowHistory } from '$lib/utils/slideshow-history';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
@@ -149,6 +151,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
const onAssetsUndoArchive = async (assets: TimelineAsset[]) => {
|
||||
if (assets.length === 0) {
|
||||
return;
|
||||
}
|
||||
const restoredAsset = assets[0];
|
||||
await assetViewerManager.setAssetId(restoredAsset.id);
|
||||
await navigate({ targetRoute: 'current', assetId: restoredAsset.id });
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
syncAssetViewerOpenClass(true);
|
||||
const slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
|
||||
@@ -475,7 +486,7 @@
|
||||
</script>
|
||||
|
||||
<CommandPaletteDefaultProvider name={$t('assets')} actions={[Tag, TagPeople]} />
|
||||
<OnEvents {onAssetUpdate} />
|
||||
<OnEvents {onAssetUpdate} {onAssetsUndoArchive} />
|
||||
|
||||
<svelte:document
|
||||
bind:fullscreenElement
|
||||
|
||||
@@ -309,12 +309,12 @@
|
||||
untrack(() => map?.jumpTo({ center, zoom }));
|
||||
});
|
||||
|
||||
const onAssetsDelete = async () => {
|
||||
const onAssetsChanged = async () => {
|
||||
mapMarkers = await loadMapMarkers();
|
||||
};
|
||||
</script>
|
||||
|
||||
<OnEvents {onAssetsDelete} />
|
||||
<OnEvents onAssetsDelete={onAssetsChanged} onAssetsArchive={onAssetsChanged} onAssetsUnarchive={onAssetsChanged} />
|
||||
|
||||
<!-- We handle style loading ourselves so we set style blank here -->
|
||||
<MapLibre
|
||||
|
||||
@@ -101,9 +101,7 @@
|
||||
{description}
|
||||
</p>
|
||||
{:else}
|
||||
<div class="pb-2">
|
||||
{@render descriptionSnippet?.()}
|
||||
</div>
|
||||
{@render descriptionSnippet?.()}
|
||||
{/if}
|
||||
|
||||
{#if inputType !== SettingInputFieldType.PASSWORD}
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
mdiToolboxOutline,
|
||||
mdiTrashCan,
|
||||
mdiTrashCanOutline,
|
||||
mdiUploadOutline,
|
||||
} from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fly } from 'svelte/transition';
|
||||
@@ -84,14 +83,6 @@
|
||||
<NavbarItem title={$t('tags')} href={Route.tags()} icon={{ icon: mdiTagMultipleOutline, flipped: true }} />
|
||||
{/if}
|
||||
|
||||
{#if authManager.preferences.recentlyAdded.sidebarWeb}
|
||||
<NavbarItem
|
||||
title={$t('recently_added')}
|
||||
href={Route.recentlyAdded()}
|
||||
icon={{ icon: mdiUploadOutline, flipped: true }}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if authManager.preferences.folders.enabled && authManager.preferences.folders.sidebarWeb}
|
||||
<NavbarItem title={$t('folders')} href={Route.folders()} icon={{ icon: mdiFolderOutline, flipped: true }} />
|
||||
{/if}
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
UserAdminResponseDto,
|
||||
WorkflowResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { BaseEventManager } from '$lib/utils/base-event-manager.svelte';
|
||||
import type { TreeNode } from '$lib/utils/tree-utils';
|
||||
|
||||
@@ -35,6 +36,8 @@ export type Events = {
|
||||
|
||||
AssetUpdate: [AssetResponseDto];
|
||||
AssetsArchive: [string[]];
|
||||
AssetsUnarchive: [TimelineAsset[]];
|
||||
AssetsUndoArchive: [TimelineAsset[]];
|
||||
AssetsDelete: [string[]];
|
||||
AssetEditsApplied: [string];
|
||||
AssetsTag: [string[]];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AssetOrder, getAssetInfo, getTimeBuckets, AssetOrderBy, type AssetResponseDto } from '@immich/sdk';
|
||||
import { AssetOrder, AssetOrderBy, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
|
||||
import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
||||
@@ -114,7 +114,15 @@ export class TimelineManager extends VirtualScrollManager {
|
||||
|
||||
this.#unsubscribes.push(
|
||||
eventManager.on({
|
||||
AssetUpdate: (asset: AssetResponseDto) => this.#updateAssets([toTimelineAsset(asset)]),
|
||||
AssetUpdate: (asset: AssetResponseDto) => {
|
||||
const timelineAsset = toTimelineAsset(asset);
|
||||
if (this.#options.albumId || this.#options.personId) {
|
||||
this.#updateAssets([timelineAsset]);
|
||||
} else {
|
||||
this.upsertAssets([timelineAsset]);
|
||||
}
|
||||
},
|
||||
AssetsUnarchive: (assets) => this.upsertAssets(assets),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { get } from 'svelte/store';
|
||||
import type { AssetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { downloadManager } from '$lib/managers/download-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import { downloadBlob, downloadRequest, withError } from '$lib/utils';
|
||||
@@ -31,6 +32,7 @@ import { getByteUnitString } from '$lib/utils/byte-units';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import { asQueryString } from '$lib/utils/shared-links';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { handleError } from './handle-error';
|
||||
|
||||
export const tagAssets = async ({
|
||||
@@ -400,7 +402,12 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
|
||||
});
|
||||
|
||||
asset.isArchived = data.isArchived;
|
||||
toastManager.primary(asset.isArchived ? $t(`added_to_archive`) : $t(`removed_from_archive`));
|
||||
if (asset.isArchived) {
|
||||
const timelineAsset = toTimelineAsset(asset);
|
||||
showUndoArchiveToast($t('added_to_archive'), [timelineAsset]);
|
||||
} else {
|
||||
toastManager.primary($t('removed_from_archive'));
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_archive', { values: { archived: asset.isArchived } }));
|
||||
}
|
||||
@@ -408,7 +415,45 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
|
||||
return asset;
|
||||
};
|
||||
|
||||
export const archiveAssets = async (assets: { id: string }[], visibility: AssetVisibility) => {
|
||||
const showUndoArchiveToast = (description: string, assets: TimelineAsset[]) => {
|
||||
const $t = get(t);
|
||||
toastManager.primary({
|
||||
description,
|
||||
button: (close) => ({
|
||||
label: $t('undo'),
|
||||
onclick: () => {
|
||||
close();
|
||||
void undoArchiveAssets(assets);
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const undoArchiveAssets = async (assets: TimelineAsset[]) => {
|
||||
const $t = get(t);
|
||||
try {
|
||||
const ids = assets.map((a) => a.id);
|
||||
if (ids.length > 0) {
|
||||
await updateAssets({
|
||||
assetBulkUpdateDto: {
|
||||
ids,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
asset.visibility = AssetVisibility.Timeline;
|
||||
}
|
||||
eventManager.emit('AssetsUnarchive', assets);
|
||||
eventManager.emit('AssetsUndoArchive', assets);
|
||||
toastManager.success($t('unarchived_count', { values: { count: assets.length } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_archive_unarchive', { values: { archived: false } }));
|
||||
}
|
||||
};
|
||||
|
||||
export const archiveAssets = async (assets: TimelineAsset[], visibility: AssetVisibility) => {
|
||||
const ids = assets.map(({ id }) => id);
|
||||
const $t = get(t);
|
||||
|
||||
@@ -419,11 +464,11 @@ export const archiveAssets = async (assets: { id: string }[], visibility: AssetV
|
||||
});
|
||||
}
|
||||
|
||||
toastManager.primary(
|
||||
visibility === AssetVisibility.Archive
|
||||
? $t('archived_count', { values: { count: ids.length } })
|
||||
: $t('unarchived_count', { values: { count: ids.length } }),
|
||||
);
|
||||
if (visibility === AssetVisibility.Archive) {
|
||||
showUndoArchiveToast($t('archived_count', { values: { count: ids.length } }), assets);
|
||||
} else {
|
||||
toastManager.primary($t('unarchived_count', { values: { count: ids.length } }));
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
|
||||
@@ -165,7 +165,10 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
||||
const people = assetResponse.people?.map((person) => person.name) || [];
|
||||
|
||||
const localDateTime = fromISODateTimeUTCToObject(assetResponse.localDateTime);
|
||||
const fileCreatedAt = fromISODateTimeToObject(assetResponse.fileCreatedAt, assetResponse.exifInfo?.timeZone ?? 'UTC');
|
||||
// Keep this consistent with the bucket loader (getTimes), which stores fileCreatedAt as UTC
|
||||
// components. The timeline sorts assets within a day by fileCreatedAt, so a mismatched
|
||||
// representation here would place re-inserted assets (e.g. undo archive) in the wrong spot.
|
||||
const fileCreatedAt = fromISODateTimeUTCToObject(assetResponse.fileCreatedAt);
|
||||
const createdAt = fromISODateTimeUTCToObject(assetResponse.createdAt);
|
||||
|
||||
return {
|
||||
|
||||
@@ -329,6 +329,7 @@
|
||||
onPersonAssetDelete={handlePersonAssetDelete}
|
||||
onAssetsDelete={updateAssetCount}
|
||||
onAssetsArchive={updateAssetCount}
|
||||
onAssetsUnarchive={updateAssetCount}
|
||||
/>
|
||||
|
||||
<main
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
// Cast
|
||||
let gCastEnabled = $state(authManager.preferences.cast?.gCastEnabled ?? false);
|
||||
|
||||
// Recently added
|
||||
let recentlyAddedSidebar = $state(authManager.preferences.recentlyAdded?.sidebarWeb ?? false);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const response = await updateMyPreferences({
|
||||
@@ -53,7 +50,6 @@
|
||||
sharedLinks: { enabled: sharedLinksEnabled, sidebarWeb: sharedLinkSidebar },
|
||||
tags: { enabled: tagsEnabled, sidebarWeb: tagsSidebar },
|
||||
cast: { gCastEnabled },
|
||||
recentlyAdded: { sidebarWeb: recentlyAddedSidebar },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -174,14 +170,6 @@
|
||||
</div>
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion key="recentlyAdded" title={$t('recently_added')} subtitle={$t('recently_added_description')}>
|
||||
<div class="mt-4 flex flex-col gap-4 sm:ms-4">
|
||||
<Field label={$t('sidebar')} description={$t('sidebar_display_description')}>
|
||||
<Switch bind:checked={recentlyAddedSidebar} />
|
||||
</Field>
|
||||
</div>
|
||||
</SettingAccordion>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Button shape="round" type="submit" size="small" onclick={() => handleSave()}>{$t('save')}</Button>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,4 @@ export const preferencesFactory = Sync.makeFactory<UserPreferencesResponseDto>({
|
||||
enabled: false,
|
||||
sidebarWeb: false,
|
||||
},
|
||||
recentlyAdded: {
|
||||
sidebarWeb: false,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user