diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index ba512571bb..a4a8ff2759 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2425,6 +2425,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'storageTemplate': SystemConfigStorageTemplateDto; + /** + * + * @type {SystemConfigThumbnailDto} + * @memberof SystemConfigDto + */ + 'thumbnail': SystemConfigThumbnailDto; } /** * @@ -2716,6 +2722,25 @@ export interface SystemConfigTemplateStorageOptionDto { */ 'yearOptions': Array; } +/** + * + * @export + * @interface SystemConfigThumbnailDto + */ +export interface SystemConfigThumbnailDto { + /** + * + * @type {number} + * @memberof SystemConfigThumbnailDto + */ + 'jpegSize': number; + /** + * + * @type {number} + * @memberof SystemConfigThumbnailDto + */ + 'webpSize': number; +} /** * * @export diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index cab64a9e05..ea0993be8c 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -104,6 +104,7 @@ doc/SystemConfigOAuthDto.md doc/SystemConfigPasswordLoginDto.md doc/SystemConfigStorageTemplateDto.md doc/SystemConfigTemplateStorageOptionDto.md +doc/SystemConfigThumbnailDto.md doc/TagApi.md doc/TagResponseDto.md doc/TagTypeEnum.md @@ -236,6 +237,7 @@ lib/model/system_config_o_auth_dto.dart lib/model/system_config_password_login_dto.dart lib/model/system_config_storage_template_dto.dart lib/model/system_config_template_storage_option_dto.dart +lib/model/system_config_thumbnail_dto.dart lib/model/tag_response_dto.dart lib/model/tag_type_enum.dart lib/model/thumbnail_format.dart @@ -355,6 +357,7 @@ test/system_config_o_auth_dto_test.dart test/system_config_password_login_dto_test.dart test/system_config_storage_template_dto_test.dart test/system_config_template_storage_option_dto_test.dart +test/system_config_thumbnail_dto_test.dart test/tag_api_test.dart test/tag_response_dto_test.dart test/tag_type_enum_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 814b08e270..2942d1d854 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -267,6 +267,7 @@ Class | Method | HTTP request | Description - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) + - [SystemConfigThumbnailDto](doc//SystemConfigThumbnailDto.md) - [TagResponseDto](doc//TagResponseDto.md) - [TagTypeEnum](doc//TagTypeEnum.md) - [ThumbnailFormat](doc//ThumbnailFormat.md) diff --git a/mobile/openapi/doc/SystemConfigDto.md b/mobile/openapi/doc/SystemConfigDto.md index f1526854a1..ebccc31ebe 100644 --- a/mobile/openapi/doc/SystemConfigDto.md +++ b/mobile/openapi/doc/SystemConfigDto.md @@ -13,6 +13,7 @@ Name | Type | Description | Notes **oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.md) | | **passwordLogin** | [**SystemConfigPasswordLoginDto**](SystemConfigPasswordLoginDto.md) | | **storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | | +**thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/SystemConfigThumbnailDto.md b/mobile/openapi/doc/SystemConfigThumbnailDto.md new file mode 100644 index 0000000000..892b863b3a --- /dev/null +++ b/mobile/openapi/doc/SystemConfigThumbnailDto.md @@ -0,0 +1,16 @@ +# openapi.model.SystemConfigThumbnailDto + +## Load the model package +```dart +import 'package:openapi/api.dart'; +``` + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**jpegSize** | **int** | | +**webpSize** | **int** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index c16d603d14..644244a103 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -132,6 +132,7 @@ part 'model/system_config_o_auth_dto.dart'; part 'model/system_config_password_login_dto.dart'; part 'model/system_config_storage_template_dto.dart'; part 'model/system_config_template_storage_option_dto.dart'; +part 'model/system_config_thumbnail_dto.dart'; part 'model/tag_response_dto.dart'; part 'model/tag_type_enum.dart'; part 'model/thumbnail_format.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index d8eb6a2c7d..fd1252cc78 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -359,6 +359,8 @@ class ApiClient { return SystemConfigStorageTemplateDto.fromJson(value); case 'SystemConfigTemplateStorageOptionDto': return SystemConfigTemplateStorageOptionDto.fromJson(value); + case 'SystemConfigThumbnailDto': + return SystemConfigThumbnailDto.fromJson(value); case 'TagResponseDto': return TagResponseDto.fromJson(value); case 'TagTypeEnum': diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index 6bc4e5e2a2..aefc97d1ba 100644 --- a/mobile/openapi/lib/model/system_config_dto.dart +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -18,6 +18,7 @@ class SystemConfigDto { required this.oauth, required this.passwordLogin, required this.storageTemplate, + required this.thumbnail, }); SystemConfigFFmpegDto ffmpeg; @@ -30,13 +31,16 @@ class SystemConfigDto { SystemConfigStorageTemplateDto storageTemplate; + SystemConfigThumbnailDto thumbnail; + @override bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && other.ffmpeg == ffmpeg && other.job == job && other.oauth == oauth && other.passwordLogin == passwordLogin && - other.storageTemplate == storageTemplate; + other.storageTemplate == storageTemplate && + other.thumbnail == thumbnail; @override int get hashCode => @@ -45,10 +49,11 @@ class SystemConfigDto { (job.hashCode) + (oauth.hashCode) + (passwordLogin.hashCode) + - (storageTemplate.hashCode); + (storageTemplate.hashCode) + + (thumbnail.hashCode); @override - String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate]'; + String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, oauth=$oauth, passwordLogin=$passwordLogin, storageTemplate=$storageTemplate, thumbnail=$thumbnail]'; Map toJson() { final json = {}; @@ -57,6 +62,7 @@ class SystemConfigDto { json[r'oauth'] = this.oauth; json[r'passwordLogin'] = this.passwordLogin; json[r'storageTemplate'] = this.storageTemplate; + json[r'thumbnail'] = this.thumbnail; return json; } @@ -73,6 +79,7 @@ class SystemConfigDto { oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, + thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!, ); } return null; @@ -125,6 +132,7 @@ class SystemConfigDto { 'oauth', 'passwordLogin', 'storageTemplate', + 'thumbnail', }; } diff --git a/mobile/openapi/lib/model/system_config_thumbnail_dto.dart b/mobile/openapi/lib/model/system_config_thumbnail_dto.dart new file mode 100644 index 0000000000..54360074e3 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_thumbnail_dto.dart @@ -0,0 +1,106 @@ +// +// 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 SystemConfigThumbnailDto { + /// Returns a new [SystemConfigThumbnailDto] instance. + SystemConfigThumbnailDto({ + required this.jpegSize, + required this.webpSize, + }); + + int jpegSize; + + int webpSize; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigThumbnailDto && + other.jpegSize == jpegSize && + other.webpSize == webpSize; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (jpegSize.hashCode) + + (webpSize.hashCode); + + @override + String toString() => 'SystemConfigThumbnailDto[jpegSize=$jpegSize, webpSize=$webpSize]'; + + Map toJson() { + final json = {}; + json[r'jpegSize'] = this.jpegSize; + json[r'webpSize'] = this.webpSize; + return json; + } + + /// Returns a new [SystemConfigThumbnailDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigThumbnailDto? fromJson(dynamic value) { + if (value is Map) { + final json = value.cast(); + + return SystemConfigThumbnailDto( + jpegSize: mapValueOfType(json, r'jpegSize')!, + webpSize: mapValueOfType(json, r'webpSize')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigThumbnailDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigThumbnailDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigThumbnailDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigThumbnailDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'jpegSize', + 'webpSize', + }; +} + diff --git a/mobile/openapi/test/system_config_dto_test.dart b/mobile/openapi/test/system_config_dto_test.dart index 34b5a8cde3..946324282e 100644 --- a/mobile/openapi/test/system_config_dto_test.dart +++ b/mobile/openapi/test/system_config_dto_test.dart @@ -41,6 +41,11 @@ void main() { // TODO }); + // SystemConfigThumbnailDto thumbnail + test('to test the property `thumbnail`', () async { + // TODO + }); + }); diff --git a/mobile/openapi/test/system_config_thumbnail_dto_test.dart b/mobile/openapi/test/system_config_thumbnail_dto_test.dart new file mode 100644 index 0000000000..3dd82cff7c --- /dev/null +++ b/mobile/openapi/test/system_config_thumbnail_dto_test.dart @@ -0,0 +1,32 @@ +// +// 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 SystemConfigThumbnailDto +void main() { + // final instance = SystemConfigThumbnailDto(); + + group('test SystemConfigThumbnailDto', () { + // int jpegSize + test('to test the property `jpegSize`', () async { + // TODO + }); + + // int webpSize + test('to test the property `webpSize`', () async { + // TODO + }); + + + }); + +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 049f0128ae..1aac93051b 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -6590,6 +6590,9 @@ }, "storageTemplate": { "$ref": "#/components/schemas/SystemConfigStorageTemplateDto" + }, + "thumbnail": { + "$ref": "#/components/schemas/SystemConfigThumbnailDto" } }, "required": [ @@ -6597,7 +6600,8 @@ "oauth", "passwordLogin", "storageTemplate", - "job" + "job", + "thumbnail" ], "type": "object" }, @@ -6828,6 +6832,21 @@ ], "type": "object" }, + "SystemConfigThumbnailDto": { + "properties": { + "jpegSize": { + "type": "integer" + }, + "webpSize": { + "type": "integer" + } + }, + "required": [ + "webpSize", + "jpegSize" + ], + "type": "object" + }, "TagResponseDto": { "properties": { "id": { diff --git a/server/src/domain/media/media.constant.ts b/server/src/domain/media/media.constant.ts index 97dd3f1d72..3a8ee414b9 100644 --- a/server/src/domain/media/media.constant.ts +++ b/server/src/domain/media/media.constant.ts @@ -1,3 +1 @@ -export const JPEG_THUMBNAIL_SIZE = 1440; -export const WEBP_THUMBNAIL_SIZE = 250; export const FACE_THUMBNAIL_SIZE = 250; diff --git a/server/src/domain/media/media.service.ts b/server/src/domain/media/media.service.ts index 0bb96fd0bd..c4ba66cdd0 100644 --- a/server/src/domain/media/media.service.ts +++ b/server/src/domain/media/media.service.ts @@ -7,7 +7,6 @@ import { IBaseJob, IEntityJob, IJobRepository, JobName, JOBS_ASSET_PAGINATION_SI import { IStorageRepository, StorageCore, StorageFolder } from '../storage'; import { ISystemConfigRepository, SystemConfigFFmpegDto } from '../system-config'; import { SystemConfigCore } from '../system-config/system-config.core'; -import { JPEG_THUMBNAIL_SIZE, WEBP_THUMBNAIL_SIZE } from './media.constant'; import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from './media.repository'; import { H264Config, HEVCConfig, NVENCConfig, QSVConfig, ThumbnailConfig, VAAPIConfig, VP9Config } from './media.util'; @@ -63,11 +62,12 @@ export class MediaService { const resizePath = this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, asset.ownerId); this.storageRepository.mkdirSync(resizePath); const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`); + const { thumbnail } = await this.configCore.getConfig(); switch (asset.type) { case AssetType.IMAGE: await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, { - size: JPEG_THUMBNAIL_SIZE, + size: thumbnail.jpegSize, format: 'jpeg', }); this.logger.log(`Successfully generated image thumbnail ${asset.id}`); @@ -80,7 +80,7 @@ export class MediaService { return false; } const { ffmpeg } = await this.configCore.getConfig(); - const config = { ...ffmpeg, targetResolution: JPEG_THUMBNAIL_SIZE.toString(), twoPass: false }; + const config = { ...ffmpeg, targetResolution: thumbnail.jpegSize.toString(), twoPass: false }; const options = new ThumbnailConfig(config).getOptions(mainVideoStream); await this.mediaRepository.transcode(asset.originalPath, jpegThumbnailPath, options); this.logger.log(`Successfully generated video thumbnail ${asset.id}`); @@ -100,7 +100,8 @@ export class MediaService { const webpPath = asset.resizePath.replace('jpeg', 'webp').replace('jpg', 'webp'); - await this.mediaRepository.resize(asset.resizePath, webpPath, { size: WEBP_THUMBNAIL_SIZE, format: 'webp' }); + const { thumbnail } = await this.configCore.getConfig(); + await this.mediaRepository.resize(asset.resizePath, webpPath, { size: thumbnail.webpSize, format: 'webp' }); await this.assetRepository.save({ id: asset.id, webpPath }); return true; diff --git a/server/src/domain/system-config/dto/index.ts b/server/src/domain/system-config/dto/index.ts index fa494a7a8d..9eb2357964 100644 --- a/server/src/domain/system-config/dto/index.ts +++ b/server/src/domain/system-config/dto/index.ts @@ -2,4 +2,5 @@ export * from './system-config-ffmpeg.dto'; export * from './system-config-oauth.dto'; export * from './system-config-password-login.dto'; export * from './system-config-storage-template.dto'; +export * from './system-config-thumbnail.dto'; export * from './system-config.dto'; diff --git a/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts b/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts new file mode 100644 index 0000000000..53d9d64a56 --- /dev/null +++ b/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsInt } from 'class-validator'; + +export class SystemConfigThumbnailDto { + @IsInt() + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + webpSize!: number; + + @IsInt() + @Type(() => Number) + @ApiProperty({ type: 'integer' }) + jpegSize!: number; +} diff --git a/server/src/domain/system-config/dto/system-config.dto.ts b/server/src/domain/system-config/dto/system-config.dto.ts index bd2fb5b5e3..f34ebf7100 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -1,3 +1,4 @@ +import { SystemConfigThumbnailDto } from '@app/domain/system-config'; import { SystemConfig } from '@app/infra/entities'; import { Type } from 'class-transformer'; import { IsObject, ValidateNested } from 'class-validator'; @@ -32,6 +33,11 @@ export class SystemConfigDto { @ValidateNested() @IsObject() job!: SystemConfigJobDto; + + @Type(() => SystemConfigThumbnailDto) + @ValidateNested() + @IsObject() + thumbnail!: SystemConfigThumbnailDto; } export function mapConfig(config: SystemConfig): SystemConfigDto { diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 5a2dc03c99..80f650571c 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -64,6 +64,11 @@ export const defaults = Object.freeze({ storageTemplate: { template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', }, + + thumbnail: { + webpSize: 250, + jpegSize: 1440, + }, }); const singleton = new Subject(); diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 1b358771ae..bb510c05b3 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -65,6 +65,10 @@ const updatedConfig = Object.freeze({ storageTemplate: { template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', }, + thumbnail: { + webpSize: 250, + jpegSize: 1440, + }, }); describe(SystemConfigService.name, () => { diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index e8ae879427..ddfad682a7 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -52,6 +52,9 @@ export enum SystemConfigKey { PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled', STORAGE_TEMPLATE = 'storageTemplate.template', + + THUMBNAIL_WEBP_SIZE = 'thumbnail.webpSize', + THUMBNAIL_JPEG_SIZE = 'thumbnail.jpegSize', } export enum TranscodePolicy { @@ -121,4 +124,8 @@ export interface SystemConfig { storageTemplate: { template: string; }; + thumbnail: { + webpSize: number; + jpegSize: number; + }; } diff --git a/server/src/infra/repositories/media.repository.ts b/server/src/infra/repositories/media.repository.ts index 682b5a4000..b4c3f8abdf 100644 --- a/server/src/infra/repositories/media.repository.ts +++ b/server/src/infra/repositories/media.repository.ts @@ -12,7 +12,7 @@ export class MediaRepository implements IMediaRepository { private logger = new Logger(MediaRepository.name); crop(input: string, options: CropOptions): Promise { - return sharp(input, { failOnError: false }) + return sharp(input, { failOn: 'none' }) .extract({ left: options.left, top: options.top, @@ -23,7 +23,7 @@ export class MediaRepository implements IMediaRepository { } async resize(input: string | Buffer, output: string, options: ResizeOptions): Promise { - await sharp(input, { failOnError: false }) + await sharp(input, { failOn: 'none' }) .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }) .rotate() .toFormat(options.format) diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index ba512571bb..a4a8ff2759 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2425,6 +2425,12 @@ export interface SystemConfigDto { * @memberof SystemConfigDto */ 'storageTemplate': SystemConfigStorageTemplateDto; + /** + * + * @type {SystemConfigThumbnailDto} + * @memberof SystemConfigDto + */ + 'thumbnail': SystemConfigThumbnailDto; } /** * @@ -2716,6 +2722,25 @@ export interface SystemConfigTemplateStorageOptionDto { */ 'yearOptions': Array; } +/** + * + * @export + * @interface SystemConfigThumbnailDto + */ +export interface SystemConfigThumbnailDto { + /** + * + * @type {number} + * @memberof SystemConfigThumbnailDto + */ + 'jpegSize': number; + /** + * + * @type {number} + * @memberof SystemConfigThumbnailDto + */ + 'webpSize': number; +} /** * * @export diff --git a/web/src/lib/components/admin-page/settings/setting-input-field.svelte b/web/src/lib/components/admin-page/settings/setting-input-field.svelte index 6f5711b4dc..65a5018e58 100644 --- a/web/src/lib/components/admin-page/settings/setting-input-field.svelte +++ b/web/src/lib/components/admin-page/settings/setting-input-field.svelte @@ -27,7 +27,7 @@ }; -
+
{#if required} @@ -45,7 +45,7 @@
{#if desc} -

+

{desc}

{/if} diff --git a/web/src/lib/components/admin-page/settings/setting-select.svelte b/web/src/lib/components/admin-page/settings/setting-select.svelte index 987cebccf7..4acc1c2ade 100644 --- a/web/src/lib/components/admin-page/settings/setting-select.svelte +++ b/web/src/lib/components/admin-page/settings/setting-select.svelte @@ -2,19 +2,23 @@ import { quintOut } from 'svelte/easing'; import { fly } from 'svelte/transition'; - export let value: string; - export let options: { value: string; text: string }[]; + export let value: string | number; + export let options: { value: string | number; text: string }[]; export let label = ''; export let desc = ''; export let name = ''; export let isEdited = false; + export let number = false; const handleChange = (e: Event) => { value = (e.target as HTMLInputElement).value; + if (number) { + value = parseInt(value); + } }; -
+
@@ -29,7 +33,7 @@
{#if desc} -

+

{desc}

{/if} diff --git a/web/src/lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte b/web/src/lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte new file mode 100644 index 0000000000..2bfce8e8d4 --- /dev/null +++ b/web/src/lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte @@ -0,0 +1,121 @@ + + +
+ {#await getConfigs() then} +
+
+
+ + + +
+ +
+ +
+
+
+ {/await} +
diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index 24a1532fdb..59390b42d9 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -2,6 +2,7 @@ import { page } from '$app/stores'; import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte'; import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte'; + import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte'; import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte'; import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte'; import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte'; @@ -22,6 +23,10 @@ {#await getConfig()} {:then configs} + + + +