mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(web): custom stylesheets (#4602)
* add initial ui and api definitions for stylesheets * proper saving * make custom css work * add textarea * rebuild api * run prettier * add typecast * update typings * move css accordion to be sorted alphabetically * set content-type properly * rename stylesheets to theme * fix server test
This commit is contained in:
		
							
								
								
									
										19
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -3307,6 +3307,12 @@ export interface SystemConfigDto { | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'storageTemplate': SystemConfigStorageTemplateDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigThemeDto} | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'theme': SystemConfigThemeDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigThumbnailDto} | ||||
| @@ -3741,6 +3747,19 @@ export interface SystemConfigTemplateStorageOptionDto { | ||||
|      */ | ||||
|     'yearOptions': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface SystemConfigThemeDto | ||||
|  */ | ||||
| export interface SystemConfigThemeDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof SystemConfigThemeDto | ||||
|      */ | ||||
|     'customCss': string; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|   | ||||
							
								
								
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							| @@ -135,6 +135,7 @@ doc/SystemConfigPasswordLoginDto.md | ||||
| doc/SystemConfigReverseGeocodingDto.md | ||||
| doc/SystemConfigStorageTemplateDto.md | ||||
| doc/SystemConfigTemplateStorageOptionDto.md | ||||
| doc/SystemConfigThemeDto.md | ||||
| doc/SystemConfigThumbnailDto.md | ||||
| doc/SystemConfigTrashDto.md | ||||
| doc/TagApi.md | ||||
| @@ -302,6 +303,7 @@ lib/model/system_config_password_login_dto.dart | ||||
| lib/model/system_config_reverse_geocoding_dto.dart | ||||
| lib/model/system_config_storage_template_dto.dart | ||||
| lib/model/system_config_template_storage_option_dto.dart | ||||
| lib/model/system_config_theme_dto.dart | ||||
| lib/model/system_config_thumbnail_dto.dart | ||||
| lib/model/system_config_trash_dto.dart | ||||
| lib/model/tag_response_dto.dart | ||||
| @@ -456,6 +458,7 @@ test/system_config_password_login_dto_test.dart | ||||
| test/system_config_reverse_geocoding_dto_test.dart | ||||
| test/system_config_storage_template_dto_test.dart | ||||
| test/system_config_template_storage_option_dto_test.dart | ||||
| test/system_config_theme_dto_test.dart | ||||
| test/system_config_thumbnail_dto_test.dart | ||||
| test/system_config_trash_dto_test.dart | ||||
| test/tag_api_test.dart | ||||
|   | ||||
							
								
								
									
										1
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @@ -318,6 +318,7 @@ Class | Method | HTTP request | Description | ||||
|  - [SystemConfigReverseGeocodingDto](doc//SystemConfigReverseGeocodingDto.md) | ||||
|  - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) | ||||
|  - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) | ||||
|  - [SystemConfigThemeDto](doc//SystemConfigThemeDto.md) | ||||
|  - [SystemConfigThumbnailDto](doc//SystemConfigThumbnailDto.md) | ||||
|  - [SystemConfigTrashDto](doc//SystemConfigTrashDto.md) | ||||
|  - [TagResponseDto](doc//TagResponseDto.md) | ||||
|   | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/SystemConfigDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/SystemConfigDto.md
									
									
									
										generated
									
									
									
								
							| @@ -16,6 +16,7 @@ Name | Type | Description | Notes | ||||
| **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) |  |  | ||||
| **reverseGeocoding** | [**SystemConfigReverseGeocodingDto**](SystemConfigReverseGeocodingDto.md) |  |  | ||||
| **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) |  |  | ||||
| **theme** | [**SystemConfigThemeDto**](SystemConfigThemeDto.md) |  |  | ||||
| **thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) |  |  | ||||
| **trash** | [**SystemConfigTrashDto**](SystemConfigTrashDto.md) |  |  | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										15
									
								
								mobile/openapi/doc/SystemConfigThemeDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								mobile/openapi/doc/SystemConfigThemeDto.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # openapi.model.SystemConfigThemeDto | ||||
| 
 | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **customCss** | **String** |  |  | ||||
| 
 | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @@ -163,6 +163,7 @@ part 'model/system_config_password_login_dto.dart'; | ||||
| part 'model/system_config_reverse_geocoding_dto.dart'; | ||||
| part 'model/system_config_storage_template_dto.dart'; | ||||
| part 'model/system_config_template_storage_option_dto.dart'; | ||||
| part 'model/system_config_theme_dto.dart'; | ||||
| part 'model/system_config_thumbnail_dto.dart'; | ||||
| part 'model/system_config_trash_dto.dart'; | ||||
| part 'model/tag_response_dto.dart'; | ||||
|   | ||||
							
								
								
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @@ -417,6 +417,8 @@ class ApiClient { | ||||
|           return SystemConfigStorageTemplateDto.fromJson(value); | ||||
|         case 'SystemConfigTemplateStorageOptionDto': | ||||
|           return SystemConfigTemplateStorageOptionDto.fromJson(value); | ||||
|         case 'SystemConfigThemeDto': | ||||
|           return SystemConfigThemeDto.fromJson(value); | ||||
|         case 'SystemConfigThumbnailDto': | ||||
|           return SystemConfigThumbnailDto.fromJson(value); | ||||
|         case 'SystemConfigTrashDto': | ||||
|   | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/system_config_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/system_config_dto.dart
									
									
									
										generated
									
									
									
								
							| @@ -21,6 +21,7 @@ class SystemConfigDto { | ||||
|     required this.passwordLogin, | ||||
|     required this.reverseGeocoding, | ||||
|     required this.storageTemplate, | ||||
|     required this.theme, | ||||
|     required this.thumbnail, | ||||
|     required this.trash, | ||||
|   }); | ||||
| @@ -41,6 +42,8 @@ class SystemConfigDto { | ||||
| 
 | ||||
|   SystemConfigStorageTemplateDto storageTemplate; | ||||
| 
 | ||||
|   SystemConfigThemeDto theme; | ||||
| 
 | ||||
|   SystemConfigThumbnailDto thumbnail; | ||||
| 
 | ||||
|   SystemConfigTrashDto trash; | ||||
| @@ -55,6 +58,7 @@ class SystemConfigDto { | ||||
|      other.passwordLogin == passwordLogin && | ||||
|      other.reverseGeocoding == reverseGeocoding && | ||||
|      other.storageTemplate == storageTemplate && | ||||
|      other.theme == theme && | ||||
|      other.thumbnail == thumbnail && | ||||
|      other.trash == trash; | ||||
| 
 | ||||
| @@ -69,11 +73,12 @@ class SystemConfigDto { | ||||
|     (passwordLogin.hashCode) + | ||||
|     (reverseGeocoding.hashCode) + | ||||
|     (storageTemplate.hashCode) + | ||||
|     (theme.hashCode) + | ||||
|     (thumbnail.hashCode) + | ||||
|     (trash.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, map=$map, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, thumbnail=$thumbnail, trash=$trash]'; | ||||
|   String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, map=$map, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, theme=$theme, thumbnail=$thumbnail, trash=$trash]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
| @@ -85,6 +90,7 @@ class SystemConfigDto { | ||||
|       json[r'passwordLogin'] = this.passwordLogin; | ||||
|       json[r'reverseGeocoding'] = this.reverseGeocoding; | ||||
|       json[r'storageTemplate'] = this.storageTemplate; | ||||
|       json[r'theme'] = this.theme; | ||||
|       json[r'thumbnail'] = this.thumbnail; | ||||
|       json[r'trash'] = this.trash; | ||||
|     return json; | ||||
| @@ -106,6 +112,7 @@ class SystemConfigDto { | ||||
|         passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, | ||||
|         reverseGeocoding: SystemConfigReverseGeocodingDto.fromJson(json[r'reverseGeocoding'])!, | ||||
|         storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, | ||||
|         theme: SystemConfigThemeDto.fromJson(json[r'theme'])!, | ||||
|         thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!, | ||||
|         trash: SystemConfigTrashDto.fromJson(json[r'trash'])!, | ||||
|       ); | ||||
| @@ -163,6 +170,7 @@ class SystemConfigDto { | ||||
|     'passwordLogin', | ||||
|     'reverseGeocoding', | ||||
|     'storageTemplate', | ||||
|     'theme', | ||||
|     'thumbnail', | ||||
|     'trash', | ||||
|   }; | ||||
|   | ||||
							
								
								
									
										98
									
								
								mobile/openapi/lib/model/system_config_theme_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								mobile/openapi/lib/model/system_config_theme_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
| 
 | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
| 
 | ||||
| part of openapi.api; | ||||
| 
 | ||||
| class SystemConfigThemeDto { | ||||
|   /// Returns a new [SystemConfigThemeDto] instance. | ||||
|   SystemConfigThemeDto({ | ||||
|     required this.customCss, | ||||
|   }); | ||||
| 
 | ||||
|   String customCss; | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is SystemConfigThemeDto && | ||||
|      other.customCss == customCss; | ||||
| 
 | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (customCss.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'SystemConfigThemeDto[customCss=$customCss]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'customCss'] = this.customCss; | ||||
|     return json; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns a new [SystemConfigThemeDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static SystemConfigThemeDto? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       return SystemConfigThemeDto( | ||||
|         customCss: mapValueOfType<String>(json, r'customCss')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   static List<SystemConfigThemeDto> listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <SystemConfigThemeDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = SystemConfigThemeDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| 
 | ||||
|   static Map<String, SystemConfigThemeDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, SystemConfigThemeDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = SystemConfigThemeDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   // maps a json object with a list of SystemConfigThemeDto-objects as value to a dart map | ||||
|   static Map<String, List<SystemConfigThemeDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<SystemConfigThemeDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       // ignore: parameter_assignments | ||||
|       json = json.cast<String, dynamic>(); | ||||
|       for (final entry in json.entries) { | ||||
|         map[entry.key] = SystemConfigThemeDto.listFromJson(entry.value, growable: growable,); | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'customCss', | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/system_config_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/system_config_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @@ -56,6 +56,11 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // SystemConfigThemeDto theme | ||||
|     test('to test the property `theme`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // SystemConfigThumbnailDto thumbnail | ||||
|     test('to test the property `thumbnail`', () async { | ||||
|       // TODO | ||||
|   | ||||
							
								
								
									
										27
									
								
								mobile/openapi/test/system_config_theme_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mobile/openapi/test/system_config_theme_dto_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
| 
 | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
| 
 | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:test/test.dart'; | ||||
| 
 | ||||
| // tests for SystemConfigThemeDto | ||||
| void main() { | ||||
|   // final instance = SystemConfigThemeDto(); | ||||
| 
 | ||||
|   group('test SystemConfigThemeDto', () { | ||||
|     // String customCss | ||||
|     test('to test the property `customCss`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
| @@ -8060,6 +8060,9 @@ | ||||
|           "storageTemplate": { | ||||
|             "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" | ||||
|           }, | ||||
|           "theme": { | ||||
|             "$ref": "#/components/schemas/SystemConfigThemeDto" | ||||
|           }, | ||||
|           "thumbnail": { | ||||
|             "$ref": "#/components/schemas/SystemConfigThumbnailDto" | ||||
|           }, | ||||
| @@ -8077,7 +8080,8 @@ | ||||
|           "storageTemplate", | ||||
|           "job", | ||||
|           "thumbnail", | ||||
|           "trash" | ||||
|           "trash", | ||||
|           "theme" | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
| @@ -8404,6 +8408,17 @@ | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
|       "SystemConfigThemeDto": { | ||||
|         "properties": { | ||||
|           "customCss": { | ||||
|             "type": "string" | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "customCss" | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
|       "SystemConfigThumbnailDto": { | ||||
|         "properties": { | ||||
|           "colorspace": { | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
|  | ||||
| export class SystemConfigThemeDto { | ||||
|   @IsString() | ||||
|   customCss!: string; | ||||
| } | ||||
| @@ -9,6 +9,7 @@ import { SystemConfigOAuthDto } from './system-config-oauth.dto'; | ||||
| import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; | ||||
| import { SystemConfigReverseGeocodingDto } from './system-config-reverse-geocoding.dto'; | ||||
| import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; | ||||
| import { SystemConfigThemeDto } from './system-config-theme.dto'; | ||||
| import { SystemConfigThumbnailDto } from './system-config-thumbnail.dto'; | ||||
| import { SystemConfigTrashDto } from './system-config-trash.dto'; | ||||
|  | ||||
| @@ -62,6 +63,11 @@ export class SystemConfigDto implements SystemConfig { | ||||
|   @ValidateNested() | ||||
|   @IsObject() | ||||
|   trash!: SystemConfigTrashDto; | ||||
|  | ||||
|   @Type(() => SystemConfigThemeDto) | ||||
|   @ValidateNested() | ||||
|   @IsObject() | ||||
|   theme!: SystemConfigThemeDto; | ||||
| } | ||||
|  | ||||
| export function mapConfig(config: SystemConfig): SystemConfigDto { | ||||
|   | ||||
| @@ -114,6 +114,9 @@ export const defaults = Object.freeze<SystemConfig>({ | ||||
|     enabled: true, | ||||
|     days: 30, | ||||
|   }, | ||||
|   theme: { | ||||
|     customCss: '', | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| export enum FeatureFlag { | ||||
|   | ||||
| @@ -115,6 +115,9 @@ const updatedConfig = Object.freeze<SystemConfig>({ | ||||
|     enabled: true, | ||||
|     days: 10, | ||||
|   }, | ||||
|   theme: { | ||||
|     customCss: '', | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| describe(SystemConfigService.name, () => { | ||||
|   | ||||
| @@ -90,6 +90,8 @@ export enum SystemConfigKey { | ||||
|  | ||||
|   TRASH_ENABLED = 'trash.enabled', | ||||
|   TRASH_DAYS = 'trash.days', | ||||
|  | ||||
|   THEME_CUSTOM_CSS = 'theme.customCss', | ||||
| } | ||||
|  | ||||
| export enum TranscodePolicy { | ||||
| @@ -221,4 +223,7 @@ export interface SystemConfig { | ||||
|     enabled: boolean; | ||||
|     days: number; | ||||
|   }; | ||||
|   theme: { | ||||
|     customCss: string; | ||||
|   }; | ||||
| } | ||||
|   | ||||
							
								
								
									
										19
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @@ -3307,6 +3307,12 @@ export interface SystemConfigDto { | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'storageTemplate': SystemConfigStorageTemplateDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigThemeDto} | ||||
|      * @memberof SystemConfigDto | ||||
|      */ | ||||
|     'theme': SystemConfigThemeDto; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {SystemConfigThumbnailDto} | ||||
| @@ -3741,6 +3747,19 @@ export interface SystemConfigTemplateStorageOptionDto { | ||||
|      */ | ||||
|     'yearOptions': Array<string>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface SystemConfigThemeDto | ||||
|  */ | ||||
| export interface SystemConfigThemeDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
|      * @memberof SystemConfigThemeDto | ||||
|      */ | ||||
|     'customCss': string; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|   | ||||
| @@ -0,0 +1,53 @@ | ||||
| <script lang="ts"> | ||||
|   import { quintOut } from 'svelte/easing'; | ||||
|   import { fly } from 'svelte/transition'; | ||||
|  | ||||
|   export let value: string; | ||||
|   export let label = ''; | ||||
|   export let desc = ''; | ||||
|   export let required = false; | ||||
|   export let disabled = false; | ||||
|   export let isEdited = false; | ||||
|  | ||||
|   const handleInput = (e: Event) => { | ||||
|     value = (e.target as HTMLInputElement).value; | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <div class="mb-4 w-full"> | ||||
|   <div class={`flex h-[26px] place-items-center gap-1`}> | ||||
|     <label class={`immich-form-label text-sm`} for={label}>{label}</label> | ||||
|     {#if required} | ||||
|       <div class="text-red-400">*</div> | ||||
|     {/if} | ||||
|  | ||||
|     {#if isEdited} | ||||
|       <div | ||||
|         transition:fly={{ x: 10, duration: 200, easing: quintOut }} | ||||
|         class="rounded-full bg-orange-100 px-2 text-[10px] text-orange-900" | ||||
|       > | ||||
|         Unsaved change | ||||
|       </div> | ||||
|     {/if} | ||||
|   </div> | ||||
|  | ||||
|   {#if desc} | ||||
|     <p class="immich-form-label pb-2 text-sm" id="{label}-desc"> | ||||
|       {desc} | ||||
|     </p> | ||||
|   {:else} | ||||
|     <slot name="desc" /> | ||||
|   {/if} | ||||
|  | ||||
|   <textarea | ||||
|     class="immich-form-input w-full pb-2" | ||||
|     aria-describedby={desc ? `${label}-desc` : undefined} | ||||
|     aria-labelledby="{label}-label" | ||||
|     id={label} | ||||
|     name={label} | ||||
|     {required} | ||||
|     {value} | ||||
|     on:input={handleInput} | ||||
|     {disabled} | ||||
|   /> | ||||
| </div> | ||||
| @@ -0,0 +1,98 @@ | ||||
| <script lang="ts"> | ||||
|   import { | ||||
|     notificationController, | ||||
|     NotificationType, | ||||
|   } from '$lib/components/shared-components/notification/notification'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import { api, SystemConfigThemeDto } from '@api'; | ||||
|   import { isEqual } from 'lodash-es'; | ||||
|   import { fade } from 'svelte/transition'; | ||||
|   import SettingButtonsRow from '../setting-buttons-row.svelte'; | ||||
|   import SettingTextarea from '../setting-textarea.svelte'; | ||||
|  | ||||
|   export let themeConfig: SystemConfigThemeDto; // this is the config that is being edited | ||||
|   export let disabled = false; | ||||
|  | ||||
|   let savedConfig: SystemConfigThemeDto; | ||||
|   let defaultConfig: SystemConfigThemeDto; | ||||
|  | ||||
|   async function getConfigs() { | ||||
|     [savedConfig, defaultConfig] = await Promise.all([ | ||||
|       api.systemConfigApi.getConfig().then((res) => res.data.theme), | ||||
|       api.systemConfigApi.getDefaults().then((res) => res.data.theme), | ||||
|     ]); | ||||
|   } | ||||
|  | ||||
|   async function saveSetting() { | ||||
|     try { | ||||
|       const { data: current } = await api.systemConfigApi.getConfig(); | ||||
|  | ||||
|       const { data: updated } = await api.systemConfigApi.updateConfig({ | ||||
|         systemConfigDto: { | ||||
|           ...current, | ||||
|           theme: themeConfig, | ||||
|         }, | ||||
|       }); | ||||
|  | ||||
|       themeConfig = { ...updated.theme }; | ||||
|       savedConfig = { ...updated.theme }; | ||||
|  | ||||
|       notificationController.show({ message: 'Theme saved', type: NotificationType.Info }); | ||||
|     } catch (error) { | ||||
|       handleError(error, 'Unable to save settings'); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async function reset() { | ||||
|     const { data: resetConfig } = await api.systemConfigApi.getConfig(); | ||||
|  | ||||
|     themeConfig = { ...resetConfig.theme }; | ||||
|     savedConfig = { ...resetConfig.theme }; | ||||
|  | ||||
|     notificationController.show({ | ||||
|       message: 'Reset theme to the recent saved theme', | ||||
|       type: NotificationType.Info, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   async function resetToDefault() { | ||||
|     const { data: configs } = await api.systemConfigApi.getDefaults(); | ||||
|  | ||||
|     themeConfig = { ...configs.theme }; | ||||
|     defaultConfig = { ...configs.theme }; | ||||
|  | ||||
|     notificationController.show({ | ||||
|       message: 'Reset theme to default', | ||||
|       type: NotificationType.Info, | ||||
|     }); | ||||
|   } | ||||
| </script> | ||||
|  | ||||
| <div> | ||||
|   {#await getConfigs() then} | ||||
|     <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"> | ||||
|             <SettingTextarea | ||||
|               {disabled} | ||||
|               label="Custom CSS" | ||||
|               desc="Cascading Style Sheets allow the design of Immich to be customized." | ||||
|               bind:value={themeConfig.customCss} | ||||
|               required={true} | ||||
|               isEdited={themeConfig.customCss !== savedConfig.customCss} | ||||
|             /> | ||||
|  | ||||
|             <SettingButtonsRow | ||||
|               on:reset={reset} | ||||
|               on:save={saveSetting} | ||||
|               on:reset-to-default={resetToDefault} | ||||
|               showResetToDefault={!isEqual(savedConfig, defaultConfig)} | ||||
|               {disabled} | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   {/await} | ||||
| </div> | ||||
| @@ -67,6 +67,7 @@ | ||||
| <svelte:head> | ||||
|   <title>{$page.data.meta?.title || 'Web'} - Immich</title> | ||||
|   <link rel="manifest" href="/manifest.json" /> | ||||
|   <link rel="stylesheet" href="/custom.css" /> | ||||
|   <meta name="theme-color" content="currentColor" /> | ||||
|   <FaviconHeader /> | ||||
|   <AppleHeader /> | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
|   import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte'; | ||||
|   import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; | ||||
|   import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte'; | ||||
|   import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte'; | ||||
|   import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; | ||||
|   import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; | ||||
|   import { downloadManager } from '$lib/stores/download'; | ||||
| @@ -96,6 +97,10 @@ | ||||
|         /> | ||||
|       </SettingAccordion> | ||||
|  | ||||
|       <SettingAccordion title="Theme Settings" subtitle="Manage customization of the Immich web interface"> | ||||
|         <ThemeSettings disabled={$featureFlags.configFile} themeConfig={configs.theme} /> | ||||
|       </SettingAccordion> | ||||
|  | ||||
|       <SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes"> | ||||
|         <ThumbnailSettings disabled={$featureFlags.configFile} thumbnailConfig={configs.thumbnail} /> | ||||
|       </SettingAccordion> | ||||
|   | ||||
							
								
								
									
										9
									
								
								web/src/routes/custom.css/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/routes/custom.css/+server.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import { RequestHandler, text } from '@sveltejs/kit'; | ||||
| export const GET = (async ({ locals: { api } }) => { | ||||
|   const { customCss } = await api.systemConfigApi.getConfig().then((res) => res.data.theme); | ||||
|   return text(customCss, { | ||||
|     headers: { | ||||
|       'Content-Type': 'text/css', | ||||
|     }, | ||||
|   }); | ||||
| }) satisfies RequestHandler; | ||||
		Reference in New Issue
	
	Block a user