mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
feat: user's features preferences (#12099)
* feat: metadata in UserPreference * feat: web metadata settings * feat: web metadata settings * fix: typo * patch openapi * fix: missing translation key * new organization of preference strucutre * feature settings on web * localization * added and used feature settings * add default value to response dto * patch openapi * format en.json file * implement helper method * use tags preference logic * Fix logic bug and add tests * fix preference can be null in detail panel
This commit is contained in:
parent
9bfaa525db
commit
ebecb60f39
32 changed files with 519 additions and 186 deletions
|
@ -4,14 +4,30 @@ dynamic upgradeDto(dynamic value, String targetType) {
|
||||||
switch (targetType) {
|
switch (targetType) {
|
||||||
case 'UserPreferencesResponseDto':
|
case 'UserPreferencesResponseDto':
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
if (value['rating'] == null) {
|
addDefault(value, 'download.includeEmbeddedVideos', false);
|
||||||
value['rating'] = RatingResponse().toJson();
|
addDefault(value, 'folders', FoldersResponse().toJson());
|
||||||
}
|
addDefault(value, 'memories', MemoriesResponse().toJson());
|
||||||
|
addDefault(value, 'ratings', RatingsResponse().toJson());
|
||||||
if (value['download']['includeEmbeddedVideos'] == null) {
|
addDefault(value, 'people', PeopleResponse().toJson());
|
||||||
value['download']['includeEmbeddedVideos'] = false;
|
addDefault(value, 'tags', TagsResponse().toJson());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDefault(dynamic value, String keys, dynamic defaultValue) {
|
||||||
|
// Loop through the keys and assign the default value if the key is not present
|
||||||
|
List<String> keyList = keys.split('.');
|
||||||
|
dynamic current = value;
|
||||||
|
|
||||||
|
for (int i = 0; i < keyList.length - 1; i++) {
|
||||||
|
if (current[keyList[i]] == null) {
|
||||||
|
current[keyList[i]] = {};
|
||||||
|
}
|
||||||
|
current = current[keyList[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current[keyList.last] == null) {
|
||||||
|
current[keyList.last] = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/folders_response.dart
generated
Normal file
BIN
mobile/openapi/lib/model/folders_response.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/model/folders_update.dart
generated
Normal file
BIN
mobile/openapi/lib/model/folders_update.dart
generated
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mobile/openapi/lib/model/people_response.dart
generated
Normal file
BIN
mobile/openapi/lib/model/people_response.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/model/people_update.dart
generated
Normal file
BIN
mobile/openapi/lib/model/people_update.dart
generated
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mobile/openapi/lib/model/tags_response.dart
generated
Normal file
BIN
mobile/openapi/lib/model/tags_response.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/model/tags_update.dart
generated
Normal file
BIN
mobile/openapi/lib/model/tags_update.dart
generated
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
49
mobile/test/modules/utils/openapi_patching_test.dart
Normal file
49
mobile/test/modules/utils/openapi_patching_test.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:immich_mobile/utils/openapi_patching.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Test OpenApi Patching', () {
|
||||||
|
test('upgradeDto', () {
|
||||||
|
dynamic value;
|
||||||
|
String targetType;
|
||||||
|
|
||||||
|
targetType = 'UserPreferencesResponseDto';
|
||||||
|
value = jsonDecode("""
|
||||||
|
{
|
||||||
|
"download": {
|
||||||
|
"archiveSize": 4294967296,
|
||||||
|
"includeEmbeddedVideos": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
upgradeDto(value, targetType);
|
||||||
|
expect(value['tags'], TagsResponse().toJson());
|
||||||
|
expect(value['download']['includeEmbeddedVideos'], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('addDefault', () {
|
||||||
|
dynamic value = jsonDecode("""
|
||||||
|
{
|
||||||
|
"download": {
|
||||||
|
"archiveSize": 4294967296,
|
||||||
|
"includeEmbeddedVideos": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
String keys = 'download.unknownKey';
|
||||||
|
dynamic defaultValue = 69420;
|
||||||
|
|
||||||
|
addDefault(value, keys, defaultValue);
|
||||||
|
expect(value['download']['unknownKey'], 69420);
|
||||||
|
|
||||||
|
keys = 'alpha.beta';
|
||||||
|
defaultValue = 'gamma';
|
||||||
|
addDefault(value, keys, defaultValue);
|
||||||
|
expect(value['alpha']['beta'], 'gamma');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -9164,6 +9164,34 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"FoldersResponse": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sidebarWeb": {
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"sidebarWeb"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FoldersUpdate": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sidebarWeb": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ImageFormat": {
|
"ImageFormat": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"jpeg",
|
"jpeg",
|
||||||
|
@ -9534,6 +9562,26 @@
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"MemoriesResponse": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"MemoriesUpdate": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"MemoryCreateDto": {
|
"MemoryCreateDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"assetIds": {
|
"assetIds": {
|
||||||
|
@ -9586,17 +9634,6 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"MemoryResponse": {
|
|
||||||
"properties": {
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"enabled"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"MemoryResponseDto": {
|
"MemoryResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"assets": {
|
"assets": {
|
||||||
|
@ -9660,14 +9697,6 @@
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"MemoryUpdate": {
|
|
||||||
"properties": {
|
|
||||||
"enabled": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"MemoryUpdateDto": {
|
"MemoryUpdateDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"isSaved": {
|
"isSaved": {
|
||||||
|
@ -9953,6 +9982,23 @@
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"PeopleResponse": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sidebarWeb": {
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"sidebarWeb"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"PeopleResponseDto": {
|
"PeopleResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"hasNextPage": {
|
"hasNextPage": {
|
||||||
|
@ -9979,6 +10025,17 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"PeopleUpdate": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sidebarWeb": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"PeopleUpdateDto": {
|
"PeopleUpdateDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"people": {
|
"people": {
|
||||||
|
@ -10300,7 +10357,7 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"RatingResponse": {
|
"RatingsResponse": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"default": false,
|
"default": false,
|
||||||
|
@ -10312,7 +10369,7 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"RatingUpdate": {
|
"RatingsUpdate": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
@ -12002,6 +12059,34 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"TagsResponse": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sidebarWeb": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"sidebarWeb"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"TagsUpdate": {
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sidebarWeb": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"TimeBucketResponseDto": {
|
"TimeBucketResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"count": {
|
||||||
|
@ -12379,23 +12464,35 @@
|
||||||
"emailNotifications": {
|
"emailNotifications": {
|
||||||
"$ref": "#/components/schemas/EmailNotificationsResponse"
|
"$ref": "#/components/schemas/EmailNotificationsResponse"
|
||||||
},
|
},
|
||||||
|
"folders": {
|
||||||
|
"$ref": "#/components/schemas/FoldersResponse"
|
||||||
|
},
|
||||||
"memories": {
|
"memories": {
|
||||||
"$ref": "#/components/schemas/MemoryResponse"
|
"$ref": "#/components/schemas/MemoriesResponse"
|
||||||
|
},
|
||||||
|
"people": {
|
||||||
|
"$ref": "#/components/schemas/PeopleResponse"
|
||||||
},
|
},
|
||||||
"purchase": {
|
"purchase": {
|
||||||
"$ref": "#/components/schemas/PurchaseResponse"
|
"$ref": "#/components/schemas/PurchaseResponse"
|
||||||
},
|
},
|
||||||
"rating": {
|
"ratings": {
|
||||||
"$ref": "#/components/schemas/RatingResponse"
|
"$ref": "#/components/schemas/RatingsResponse"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"$ref": "#/components/schemas/TagsResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"avatar",
|
"avatar",
|
||||||
"download",
|
"download",
|
||||||
"emailNotifications",
|
"emailNotifications",
|
||||||
|
"folders",
|
||||||
"memories",
|
"memories",
|
||||||
|
"people",
|
||||||
"purchase",
|
"purchase",
|
||||||
"rating"
|
"ratings",
|
||||||
|
"tags"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
@ -12410,14 +12507,23 @@
|
||||||
"emailNotifications": {
|
"emailNotifications": {
|
||||||
"$ref": "#/components/schemas/EmailNotificationsUpdate"
|
"$ref": "#/components/schemas/EmailNotificationsUpdate"
|
||||||
},
|
},
|
||||||
|
"folders": {
|
||||||
|
"$ref": "#/components/schemas/FoldersUpdate"
|
||||||
|
},
|
||||||
"memories": {
|
"memories": {
|
||||||
"$ref": "#/components/schemas/MemoryUpdate"
|
"$ref": "#/components/schemas/MemoriesUpdate"
|
||||||
|
},
|
||||||
|
"people": {
|
||||||
|
"$ref": "#/components/schemas/PeopleUpdate"
|
||||||
},
|
},
|
||||||
"purchase": {
|
"purchase": {
|
||||||
"$ref": "#/components/schemas/PurchaseUpdate"
|
"$ref": "#/components/schemas/PurchaseUpdate"
|
||||||
},
|
},
|
||||||
"rating": {
|
"ratings": {
|
||||||
"$ref": "#/components/schemas/RatingUpdate"
|
"$ref": "#/components/schemas/RatingsUpdate"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"$ref": "#/components/schemas/TagsUpdate"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
|
@ -93,23 +93,38 @@ export type EmailNotificationsResponse = {
|
||||||
albumUpdate: boolean;
|
albumUpdate: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
export type MemoryResponse = {
|
export type FoldersResponse = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
sidebarWeb: boolean;
|
||||||
|
};
|
||||||
|
export type MemoriesResponse = {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
export type PeopleResponse = {
|
||||||
|
enabled: boolean;
|
||||||
|
sidebarWeb: boolean;
|
||||||
};
|
};
|
||||||
export type PurchaseResponse = {
|
export type PurchaseResponse = {
|
||||||
hideBuyButtonUntil: string;
|
hideBuyButtonUntil: string;
|
||||||
showSupportBadge: boolean;
|
showSupportBadge: boolean;
|
||||||
};
|
};
|
||||||
export type RatingResponse = {
|
export type RatingsResponse = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
export type TagsResponse = {
|
||||||
|
enabled: boolean;
|
||||||
|
sidebarWeb: boolean;
|
||||||
|
};
|
||||||
export type UserPreferencesResponseDto = {
|
export type UserPreferencesResponseDto = {
|
||||||
avatar: AvatarResponse;
|
avatar: AvatarResponse;
|
||||||
download: DownloadResponse;
|
download: DownloadResponse;
|
||||||
emailNotifications: EmailNotificationsResponse;
|
emailNotifications: EmailNotificationsResponse;
|
||||||
memories: MemoryResponse;
|
folders: FoldersResponse;
|
||||||
|
memories: MemoriesResponse;
|
||||||
|
people: PeopleResponse;
|
||||||
purchase: PurchaseResponse;
|
purchase: PurchaseResponse;
|
||||||
rating: RatingResponse;
|
ratings: RatingsResponse;
|
||||||
|
tags: TagsResponse;
|
||||||
};
|
};
|
||||||
export type AvatarUpdate = {
|
export type AvatarUpdate = {
|
||||||
color?: UserAvatarColor;
|
color?: UserAvatarColor;
|
||||||
|
@ -123,23 +138,38 @@ export type EmailNotificationsUpdate = {
|
||||||
albumUpdate?: boolean;
|
albumUpdate?: boolean;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
export type MemoryUpdate = {
|
export type FoldersUpdate = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
sidebarWeb?: boolean;
|
||||||
|
};
|
||||||
|
export type MemoriesUpdate = {
|
||||||
|
enabled?: boolean;
|
||||||
|
};
|
||||||
|
export type PeopleUpdate = {
|
||||||
|
enabled?: boolean;
|
||||||
|
sidebarWeb?: boolean;
|
||||||
};
|
};
|
||||||
export type PurchaseUpdate = {
|
export type PurchaseUpdate = {
|
||||||
hideBuyButtonUntil?: string;
|
hideBuyButtonUntil?: string;
|
||||||
showSupportBadge?: boolean;
|
showSupportBadge?: boolean;
|
||||||
};
|
};
|
||||||
export type RatingUpdate = {
|
export type RatingsUpdate = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
export type TagsUpdate = {
|
||||||
|
enabled?: boolean;
|
||||||
|
sidebarWeb?: boolean;
|
||||||
|
};
|
||||||
export type UserPreferencesUpdateDto = {
|
export type UserPreferencesUpdateDto = {
|
||||||
avatar?: AvatarUpdate;
|
avatar?: AvatarUpdate;
|
||||||
download?: DownloadUpdate;
|
download?: DownloadUpdate;
|
||||||
emailNotifications?: EmailNotificationsUpdate;
|
emailNotifications?: EmailNotificationsUpdate;
|
||||||
memories?: MemoryUpdate;
|
folders?: FoldersUpdate;
|
||||||
|
memories?: MemoriesUpdate;
|
||||||
|
people?: PeopleUpdate;
|
||||||
purchase?: PurchaseUpdate;
|
purchase?: PurchaseUpdate;
|
||||||
rating?: RatingUpdate;
|
ratings?: RatingsUpdate;
|
||||||
|
tags?: TagsUpdate;
|
||||||
};
|
};
|
||||||
export type AlbumUserResponseDto = {
|
export type AlbumUserResponseDto = {
|
||||||
role: AlbumUserRole;
|
role: AlbumUserRole;
|
||||||
|
|
|
@ -12,16 +12,40 @@ class AvatarUpdate {
|
||||||
color?: UserAvatarColor;
|
color?: UserAvatarColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MemoryUpdate {
|
class MemoriesUpdate {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RatingUpdate {
|
class RatingsUpdate {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FoldersUpdate {
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
enabled?: boolean;
|
||||||
|
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
sidebarWeb?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeopleUpdate {
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
enabled?: boolean;
|
||||||
|
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
sidebarWeb?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagsUpdate {
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
enabled?: boolean;
|
||||||
|
|
||||||
|
@ValidateBoolean({ optional: true })
|
||||||
|
sidebarWeb?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
class EmailNotificationsUpdate {
|
class EmailNotificationsUpdate {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
@ -56,19 +80,34 @@ class PurchaseUpdate {
|
||||||
export class UserPreferencesUpdateDto {
|
export class UserPreferencesUpdateDto {
|
||||||
@Optional()
|
@Optional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => RatingUpdate)
|
@Type(() => FoldersUpdate)
|
||||||
rating?: RatingUpdate;
|
folders?: FoldersUpdate;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => MemoriesUpdate)
|
||||||
|
memories?: MemoriesUpdate;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => PeopleUpdate)
|
||||||
|
people?: PeopleUpdate;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => RatingsUpdate)
|
||||||
|
ratings?: RatingsUpdate;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => TagsUpdate)
|
||||||
|
tags?: TagsUpdate;
|
||||||
|
|
||||||
@Optional()
|
@Optional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => AvatarUpdate)
|
@Type(() => AvatarUpdate)
|
||||||
avatar?: AvatarUpdate;
|
avatar?: AvatarUpdate;
|
||||||
|
|
||||||
@Optional()
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(() => MemoryUpdate)
|
|
||||||
memories?: MemoryUpdate;
|
|
||||||
|
|
||||||
@Optional()
|
@Optional()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => EmailNotificationsUpdate)
|
@Type(() => EmailNotificationsUpdate)
|
||||||
|
@ -90,12 +129,27 @@ class AvatarResponse {
|
||||||
color!: UserAvatarColor;
|
color!: UserAvatarColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RatingResponse {
|
class RatingsResponse {
|
||||||
enabled: boolean = false;
|
enabled: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MemoryResponse {
|
class MemoriesResponse {
|
||||||
enabled!: boolean;
|
enabled: boolean = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FoldersResponse {
|
||||||
|
enabled: boolean = false;
|
||||||
|
sidebarWeb: boolean = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeopleResponse {
|
||||||
|
enabled: boolean = true;
|
||||||
|
sidebarWeb: boolean = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagsResponse {
|
||||||
|
enabled: boolean = true;
|
||||||
|
sidebarWeb: boolean = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmailNotificationsResponse {
|
class EmailNotificationsResponse {
|
||||||
|
@ -117,8 +171,11 @@ class PurchaseResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserPreferencesResponseDto implements UserPreferences {
|
export class UserPreferencesResponseDto implements UserPreferences {
|
||||||
rating!: RatingResponse;
|
folders!: FoldersResponse;
|
||||||
memories!: MemoryResponse;
|
memories!: MemoriesResponse;
|
||||||
|
people!: PeopleResponse;
|
||||||
|
ratings!: RatingsResponse;
|
||||||
|
tags!: TagsResponse;
|
||||||
avatar!: AvatarResponse;
|
avatar!: AvatarResponse;
|
||||||
emailNotifications!: EmailNotificationsResponse;
|
emailNotifications!: EmailNotificationsResponse;
|
||||||
download!: DownloadResponse;
|
download!: DownloadResponse;
|
||||||
|
|
|
@ -19,12 +19,24 @@ export class UserMetadataEntity<T extends keyof UserMetadata = UserMetadataKey>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserPreferences {
|
export interface UserPreferences {
|
||||||
rating: {
|
folders: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
sidebarWeb: boolean;
|
||||||
};
|
};
|
||||||
memories: {
|
memories: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
people: {
|
||||||
|
enabled: boolean;
|
||||||
|
sidebarWeb: boolean;
|
||||||
|
};
|
||||||
|
ratings: {
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
tags: {
|
||||||
|
enabled: boolean;
|
||||||
|
sidebarWeb: boolean;
|
||||||
|
};
|
||||||
avatar: {
|
avatar: {
|
||||||
color: UserAvatarColor;
|
color: UserAvatarColor;
|
||||||
};
|
};
|
||||||
|
@ -50,12 +62,24 @@ export const getDefaultPreferences = (user: { email: string }): UserPreferences
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rating: {
|
folders: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
sidebarWeb: false,
|
||||||
},
|
},
|
||||||
memories: {
|
memories: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
people: {
|
||||||
|
enabled: true,
|
||||||
|
sidebarWeb: false,
|
||||||
|
},
|
||||||
|
ratings: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
enabled: false,
|
||||||
|
sidebarWeb: false,
|
||||||
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
color: values[randomIndex],
|
color: values[randomIndex],
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !isSharedLink() && $preferences?.rating?.enabled}
|
{#if !isSharedLink() && $preferences?.ratings.enabled}
|
||||||
<section class="px-4 pt-2">
|
<section class="px-4 pt-2">
|
||||||
<StarRating {rating} readOnly={!isOwner} onRating={(rating) => handlePromiseError(handleChangeRating(rating))} />
|
<StarRating {rating} readOnly={!isOwner} onRating={(rating) => handlePromiseError(handleChangeRating(rating))} />
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, handlePromiseError, isSharedLink } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, handlePromiseError, isSharedLink } from '$lib/utils';
|
||||||
import { delay, isFlipped } from '$lib/utils/asset-utils';
|
import { delay, isFlipped } from '$lib/utils/asset-utils';
|
||||||
import {
|
import {
|
||||||
|
@ -502,9 +502,11 @@
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section class="relative px-2 pb-12 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
{#if $preferences?.tags?.enabled}
|
||||||
<DetailPanelTags {asset} {isOwner} />
|
<section class="relative px-2 pb-12 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
||||||
</section>
|
<DetailPanelTags {asset} {isOwner} />
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if showEditFaces}
|
{#if showEditFaces}
|
||||||
<PersonSidePanel
|
<PersonSidePanel
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
export let subtitle = '';
|
export let subtitle = '';
|
||||||
export let key: string;
|
export let key: string;
|
||||||
export let isOpen = $accordionState.has(key);
|
export let isOpen = $accordionState.has(key);
|
||||||
|
export let autoScrollTo = false;
|
||||||
|
|
||||||
let accordionElement: HTMLDivElement;
|
let accordionElement: HTMLDivElement;
|
||||||
|
|
||||||
|
@ -18,12 +19,14 @@
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
$accordionState = $accordionState.add(key);
|
$accordionState = $accordionState.add(key);
|
||||||
|
|
||||||
setTimeout(() => {
|
if (autoScrollTo) {
|
||||||
accordionElement.scrollIntoView({
|
setTimeout(() => {
|
||||||
behavior: 'smooth',
|
accordionElement.scrollIntoView({
|
||||||
block: 'start',
|
behavior: 'smooth',
|
||||||
});
|
block: 'start',
|
||||||
}, 200);
|
});
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$accordionState.delete(key);
|
$accordionState.delete(key);
|
||||||
$accordionState = $accordionState;
|
$accordionState = $accordionState;
|
||||||
|
@ -72,7 +75,7 @@
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<ul transition:slide={{ duration: 250 }} class="mb-2 ml-4">
|
<ul transition:slide={{ duration: 150 }} class="mb-2 ml-4">
|
||||||
<slot />
|
<slot />
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { sidebarSettings } from '$lib/stores/preferences.store';
|
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import {
|
import {
|
||||||
mdiAccount,
|
mdiAccount,
|
||||||
|
@ -29,6 +28,7 @@
|
||||||
import MoreInformationAlbums from '$lib/components/shared-components/side-bar/more-information-albums.svelte';
|
import MoreInformationAlbums from '$lib/components/shared-components/side-bar/more-information-albums.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
||||||
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
|
||||||
let isArchiveSelected: boolean;
|
let isArchiveSelected: boolean;
|
||||||
let isFavoritesSelected: boolean;
|
let isFavoritesSelected: boolean;
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
<MoreInformationAssets assetStats={{ isArchived: false }} />
|
<MoreInformationAssets assetStats={{ isArchived: false }} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</SideBarLink>
|
</SideBarLink>
|
||||||
|
|
||||||
{#if $featureFlags.search}
|
{#if $featureFlags.search}
|
||||||
<SideBarLink title={$t('explore')} routeId="/(user)/explore" icon={mdiMagnify} />
|
<SideBarLink title={$t('explore')} routeId="/(user)/explore" icon={mdiMagnify} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -65,7 +66,7 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $sidebarSettings.people}
|
{#if $preferences.people.enabled && $preferences.people.sidebarWeb}
|
||||||
<SideBarLink
|
<SideBarLink
|
||||||
title={$t('people')}
|
title={$t('people')}
|
||||||
routeId="/(user)/people"
|
routeId="/(user)/people"
|
||||||
|
@ -73,23 +74,23 @@
|
||||||
icon={isPeopleSelected ? mdiAccount : mdiAccountOutline}
|
icon={isPeopleSelected ? mdiAccount : mdiAccountOutline}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $sidebarSettings.sharing}
|
|
||||||
<SideBarLink
|
<SideBarLink
|
||||||
title={$t('sharing')}
|
title={$t('sharing')}
|
||||||
routeId="/(user)/sharing"
|
routeId="/(user)/sharing"
|
||||||
icon={isSharingSelected ? mdiAccountMultiple : mdiAccountMultipleOutline}
|
icon={isSharingSelected ? mdiAccountMultiple : mdiAccountMultipleOutline}
|
||||||
bind:isSelected={isSharingSelected}
|
bind:isSelected={isSharingSelected}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="moreInformation">
|
<svelte:fragment slot="moreInformation">
|
||||||
<MoreInformationAlbums albumType="shared" />
|
<MoreInformationAlbums albumType="shared" />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</SideBarLink>
|
</SideBarLink>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="text-xs transition-all duration-200 dark:text-immich-dark-fg">
|
<div class="text-xs transition-all duration-200 dark:text-immich-dark-fg">
|
||||||
<p class="hidden p-6 group-hover:sm:block md:block">{$t('library').toUpperCase()}</p>
|
<p class="hidden p-6 group-hover:sm:block md:block">{$t('library').toUpperCase()}</p>
|
||||||
<hr class="mx-4 mb-[31px] mt-8 block group-hover:sm:hidden md:hidden" />
|
<hr class="mx-4 mb-[31px] mt-8 block group-hover:sm:hidden md:hidden" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SideBarLink
|
<SideBarLink
|
||||||
title={$t('favorites')}
|
title={$t('favorites')}
|
||||||
routeId="/(user)/favorites"
|
routeId="/(user)/favorites"
|
||||||
|
@ -100,15 +101,20 @@
|
||||||
<MoreInformationAssets assetStats={{ isFavorite: true }} />
|
<MoreInformationAssets assetStats={{ isFavorite: true }} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</SideBarLink>
|
</SideBarLink>
|
||||||
|
|
||||||
<SideBarLink title={$t('albums')} routeId="/(user)/albums" icon={mdiImageAlbum} flippedLogo>
|
<SideBarLink title={$t('albums')} routeId="/(user)/albums" icon={mdiImageAlbum} flippedLogo>
|
||||||
<svelte:fragment slot="moreInformation">
|
<svelte:fragment slot="moreInformation">
|
||||||
<MoreInformationAlbums albumType="owned" />
|
<MoreInformationAlbums albumType="owned" />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</SideBarLink>
|
</SideBarLink>
|
||||||
|
|
||||||
<SideBarLink title={$t('tags')} routeId="/(user)/tags" icon={mdiTagMultipleOutline} flippedLogo />
|
{#if $preferences.tags.enabled && $preferences.tags.sidebarWeb}
|
||||||
|
<SideBarLink title={$t('tags')} routeId="/(user)/tags" icon={mdiTagMultipleOutline} flippedLogo />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<SideBarLink title={$t('folders')} routeId="/(user)/folders" icon={mdiFolderOutline} flippedLogo />
|
{#if $preferences.folders.enabled && $preferences.folders.sidebarWeb}
|
||||||
|
<SideBarLink title={$t('folders')} routeId="/(user)/folders" icon={mdiFolderOutline} flippedLogo />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<SideBarLink
|
<SideBarLink
|
||||||
title={$t('utilities')}
|
title={$t('utilities')}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
loopVideo,
|
loopVideo,
|
||||||
playVideoThumbnailOnHover,
|
playVideoThumbnailOnHover,
|
||||||
showDeleteModal,
|
showDeleteModal,
|
||||||
sidebarSettings,
|
|
||||||
} from '$lib/stores/preferences.store';
|
} from '$lib/stores/preferences.store';
|
||||||
import { findLocale } from '$lib/utils';
|
import { findLocale } from '$lib/utils';
|
||||||
import { getClosestAvailableLocale, langCodes } from '$lib/utils/i18n';
|
import { getClosestAvailableLocale, langCodes } from '$lib/utils/i18n';
|
||||||
|
@ -19,13 +18,6 @@
|
||||||
import { locale as i18nLocale, t } from 'svelte-i18n';
|
import { locale as i18nLocale, t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
|
||||||
import { updateMyPreferences } from '@immich/sdk';
|
|
||||||
import { handleError } from '../../utils/handle-error';
|
|
||||||
import {
|
|
||||||
notificationController,
|
|
||||||
NotificationType,
|
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
|
||||||
|
|
||||||
let time = new Date();
|
let time = new Date();
|
||||||
|
|
||||||
|
@ -46,7 +38,6 @@
|
||||||
label: findLocale(editedLocale).name || fallbackLocale.name,
|
label: findLocale(editedLocale).name || fallbackLocale.name,
|
||||||
};
|
};
|
||||||
$: closestLanguage = getClosestAvailableLocale([$lang], langCodes);
|
$: closestLanguage = getClosestAvailableLocale([$lang], langCodes);
|
||||||
$: ratingEnabled = $preferences?.rating?.enabled;
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
@ -98,17 +89,6 @@
|
||||||
$locale = newLocale;
|
$locale = newLocale;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRatingChange = async (enabled: boolean) => {
|
|
||||||
try {
|
|
||||||
const data = await updateMyPreferences({ userPreferencesUpdateDto: { rating: { enabled } } });
|
|
||||||
$preferences.rating.enabled = data.rating.enabled;
|
|
||||||
|
|
||||||
notificationController.show({ message: $t('saved_settings'), type: NotificationType.Info });
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.unable_to_update_settings'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="my-4">
|
<section class="my-4">
|
||||||
|
@ -189,29 +169,6 @@
|
||||||
bind:checked={$showDeleteModal}
|
bind:checked={$showDeleteModal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-4">
|
|
||||||
<SettingSwitch
|
|
||||||
title={$t('people')}
|
|
||||||
subtitle={$t('people_sidebar_description')}
|
|
||||||
bind:checked={$sidebarSettings.people}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<SettingSwitch
|
|
||||||
title={$t('sharing')}
|
|
||||||
subtitle={$t('sharing_sidebar_description')}
|
|
||||||
bind:checked={$sidebarSettings.sharing}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<SettingSwitch
|
|
||||||
title={$t('rating')}
|
|
||||||
subtitle={$t('rating_description')}
|
|
||||||
bind:checked={ratingEnabled}
|
|
||||||
on:toggle={({ detail: enabled }) => handleRatingChange(enabled)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType,
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { updateMyPreferences } from '@immich/sdk';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { handleError } from '../../utils/handle-error';
|
||||||
|
|
||||||
|
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||||
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
import Button from '../elements/buttons/button.svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
|
||||||
|
|
||||||
|
// Folders
|
||||||
|
let foldersEnabled = $preferences?.folders?.enabled ?? false;
|
||||||
|
let foldersSidebar = $preferences?.folders?.sidebarWeb ?? false;
|
||||||
|
|
||||||
|
// Memories
|
||||||
|
let memoriesEnabled = $preferences?.memories?.enabled ?? true;
|
||||||
|
|
||||||
|
// People
|
||||||
|
let peopleEnabled = $preferences?.people?.enabled ?? false;
|
||||||
|
let peopleSidebar = $preferences?.people?.sidebarWeb ?? false;
|
||||||
|
|
||||||
|
// Ratings
|
||||||
|
let ratingsEnabled = $preferences?.ratings?.enabled ?? false;
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
let tagsEnabled = $preferences?.tags?.enabled ?? false;
|
||||||
|
let tagsSidebar = $preferences?.tags?.sidebarWeb ?? false;
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
const data = await updateMyPreferences({
|
||||||
|
userPreferencesUpdateDto: {
|
||||||
|
folders: { enabled: foldersEnabled, sidebarWeb: foldersSidebar },
|
||||||
|
memories: { enabled: memoriesEnabled },
|
||||||
|
people: { enabled: peopleEnabled, sidebarWeb: peopleSidebar },
|
||||||
|
ratings: { enabled: ratingsEnabled },
|
||||||
|
tags: { enabled: tagsEnabled, sidebarWeb: tagsSidebar },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$preferences = { ...data };
|
||||||
|
|
||||||
|
notificationController.show({ message: $t('saved_settings'), type: NotificationType.Info });
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, $t('errors.unable_to_update_settings'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="my-4">
|
||||||
|
<div in:fade={{ duration: 500 }}>
|
||||||
|
<form autocomplete="off" on:submit|preventDefault>
|
||||||
|
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||||
|
<SettingAccordion key="folders" title={$t('folders')} subtitle={$t('folders_feature_description')}>
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch title={$t('enable')} bind:checked={foldersEnabled} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if foldersEnabled}
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch
|
||||||
|
title={$t('sidebar')}
|
||||||
|
subtitle={$t('sidebar_display_description')}
|
||||||
|
bind:checked={foldersSidebar}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion key="memories" title={$t('time_based_memories')} subtitle={$t('photos_from_previous_years')}>
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch title={$t('enable')} bind:checked={memoriesEnabled} />
|
||||||
|
</div>
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion key="people" title={$t('people')} subtitle={$t('people_feature_description')}>
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch title={$t('enable')} bind:checked={peopleEnabled} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if peopleEnabled}
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch
|
||||||
|
title={$t('sidebar')}
|
||||||
|
subtitle={$t('sidebar_display_description')}
|
||||||
|
bind:checked={peopleSidebar}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion key="rating" title={$t('rating')} subtitle={$t('rating_description')}>
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch title={$t('enable')} bind:checked={ratingsEnabled} />
|
||||||
|
</div>
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<SettingAccordion key="tags" title={$t('tags')} subtitle={$t('tag_feature_description')}>
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch title={$t('enable')} bind:checked={tagsEnabled} />
|
||||||
|
</div>
|
||||||
|
{#if tagsEnabled}
|
||||||
|
<div class="ml-4 mt-6">
|
||||||
|
<SettingSwitch
|
||||||
|
title={$t('sidebar')}
|
||||||
|
subtitle={$t('sidebar_display_description')}
|
||||||
|
bind:checked={tagsSidebar}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<Button type="submit" size="sm" on:click={() => handleSave()}>{$t('save')}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -1,46 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
notificationController,
|
|
||||||
NotificationType,
|
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
|
||||||
import { updateMyPreferences } from '@immich/sdk';
|
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import { handleError } from '../../utils/handle-error';
|
|
||||||
|
|
||||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
|
||||||
import { preferences } from '$lib/stores/user.store';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
|
|
||||||
let memoriesEnabled = $preferences?.memories?.enabled ?? false;
|
|
||||||
|
|
||||||
const handleSave = async () => {
|
|
||||||
try {
|
|
||||||
const data = await updateMyPreferences({ userPreferencesUpdateDto: { memories: { enabled: memoriesEnabled } } });
|
|
||||||
$preferences.memories.enabled = data.memories.enabled;
|
|
||||||
|
|
||||||
notificationController.show({ message: $t('saved_settings'), type: NotificationType.Info });
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.unable_to_update_settings'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section class="my-4">
|
|
||||||
<div in:fade={{ duration: 500 }}>
|
|
||||||
<form autocomplete="off" on:submit|preventDefault>
|
|
||||||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
|
||||||
<div class="ml-4">
|
|
||||||
<SettingSwitch
|
|
||||||
title={$t('time_based_memories')}
|
|
||||||
subtitle={$t('photos_from_previous_years')}
|
|
||||||
bind:checked={memoriesEnabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<Button type="submit" size="sm" on:click={() => handleSave()}>{$t('save')}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
|
@ -10,7 +10,6 @@
|
||||||
import AppSettings from './app-settings.svelte';
|
import AppSettings from './app-settings.svelte';
|
||||||
import ChangePasswordSettings from './change-password-settings.svelte';
|
import ChangePasswordSettings from './change-password-settings.svelte';
|
||||||
import DeviceList from './device-list.svelte';
|
import DeviceList from './device-list.svelte';
|
||||||
import MemoriesSettings from './memories-settings.svelte';
|
|
||||||
import OAuthSettings from './oauth-settings.svelte';
|
import OAuthSettings from './oauth-settings.svelte';
|
||||||
import PartnerSettings from './partner-settings.svelte';
|
import PartnerSettings from './partner-settings.svelte';
|
||||||
import UserAPIKeyList from './user-api-key-list.svelte';
|
import UserAPIKeyList from './user-api-key-list.svelte';
|
||||||
|
@ -19,6 +18,7 @@
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import DownloadSettings from '$lib/components/user-settings-page/download-settings.svelte';
|
import DownloadSettings from '$lib/components/user-settings-page/download-settings.svelte';
|
||||||
import UserPurchaseSettings from '$lib/components/user-settings-page/user-purchase-settings.svelte';
|
import UserPurchaseSettings from '$lib/components/user-settings-page/user-purchase-settings.svelte';
|
||||||
|
import FeatureSettings from '$lib/components/user-settings-page/feature-settings.svelte';
|
||||||
|
|
||||||
export let keys: ApiKeyResponseDto[] = [];
|
export let keys: ApiKeyResponseDto[] = [];
|
||||||
export let sessions: SessionResponseDto[] = [];
|
export let sessions: SessionResponseDto[] = [];
|
||||||
|
@ -53,8 +53,8 @@
|
||||||
<DownloadSettings />
|
<DownloadSettings />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion key="memories" title={$t('memories')} subtitle={$t('memories_setting_description')}>
|
<SettingAccordion key="feature" title={$t('features')} subtitle={$t('features_setting_description')}>
|
||||||
<MemoriesSettings />
|
<FeatureSettings />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion key="notifications" title={$t('notifications')} subtitle={$t('notifications_setting_description')}>
|
<SettingAccordion key="notifications" title={$t('notifications')} subtitle={$t('notifications_setting_description')}>
|
||||||
|
@ -84,6 +84,7 @@
|
||||||
key="user-purchase-settings"
|
key="user-purchase-settings"
|
||||||
title={$t('user_purchase_settings')}
|
title={$t('user_purchase_settings')}
|
||||||
subtitle={$t('user_purchase_settings_description')}
|
subtitle={$t('user_purchase_settings_description')}
|
||||||
|
autoScrollTo={true}
|
||||||
>
|
>
|
||||||
<UserPurchaseSettings />
|
<UserPurchaseSettings />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
|
|
|
@ -701,6 +701,8 @@
|
||||||
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
"favorite_or_unfavorite_photo": "Favorite or unfavorite photo",
|
||||||
"favorites": "Favorites",
|
"favorites": "Favorites",
|
||||||
"feature_photo_updated": "Feature photo updated",
|
"feature_photo_updated": "Feature photo updated",
|
||||||
|
"features": "Features",
|
||||||
|
"features_setting_description": "Manage the app features",
|
||||||
"file_name": "File name",
|
"file_name": "File name",
|
||||||
"file_name_or_extension": "File name or extension",
|
"file_name_or_extension": "File name or extension",
|
||||||
"filename": "Filename",
|
"filename": "Filename",
|
||||||
|
@ -709,6 +711,7 @@
|
||||||
"find_them_fast": "Find them fast by name with search",
|
"find_them_fast": "Find them fast by name with search",
|
||||||
"fix_incorrect_match": "Fix incorrect match",
|
"fix_incorrect_match": "Fix incorrect match",
|
||||||
"folders": "Folders",
|
"folders": "Folders",
|
||||||
|
"folders_feature_description": "Browsing the folder view for the photos and videos on the file system",
|
||||||
"force_re-scan_library_files": "Force Re-scan All Library Files",
|
"force_re-scan_library_files": "Force Re-scan All Library Files",
|
||||||
"forward": "Forward",
|
"forward": "Forward",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
|
@ -912,6 +915,7 @@
|
||||||
"pending": "Pending",
|
"pending": "Pending",
|
||||||
"people": "People",
|
"people": "People",
|
||||||
"people_edits_count": "Edited {count, plural, one {# person} other {# people}}",
|
"people_edits_count": "Edited {count, plural, one {# person} other {# people}}",
|
||||||
|
"people_feature_description": "Browsing photos and videos grouped by people",
|
||||||
"people_sidebar_description": "Display a link to People in the sidebar",
|
"people_sidebar_description": "Display a link to People in the sidebar",
|
||||||
"permanent_deletion_warning": "Permanent deletion warning",
|
"permanent_deletion_warning": "Permanent deletion warning",
|
||||||
"permanent_deletion_warning_setting_description": "Show a warning when permanently deleting assets",
|
"permanent_deletion_warning_setting_description": "Show a warning when permanently deleting assets",
|
||||||
|
@ -981,7 +985,7 @@
|
||||||
"rating": "Star rating",
|
"rating": "Star rating",
|
||||||
"rating_clear": "Clear rating",
|
"rating_clear": "Clear rating",
|
||||||
"rating_count": "{count, plural, one {# star} other {# stars}}",
|
"rating_count": "{count, plural, one {# star} other {# stars}}",
|
||||||
"rating_description": "Display the exif rating in the info panel",
|
"rating_description": "Display the EXIF rating in the info panel",
|
||||||
"reaction_options": "Reaction options",
|
"reaction_options": "Reaction options",
|
||||||
"read_changelog": "Read Changelog",
|
"read_changelog": "Read Changelog",
|
||||||
"reassign": "Reassign",
|
"reassign": "Reassign",
|
||||||
|
@ -1130,6 +1134,8 @@
|
||||||
"show_supporter_badge": "Supporter badge",
|
"show_supporter_badge": "Supporter badge",
|
||||||
"show_supporter_badge_description": "Show a supporter badge",
|
"show_supporter_badge_description": "Show a supporter badge",
|
||||||
"shuffle": "Shuffle",
|
"shuffle": "Shuffle",
|
||||||
|
"sidebar": "Sidebar",
|
||||||
|
"sidebar_display_description": "Display a link to the view in the sidebar",
|
||||||
"sign_out": "Sign Out",
|
"sign_out": "Sign Out",
|
||||||
"sign_up": "Sign up",
|
"sign_up": "Sign up",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
|
@ -1169,6 +1175,7 @@
|
||||||
"tag": "Tag",
|
"tag": "Tag",
|
||||||
"tag_assets": "Tag assets",
|
"tag_assets": "Tag assets",
|
||||||
"tag_created": "Created tag: {tag}",
|
"tag_created": "Created tag: {tag}",
|
||||||
|
"tag_feature_description": "Browsing photos and videos grouped by logical tag topics",
|
||||||
"tag_updated": "Updated tag: {tag}",
|
"tag_updated": "Updated tag: {tag}",
|
||||||
"tagged_assets": "Tagged {count, plural, one {# asset} other {# assets}}",
|
"tagged_assets": "Tagged {count, plural, one {# asset} other {# assets}}",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
|
|
@ -96,11 +96,6 @@ export interface SidebarSettings {
|
||||||
sharing: boolean;
|
sharing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sidebarSettings = persisted<SidebarSettings>('sidebar-settings-1', {
|
|
||||||
people: false,
|
|
||||||
sharing: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export enum SortOrder {
|
export enum SortOrder {
|
||||||
Asc = 'asc',
|
Asc = 'asc',
|
||||||
Desc = 'desc',
|
Desc = 'desc',
|
||||||
|
|
|
@ -81,7 +81,9 @@
|
||||||
<ChangeDate menuItem />
|
<ChangeDate menuItem />
|
||||||
<ChangeLocation menuItem />
|
<ChangeLocation menuItem />
|
||||||
<ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
|
<ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||||
<TagAction menuItem />
|
{#if $preferences.tags.enabled}
|
||||||
|
<TagAction menuItem />
|
||||||
|
{/if}
|
||||||
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||||
<hr />
|
<hr />
|
||||||
<AssetJobActions />
|
<AssetJobActions />
|
||||||
|
|
Loading…
Reference in a new issue