Fix constraining logic of stored client metadata

By the time the constraining function was called, the stored values were still of type string and were not converted to the correct type yet.
This commit is contained in:
schroda
2025-06-25 00:38:01 +02:00
parent 56f01efc1e
commit cb74984659
8 changed files with 146 additions and 249 deletions

View File

@@ -7,7 +7,6 @@
*/
import { useEffect, useMemo } from 'react';
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
import { requestUpdateCategoryMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { LibraryOptions } from '@/modules/library/Library.types.ts';
@@ -16,7 +15,6 @@ import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverte
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
import {
AllowedMetadataValueTypes,
AppMetadataKeys,
GqlMetaHolder,
Metadata,
MetadataHolder,
@@ -45,24 +43,11 @@ const convertAppMetadataToGqlMetadata = (
hasStatus: metadata.hasStatus ? JSON.stringify(metadata.hasStatus) : undefined,
});
const convertGqlMetadataToAppMetadata = (
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
): ICategoryMetadata => ({
...(metadata as unknown as ICategoryMetadata),
hasTrackerBinding:
jsonSaveParse<ICategoryMetadata['hasTrackerBinding']>(metadata.hasTrackerBinding as string) ??
(undefined as any),
hasStatus: jsonSaveParse<ICategoryMetadata['hasStatus']>(metadata.hasStatus as string) ?? (undefined as any),
});
const getCategoryMetadataWithDefaultValueFallback = (
meta: CategoryIdInfo & MetadataHolder,
defaultMetadata: ICategoryMetadata = DEFAULT_CATEGORY_METADATA,
useEffectFn?: typeof useEffect,
): ICategoryMetadata =>
convertGqlMetadataToAppMetadata(
getMetadataFrom('category', meta, convertAppMetadataToGqlMetadata(defaultMetadata), undefined, useEffectFn),
);
): ICategoryMetadata => getMetadataFrom('category', meta, defaultMetadata, undefined, useEffectFn);
const getMetadata = (
metaHolder: CategoryIdInfo & GqlMetaHolder,

View File

@@ -12,7 +12,6 @@ import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
import { MangaIdInfo, MangaMetadata, MangaMetadataKeys } from '@/modules/manga/Manga.types.ts';
import {
AllowedMetadataValueTypes,
AppMetadataKeys,
GqlMetaHolder,
Metadata,
MetadataHolder,
@@ -20,7 +19,6 @@ import {
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
const DEFAULT_MANGA_METADATA: MangaMetadata = {
...DEFAULT_CHAPTER_OPTIONS,
@@ -33,21 +31,11 @@ const convertAppMetadataToGqlMetadata = (
excludedScanlators: JSON.stringify(metadata.excludedScanlators),
});
const convertGqlMetadataToAppMetadata = (
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
): MangaMetadata => ({
...(metadata as unknown as MangaMetadata),
excludedScanlators: jsonSaveParse<MangaMetadata['excludedScanlators']>(metadata.excludedScanlators as string) ?? [],
});
const getMangaMetadataWithDefaultValueFallback = (
meta: MangaIdInfo & MetadataHolder,
defaultMetadata: MangaMetadata = DEFAULT_MANGA_METADATA,
useEffectFn?: typeof useEffect,
): MangaMetadata =>
convertGqlMetadataToAppMetadata(
getMetadataFrom('manga', meta, convertAppMetadataToGqlMetadata(defaultMetadata), undefined, useEffectFn),
);
): MangaMetadata => getMetadataFrom('manga', meta, defaultMetadata, undefined, useEffectFn);
const getMetadata = (
metaHolder: MangaIdInfo & GqlMetaHolder,

View File

@@ -6,6 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// eslint-disable-next-line import/no-extraneous-dependencies,no-restricted-imports
import deepmerge from '@mui/utils/deepmerge';
import { AppMetadataKeys, IMetadataMigration } from '@/modules/metadata/Metadata.types.ts';
import {
IReaderSettings,
@@ -23,173 +25,190 @@ import {
PROGRESS_BAR_SIZE,
SCROLL_AMOUNT,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { coerceIn } from '@/lib/HelperFunctions.ts';
import { coerceIn, jsonSaveParse } from '@/lib/HelperFunctions.ts';
import { DOWNLOAD_AHEAD } from '@/modules/downloads/Downloads.constants.ts';
import { MANGA_GRID_WIDTH } from '@/modules/settings/Settings.constants.ts';
import { NullAndUndefined } from '@/Base.types.ts';
import { SortSettings } from '@/modules/migration/Migration.types.ts';
import { ISourceMetadata } from '@/modules/source/Source.types.ts';
import { LibraryOptions } from '@/modules/library/Library.types.ts';
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
import { TapZoneInvertMode } from '@/modules/reader/types/TapZoneLayout.types.ts';
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".
// "auto" can be used to try to automatically convert the value to a specific type (string, number, boolean, undefined, null)
const convertToTypeNullAndUndefined = <T>(value: string, convertToType: (value: string) => T): NullAndUndefined<T> => {
if (value === 'null') return null;
if (value === 'undefined') return undefined;
return convertToType(value);
};
const convertToString = (value: string): string => value;
const convertToNumber = (value: string): number => +value;
const convertToBoolean = (value: string): boolean => value === 'true';
const convertToStringNullAndUndefined = (value: string) => convertToTypeNullAndUndefined(value, convertToString);
const convertToBooleanNullAndUndefined = (value: string) => convertToTypeNullAndUndefined(value, convertToBoolean);
const convertToObject = <T>(value: string, defaultValue: T): T =>
deepmerge(defaultValue, jsonSaveParse<T>(value) ?? defaultValue);
export const APP_METADATA: Record<
AppMetadataKeys,
{
type: 'auto' | 'string' | 'number' | 'boolean';
toValidValue?: (value: any) => any;
convert: (value: string, defaultValue: any) => any;
toConstrainedValue?: (value: any) => any;
}
> = {
migration: {
type: 'number',
convert: convertToNumber,
},
deleteChaptersManuallyMarkedRead: {
type: 'boolean',
convert: convertToBoolean,
},
deleteChaptersWhileReading: {
type: 'number',
convert: convertToNumber,
},
deleteChaptersWithBookmark: {
type: 'boolean',
convert: convertToBoolean,
},
downloadAheadLimit: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, DOWNLOAD_AHEAD.min, DOWNLOAD_AHEAD.max),
convert: convertToNumber,
toConstrainedValue: (value: number) => coerceIn(value, DOWNLOAD_AHEAD.min, DOWNLOAD_AHEAD.max),
},
showAddToLibraryCategorySelectDialog: {
type: 'boolean',
convert: convertToBoolean,
},
ignoreFilters: {
type: 'boolean',
convert: convertToBoolean,
},
removeMangaFromCategories: {
type: 'boolean',
convert: convertToBoolean,
},
showTabSize: {
type: 'boolean',
convert: convertToBoolean,
},
devices: {
type: 'string', // string[]
convert: convertToObject<string[]>,
},
migrateChapters: {
type: 'boolean',
convert: convertToBoolean,
},
migrateCategories: {
type: 'boolean',
convert: convertToBoolean,
},
migrateTracking: {
type: 'boolean',
convert: convertToBoolean,
},
deleteChapters: {
type: 'boolean',
convert: convertToBoolean,
},
migrateSortSettings: {
type: 'string', // SortSettings (object)
convert: convertToObject<SortSettings>,
},
hideLibraryEntries: {
type: 'boolean',
convert: convertToBoolean,
},
updateProgressAfterReading: {
type: 'boolean',
convert: convertToBoolean,
},
updateProgressManualMarkRead: {
type: 'boolean',
convert: convertToBoolean,
},
webUIInformAvailableUpdate: {
type: 'boolean',
convert: convertToBoolean,
},
serverInformAvailableUpdate: {
type: 'boolean',
convert: convertToBoolean,
},
readerWidth: {
type: 'string', // object
convert: convertToObject<IReaderSettings['readerWidth']>,
},
savedSearches: {
type: 'string', // object
convert: convertToObject<ISourceMetadata['savedSearches']>,
},
showContinueReadingButton: {
type: 'boolean',
convert: convertToBoolean,
},
showDownloadBadge: {
type: 'boolean',
convert: convertToBoolean,
},
showUnreadBadge: {
type: 'boolean',
convert: convertToBoolean,
},
gridLayout: {
type: 'number', // GridLayout (enum)
convert: convertToNumber, // GridLayout (enum)
},
sortBy: {
type: 'auto', // LibrarySortMode undefined null
convert: convertToStringNullAndUndefined, // LibrarySortMode
},
sortDesc: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
hasDownloadedChapters: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
hasBookmarkedChapters: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
hasUnreadChapters: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
hasReadChapters: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
hasDuplicateChapters: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
hasTrackerBinding: {
type: 'string', // object
convert: convertToObject<LibraryOptions['hasTrackerBinding']>,
},
hasStatus: {
type: 'string', // object
convert: convertToObject<LibraryOptions['hasStatus']>,
},
customThemes: {
type: 'string', // object
convert: convertToObject<MetadataThemeSettings['customThemes']>,
},
mangaThumbnailBackdrop: {
type: 'boolean',
convert: convertToBoolean,
},
mangaDynamicColorSchemes: {
type: 'boolean',
convert: convertToBoolean,
},
tapZoneLayout: {
type: 'number', // TapZoneLayouts (enum)
convert: convertToNumber, // TapZoneLayouts (enum)
},
tapZoneInvertMode: {
type: 'string', // TapZoneInvertMode (object)
convert: convertToObject<TapZoneInvertMode>, // TapZoneInvertMode (object)
},
readingDirection: {
type: 'number', // ReadingDirection (enum)
convert: convertToNumber, // ReadingDirection (enum)
},
progressBarType: {
type: 'number', // ProgressBarType (enum)
convert: convertToNumber, // ProgressBarType (enum)
},
progressBarSize: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, PROGRESS_BAR_SIZE.min, PROGRESS_BAR_SIZE.max),
convert: convertToNumber,
toConstrainedValue: (value: number) => coerceIn(value, PROGRESS_BAR_SIZE.min, PROGRESS_BAR_SIZE.max),
},
progressBarPosition: {
type: 'number', // ProgressBarPosition (enum)
convert: convertToNumber, // ProgressBarPosition (enum)
},
progressBarPositionAutoVertical: {
type: 'number', // TProgressBarPositionAutoVertical (enum)
convert: convertToNumber, // TProgressBarPositionAutoVertical (enum)
},
readingMode: {
type: 'number', // ReadingMode (enum)
convert: convertToNumber, // ReadingMode (enum)
},
pageScaleMode: {
type: 'number', // ReaderPageScaleMode (enum)
convert: convertToNumber, // ReaderPageScaleMode (enum)
},
shouldOffsetDoubleSpreads: {
type: 'boolean',
convert: convertToBoolean,
},
exitMode: {
type: 'number', // ReaderExitMode (enum)
convert: convertToNumber, // ReaderExitMode (enum)
},
customFilter: {
type: 'string', // ReaderCustomFilter (object)
toValidValue: (value: ReaderCustomFilter): ReaderCustomFilter => ({
convert: convertToObject<ReaderCustomFilter>,
toConstrainedValue: (value: ReaderCustomFilter): ReaderCustomFilter => ({
...value,
brightness: {
...value.brightness,
@@ -220,120 +239,120 @@ export const APP_METADATA: Record<
}),
},
shouldSkipDupChapters: {
type: 'boolean',
convert: convertToBoolean,
},
isStaticNav: {
type: 'boolean',
convert: convertToBoolean,
},
overlayMode: {
type: 'number', // ReaderOverlayMode (enum)
convert: convertToNumber, // ReaderOverlayMode (enum)
},
shouldStretchPage: {
type: 'boolean',
convert: convertToBoolean,
},
shouldShowPageNumber: {
type: 'boolean',
convert: convertToBoolean,
},
backgroundColor: {
type: 'number', // ReaderBackgroundColor (enum)
convert: convertToNumber, // ReaderBackgroundColor (enum)
},
pageGap: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, PAGE_GAP.min, PAGE_GAP.max),
convert: convertToNumber,
toConstrainedValue: (value: number) => coerceIn(value, PAGE_GAP.min, PAGE_GAP.max),
},
hotkeys: {
type: 'string', // object
convert: convertToObject<IReaderSettings['hotkeys']>,
},
imagePreLoadAmount: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, IMAGE_PRE_LOAD_AMOUNT.min, IMAGE_PRE_LOAD_AMOUNT.max),
convert: convertToNumber,
toConstrainedValue: (value: number) => coerceIn(value, IMAGE_PRE_LOAD_AMOUNT.min, IMAGE_PRE_LOAD_AMOUNT.max),
},
shouldUseAutoWebtoonMode: {
type: 'boolean',
convert: convertToBoolean,
},
autoScroll: {
type: 'string', // object
toValidValue: (value: IReaderSettings['autoScroll']): IReaderSettings['autoScroll'] => ({
convert: convertToObject<IReaderSettings['autoScroll']>,
toConstrainedValue: (value: IReaderSettings['autoScroll']): IReaderSettings['autoScroll'] => ({
...value,
value: coerceIn(value.value, AUTO_SCROLL_SPEED.min, AUTO_SCROLL_SPEED.max),
}),
},
shouldShowReadingModePreview: {
type: 'boolean',
convert: convertToBoolean,
},
shouldShowTapZoneLayoutPreview: {
type: 'boolean',
convert: convertToBoolean,
},
shouldInformAboutMissingChapter: {
type: 'boolean',
convert: convertToBoolean,
},
shouldInformAboutScanlatorChange: {
type: 'boolean',
convert: convertToBoolean,
},
hideHistory: {
type: 'boolean',
convert: convertToBoolean,
},
scrollAmount: {
type: 'number', // ReaderScrollAmount (enum)
toValidValue: (value: number) => coerceIn(value, SCROLL_AMOUNT.min, SCROLL_AMOUNT.max),
convert: convertToNumber, // ReaderScrollAmount (enum)
toConstrainedValue: (value: number) => coerceIn(value, SCROLL_AMOUNT.min, SCROLL_AMOUNT.max),
},
reverse: {
type: 'boolean',
convert: convertToBoolean,
},
bookmarked: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
downloaded: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
unread: {
type: 'auto', // boolean undefined null
convert: convertToBooleanNullAndUndefined,
},
showChapterNumber: {
type: 'boolean',
convert: convertToBoolean,
},
extensionLanguages: {
type: 'string', // string[]
convert: convertToObject<string[]>,
},
sourceLanguages: {
type: 'string', // string[]
convert: convertToObject<string[]>,
},
showNsfw: {
type: 'boolean',
convert: convertToBoolean,
},
shouldUseInfiniteScroll: {
type: 'boolean',
convert: convertToBoolean,
},
shouldShowTransitionPage: {
type: 'boolean',
convert: convertToBoolean,
},
appTheme: {
type: 'string',
convert: convertToString,
},
themeMode: {
type: 'string', // ThemeMode (enum)
convert: convertToString, // ThemeMode (enum)
},
shouldUsePureBlackMode: {
type: 'boolean',
convert: convertToBoolean,
},
mangaGridItemWidth: {
type: 'number',
toValidValue: (value: number) => coerceIn(value, MANGA_GRID_WIDTH.min, MANGA_GRID_WIDTH.max),
convert: convertToNumber,
toConstrainedValue: (value: number) => coerceIn(value, MANGA_GRID_WIDTH.min, MANGA_GRID_WIDTH.max),
},
isPinned: {
type: 'boolean',
convert: convertToBoolean,
},
isEnabled: {
type: 'boolean',
convert: convertToBoolean,
},
lastUsedSourceId: {
type: 'string',
convert: convertToString,
},
shouldShowOnlySourcesWithResults: {
type: 'boolean',
convert: convertToBoolean,
},
excludedScanlators: {
type: 'string', // string[]
convert: convertToObject<string[]>,
},
} as const;

View File

@@ -13,28 +13,8 @@ import { APP_METADATA } from '@/modules/metadata/Metadata.constants.ts';
export const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
key: string,
value: string,
): T => {
const typeOfKey = APP_METADATA[key as AppMetadataKeys].type;
const isAutoType = typeOfKey === 'auto';
if ((isAutoType && !Number.isNaN(+value)) || typeOfKey === 'number') {
return +value as T;
}
if ((isAutoType && (value === 'true' || value === 'false')) || typeOfKey === 'boolean') {
return (value === 'true') as T;
}
if (isAutoType && value === 'undefined') {
return undefined as T;
}
if (isAutoType && value === 'null') {
return null as T;
}
return value as T;
};
defaultValue: any,
): T => APP_METADATA[key as AppMetadataKeys].convert(value, defaultValue);
export const convertFromGqlMeta = (
gqlMetadata?: MetaType[],

View File

@@ -38,47 +38,47 @@ const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends Allowed
return defaultValue;
}
const convertedValue = convertValueFromMetadata(key, metadata[getMetadataKey(key, prefixes)]);
const convertedValue = convertValueFromMetadata(key, metadata[getMetadataKey(key, prefixes)], defaultValue);
return APP_METADATA[key].toValidValue?.(convertedValue) ?? convertedValue;
return APP_METADATA[key].toConstrainedValue?.(convertedValue) ?? convertedValue;
};
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, any>>>(
type: 'global',
metadataHolder: MetadataHolder,
metadataWithDefaultValues: METADATA,
prefixes?: string[],
useEffectFn?: typeof useEffect,
): METADATA;
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, any>>>(
type: 'manga',
metadataHolder: MangaIdInfo & MetadataHolder,
metadataWithDefaultValues: METADATA,
prefixes?: string[],
useEffectFn?: typeof useEffect,
): METADATA;
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, any>>>(
type: 'chapter',
metadataHolder: ChapterIdInfo & MetadataHolder,
metadataWithDefaultValues: METADATA,
prefixes?: string[],
useEffectFn?: typeof useEffect,
): METADATA;
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, any>>>(
type: 'category',
metadataHolder: CategoryIdInfo & MetadataHolder,
metadataWithDefaultValues: METADATA,
prefixes?: string[],
useEffectFn?: typeof useEffect,
): METADATA;
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, any>>>(
type: 'source',
metadataHolder: SourceIdInfo & MetadataHolder,
metadataWithDefaultValues: METADATA,
prefixes?: string[],
useEffectFn?: typeof useEffect,
): METADATA;
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
export function getMetadataFrom<METADATA extends Partial<Metadata<AppMetadataKeys, any>>>(
type: MetadataHolderType,
metadataHolder:
| MetadataHolder

View File

@@ -8,7 +8,6 @@
import { useEffect, useMemo } from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies,no-restricted-imports
import { deepmerge } from '@mui/utils';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import {
requestUpdateMangaMetadata,
@@ -20,14 +19,12 @@ import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverte
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
import {
AllowedMetadataValueTypes,
AppMetadataKeys,
GqlMetaHolder,
Metadata,
MetadataHolder,
MetadataHolderType,
} from '@/modules/metadata/Metadata.types.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import {
DEFAULT_READER_SETTINGS,
@@ -103,47 +100,19 @@ export const DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG = convertToSettingsWithDe
{ meta: convertSettingsToMetadata(DEFAULT_READER_SETTINGS) as Metadata },
);
const convertMetadataToSettings = (
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
defaultSettings: IReaderSettings,
): IReaderSettings => ({
...(metadata as unknown as IReaderSettings),
tapZoneInvertMode:
jsonSaveParse<IReaderSettings['tapZoneInvertMode']>((metadata.tapZoneInvertMode as string) ?? '') ??
defaultSettings.tapZoneInvertMode,
customFilter: deepmerge(
defaultSettings.customFilter,
jsonSaveParse<IReaderSettings['customFilter']>((metadata.customFilter as string) ?? '') ??
defaultSettings.customFilter,
),
readerWidth:
jsonSaveParse<IReaderSettings['readerWidth']>((metadata.readerWidth as string) ?? '') ??
defaultSettings.readerWidth,
hotkeys: {
...defaultSettings.hotkeys,
...(jsonSaveParse<IReaderSettings['hotkeys']>((metadata.hotkeys as string) ?? '') ?? defaultSettings.hotkeys),
},
autoScroll:
jsonSaveParse<IReaderSettings['autoScroll']>((metadata.autoScroll as string) ?? '') ??
defaultSettings.autoScroll,
});
export const getReaderSettings = (
type: Extract<MetadataHolderType, 'global' | 'manga'>,
metadataHolder: (MangaIdInfo & MetadataHolder) | MetadataHolder,
defaultSettings: IReaderSettings = DEFAULT_READER_SETTINGS,
useEffectFn?: typeof useEffect,
profile?: ReadingMode,
) =>
convertMetadataToSettings(
getMetadataFrom(
type as Parameters<typeof getMetadataFrom>[0],
metadataHolder as Parameters<typeof getMetadataFrom>[1],
convertSettingsToMetadata(defaultSettings),
profile !== undefined ? [profile.toString()] : undefined,
useEffectFn,
),
defaultSettings as IReaderSettings,
): IReaderSettings =>
getMetadataFrom(
type as Parameters<typeof getMetadataFrom>[0],
metadataHolder as Parameters<typeof getMetadataFrom>[1],
defaultSettings,
profile !== undefined ? [profile.toString()] : undefined,
useEffectFn,
);
function getReaderSettingsWithDefaultValueFallback(

View File

@@ -9,15 +9,12 @@
import { useEffect, useMemo } from 'react';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { requestUpdateServerMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
import { MetadataThemeSettings } from '@/modules/theme/AppTheme.types.ts';
import { SERVER_SETTINGS_METADATA_DEFAULT } from '@/modules/settings/Settings.constants.ts';
import { MetadataServerSettingKeys, MetadataServerSettings } from '@/modules/settings/Settings.types.ts';
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
import { AllowedMetadataValueTypes, AppMetadataKeys, Metadata } from '@/modules/metadata/Metadata.types.ts';
import { AllowedMetadataValueTypes, Metadata } from '@/modules/metadata/Metadata.types.ts';
export const convertSettingsToMetadata = (
settings: Partial<MetadataServerSettings>,
@@ -30,37 +27,11 @@ export const convertSettingsToMetadata = (
sourceLanguages: JSON.stringify(settings.sourceLanguages),
});
export const convertMetadataToSettings = (
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
): MetadataServerSettings =>
({
...SERVER_SETTINGS_METADATA_DEFAULT,
...(metadata as unknown as MetadataServerSettings),
devices:
jsonSaveParse<string[]>((metadata.devices as string) ?? '') ?? SERVER_SETTINGS_METADATA_DEFAULT.devices,
customThemes:
jsonSaveParse<MetadataThemeSettings['customThemes']>((metadata.customThemes as string) ?? '') ??
SERVER_SETTINGS_METADATA_DEFAULT.customThemes,
migrateSortSettings:
jsonSaveParse<MetadataMigrationSettings['migrateSortSettings']>(
(metadata.migrateSortSettings as string) ?? '',
) ?? SERVER_SETTINGS_METADATA_DEFAULT.migrateSortSettings,
extensionLanguages:
jsonSaveParse<string[]>((metadata.extensionLanguages as string) ?? '') ??
SERVER_SETTINGS_METADATA_DEFAULT.extensionLanguages,
sourceLanguages:
jsonSaveParse<string[]>((metadata.sourceLanguages as string) ?? '') ??
SERVER_SETTINGS_METADATA_DEFAULT.sourceLanguages,
}) satisfies MetadataServerSettings;
const getMetadataServerSettingsWithDefaultFallback = (
meta?: Metadata,
defaultSettings: MetadataServerSettings = SERVER_SETTINGS_METADATA_DEFAULT,
useEffectFn?: typeof useEffect,
): MetadataServerSettings =>
convertMetadataToSettings(
getMetadataFrom('global', { meta }, convertSettingsToMetadata(defaultSettings), undefined, useEffectFn),
);
): MetadataServerSettings => getMetadataFrom('global', { meta }, defaultSettings, undefined, useEffectFn);
export const useMetadataServerSettings = (): {
metadata?: Metadata;

View File

@@ -7,18 +7,12 @@
*/
import { useEffect, useMemo } from 'react';
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
import { requestUpdateSourceMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { ISourceMetadata, SourceIdInfo, SourceMetadataKeys } from '@/modules/source/Source.types.ts';
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
import {
AllowedMetadataValueTypes,
AppMetadataKeys,
GqlMetaHolder,
Metadata,
} from '@/modules/metadata/Metadata.types.ts';
import { AllowedMetadataValueTypes, GqlMetaHolder, Metadata } from '@/modules/metadata/Metadata.types.ts';
const DEFAULT_SOURCE_METADATA: ISourceMetadata = {
savedSearches: undefined,
@@ -33,22 +27,13 @@ const convertAppMetadataToGqlMetadata = (
savedSearches: metadata.savedSearches ? JSON.stringify(metadata.savedSearches) : undefined,
});
const convertGqlMetadataToAppMetadata = (
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
): ISourceMetadata => ({
...(metadata as unknown as ISourceMetadata),
savedSearches: jsonSaveParse<ISourceMetadata['savedSearches']>(metadata.savedSearches as string) ?? undefined,
});
const getMetadata = (metaHolder: SourceIdInfo & GqlMetaHolder, useEffectFn?: typeof useEffect): ISourceMetadata =>
convertGqlMetadataToAppMetadata(
getMetadataFrom(
'source',
{ ...metaHolder, meta: convertFromGqlMeta(metaHolder.meta) },
convertAppMetadataToGqlMetadata(DEFAULT_SOURCE_METADATA),
undefined,
useEffectFn,
),
getMetadataFrom(
'source',
{ ...metaHolder, meta: convertFromGqlMeta(metaHolder.meta) },
DEFAULT_SOURCE_METADATA,
undefined,
useEffectFn,
);
export const getSourceMetadata = (metaHolder: SourceIdInfo & GqlMetaHolder): ISourceMetadata => getMetadata(metaHolder);