Ensure stored client metadata values respect constraints

This commit is contained in:
schroda
2025-06-24 22:22:53 +02:00
parent 09b5f7e558
commit 56f01efc1e
20 changed files with 520 additions and 195 deletions

View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
export const DOWNLOAD_AHEAD = {
min: 2,
max: 10,
default: 2,
step: 1,
};

View File

@@ -18,10 +18,7 @@ import { makeToast } from '@/modules/core/utils/Toast.ts';
import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts'; import { MetadataDownloadSettings } from '@/modules/downloads/Downloads.types.ts';
import { MetadataServerSettings } from '@/modules/settings/Settings.types.ts'; import { MetadataServerSettings } from '@/modules/settings/Settings.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { DOWNLOAD_AHEAD } from '@/modules/downloads/Downloads.constants.ts';
const MIN_LIMIT = 2;
const MAX_LIMIT = 10;
const DEFAULT_LIMIT = MIN_LIMIT;
export const DownloadAheadSetting = ({ export const DownloadAheadSetting = ({
downloadAheadLimit, downloadAheadLimit,
@@ -33,7 +30,7 @@ export const DownloadAheadSetting = ({
const shouldDownloadAhead = !!downloadAheadLimit; const shouldDownloadAhead = !!downloadAheadLimit;
const [currentDownloadAheadLimit, persistDownloadAheadLimit] = usePersistedValue( const [currentDownloadAheadLimit, persistDownloadAheadLimit] = usePersistedValue(
'lastDownloadAheadLimit', 'lastDownloadAheadLimit',
DEFAULT_LIMIT, DOWNLOAD_AHEAD.default,
downloadAheadLimit, downloadAheadLimit,
getPersistedServerSetting, getPersistedServerSetting,
); );
@@ -63,9 +60,10 @@ export const DownloadAheadSetting = ({
count: currentDownloadAheadLimit, count: currentDownloadAheadLimit,
})} })}
value={currentDownloadAheadLimit} value={currentDownloadAheadLimit}
minValue={MIN_LIMIT} minValue={DOWNLOAD_AHEAD.min}
maxValue={MAX_LIMIT} maxValue={DOWNLOAD_AHEAD.max}
defaultValue={DEFAULT_LIMIT} defaultValue={DOWNLOAD_AHEAD.default}
stepSize={DOWNLOAD_AHEAD.step}
showSlider showSlider
dialogDescription={t('download.settings.download_ahead.label.description')} dialogDescription={t('download.settings.download_ahead.label.description')}
dialogDisclaimer={t('download.settings.download_ahead.label.disclaimer')} dialogDisclaimer={t('download.settings.download_ahead.label.disclaimer')}

View File

@@ -7,103 +7,337 @@
*/ */
import { AppMetadataKeys, IMetadataMigration } from '@/modules/metadata/Metadata.types.ts'; import { AppMetadataKeys, IMetadataMigration } from '@/modules/metadata/Metadata.types.ts';
import { ProgressBarPosition, ReaderPageScaleMode, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; import {
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; IReaderSettings,
ProgressBarPosition,
ReaderCustomFilter,
ReaderPageScaleMode,
ReadingMode,
} from '@/modules/reader/types/Reader.types.ts';
import {
AUTO_SCROLL_SPEED,
CUSTOM_FILTER,
DEFAULT_READER_SETTINGS,
IMAGE_PRE_LOAD_AMOUNT,
PAGE_GAP,
PROGRESS_BAR_SIZE,
SCROLL_AMOUNT,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { coerceIn } from '@/lib/HelperFunctions.ts';
import { DOWNLOAD_AHEAD } from '@/modules/downloads/Downloads.constants.ts';
import { MANGA_GRID_WIDTH } from '@/modules/settings/Settings.constants.ts';
export const APP_METADATA_KEY_PREFIX = 'webUI'; export const APP_METADATA_KEY_PREFIX = 'webUI';
// At the moment any non-primitive types need to be specified as "string" and handled in the according "MetadataService". // At the moment any non-primitive types need to be specified as "string" and handled in the according "MetadataService".
// "auto" can be used to try to automatically convert the value to a specific type (string, number, boolean, undefined, null) // "auto" can be used to try to automatically convert the value to a specific type (string, number, boolean, undefined, null)
export const APP_METADATA_KEY_TO_TYPE = { export const APP_METADATA: Record<
migration: 'number', AppMetadataKeys,
deleteChaptersManuallyMarkedRead: 'boolean', {
deleteChaptersWhileReading: 'number', type: 'auto' | 'string' | 'number' | 'boolean';
deleteChaptersWithBookmark: 'boolean', toValidValue?: (value: any) => any;
downloadAheadLimit: 'number', }
showAddToLibraryCategorySelectDialog: 'boolean', > = {
ignoreFilters: 'boolean', migration: {
removeMangaFromCategories: 'boolean', type: 'number',
showTabSize: 'boolean', },
devices: 'string', // string[] deleteChaptersManuallyMarkedRead: {
migrateChapters: 'boolean', type: 'boolean',
migrateCategories: 'boolean', },
migrateTracking: 'boolean', deleteChaptersWhileReading: {
deleteChapters: 'boolean', type: 'number',
migrateSortSettings: 'string', // SortSettings (object) },
hideLibraryEntries: 'boolean', deleteChaptersWithBookmark: {
updateProgressAfterReading: 'boolean', type: 'boolean',
updateProgressManualMarkRead: 'boolean', },
webUIInformAvailableUpdate: 'boolean', downloadAheadLimit: {
serverInformAvailableUpdate: 'boolean', type: 'number',
readerWidth: 'string', // object toValidValue: (value: number) => coerceIn(value, DOWNLOAD_AHEAD.min, DOWNLOAD_AHEAD.max),
savedSearches: 'string', // object },
showContinueReadingButton: 'boolean', showAddToLibraryCategorySelectDialog: {
showDownloadBadge: 'boolean', type: 'boolean',
showUnreadBadge: 'boolean', },
gridLayout: 'number', // GridLayout (enum) ignoreFilters: {
sortBy: 'auto', // LibrarySortMode undefined null type: 'boolean',
sortDesc: 'auto', // boolean undefined null },
hasDownloadedChapters: 'auto', // boolean undefined null removeMangaFromCategories: {
hasBookmarkedChapters: 'auto', // boolean undefined null type: 'boolean',
hasUnreadChapters: 'auto', // boolean undefined null },
hasReadChapters: 'auto', // boolean undefined null showTabSize: {
hasDuplicateChapters: 'auto', // boolean undefined null type: 'boolean',
hasTrackerBinding: 'string', // object },
hasStatus: 'string', // object devices: {
customThemes: 'string', // object type: 'string', // string[]
mangaThumbnailBackdrop: 'boolean', },
mangaDynamicColorSchemes: 'boolean', migrateChapters: {
tapZoneLayout: 'number', // TapZoneLayouts (enum) type: 'boolean',
tapZoneInvertMode: 'string', // TapZoneInvertMode (object) },
readingDirection: 'number', // ReadingDirection (enum) migrateCategories: {
progressBarType: 'number', // ProgressBarType (enum) type: 'boolean',
progressBarSize: 'number', },
progressBarPosition: 'number', // ProgressBarPosition (enum) migrateTracking: {
progressBarPositionAutoVertical: 'number', // TProgressBarPositionAutoVertical (enum) type: 'boolean',
readingMode: 'number', // ReadingMode (enum) },
pageScaleMode: 'number', // ReaderPageScaleMode (enum) deleteChapters: {
shouldOffsetDoubleSpreads: 'boolean', type: 'boolean',
exitMode: 'number', // ReaderExitMode (enum) },
customFilter: 'string', // ReaderCustomFilter (object) migrateSortSettings: {
shouldSkipDupChapters: 'boolean', type: 'string', // SortSettings (object)
isStaticNav: 'boolean', },
overlayMode: 'number', // ReaderOverlayMode (enum) hideLibraryEntries: {
shouldStretchPage: 'boolean', type: 'boolean',
shouldShowPageNumber: 'boolean', },
backgroundColor: 'number', // ReaderBackgroundColor (enum) updateProgressAfterReading: {
pageGap: 'number', type: 'boolean',
hotkeys: 'string', // object },
imagePreLoadAmount: 'number', updateProgressManualMarkRead: {
shouldUseAutoWebtoonMode: 'boolean', type: 'boolean',
autoScroll: 'string', // object },
shouldShowReadingModePreview: 'boolean', webUIInformAvailableUpdate: {
shouldShowTapZoneLayoutPreview: 'boolean', type: 'boolean',
shouldInformAboutMissingChapter: 'boolean', },
shouldInformAboutScanlatorChange: 'boolean', serverInformAvailableUpdate: {
hideHistory: 'boolean', type: 'boolean',
scrollAmount: 'number', // ReaderScrollAmount (enum) },
reverse: 'boolean', readerWidth: {
bookmarked: 'auto', // boolean undefined null type: 'string', // object
downloaded: 'auto', // boolean undefined null },
unread: 'auto', // boolean undefined null savedSearches: {
showChapterNumber: 'boolean', type: 'string', // object
extensionLanguages: 'string', // string[] },
sourceLanguages: 'string', // string[] showContinueReadingButton: {
showNsfw: 'boolean', type: 'boolean',
shouldUseInfiniteScroll: 'boolean', },
shouldShowTransitionPage: 'boolean', showDownloadBadge: {
appTheme: 'string', type: 'boolean',
themeMode: 'string', // ThemeMode (enum) },
shouldUsePureBlackMode: 'boolean', showUnreadBadge: {
mangaGridItemWidth: 'number', type: 'boolean',
isPinned: 'boolean', },
isEnabled: 'boolean', gridLayout: {
lastUsedSourceId: 'string', type: 'number', // GridLayout (enum)
shouldShowOnlySourcesWithResults: 'boolean', },
excludedScanlators: 'string', // string[] sortBy: {
} as const satisfies Record<AppMetadataKeys, 'auto' | 'string' | 'number' | 'boolean'>; type: 'auto', // LibrarySortMode undefined null
},
sortDesc: {
type: 'auto', // boolean undefined null
},
hasDownloadedChapters: {
type: 'auto', // boolean undefined null
},
hasBookmarkedChapters: {
type: 'auto', // boolean undefined null
},
hasUnreadChapters: {
type: 'auto', // boolean undefined null
},
hasReadChapters: {
type: 'auto', // boolean undefined null
},
hasDuplicateChapters: {
type: 'auto', // boolean undefined null
},
hasTrackerBinding: {
type: 'string', // object
},
hasStatus: {
type: 'string', // object
},
customThemes: {
type: 'string', // object
},
mangaThumbnailBackdrop: {
type: 'boolean',
},
mangaDynamicColorSchemes: {
type: 'boolean',
},
tapZoneLayout: {
type: 'number', // TapZoneLayouts (enum)
},
tapZoneInvertMode: {
type: 'string', // TapZoneInvertMode (object)
},
readingDirection: {
type: 'number', // ReadingDirection (enum)
},
progressBarType: {
type: 'number', // ProgressBarType (enum)
},
progressBarSize: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, PROGRESS_BAR_SIZE.min, PROGRESS_BAR_SIZE.max),
},
progressBarPosition: {
type: 'number', // ProgressBarPosition (enum)
},
progressBarPositionAutoVertical: {
type: 'number', // TProgressBarPositionAutoVertical (enum)
},
readingMode: {
type: 'number', // ReadingMode (enum)
},
pageScaleMode: {
type: 'number', // ReaderPageScaleMode (enum)
},
shouldOffsetDoubleSpreads: {
type: 'boolean',
},
exitMode: {
type: 'number', // ReaderExitMode (enum)
},
customFilter: {
type: 'string', // ReaderCustomFilter (object)
toValidValue: (value: ReaderCustomFilter): ReaderCustomFilter => ({
...value,
brightness: {
...value.brightness,
value: coerceIn(value.brightness.value, CUSTOM_FILTER.brightness.min, CUSTOM_FILTER.brightness.max),
},
contrast: {
...value.contrast,
value: coerceIn(value.contrast.value, CUSTOM_FILTER.contrast.min, CUSTOM_FILTER.contrast.max),
},
saturate: {
...value.saturate,
value: coerceIn(value.saturate.value, CUSTOM_FILTER.saturate.min, CUSTOM_FILTER.saturate.max),
},
hue: {
...value.hue,
value: coerceIn(value.hue.value, CUSTOM_FILTER.hue.min, CUSTOM_FILTER.hue.max),
},
rgba: {
...value.rgba,
value: {
...value.rgba.value,
red: coerceIn(value.rgba.value.red, CUSTOM_FILTER.rgba.red.min, CUSTOM_FILTER.rgba.red.max),
green: coerceIn(value.rgba.value.green, CUSTOM_FILTER.rgba.green.min, CUSTOM_FILTER.rgba.green.max),
blue: coerceIn(value.rgba.value.blue, CUSTOM_FILTER.rgba.blue.min, CUSTOM_FILTER.rgba.blue.max),
alpha: coerceIn(value.rgba.value.alpha, CUSTOM_FILTER.rgba.alpha.min, CUSTOM_FILTER.rgba.alpha.max),
},
},
}),
},
shouldSkipDupChapters: {
type: 'boolean',
},
isStaticNav: {
type: 'boolean',
},
overlayMode: {
type: 'number', // ReaderOverlayMode (enum)
},
shouldStretchPage: {
type: 'boolean',
},
shouldShowPageNumber: {
type: 'boolean',
},
backgroundColor: {
type: 'number', // ReaderBackgroundColor (enum)
},
pageGap: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, PAGE_GAP.min, PAGE_GAP.max),
},
hotkeys: {
type: 'string', // object
},
imagePreLoadAmount: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, IMAGE_PRE_LOAD_AMOUNT.min, IMAGE_PRE_LOAD_AMOUNT.max),
},
shouldUseAutoWebtoonMode: {
type: 'boolean',
},
autoScroll: {
type: 'string', // object
toValidValue: (value: IReaderSettings['autoScroll']): IReaderSettings['autoScroll'] => ({
...value,
value: coerceIn(value.value, AUTO_SCROLL_SPEED.min, AUTO_SCROLL_SPEED.max),
}),
},
shouldShowReadingModePreview: {
type: 'boolean',
},
shouldShowTapZoneLayoutPreview: {
type: 'boolean',
},
shouldInformAboutMissingChapter: {
type: 'boolean',
},
shouldInformAboutScanlatorChange: {
type: 'boolean',
},
hideHistory: {
type: 'boolean',
},
scrollAmount: {
type: 'number', // ReaderScrollAmount (enum)
toValidValue: (value: number) => coerceIn(value, SCROLL_AMOUNT.min, SCROLL_AMOUNT.max),
},
reverse: {
type: 'boolean',
},
bookmarked: {
type: 'auto', // boolean undefined null
},
downloaded: {
type: 'auto', // boolean undefined null
},
unread: {
type: 'auto', // boolean undefined null
},
showChapterNumber: {
type: 'boolean',
},
extensionLanguages: {
type: 'string', // string[]
},
sourceLanguages: {
type: 'string', // string[]
},
showNsfw: {
type: 'boolean',
},
shouldUseInfiniteScroll: {
type: 'boolean',
},
shouldShowTransitionPage: {
type: 'boolean',
},
appTheme: {
type: 'string',
},
themeMode: {
type: 'string', // ThemeMode (enum)
},
shouldUsePureBlackMode: {
type: 'boolean',
},
mangaGridItemWidth: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, MANGA_GRID_WIDTH.min, MANGA_GRID_WIDTH.max),
},
isPinned: {
type: 'boolean',
},
isEnabled: {
type: 'boolean',
},
lastUsedSourceId: {
type: 'string',
},
shouldShowOnlySourcesWithResults: {
type: 'boolean',
},
excludedScanlators: {
type: 'string', // string[]
},
} as const;
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_KEY_TO_TYPE); export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA);
export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
// metadata applied migration id // metadata applied migration id

