Ensure stored client metadata values respect constraints
This commit is contained in:
14
src/modules/downloads/Downloads.constants.ts
Normal file
14
src/modules/downloads/Downloads.constants.ts
Normal 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,
|
||||||
|
};
|
||||||
@@ -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')}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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>>>(
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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]: {
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
Reference in New Issue
Block a user