diff --git a/src/components/navbar/ReaderNavBar.tsx b/src/components/navbar/ReaderNavBar.tsx
index 71e59d4b..b07a98c9 100644
--- a/src/components/navbar/ReaderNavBar.tsx
+++ b/src/components/navbar/ReaderNavBar.tsx
@@ -256,7 +256,7 @@ export default function ReaderNavBar(props: IProps) {
setSettingValue={updateSettingValue}
staticNav={settings.staticNav}
showPageNumber={settings.showPageNumber}
- loadNextonEnding={settings.loadNextonEnding}
+ loadNextOnEnding={settings.loadNextOnEnding}
readerType={settings.readerType}
/>
diff --git a/src/components/reader/ReaderSettingsOptions.tsx b/src/components/reader/ReaderSettingsOptions.tsx
index 12197b83..8ebdc95b 100644
--- a/src/components/reader/ReaderSettingsOptions.tsx
+++ b/src/components/reader/ReaderSettingsOptions.tsx
@@ -19,7 +19,7 @@ interface IProps extends IReaderSettings {
}
export default function ReaderSettingsOptions({
- staticNav, loadNextonEnding, readerType, showPageNumber, setSettingValue,
+ staticNav, loadNextOnEnding, readerType, showPageNumber, setSettingValue,
}: IProps) {
return (
<>
@@ -49,8 +49,8 @@ export default function ReaderSettingsOptions({
setSettingValue('loadNextonEnding', e.target.checked)}
+ checked={loadNextOnEnding}
+ onChange={(e) => setSettingValue('loadNextOnEnding', e.target.checked)}
/>
diff --git a/src/components/reader/pager/DoublePagedPager.tsx b/src/components/reader/pager/DoublePagedPager.tsx
index 8dab412f..ade3fd6a 100644
--- a/src/components/reader/pager/DoublePagedPager.tsx
+++ b/src/components/reader/pager/DoublePagedPager.tsx
@@ -102,7 +102,7 @@ export default function DoublePagedPager(props: IReaderProps) {
if (curPage < pages.length - 1) {
const nextCurPage = curPage + pagesDisplayed.current;
setCurPage((nextCurPage >= pages.length) ? pages.length - 1 : nextCurPage);
- } else if (settings.loadNextonEnding) {
+ } else if (settings.loadNextOnEnding) {
nextChapter();
}
}
diff --git a/src/components/reader/pager/HorizontalPager.tsx b/src/components/reader/pager/HorizontalPager.tsx
index e6fe387b..8fa78004 100644
--- a/src/components/reader/pager/HorizontalPager.tsx
+++ b/src/components/reader/pager/HorizontalPager.tsx
@@ -42,7 +42,7 @@ export default function HorizontalPager(props: IReaderProps) {
if (curPage < pages.length - 1) {
pagesRef.current[curPage + 1]?.scrollIntoView({ inline: 'center' });
setCurPage((page) => page + 1);
- } else if (settings.loadNextonEnding) {
+ } else if (settings.loadNextOnEnding) {
nextChapter();
}
}
@@ -126,7 +126,7 @@ export default function HorizontalPager(props: IReaderProps) {
}, [selfRef]);
useEffect(() => {
- if (settings.loadNextonEnding) {
+ if (settings.loadNextOnEnding) {
document.addEventListener('scroll', handleLoadNextonEnding);
}
selfRef.current?.addEventListener('mousedown', clickControl);
diff --git a/src/components/reader/pager/PagedPager.tsx b/src/components/reader/pager/PagedPager.tsx
index 212ccf8e..74401ab7 100644
--- a/src/components/reader/pager/PagedPager.tsx
+++ b/src/components/reader/pager/PagedPager.tsx
@@ -24,7 +24,7 @@ export default function PagedReader(props: IReaderProps) {
function nextPage() {
if (curPage < pages.length - 1) {
changePage(curPage + 1);
- } else if (settings.loadNextonEnding) {
+ } else if (settings.loadNextOnEnding) {
nextChapter();
}
}
diff --git a/src/components/reader/pager/VerticalPager.tsx b/src/components/reader/pager/VerticalPager.tsx
index b4945c54..7f2e01b9 100644
--- a/src/components/reader/pager/VerticalPager.tsx
+++ b/src/components/reader/pager/VerticalPager.tsx
@@ -55,7 +55,7 @@ export default function VerticalPager(props: IReaderProps) {
setCurPage(currentPageRef.current);
// Go to next chapter if configured to and at bottom
- if (settings.loadNextonEnding) {
+ if (settings.loadNextOnEnding) {
nextChapter();
}
} else {
@@ -72,7 +72,7 @@ export default function VerticalPager(props: IReaderProps) {
return () => {
window.removeEventListener('scroll', handleScroll);
};
- }, [settings.loadNextonEnding]);
+ }, [settings.loadNextOnEnding]);
const go = useCallback((direction: 'up' | 'down') => {
if (direction === 'down' && isAtBottom()) {
diff --git a/src/screens/settings/DefaultReaderSettings.tsx b/src/screens/settings/DefaultReaderSettings.tsx
index d8110a70..4664c41d 100644
--- a/src/screens/settings/DefaultReaderSettings.tsx
+++ b/src/screens/settings/DefaultReaderSettings.tsx
@@ -53,7 +53,7 @@ export default function DefaultReaderSettings() {
setSettingValue={setSettingValue}
staticNav={settings.staticNav}
showPageNumber={settings.showPageNumber}
- loadNextonEnding={settings.loadNextonEnding}
+ loadNextOnEnding={settings.loadNextOnEnding}
readerType={settings.readerType}
/>
);
diff --git a/src/typings.d.ts b/src/typings.d.ts
index 3206cce4..ed1871eb 100644
--- a/src/typings.d.ts
+++ b/src/typings.d.ts
@@ -54,6 +54,11 @@ interface IState {
index: number
}
+interface IMetadataMigration {
+ appKeyPrefix?: { oldPrefix: string, newPrefix: string }
+ keys?: { oldKey: string, newKey: string }[]
+}
+
interface IMetadata {
[key: string]: VALUES;
}
@@ -168,7 +173,7 @@ type ReaderType =
interface IReaderSettings{
staticNav: boolean
showPageNumber: boolean
- loadNextonEnding: boolean
+ loadNextOnEnding: boolean
readerType: ReaderType
}
diff --git a/src/util/metadata.ts b/src/util/metadata.ts
index 04996030..b102c917 100644
--- a/src/util/metadata.ts
+++ b/src/util/metadata.ts
@@ -11,7 +11,21 @@ import client from './client';
const APP_METADATA_KEY_PREFIX = 'webUI_';
-const getMetadataKey = (key: string) => `${APP_METADATA_KEY_PREFIX}${key}`;
+const migrations: IMetadataMigration[] = [
+ {
+ keys: [
+ { oldKey: 'loadNextonEnding', newKey: 'loadNextOnEnding' },
+ ],
+ },
+];
+
+const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => `${appPrefix}${key}`;
+
+const doesMetadataKeyExistIn = (
+ meta: IMetadata | undefined,
+ key: string,
+ appPrefix?: string,
+): boolean => Object.prototype.hasOwnProperty.call(meta ?? {}, getMetadataKey(key, appPrefix));
const convertValueFromMetadata = <
T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes,
@@ -33,31 +47,121 @@ const convertValueFromMetadata = <
return value as T;
};
+const getAppMetadataFrom = (
+ meta: IMetadata,
+ appPrefix: string = APP_METADATA_KEY_PREFIX,
+): IMetadata => {
+ const appMetadata: IMetadata = {};
+
+ Object.entries(meta).forEach(([key, value]) => {
+ if (key.startsWith(appPrefix)) {
+ appMetadata[key] = value;
+ }
+ });
+
+ return appMetadata;
+};
+
+const applyAppKeyPrefixMigration = (meta: IMetadata, migration: IMetadataMigration): IMetadata => {
+ const migratedMetadata: IMetadata = { ...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 applyMetadataKeyMigration = (meta: IMetadata, migration: IMetadataMigration): IMetadata => {
+ const migratedMetadata: IMetadata = { ...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?: IMetadata): IMetadata | undefined => {
+ if (!meta) {
+ return undefined;
+ }
+
+ const migrationToMetadata: [number, IMetadata][] = [[0, meta]];
+
+ migrations.forEach((migration, index) => {
+ const migrationId = index + 1;
+ const metadataToMigrate = migrationToMetadata[migrationId - 1][1];
+ const appKeyPrefixMigrated = applyAppKeyPrefixMigration(metadataToMigrate, migration);
+ const metadataKeysMigrated = applyMetadataKeyMigration(appKeyPrefixMigrated, migration);
+
+ migrationToMetadata.push([migrationId, metadataKeysMigrated]);
+ });
+
+ const appliedMigration = migrationToMetadata.length > 1;
+ if (!appliedMigration) {
+ return { ...meta };
+ }
+
+ return migrationToMetadata.pop()![1];
+};
+
export const getMetadataValueFrom = <
T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes,
>(
{ meta }: IMetadataHolder,
key: AppMetadataKeys,
defaultValue?: T,
+ applyMigrations: boolean = true,
): T | undefined => {
- const metadataKey = getMetadataKey(key);
+ const metadata = applyMigrations ? applyMetadataMigrations(meta) : meta;
- const isMissingKey = !Object.prototype.hasOwnProperty.call(meta ?? {}, metadataKey);
- if (meta === undefined || isMissingKey) {
+ if (metadata === undefined || !doesMetadataKeyExistIn(metadata, key)) {
return defaultValue;
}
- return convertValueFromMetadata(meta[metadataKey]);
+ return convertValueFromMetadata(metadata[getMetadataKey(key)]);
};
export const getMetadataFrom = (
{ meta }: IMetadataHolder,
keysToDefaultValues: MetadataKeyValuePair[],
+ applyMigrations?: boolean,
): IMetadata => {
const appMetadata: IMetadata = {};
keysToDefaultValues.forEach(([key, defaultValue]) => {
- appMetadata[key] = getMetadataValueFrom({ meta }, key, defaultValue);
+ appMetadata[key] = getMetadataValueFrom({ meta }, key, defaultValue, applyMigrations);
});
return appMetadata;
diff --git a/src/util/readerSettings.ts b/src/util/readerSettings.ts
index 37004b9d..db796498 100644
--- a/src/util/readerSettings.ts
+++ b/src/util/readerSettings.ts
@@ -13,31 +13,35 @@ export const getDefaultSettings = (forceUndefined: boolean = false) => ({
staticNav: forceUndefined ? undefined : false,
showPageNumber: forceUndefined ? undefined : true,
continuesPageGap: forceUndefined ? undefined : false,
- loadNextonEnding: forceUndefined ? undefined : false,
+ loadNextOnEnding: forceUndefined ? undefined : false,
readerType: forceUndefined ? undefined : 'ContinuesVertical',
} as IReaderSettings);
const getReaderSettingsWithDefaultValueFallback = (
meta?: IMetadata,
defaultSettings?: IReaderSettings,
+ applyMetadataMigration: boolean = true,
): IReaderSettings => ({
...getMetadataFrom(
{ meta },
Object.entries(defaultSettings ?? getDefaultSettings()) as MetadataKeyValuePair[],
+ applyMetadataMigration,
) as unknown as IReaderSettings,
});
export const getReaderSettingsFromMetadata = (
meta?: IMetadata,
defaultSettings?: IReaderSettings,
+ applyMetadataMigration?: boolean,
): IReaderSettings => ({
- ...getReaderSettingsWithDefaultValueFallback(meta, defaultSettings),
+ ...getReaderSettingsWithDefaultValueFallback(meta, defaultSettings, applyMetadataMigration),
});
export const getReaderSettingsFor = (
{ meta }: IMetadataHolder,
defaultSettings?: IReaderSettings,
-): IReaderSettings => getReaderSettingsFromMetadata(meta, defaultSettings);
+ applyMetadataMigration?: boolean,
+): IReaderSettings => getReaderSettingsFromMetadata(meta, defaultSettings, applyMetadataMigration);
export const useDefaultReaderSettings = (): {
metadata?: IMetadata,
@@ -63,7 +67,7 @@ export const checkAndHandleMissingStoredReaderSettings = async (
defaultSettings: IReaderSettings,
): Promise => {
const meta = metadataHolder.meta ?? metadataHolder as IMetadata;
- const settingsToCheck = getReaderSettingsFor({ meta }, getDefaultSettings(true));
+ const settingsToCheck = getReaderSettingsFor({ meta }, getDefaultSettings(true), false);
const newSettings = getReaderSettingsFor({ meta }, defaultSettings);
const undefinedSettings = Object.entries(settingsToCheck)