View File

@@ -8,13 +8,13 @@
import { MetaType } from '@/lib/graphql/generated/graphql.ts'; import { MetaType } from '@/lib/graphql/generated/graphql.ts';
import { AllowedMetadataValueTypes, AppMetadataKeys, Metadata } from '@/modules/metadata/Metadata.types.ts'; import { AllowedMetadataValueTypes, AppMetadataKeys, Metadata } from '@/modules/metadata/Metadata.types.ts';
import { APP_METADATA_KEY_TO_TYPE } from '@/modules/metadata/Metadata.constants.ts'; import { APP_METADATA } from '@/modules/metadata/Metadata.constants.ts';
export const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>( export const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
key: string, key: string,
value: string, value: string,
): T => { ): T => {
const typeOfKey = APP_METADATA_KEY_TO_TYPE[key as AppMetadataKeys]; const typeOfKey = APP_METADATA[key as AppMetadataKeys].type;
const isAutoType = typeOfKey === 'auto'; const isAutoType = typeOfKey === 'auto';
if ((isAutoType && !Number.isNaN(+value)) || typeOfKey === 'number') { if ((isAutoType && !Number.isNaN(+value)) || typeOfKey === 'number') {

View File

@@ -22,6 +22,7 @@ import { doesMetadataKeyExistIn, getMetadataKey } from '@/modules/metadata/Metad
import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts'; import { applyMetadataMigrations } from '@/modules/metadata/services/MetadataMigrations.ts';
import { SourceIdInfo } from '@/modules/source/Source.types.ts'; import { SourceIdInfo } from '@/modules/source/Source.types.ts';
import { ChapterIdInfo } from '@/modules/chapter/Chapter.types.ts'; import { ChapterIdInfo } from '@/modules/chapter/Chapter.types.ts';
import { APP_METADATA } from '@/modules/metadata/Metadata.constants.ts';
const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends AllowedMetadataValueTypes>( const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends AllowedMetadataValueTypes>(
metadata: Metadata | undefined, metadata: Metadata | undefined,
@@ -37,7 +38,9 @@ const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends Allowed
return defaultValue; return defaultValue;
} }
return convertValueFromMetadata(key, metadata[getMetadataKey(key, prefixes)]); const convertedValue = convertValueFromMetadata(key, metadata[getMetadataKey(key, prefixes)]);
return APP_METADATA[key].toValidValue?.(convertedValue) ?? convertedValue;
}; };
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>( export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(

View File

@@ -16,7 +16,10 @@ import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.ts
import { ReaderSettingExitMode } from '@/modules/reader/components/settings/behaviour/ReaderSettingExitMode.tsx'; import { ReaderSettingExitMode } from '@/modules/reader/components/settings/behaviour/ReaderSettingExitMode.tsx';
import { isOffsetDoubleSpreadPagesEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { isOffsetDoubleSpreadPagesEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import {
DEFAULT_READER_SETTINGS,
IMAGE_PRE_LOAD_AMOUNT,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { ReaderSettingAutoScroll } from '@/modules/reader/components/settings/behaviour/ReaderSettingAutoScroll.tsx'; import { ReaderSettingAutoScroll } from '@/modules/reader/components/settings/behaviour/ReaderSettingAutoScroll.tsx';
import { ReaderSettingScrollAmount } from '@/modules/reader/components/settings/behaviour/ReaderSettingScrollAmount.tsx'; import { ReaderSettingScrollAmount } from '@/modules/reader/components/settings/behaviour/ReaderSettingScrollAmount.tsx';
@@ -129,9 +132,9 @@ export const ReaderBehaviourSettings = ({
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.imagePreLoadAmount, defaultValue: DEFAULT_READER_SETTINGS.imagePreLoadAmount,
value: settings.imagePreLoadAmount, value: settings.imagePreLoadAmount,
step: 1, step: IMAGE_PRE_LOAD_AMOUNT.step,
min: 1, min: IMAGE_PRE_LOAD_AMOUNT.min,
max: 20, max: IMAGE_PRE_LOAD_AMOUNT.max,
onChange: (_, value) => { onChange: (_, value) => {
updateSetting('imagePreLoadAmount', value as number, false); updateSetting('imagePreLoadAmount', value as number, false);
}, },

View File

@@ -7,15 +7,11 @@
*/ */
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts'; import { IReaderSettings, ReaderScrollAmount } from '@/modules/reader/types/Reader.types.ts';
import { ValueToDisplayData } from '@/modules/core/Core.types.ts'; import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx'; import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { import { DEFAULT_READER_SETTINGS, SCROLL_AMOUNT } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
ReaderScrollAmount,
DEFAULT_READER_SETTINGS,
SCROLL_AMOUNT,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderScrollAmount> = { const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderScrollAmount> = {
[ReaderScrollAmount.TINY]: { [ReaderScrollAmount.TINY]: {

View File

@@ -12,7 +12,7 @@ import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { CUSTOM_FILTER, DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingBrightness = ({ export const ReaderSettingBrightness = ({
brightness, brightness,
@@ -48,9 +48,9 @@ export const ReaderSettingBrightness = ({
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.brightness.value, defaultValue: DEFAULT_READER_SETTINGS.customFilter.brightness.value,
value: brightness.value, value: brightness.value,
step: 1, step: CUSTOM_FILTER.brightness.step,
min: 5, min: CUSTOM_FILTER.brightness.min,
max: 200, max: CUSTOM_FILTER.brightness.max,
onChange: (_, value) => { onChange: (_, value) => {
updateSetting('brightness', { ...brightness, value: value as number }, false); updateSetting('brightness', { ...brightness, value: value as number }, false);
}, },

View File

@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts'; import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { CUSTOM_FILTER, DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingContrast = ({ export const ReaderSettingContrast = ({
contrast, contrast,
@@ -47,9 +47,9 @@ export const ReaderSettingContrast = ({
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.contrast.value, defaultValue: DEFAULT_READER_SETTINGS.customFilter.contrast.value,
value: contrast.value, value: contrast.value,
step: 1, step: CUSTOM_FILTER.contrast.step,
min: 5, min: CUSTOM_FILTER.contrast.min,
max: 200, max: CUSTOM_FILTER.contrast.max,
onChange: (_, newValue) => { onChange: (_, newValue) => {
updateSetting('contrast', { ...contrast, value: newValue as number }, false); updateSetting('contrast', { ...contrast, value: newValue as number }, false);
}, },

View File

@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts'; import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { CUSTOM_FILTER, DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingHue = ({ export const ReaderSettingHue = ({
hue, hue,
@@ -43,9 +43,9 @@ export const ReaderSettingHue = ({
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.hue.value, defaultValue: DEFAULT_READER_SETTINGS.customFilter.hue.value,
value: hue.value, value: hue.value,
step: 1, step: CUSTOM_FILTER.hue.step,
min: 0, min: CUSTOM_FILTER.hue.min,
max: 200, max: CUSTOM_FILTER.hue.max,
onChange: (_, newValue) => { onChange: (_, newValue) => {
updateSetting('hue', { ...hue, value: newValue as number }, false); updateSetting('hue', { ...hue, value: newValue as number }, false);
}, },

View File

@@ -14,6 +14,7 @@ import { TranslationKey } from '@/Base.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { import {
CUSTOM_FILTER,
DEFAULT_READER_SETTINGS, DEFAULT_READER_SETTINGS,
READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA, READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA,
READER_BLEND_MODE_VALUES, READER_BLEND_MODE_VALUES,
@@ -29,13 +30,6 @@ const RGBA_TYPE_TO_TRANSLATION_KEY: Record<RGBAType, TranslationKey> = {
alpha: 'reader.settings.custom_filter.rgba.alpha', alpha: 'reader.settings.custom_filter.rgba.alpha',
}; };
const RGBA_TYPE_TO_MAX_VALUE: Record<RGBAType, number> = {
red: 255,
green: 255,
blue: 255,
alpha: 100,
};
export const ReaderSettingRGBA = ({ export const ReaderSettingRGBA = ({
rgba, rgba,
updateSetting, updateSetting,
@@ -84,9 +78,9 @@ export const ReaderSettingRGBA = ({
slider: { slider: {
value, value,
defaultValue: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType], defaultValue: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType],
step: 1, step: CUSTOM_FILTER.rgba[key as RGBAType].step,
min: 0, min: CUSTOM_FILTER.rgba[key as RGBAType].min,
max: RGBA_TYPE_TO_MAX_VALUE[key as RGBAType], max: CUSTOM_FILTER.rgba[key as RGBAType].max,
onChange: (_, newValue) => { onChange: (_, newValue) => {
updateSetting( updateSetting(
'rgba', 'rgba',

View File

@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts'; import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx'; import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { CUSTOM_FILTER, DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingSaturate = ({ export const ReaderSettingSaturate = ({
saturate, saturate,
@@ -47,9 +47,9 @@ export const ReaderSettingSaturate = ({
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.customFilter.saturate.value, defaultValue: DEFAULT_READER_SETTINGS.customFilter.saturate.value,
value: saturate.value, value: saturate.value,
step: 1, step: CUSTOM_FILTER.saturate.step,
min: 0, min: CUSTOM_FILTER.saturate.min,
max: 200, max: CUSTOM_FILTER.saturate.max,
onChange: (_, value) => { onChange: (_, value) => {
updateSetting('saturate', { ...saturate, value: value as number }, false); updateSetting('saturate', { ...saturate, value: value as number }, false);
}, },

View File

@@ -9,7 +9,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IReaderSettings, ProgressBarType, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts'; import { IReaderSettings, ProgressBarType, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { AUTO_SCROLL_SPEED, DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export const ReaderSettingProgressBarSize = ({ export const ReaderSettingProgressBarSize = ({
overlayMode, overlayMode,
@@ -37,9 +37,9 @@ export const ReaderSettingProgressBarSize = ({
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.progressBarSize, defaultValue: DEFAULT_READER_SETTINGS.progressBarSize,
value: progressBarSize, value: progressBarSize,
step: 1, step: AUTO_SCROLL_SPEED.step,
min: 2, min: AUTO_SCROLL_SPEED.min,
max: 20, max: AUTO_SCROLL_SPEED.max,
onChange: (_, value) => { onChange: (_, value) => {
setProgressBarSize(value as number, false); setProgressBarSize(value as number, false);
}, },

View File

@@ -9,7 +9,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IReaderSettings, IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; import { IReaderSettings, IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { DEFAULT_READER_SETTINGS, PAGE_GAP } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts'; import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
@@ -37,11 +37,11 @@ export const ReaderSettingPageGap = ({
onDefault={isDefaultable ? onDefault : undefined} onDefault={isDefaultable ? onDefault : undefined}
slotProps={{ slotProps={{
slider: { slider: {
defaultValue: DEFAULT_READER_SETTINGS.readerWidth.value, defaultValue: DEFAULT_READER_SETTINGS.pageGap,
value: pageGap.value, value: pageGap.value,
step: 1, step: PAGE_GAP.step,
min: 0, min: PAGE_GAP.min,
max: 20, max: PAGE_GAP.max,
onChange: (_, value) => { onChange: (_, value) => {
updateSetting(value as number, false); updateSetting(value as number, false);
}, },

View File

@@ -25,6 +25,7 @@ import {
ReaderHotkey, ReaderHotkey,
ReaderOverlayMode, ReaderOverlayMode,
ReaderPageScaleMode, ReaderPageScaleMode,
ReaderScrollAmount,
ReadingDirection, ReadingDirection,
ReadingMode, ReadingMode,
} from '@/modules/reader/types/Reader.types.ts'; } from '@/modules/reader/types/Reader.types.ts';
@@ -36,15 +37,93 @@ import { TranslationKey } from '@/Base.types.ts';
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts'; import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
import { WebtoonPageIcon } from '@/assets/icons/svg/WebtoonPageIcon.tsx'; import { WebtoonPageIcon } from '@/assets/icons/svg/WebtoonPageIcon.tsx';
/** export const AUTO_SCROLL_SPEED = {
* percentage values min: 0.5,
*/ max: 60,
export enum ReaderScrollAmount { step: 0.5,
TINY = 10, default: 5,
SMALL = 25, };
MEDIUM = 75,
LARGE = 95, export const SCROLL_AMOUNT = {
} min: 5,
max: 100,
default: ReaderScrollAmount.LARGE,
step: 5,
};
export const PROGRESS_BAR_SIZE = {
min: 2,
max: 20,
step: 1,
default: 4,
};
export const IMAGE_PRE_LOAD_AMOUNT = {
min: 1,
max: 20,
default: 5,
step: 1,
};
export const PAGE_GAP = {
min: 0,
max: 20,
default: 5,
step: 1,
};
export const CUSTOM_FILTER = {
brightness: {
min: 5,
max: 200,
step: 1,
default: 100,
},
contrast: {
min: 5,
max: 200,
step: 1,
default: 100,
},
saturate: {
min: 0,
max: 200,
step: 1,
default: 100,
},
hue: {
min: 0,
max: 200,
step: 1,
default: 0,
},
rgba: {
red: {
min: 0,
max: 255,
step: 1,
default: 0,
},
green: {
min: 0,
max: 255,
step: 1,
default: 0,
},
blue: {
min: 0,
max: 255,
step: 1,
default: 0,
},
alpha: {
min: 0,
max: 100,
step: 1,
default: 0,
},
},
} as const;
export const READING_DIRECTION_TO_THEME_DIRECTION: Record<ReadingDirection, Direction> = { export const READING_DIRECTION_TO_THEME_DIRECTION: Record<ReadingDirection, Direction> = {
[ReadingDirection.LTR]: 'ltr', [ReadingDirection.LTR]: 'ltr',
@@ -84,7 +163,7 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
tapZoneLayout: TapZoneLayouts.RIGHT_LEFT, tapZoneLayout: TapZoneLayouts.RIGHT_LEFT,
tapZoneInvertMode: { vertical: false, horizontal: false }, tapZoneInvertMode: { vertical: false, horizontal: false },
progressBarType: ProgressBarType.STANDARD, progressBarType: ProgressBarType.STANDARD,
progressBarSize: 4, progressBarSize: PROGRESS_BAR_SIZE.default,
progressBarPosition: ProgressBarPosition.AUTO, progressBarPosition: ProgressBarPosition.AUTO,
progressBarPositionAutoVertical: ProgressBarPosition.RIGHT, progressBarPositionAutoVertical: ProgressBarPosition.RIGHT,
pageScaleMode: ReaderPageScaleMode.ORIGINAL, pageScaleMode: ReaderPageScaleMode.ORIGINAL,
@@ -99,27 +178,27 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
backgroundColor: ReaderBackgroundColor.THEME, backgroundColor: ReaderBackgroundColor.THEME,
customFilter: { customFilter: {
brightness: { brightness: {
value: 100, value: CUSTOM_FILTER.brightness.default,
enabled: false, enabled: false,
}, },
contrast: { contrast: {
value: 100, value: CUSTOM_FILTER.contrast.default,
enabled: false, enabled: false,
}, },
saturate: { saturate: {
value: 100, value: CUSTOM_FILTER.saturate.default,
enabled: false, enabled: false,
}, },
hue: { hue: {
value: 0, value: CUSTOM_FILTER.hue.default,
enabled: false, enabled: false,
}, },
rgba: { rgba: {
value: { value: {
red: 0, red: CUSTOM_FILTER.rgba.red.default,
green: 0, green: CUSTOM_FILTER.rgba.green.default,
blue: 0, blue: CUSTOM_FILTER.rgba.blue.default,
alpha: 0, alpha: CUSTOM_FILTER.rgba.alpha.default,
blendMode: ReaderBlendMode.DEFAULT, blendMode: ReaderBlendMode.DEFAULT,
}, },
enabled: false, enabled: false,
@@ -128,7 +207,7 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
grayscale: false, grayscale: false,
invert: false, invert: false,
}, },
pageGap: 5, pageGap: PAGE_GAP.default,
hotkeys: { hotkeys: {
[ReaderHotkey.PREVIOUS_PAGE]: ['arrowleft', 'a'], [ReaderHotkey.PREVIOUS_PAGE]: ['arrowleft', 'a'],
[ReaderHotkey.NEXT_PAGE]: ['arrowright', 'd'], [ReaderHotkey.NEXT_PAGE]: ['arrowright', 'd'],
@@ -147,10 +226,10 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
[ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE]: ['v'], [ReaderHotkey.AUTO_SCROLL_SPEED_DECREASE]: ['v'],
[ReaderHotkey.EXIT_READER]: ['c'], [ReaderHotkey.EXIT_READER]: ['c'],
}, },
imagePreLoadAmount: 5, imagePreLoadAmount: IMAGE_PRE_LOAD_AMOUNT.default,
shouldUseAutoWebtoonMode: true, shouldUseAutoWebtoonMode: true,
autoScroll: { autoScroll: {
value: 5, value: AUTO_SCROLL_SPEED.default,
smooth: true, smooth: true,
}, },
shouldShowReadingModePreview: true, shouldShowReadingModePreview: true,
@@ -303,18 +382,6 @@ export const CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION: Record<
[ReadingMode.WEBTOON]: ScrollDirection.Y, [ReadingMode.WEBTOON]: ScrollDirection.Y,
}; };
export const AUTO_SCROLL_SPEED = {
min: 0.5,
max: 60,
step: 0.5,
};
export const SCROLL_AMOUNT = {
min: 5,
max: 100,
step: 5,
};
export const READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA = { export const READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA = {
[ReaderBlendMode.DEFAULT]: { [ReaderBlendMode.DEFAULT]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.default', title: 'reader.settings.custom_filter.rgba.blend_mode.default',

View File

@@ -9,16 +9,18 @@
import { memo, ReactNode, useCallback, useMemo, useState } from 'react'; import { memo, ReactNode, useCallback, useMemo, useState } from 'react';
import { Direction, useTheme } from '@mui/material/styles'; import { Direction, useTheme } from '@mui/material/styles';
import { useAutomaticScrolling } from '@/modules/core/hooks/useAutomaticScrolling.ts'; import { useAutomaticScrolling } from '@/modules/core/hooks/useAutomaticScrolling.ts';
import { IReaderSettings, ReadingMode, TReaderAutoScrollContext } from '@/modules/reader/types/Reader.types.ts'; import {
IReaderSettings,
ReaderScrollAmount,
ReadingMode,
TReaderAutoScrollContext,
} from '@/modules/reader/types/Reader.types.ts';
import { ReaderAutoScrollContext } from '@/modules/reader/contexts/ReaderAutoScrollContext.tsx'; import { ReaderAutoScrollContext } from '@/modules/reader/contexts/ReaderAutoScrollContext.tsx';
import { ReaderControls } from '@/modules/reader/services/ReaderControls'; import { ReaderControls } from '@/modules/reader/services/ReaderControls';
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { import { CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION,
ReaderScrollAmount,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
const BaseReaderAutoScrollContextProvider = ({ const BaseReaderAutoScrollContextProvider = ({
children, children,

View File

@@ -24,15 +24,13 @@ import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/R
import { import {
PageInViewportType, PageInViewportType,
ProgressBarPosition, ProgressBarPosition,
ReaderScrollAmount,
ReaderTransitionPageMode, ReaderTransitionPageMode,
ReadingDirection, ReadingDirection,
ReadingMode, ReadingMode,
} from '@/modules/reader/types/Reader.types.ts'; } from '@/modules/reader/types/Reader.types.ts';
import { ScrollDirection, ScrollOffset } from '@/modules/core/Core.types.ts'; import { ScrollDirection, ScrollOffset } from '@/modules/core/Core.types.ts';
import { import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
ReaderScrollAmount,
READING_DIRECTION_TO_THEME_DIRECTION,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { import {
isATransitionPageVisible, isATransitionPageVisible,
isEndOfPageInViewport, isEndOfPageInViewport,

View File

@@ -14,7 +14,6 @@ import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types
import { TMangaReader } from '@/modules/manga/Manga.types.ts'; import { TMangaReader } from '@/modules/manga/Manga.types.ts';
import { useAutomaticScrolling } from '@/modules/core/hooks/useAutomaticScrolling.ts'; import { useAutomaticScrolling } from '@/modules/core/hooks/useAutomaticScrolling.ts';
import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts';
import { ReaderScrollAmount } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
export enum ProgressBarType { export enum ProgressBarType {
HIDDEN, HIDDEN,
@@ -130,6 +129,16 @@ export interface ReaderCustomFilter {
invert: boolean; invert: boolean;
} }
/**
* percentage values
*/
export enum ReaderScrollAmount {
TINY = 10,
SMALL = 25,
MEDIUM = 75,
LARGE = 95,
}
export interface IReaderSettingsGlobal { export interface IReaderSettingsGlobal {
overlayMode: ReaderOverlayMode; overlayMode: ReaderOverlayMode;
exitMode: ReaderExitMode; exitMode: ReaderExitMode;

View File

@@ -13,6 +13,13 @@ import { GridLayout } from '@/modules/core/Core.types.ts';
import { getDefaultLanguages } from '@/modules/core/utils/Languages.ts'; import { getDefaultLanguages } from '@/modules/core/utils/Languages.ts';
import { ThemeMode } from '@/modules/theme/contexts/AppThemeContext.tsx'; import { ThemeMode } from '@/modules/theme/contexts/AppThemeContext.tsx';
export const MANGA_GRID_WIDTH = {
min: 100,
max: 1000,
step: 10,
default: 300,
};
export const SERVER_SETTINGS_METADATA_DEFAULT: MetadataServerSettings = { export const SERVER_SETTINGS_METADATA_DEFAULT: MetadataServerSettings = {
// downloads // downloads
deleteChaptersManuallyMarkedRead: false, deleteChaptersManuallyMarkedRead: false,
@@ -66,5 +73,5 @@ export const SERVER_SETTINGS_METADATA_DEFAULT: MetadataServerSettings = {
customThemes: {}, customThemes: {},
mangaThumbnailBackdrop: true, mangaThumbnailBackdrop: true,
mangaDynamicColorSchemes: true, mangaDynamicColorSchemes: true,
mangaGridItemWidth: 300, mangaGridItemWidth: MANGA_GRID_WIDTH.default,
}; };

View File

@@ -34,7 +34,7 @@ import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { AppStorage } from '@/lib/storage/AppStorage.ts'; import { AppStorage } from '@/lib/storage/AppStorage.ts';
import { useAppTitle } from '@/modules/navigation-bar/hooks/useAppTitle.ts'; import { useAppTitle } from '@/modules/navigation-bar/hooks/useAppTitle.ts';
import { SERVER_SETTINGS_METADATA_DEFAULT } from '@/modules/settings/Settings.constants.ts'; import { MANGA_GRID_WIDTH, SERVER_SETTINGS_METADATA_DEFAULT } from '@/modules/settings/Settings.constants.ts';
export const Appearance = () => { export const Appearance = () => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
@@ -155,9 +155,9 @@ export const Appearance = () => {
settingValue={`px: ${mangaGridItemWidth}`} settingValue={`px: ${mangaGridItemWidth}`}
value={mangaGridItemWidth} value={mangaGridItemWidth}
defaultValue={SERVER_SETTINGS_METADATA_DEFAULT.mangaGridItemWidth} defaultValue={SERVER_SETTINGS_METADATA_DEFAULT.mangaGridItemWidth}
minValue={100} minValue={MANGA_GRID_WIDTH.min}
maxValue={1000} maxValue={MANGA_GRID_WIDTH.max}
stepSize={10} stepSize={MANGA_GRID_WIDTH.step}
valueUnit="px" valueUnit="px"
showSlider showSlider
handleUpdate={(width) => updateMetadataSetting('mangaGridItemWidth', width)} handleUpdate={(width) => updateMetadataSetting('mangaGridItemWidth', width)}