Feature/cleanup metadata related logic (#657)
* Remove unnecessary parameter from "requestUpdateServerMetadata" * Streamline updating metadata server settings * Move metadata related logic
This commit is contained in:
@@ -12,7 +12,7 @@ import { DocumentNode } from '@apollo/client';
|
||||
import { ChapterOffset, TChapter, TranslationKey } from '@/typings.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { getMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||
import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||
import { FULL_CHAPTER_FIELDS } from '@/lib/graphql/Fragments.ts';
|
||||
|
||||
export type ChapterAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from '@/lib/graphql/generated/graphql.ts';
|
||||
import { Chapters } from '@/lib/data/Chapters.ts';
|
||||
import { makeToast } from '@/components/util/Toast.tsx';
|
||||
import { getMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||
import { getMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
|
||||
|
||||
export type MangaAction =
|
||||
| 'download'
|
||||
|
||||
391
src/lib/metadata/metadata.ts
Normal file
391
src/lib/metadata/metadata.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import {
|
||||
AllowedMetadataValueTypes,
|
||||
AppMetadataKeys,
|
||||
GqlMetaHolder,
|
||||
IMetadataMigration,
|
||||
Metadata,
|
||||
MetadataHolder,
|
||||
MetadataKeyValuePair,
|
||||
TCategory,
|
||||
TChapter,
|
||||
TManga,
|
||||
} from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { DEFAULT_DEVICE, getActiveDevice } from '@/util/device.ts';
|
||||
|
||||
const APP_METADATA_KEY_PREFIX = 'webUI_';
|
||||
|
||||
const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
||||
// downloads
|
||||
'deleteChaptersManuallyMarkedRead',
|
||||
'deleteChaptersWhileReading',
|
||||
'deleteChaptersWithBookmark',
|
||||
|
||||
// library
|
||||
'showAddToLibraryCategorySelectDialog',
|
||||
'ignoreFilters',
|
||||
'removeMangaFromCategories',
|
||||
|
||||
// client
|
||||
'devices',
|
||||
];
|
||||
|
||||
/**
|
||||
* Once all changes have been done in the current branch, a new migration for all changes should be
|
||||
* created.
|
||||
*
|
||||
* In case a value should be migrated for a specific key, and in the same migration the key is
|
||||
* getting migrated, the key in the value migration is the "old" key (before the migration to the
|
||||
* new key).
|
||||
*
|
||||
* Migration order (function "applyMetadataMigrations"):
|
||||
* 1. app metadata key prefix
|
||||
* 2. app metadata values
|
||||
* 3. app metadata keys
|
||||
*
|
||||
* @example
|
||||
* // changes:
|
||||
* // commit: change key "X" to "Z"
|
||||
* // commit: change key "Y" to "A"
|
||||
* // commit: fix typo in reader setting "someTipo"
|
||||
* // commit: add metadata migration
|
||||
*
|
||||
* // result:
|
||||
* // old migrations:
|
||||
* // const migrations = [
|
||||
* // {
|
||||
* // keys: [{ oldKey: 'loadNextonEnding', newKey: 'loadNextOnEnding' }],
|
||||
* // }
|
||||
* // ];
|
||||
*
|
||||
* // updated migrations
|
||||
* const migrations = [
|
||||
* {
|
||||
* keys: [{ oldKey: 'loadNextonEnding', newKey: 'loadNextOnEnding' }],
|
||||
* },
|
||||
* {
|
||||
* keys: [
|
||||
* { oldKey: 'X', newKey: 'Z' },
|
||||
* { oldKey: 'Y', newKey: 'A' },
|
||||
* ],
|
||||
* // all stored values in the metadata (of this app) will get migrated
|
||||
* values: [
|
||||
* {
|
||||
* oldValue: 'someTipo',
|
||||
* newValue: 'someTypo'
|
||||
* },
|
||||
* ],
|
||||
* // to migrate only the value of a specific key (in this case of "someKey"):
|
||||
* // values: [
|
||||
* // {
|
||||
* // key: 'someKey',
|
||||
* // oldValue: 'someTipo',
|
||||
* // newValue: 'someTypo',
|
||||
* // },
|
||||
* // ],
|
||||
* },
|
||||
* ];
|
||||
*/
|
||||
const migrations: IMetadataMigration[] = [
|
||||
{
|
||||
keys: [{ oldKey: 'loadNextonEnding', newKey: 'loadNextOnEnding' }],
|
||||
},
|
||||
{
|
||||
keys: [{ oldKey: 'deleteChaptersAutoMarkedRead', newKey: 'deleteChaptersWhileReading' }],
|
||||
},
|
||||
];
|
||||
|
||||
const getAppKeyPrefixForMigration = (migrationId: number): string => {
|
||||
const appKeyPrefix = migrations
|
||||
.slice(0, migrationId)
|
||||
.reverse()
|
||||
.find((migration) => !!migration.appKeyPrefix);
|
||||
|
||||
return appKeyPrefix?.appKeyPrefix?.newPrefix ?? APP_METADATA_KEY_PREFIX;
|
||||
};
|
||||
|
||||
const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => {
|
||||
const isGlobalMetadataKey = GLOBAL_METADATA_KEYS.includes(key as AppMetadataKeys);
|
||||
const addActiveDevicePrefix = !isGlobalMetadataKey && getActiveDevice() !== DEFAULT_DEVICE;
|
||||
|
||||
return `${appPrefix}${addActiveDevicePrefix ? `${getActiveDevice()}_` : ''}${key}`;
|
||||
};
|
||||
|
||||
const doesMetadataKeyExistIn = (meta: Metadata | undefined, key: string, appPrefix?: string): boolean =>
|
||||
Object.prototype.hasOwnProperty.call(meta ?? {}, getMetadataKey(key, appPrefix));
|
||||
|
||||
const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
|
||||
value: string,
|
||||
): T => {
|
||||
if (!Number.isNaN(+value)) {
|
||||
return +value as T;
|
||||
}
|
||||
|
||||
if (value === 'true' || value === 'false') {
|
||||
return (value === 'true') as T;
|
||||
}
|
||||
|
||||
if (value === 'undefined') {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return value as T;
|
||||
};
|
||||
|
||||
export const convertFromGqlMeta = (gqlMetadata?: MetaType[]): Metadata | undefined => {
|
||||
if (!gqlMetadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const metadata: Metadata = {};
|
||||
gqlMetadata.forEach(({ key, value }) => {
|
||||
metadata[key] = value;
|
||||
});
|
||||
|
||||
return metadata;
|
||||
};
|
||||
|
||||
export const convertToGqlMeta = (metadata?: Metadata): MetaType[] | undefined => {
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.entries(metadata).map(([key, value]) => ({ key, value }));
|
||||
};
|
||||
|
||||
const getAppMetadataFrom = (meta: Metadata, appPrefix: string = APP_METADATA_KEY_PREFIX): Metadata => {
|
||||
const appMetadata: Metadata = {};
|
||||
|
||||
Object.entries(meta).forEach(([key, value]) => {
|
||||
if (key.startsWith(appPrefix)) {
|
||||
appMetadata[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return appMetadata;
|
||||
};
|
||||
|
||||
const applyAppKeyPrefixMigration = (meta: Metadata, migration: IMetadataMigration): Metadata => {
|
||||
const migratedMetadata: Metadata = { ...meta };
|
||||
|
||||
if (!migration.appKeyPrefix) {
|
||||
return migratedMetadata;
|
||||
}
|
||||
|
||||
const { oldPrefix, newPrefix } = migration.appKeyPrefix;
|
||||
|
||||
const oldAppMetadata = getAppMetadataFrom(meta, oldPrefix);
|
||||
const newAppMetadata = getAppMetadataFrom(meta, newPrefix);
|
||||
|
||||
const missingMetadataKeys = Object.keys(oldAppMetadata).filter((key) => !Object.keys(newAppMetadata).includes(key));
|
||||
|
||||
const isMissingOldMetadata = missingMetadataKeys.length;
|
||||
if (isMissingOldMetadata) {
|
||||
missingMetadataKeys.forEach((oldKey) => {
|
||||
const keyWithNewPrefix = oldKey.replace(oldPrefix, newPrefix);
|
||||
migratedMetadata[keyWithNewPrefix] = oldAppMetadata[oldKey];
|
||||
});
|
||||
}
|
||||
|
||||
return migratedMetadata;
|
||||
};
|
||||
|
||||
const applyMetadataValueMigration = (meta: Metadata, migration: IMetadataMigration, appKeyPrefix: string): Metadata => {
|
||||
const migratedMetadata: Metadata = { ...meta };
|
||||
|
||||
if (!migration.values) {
|
||||
return migratedMetadata;
|
||||
}
|
||||
|
||||
const appMetadata = getAppMetadataFrom(meta, appKeyPrefix);
|
||||
const metadataValueChanges = migration.values;
|
||||
|
||||
metadataValueChanges.forEach(({ key, oldValue, newValue }) => {
|
||||
const migrateValue = (metaKey: string) => {
|
||||
if (meta[metaKey] === oldValue) {
|
||||
migratedMetadata[metaKey] = newValue;
|
||||
}
|
||||
};
|
||||
|
||||
const migrateValueOfAllAppKeys = key === undefined;
|
||||
if (migrateValueOfAllAppKeys) {
|
||||
Object.keys(appMetadata).forEach((metaKey) => {
|
||||
migrateValue(metaKey);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doesMetadataKeyExistIn(meta, key, appKeyPrefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
migrateValue(getMetadataKey(key, appKeyPrefix));
|
||||
});
|
||||
|
||||
return migratedMetadata;
|
||||
};
|
||||
|
||||
const applyMetadataKeyMigration = (meta: Metadata, migration: IMetadataMigration): Metadata => {
|
||||
const migratedMetadata: Metadata = { ...meta };
|
||||
|
||||
if (!migration.keys) {
|
||||
return migratedMetadata;
|
||||
}
|
||||
|
||||
const metadataKeyChanges = migration.keys;
|
||||
|
||||
metadataKeyChanges.forEach(({ oldKey, newKey }) => {
|
||||
if (!doesMetadataKeyExistIn(meta, oldKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doesMetadataKeyExistIn(meta, newKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
migratedMetadata[getMetadataKey(newKey)] = meta[getMetadataKey(oldKey)];
|
||||
});
|
||||
|
||||
return migratedMetadata;
|
||||
};
|
||||
|
||||
const applyMetadataMigrations = (meta?: Metadata): Metadata | undefined => {
|
||||
if (!meta) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const migrationToMetadata: [number, Metadata][] = [[0, meta]];
|
||||
|
||||
migrations.forEach((migration, index) => {
|
||||
const migrationId = index + 1;
|
||||
const metadataToMigrate = migrationToMetadata[migrationId - 1][1];
|
||||
const appKeyPrefixMigrated = applyAppKeyPrefixMigration(metadataToMigrate, migration);
|
||||
const metadataValuesMigrated = applyMetadataValueMigration(
|
||||
appKeyPrefixMigrated,
|
||||
migration,
|
||||
getAppKeyPrefixForMigration(migrationId),
|
||||
);
|
||||
const metadataKeysMigrated = applyMetadataKeyMigration(metadataValuesMigrated, migration);
|
||||
|
||||
migrationToMetadata.push([migrationId, metadataKeysMigrated]);
|
||||
});
|
||||
|
||||
const appliedMigration = migrationToMetadata.length > 1;
|
||||
if (!appliedMigration) {
|
||||
return { ...meta };
|
||||
}
|
||||
|
||||
return migrationToMetadata.pop()![1];
|
||||
};
|
||||
|
||||
export const getMetadataValueFrom = <Key extends AppMetadataKeys, Value extends AllowedMetadataValueTypes>(
|
||||
{ meta }: MetadataHolder,
|
||||
key: Key,
|
||||
defaultValue?: Value,
|
||||
applyMigrations: boolean = true,
|
||||
): Value | undefined => {
|
||||
const metadata = applyMigrations ? applyMetadataMigrations(meta) : meta;
|
||||
|
||||
if (metadata === undefined || !doesMetadataKeyExistIn(metadata, key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return convertValueFromMetadata(metadata[getMetadataKey(key)]);
|
||||
};
|
||||
|
||||
export const getMetadataFrom = <METADATA extends Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>>(
|
||||
{ meta }: MetadataHolder,
|
||||
metadataWithDefaultValues: METADATA,
|
||||
applyMigrations?: boolean,
|
||||
): METADATA => {
|
||||
const appMetadata = {} as METADATA;
|
||||
|
||||
Object.entries(metadataWithDefaultValues).forEach(([key, defaultValue]) => {
|
||||
appMetadata[key as AppMetadataKeys] = getMetadataValueFrom(
|
||||
{ meta },
|
||||
key as AppMetadataKeys,
|
||||
defaultValue,
|
||||
applyMigrations,
|
||||
);
|
||||
});
|
||||
|
||||
return appMetadata;
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const wrapMetadataWithMetaKey = (wrap: boolean, metadata: Metadata): MetadataHolder => {
|
||||
if (wrap) {
|
||||
return {
|
||||
meta: {
|
||||
...metadata,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
};
|
||||
};
|
||||
|
||||
type MetadataHolderType = 'manga' | 'chapter' | 'category' | 'global';
|
||||
|
||||
export const requestUpdateMetadataValue = async (
|
||||
metadataHolder: GqlMetaHolder,
|
||||
holderType: MetadataHolderType,
|
||||
key: AppMetadataKeys,
|
||||
value: AllowedMetadataValueTypes,
|
||||
): Promise<void> => {
|
||||
const metadataKey = getMetadataKey(key);
|
||||
|
||||
switch (holderType) {
|
||||
case 'category':
|
||||
await requestManager.setCategoryMeta((metadataHolder as TCategory).id, metadataKey, value).response;
|
||||
break;
|
||||
case 'chapter':
|
||||
await requestManager.setChapterMeta((metadataHolder as TChapter).id, metadataKey, value).response;
|
||||
break;
|
||||
case 'global':
|
||||
await requestManager.setGlobalMetadata(metadataKey, value).response;
|
||||
break;
|
||||
case 'manga':
|
||||
await requestManager.setMangaMeta((metadataHolder as TManga).id, metadataKey, value).response;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`requestUpdateMetadataValue: unknown holderType "${holderType}"`);
|
||||
}
|
||||
};
|
||||
|
||||
export const requestUpdateMetadata = async (
|
||||
metadataHolder: GqlMetaHolder,
|
||||
holderType: MetadataHolderType,
|
||||
keysToValues: MetadataKeyValuePair[],
|
||||
): Promise<void[]> =>
|
||||
Promise.all(keysToValues.map(([key, value]) => requestUpdateMetadataValue(metadataHolder, holderType, key, value)));
|
||||
|
||||
export const requestUpdateServerMetadata = async (keysToValues: MetadataKeyValuePair[]): Promise<void[]> =>
|
||||
requestUpdateMetadata({}, 'global', keysToValues);
|
||||
|
||||
export const requestUpdateMangaMetadata = async (
|
||||
manga: TManga,
|
||||
keysToValues: MetadataKeyValuePair[],
|
||||
): Promise<void[]> => requestUpdateMetadata(manga, 'manga', keysToValues);
|
||||
|
||||
export const requestUpdateChapterMetadata = async (
|
||||
chapter: TChapter,
|
||||
keysToValues: MetadataKeyValuePair[],
|
||||
): Promise<void[]> => requestUpdateMetadata(chapter, 'chapter', keysToValues);
|
||||
|
||||
export const requestUpdateCategoryMetadata = async (
|
||||
category: TCategory,
|
||||
keysToValues: MetadataKeyValuePair[],
|
||||
): Promise<void[]> => requestUpdateMetadata(category, 'category', keysToValues);
|
||||
104
src/lib/metadata/metadataServerSettings.ts
Normal file
104
src/lib/metadata/metadataServerSettings.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import {
|
||||
AllowedMetadataValueTypes,
|
||||
AppMetadataKeys,
|
||||
Metadata,
|
||||
MetadataServerSettingKeys,
|
||||
MetadataServerSettings,
|
||||
} from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { convertFromGqlMeta, getMetadataFrom, requestUpdateServerMetadata } from '@/lib/metadata/metadata.ts';
|
||||
import { jsonSaveParse } from '@/util/HelperFunctions.ts';
|
||||
import { DEFAULT_DEVICE } from '@/util/device.ts';
|
||||
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
|
||||
|
||||
export const getDefaultSettings = (): MetadataServerSettings => ({
|
||||
// downloads
|
||||
deleteChaptersManuallyMarkedRead: false,
|
||||
deleteChaptersWhileReading: 0,
|
||||
deleteChaptersWithBookmark: false,
|
||||
downloadAheadLimit: 0,
|
||||
|
||||
// library
|
||||
showAddToLibraryCategorySelectDialog: true,
|
||||
ignoreFilters: false,
|
||||
removeMangaFromCategories: false,
|
||||
|
||||
// client
|
||||
devices: [DEFAULT_DEVICE],
|
||||
|
||||
// migration
|
||||
includeChapters: true,
|
||||
includeCategories: true,
|
||||
deleteChapters: true,
|
||||
});
|
||||
|
||||
export const convertSettingsToMetadata = (
|
||||
settings: Partial<MetadataServerSettings>,
|
||||
): Metadata<string, AllowedMetadataValueTypes> => ({
|
||||
...settings,
|
||||
devices: JSON.stringify(settings.devices),
|
||||
});
|
||||
|
||||
export const convertMetadataToSettings = (
|
||||
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
||||
): MetadataServerSettings =>
|
||||
({
|
||||
...getDefaultSettings(),
|
||||
...(metadata as unknown as MetadataServerSettings),
|
||||
devices: jsonSaveParse<string[]>((metadata.devices as string) ?? '') ?? getDefaultSettings().devices,
|
||||
}) satisfies MetadataServerSettings;
|
||||
|
||||
const getMetadataServerSettingsWithDefaultFallback = (
|
||||
meta?: Metadata,
|
||||
defaultSettings: MetadataServerSettings = getDefaultSettings(),
|
||||
applyMetadataMigration: boolean = true,
|
||||
): MetadataServerSettings =>
|
||||
convertMetadataToSettings(
|
||||
getMetadataFrom({ meta }, convertSettingsToMetadata(defaultSettings), applyMetadataMigration),
|
||||
);
|
||||
export const useMetadataServerSettings = (): {
|
||||
metadata?: Metadata;
|
||||
settings: MetadataServerSettings;
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, loading } = requestManager.useGetGlobalMeta();
|
||||
const metadata = convertFromGqlMeta(data?.metas.nodes);
|
||||
const settings = getMetadataServerSettingsWithDefaultFallback(metadata);
|
||||
|
||||
return { metadata, settings, loading };
|
||||
};
|
||||
|
||||
export const getMetadataServerSettings = async (): Promise<MetadataServerSettings> => {
|
||||
const { data, error } = await requestManager.getGlobalMeta().response;
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const metadata = convertFromGqlMeta(data?.metas.nodes);
|
||||
return getMetadataServerSettingsWithDefaultFallback(metadata);
|
||||
};
|
||||
|
||||
export const updateMetadataServerSettings = async <
|
||||
Settings extends MetadataServerSettingKeys = MetadataServerSettingKeys,
|
||||
Setting extends Settings = Settings,
|
||||
>(
|
||||
setting: Setting,
|
||||
value: MetadataServerSettings[Setting],
|
||||
): Promise<void[]> =>
|
||||
requestUpdateServerMetadata([[setting, convertSettingsToMetadata({ [setting]: value })[setting]]]);
|
||||
|
||||
export const createUpdateMetadataServerSettings =
|
||||
<Settings extends MetadataServerSettingKeys>(
|
||||
handleError: (error: any) => void = defaultPromiseErrorHandler('createUpdateMetadataServerSettings'),
|
||||
): ((...args: Parameters<typeof updateMetadataServerSettings<Settings>>) => Promise<void | void[]>) =>
|
||||
(setting, value) =>
|
||||
updateMetadataServerSettings(setting, value).catch(handleError);
|
||||
115
src/lib/metadata/readerSettings.ts
Normal file
115
src/lib/metadata/readerSettings.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import { Metadata, IReaderSettings, MetadataKeyValuePair, GqlMetaHolder, TManga } from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import {
|
||||
convertFromGqlMeta,
|
||||
getMetadataFrom,
|
||||
requestUpdateMangaMetadata,
|
||||
requestUpdateServerMetadata,
|
||||
} from '@/lib/metadata/metadata.ts';
|
||||
import { MetaType } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
type UndefinedReaderSettings = {
|
||||
[setting in keyof IReaderSettings]: IReaderSettings[setting] | undefined;
|
||||
};
|
||||
|
||||
export const getDefaultSettings = (): IReaderSettings => ({
|
||||
staticNav: false,
|
||||
showPageNumber: true,
|
||||
loadNextOnEnding: false,
|
||||
skipDupChapters: true,
|
||||
fitPageToWindow: true,
|
||||
scalePage: false,
|
||||
readerType: 'ContinuesVertical',
|
||||
offsetFirstPage: false,
|
||||
readerWidth: 50,
|
||||
});
|
||||
|
||||
const getReaderSettingsWithDefaultValueFallback = <DefaultSettings extends IReaderSettings | UndefinedReaderSettings>(
|
||||
meta?: Metadata,
|
||||
defaultSettings: DefaultSettings = getDefaultSettings() as DefaultSettings,
|
||||
applyMetadataMigration: boolean = true,
|
||||
): DefaultSettings => getMetadataFrom({ meta }, defaultSettings, applyMetadataMigration);
|
||||
|
||||
export const getReaderSettingsFromMetadata = (
|
||||
meta?: Metadata,
|
||||
defaultSettings?: IReaderSettings,
|
||||
applyMetadataMigration?: boolean,
|
||||
): IReaderSettings => getReaderSettingsWithDefaultValueFallback(meta, defaultSettings, applyMetadataMigration);
|
||||
|
||||
export const getReaderSettingsFor = (
|
||||
{ meta }: GqlMetaHolder = {},
|
||||
defaultSettings?: IReaderSettings,
|
||||
applyMetadataMigration?: boolean,
|
||||
): IReaderSettings => getReaderSettingsFromMetadata(convertFromGqlMeta(meta), defaultSettings, applyMetadataMigration);
|
||||
|
||||
export const useDefaultReaderSettings = (): {
|
||||
metadata?: Metadata;
|
||||
settings: IReaderSettings;
|
||||
loading: boolean;
|
||||
} => {
|
||||
const { data, loading } = requestManager.useGetGlobalMeta();
|
||||
const metadata = convertFromGqlMeta(data?.metas.nodes);
|
||||
const settings = getReaderSettingsWithDefaultValueFallback<IReaderSettings>(metadata);
|
||||
|
||||
return { metadata, settings, loading };
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves all missing reader settings from the metadata to the server
|
||||
*
|
||||
* @param metadataHolder
|
||||
* @param metadataHolderType
|
||||
* @param defaultSettings
|
||||
*/
|
||||
export const checkAndHandleMissingStoredReaderSettings = async (
|
||||
metadataHolder: Required<GqlMetaHolder> | MetaType[],
|
||||
metadataHolderType: 'manga' | 'server',
|
||||
defaultSettings: IReaderSettings,
|
||||
): Promise<void | void[]> => {
|
||||
const getMeta = () => (Array.isArray(metadataHolder) ? metadataHolder : metadataHolder.meta);
|
||||
const meta = convertFromGqlMeta(getMeta())!;
|
||||
const settingsToCheck = getReaderSettingsWithDefaultValueFallback(
|
||||
meta,
|
||||
{
|
||||
staticNav: undefined,
|
||||
showPageNumber: undefined,
|
||||
loadNextOnEnding: undefined,
|
||||
skipDupChapters: undefined,
|
||||
fitPageToWindow: undefined,
|
||||
scalePage: undefined,
|
||||
readerType: undefined,
|
||||
offsetFirstPage: undefined,
|
||||
readerWidth: undefined,
|
||||
},
|
||||
false,
|
||||
);
|
||||
const newSettings = getReaderSettingsFor({ meta: getMeta() }, defaultSettings);
|
||||
|
||||
const undefinedSettings = Object.entries(settingsToCheck).filter((setting) => setting[1] === undefined);
|
||||
|
||||
const settingsToUpdate: MetadataKeyValuePair[] = [];
|
||||
undefinedSettings.forEach((setting) => {
|
||||
const key = setting[0] as keyof IReaderSettings;
|
||||
|
||||
settingsToUpdate.push([key, newSettings[key]]);
|
||||
});
|
||||
|
||||
if (!undefinedSettings.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadataHolderType === 'manga') {
|
||||
await requestUpdateMangaMetadata(metadataHolder as TManga, settingsToUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
await requestUpdateServerMetadata(settingsToUpdate);
|
||||
};
|
||||
Reference in New Issue
Block a user