Reader settings
This commit is contained in:
@@ -344,6 +344,11 @@
|
|||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
"submit": "Submit"
|
"submit": "Submit"
|
||||||
},
|
},
|
||||||
|
"colors": {
|
||||||
|
"black": "Black",
|
||||||
|
"gray": "Gray",
|
||||||
|
"white": "White"
|
||||||
|
},
|
||||||
"date": {
|
"date": {
|
||||||
"label": {
|
"label": {
|
||||||
"day_one": "Day",
|
"day_one": "Day",
|
||||||
@@ -389,22 +394,30 @@
|
|||||||
"label": {
|
"label": {
|
||||||
"advanced": "Advanced",
|
"advanced": "Advanced",
|
||||||
"are_you_sure": "Are you sure?",
|
"are_you_sure": "Are you sure?",
|
||||||
|
"auto": "Auto",
|
||||||
|
"both": "Both",
|
||||||
|
"bottom": "Bottom",
|
||||||
"browse": "Browse",
|
"browse": "Browse",
|
||||||
"client": "Client",
|
"client": "Client",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"copied_clipboard": "Copied to clipboard",
|
"copied_clipboard": "Copied to clipboard",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
|
"desktop": "Desktop",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"display": "Display",
|
"display": "Display",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
|
"hidden": "Hidden",
|
||||||
|
"horizontal": "Horizontal",
|
||||||
|
"left": "Left",
|
||||||
"links": "Links",
|
"links": "Links",
|
||||||
"load_in_progress": "Still loading required data…",
|
"load_in_progress": "Still loading required data…",
|
||||||
"loading": "Loading…",
|
"loading": "Loading…",
|
||||||
"logged_in": "Logged in",
|
"logged_in": "Logged in",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
|
"mobile": "Mobile",
|
||||||
"never": "Never",
|
"never": "Never",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
@@ -413,12 +426,15 @@
|
|||||||
"password": "Password",
|
"password": "Password",
|
||||||
"placeholder": "Placeholder",
|
"placeholder": "Placeholder",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
|
"right": "Right",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"sort": "Sort",
|
"sort": "Sort",
|
||||||
|
"standard": "Standard",
|
||||||
"started": "Started",
|
"started": "Started",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"username": "Username"
|
"username": "Username",
|
||||||
|
"vertical": "Vertical"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"label": {
|
"label": {
|
||||||
@@ -453,6 +469,9 @@
|
|||||||
"value": "{{count}} $t(global.time.seconds.second, lowercase)"
|
"value": "{{count}} $t(global.time.seconds.second, lowercase)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"unit": {
|
||||||
|
"px": "px"
|
||||||
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"label": {
|
"label": {
|
||||||
"available": "Update available",
|
"available": "Update available",
|
||||||
@@ -472,7 +491,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"value": "{{value}}{{unit}}"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"action": {
|
"action": {
|
||||||
@@ -779,16 +799,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"background_color": "Background color",
|
||||||
|
"custom_filter": {
|
||||||
|
"brightness": {
|
||||||
|
"title": "Custom brightness",
|
||||||
|
"value": "$t(reader.settings.custom_filter.brightness.title)"
|
||||||
|
},
|
||||||
|
"contrast": {
|
||||||
|
"title": "Custom contrast",
|
||||||
|
"value": "$t(reader.settings.custom_filter.contrast.title)"
|
||||||
|
},
|
||||||
|
"grayscale": "Grayscale",
|
||||||
|
"hue": {
|
||||||
|
"title": "Custom hue",
|
||||||
|
"value": "$t(reader.settings.custom_filter.hue.title)"
|
||||||
|
},
|
||||||
|
"invert": "Invert",
|
||||||
|
"rgba": {
|
||||||
|
"alpha": "Alpha",
|
||||||
|
"blue": "Blue",
|
||||||
|
"green": "Green",
|
||||||
|
"red": "Red",
|
||||||
|
"title": "Custom color filters"
|
||||||
|
},
|
||||||
|
"saturate": {
|
||||||
|
"title": "Custom saturate",
|
||||||
|
"value": "$t(reader.settings.custom_filter.saturate.title)"
|
||||||
|
},
|
||||||
|
"sepia": "Sepia",
|
||||||
|
"title": "Custom filter"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"label": {
|
"label": {
|
||||||
"failed_to_save_default_settings": "Could not save the default reader settings to the server",
|
"failed_to_save_default_settings": "Could not save the default reader settings to the server",
|
||||||
"failed_to_save_settings": "Could not save the reader settings to the server"
|
"failed_to_save_settings": "Could not save the reader settings to the server"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"exit_mode": "Open page on exit",
|
||||||
"label": {
|
"label": {
|
||||||
|
"behaviour": "Behaviour",
|
||||||
"fit_page_to_window": "Fit page to window",
|
"fit_page_to_window": "Fit page to window",
|
||||||
|
"layout": "Layout",
|
||||||
|
"limit_reader_width": "Limit reader width",
|
||||||
"load_next_chapter": "Load next chapter at ending",
|
"load_next_chapter": "Load next chapter at ending",
|
||||||
"offset_double_spread": "Offset double spreads",
|
"offset_double_spread": "Offset double spreads",
|
||||||
|
"page_gap": "Page gap",
|
||||||
"reader_type": "Reader type",
|
"reader_type": "Reader type",
|
||||||
"reader_width": "Reader width",
|
"reader_width": "Reader width",
|
||||||
"reading_direction": "Reading direction",
|
"reading_direction": "Reading direction",
|
||||||
@@ -798,13 +853,20 @@
|
|||||||
"skip_dup_chapters": "Skip duplicate chapters",
|
"skip_dup_chapters": "Skip duplicate chapters",
|
||||||
"static_navigation": "Static navigation"
|
"static_navigation": "Static navigation"
|
||||||
},
|
},
|
||||||
|
"overlay_mode": "Overlay mode",
|
||||||
"page_scale": {
|
"page_scale": {
|
||||||
"height": "Fit height",
|
"height": "Fit height",
|
||||||
"original": "Original size",
|
"original": "Original size",
|
||||||
"screen": "Fit screen",
|
"screen": "Fit screen",
|
||||||
"stretch": "Stretch small pages",
|
"stretch": "Stretch small pages",
|
||||||
|
"title": "Scale type",
|
||||||
"width": "Fit width"
|
"width": "Fit width"
|
||||||
},
|
},
|
||||||
|
"progress_bar": {
|
||||||
|
"position": "Progress bar position",
|
||||||
|
"size": "Progress bar size",
|
||||||
|
"style": "Progress bar style"
|
||||||
|
},
|
||||||
"reader_type": {
|
"reader_type": {
|
||||||
"label": {
|
"label": {
|
||||||
"continuous_horizontal": "Continuous horizontal",
|
"continuous_horizontal": "Continuous horizontal",
|
||||||
@@ -818,6 +880,14 @@
|
|||||||
"ltr": "Left to right",
|
"ltr": "Left to right",
|
||||||
"rtl": "Right to left"
|
"rtl": "Right to left"
|
||||||
},
|
},
|
||||||
|
"tap_zones": {
|
||||||
|
"edge": "Edge",
|
||||||
|
"invert": "Invert tap zones",
|
||||||
|
"kindle": "Kindle",
|
||||||
|
"l_shape": "L-Shape",
|
||||||
|
"right_left": "Right and left",
|
||||||
|
"title": "Tap zones"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"default_reader_settings": "Default Reader Settings",
|
"default_reader_settings": "Default Reader Settings",
|
||||||
"reader_settings": "Reader Settings"
|
"reader_settings": "Reader Settings"
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ const { LibraryDuplicates } = loadable(
|
|||||||
lazyLoadFallback,
|
lazyLoadFallback,
|
||||||
);
|
);
|
||||||
const { Appearance } = loadable(() => import('@/modules/settings/screens/Appearance.tsx'), lazyLoadFallback);
|
const { Appearance } = loadable(() => import('@/modules/settings/screens/Appearance.tsx'), lazyLoadFallback);
|
||||||
|
const { DefaultReaderSettings } = loadable(
|
||||||
|
() => import('@/modules/reader/screens/DefaultReaderSettings.tsx'),
|
||||||
|
lazyLoadFallback,
|
||||||
|
);
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
// Adds messages only in a dev environment
|
// Adds messages only in a dev environment
|
||||||
@@ -114,7 +118,7 @@ const MainApp = () => {
|
|||||||
<Route index element={<Settings />} />
|
<Route index element={<Settings />} />
|
||||||
<Route path="about" element={<About />} />
|
<Route path="about" element={<About />} />
|
||||||
<Route path="categories" element={<CategorySettings />} />
|
<Route path="categories" element={<CategorySettings />} />
|
||||||
<Route path="defaultReaderSettings" element={null} />
|
<Route path="defaultReaderSettings" element={<DefaultReaderSettings />} />
|
||||||
<Route path="librarySettings">
|
<Route path="librarySettings">
|
||||||
<Route index element={<LibrarySettings />} />
|
<Route index element={<LibrarySettings />} />
|
||||||
<Route path="duplicates" element={<LibraryDuplicates />} />
|
<Route path="duplicates" element={<LibraryDuplicates />} />
|
||||||
|
|||||||
6
src/UtilTypes.d.ts
vendored
6
src/UtilTypes.d.ts
vendored
@@ -26,6 +26,10 @@ type PropertiesNever<T> = { [key in keyof T]?: never };
|
|||||||
|
|
||||||
type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never;
|
type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never;
|
||||||
|
|
||||||
type TupleUnion<U extends string, R extends any[] = []> = {
|
type TupleUnion<U extends string | number | symbol, R extends any[] = []> = {
|
||||||
[S in U]: Exclude<U, S> extends never ? [...R, S] : TupleUnion<Exclude<U, S>, [...R, S]>;
|
[S in U]: Exclude<U, S> extends never ? [...R, S] : TupleUnion<Exclude<U, S>, [...R, S]>;
|
||||||
}[U];
|
}[U];
|
||||||
|
|
||||||
|
type TransformRecordToWithDefaultFlag<T extends Record<string, any>> = {
|
||||||
|
[K in keyof T]: { value: T[K]; isDefault: boolean };
|
||||||
|
};
|
||||||
|
|||||||
24
src/assets/icons/svg/HiddenProgressBarIcon.tsx
Normal file
24
src/assets/icons/svg/HiddenProgressBarIcon.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
export const HiddenProgressBarIcon = () => (
|
||||||
|
<SvgIcon>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4 15h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
24
src/assets/icons/svg/StandardProgressBarIcon.tsx
Normal file
24
src/assets/icons/svg/StandardProgressBarIcon.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
export const StandardProgressBarIcon = () => (
|
||||||
|
<SvgIcon>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4 15h16M7 15v-5m10 5v-5M4 9h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
} from '@apollo/client';
|
} from '@apollo/client';
|
||||||
import { OperationVariables, Reference } from '@apollo/client/core';
|
import { OperationVariables, Reference } from '@apollo/client/core';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ReadFieldFunction } from '@apollo/client/cache/core/types/common';
|
|
||||||
import { IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
|
import { IRestClient, RestClient } from '@/lib/requests/client/RestClient.ts';
|
||||||
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
|
import { GraphQLClient } from '@/lib/requests/client/GraphQLClient.ts';
|
||||||
import {
|
import {
|
||||||
@@ -327,6 +326,7 @@ import { SOURCE_META_FIELDS } from '@/lib/graphql/fragments/SourceFragments.ts';
|
|||||||
import { CHAPTER_META_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts';
|
import { CHAPTER_META_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts';
|
||||||
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
|
import { MetadataMigrationSettings } from '@/modules/migration/Migration.types.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import { updateMetadataList } from '@/modules/metadata/services/MetadataApolloCacheHandler.ts';
|
||||||
|
|
||||||
enum GQLMethod {
|
enum GQLMethod {
|
||||||
QUERY = 'QUERY',
|
QUERY = 'QUERY',
|
||||||
@@ -427,24 +427,6 @@ export const SPECIAL_ED_SOURCES = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateMetadata = (
|
|
||||||
key: string,
|
|
||||||
existingMetas: Reference[] | undefined,
|
|
||||||
readField: ReadFieldFunction,
|
|
||||||
createMetaRef: () => Reference | undefined,
|
|
||||||
): (Reference | undefined)[] | undefined => {
|
|
||||||
if (!existingMetas) {
|
|
||||||
return existingMetas;
|
|
||||||
}
|
|
||||||
|
|
||||||
const exists = existingMetas.some((metaRef: Reference) => readField('key', metaRef) === key);
|
|
||||||
if (exists) {
|
|
||||||
return existingMetas;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...existingMetas, createMetaRef()];
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO - extract logic to reduce the size of this file... grew waaaaaaaaaaaaay too big peepoFat
|
// TODO - extract logic to reduce the size of this file... grew waaaaaaaaaaaaay too big peepoFat
|
||||||
// TODO - correctly update cache after all mutations instead of refetching queries
|
// TODO - correctly update cache after all mutations instead of refetching queries
|
||||||
export class RequestManager {
|
export class RequestManager {
|
||||||
@@ -1599,7 +1581,7 @@ export class RequestManager {
|
|||||||
id: cache.identify({ __typename: 'SourceType', id: sourceId }),
|
id: cache.identify({ __typename: 'SourceType', id: sourceId }),
|
||||||
fields: {
|
fields: {
|
||||||
meta(existingMetas, { readField }) {
|
meta(existingMetas, { readField }) {
|
||||||
return updateMetadata(key, existingMetas, readField, () =>
|
return updateMetadataList(key, existingMetas, readField, () =>
|
||||||
cache.writeFragment({
|
cache.writeFragment({
|
||||||
data: data!.setSourceMeta!.meta,
|
data: data!.setSourceMeta!.meta,
|
||||||
fragment: SOURCE_META_FIELDS,
|
fragment: SOURCE_META_FIELDS,
|
||||||
@@ -1637,7 +1619,7 @@ export class RequestManager {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
update(cache) {
|
update(cache) {
|
||||||
cache.evict({ id: cache.identify({ __typename: 'SourceMetaType', id: sourceId, key }) });
|
cache.evict({ id: cache.identify({ __typename: 'SourceMetaType', sourceId, key }) });
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
@@ -2123,7 +2105,7 @@ export class RequestManager {
|
|||||||
id: cache.identify({ __typename: 'MangaType', id: mangaId }),
|
id: cache.identify({ __typename: 'MangaType', id: mangaId }),
|
||||||
fields: {
|
fields: {
|
||||||
meta(existingMetas, { readField }) {
|
meta(existingMetas, { readField }) {
|
||||||
return updateMetadata(key, existingMetas, readField, () =>
|
return updateMetadataList(key, existingMetas, readField, () =>
|
||||||
cache.writeFragment({
|
cache.writeFragment({
|
||||||
data: data!.setMangaMeta!.meta,
|
data: data!.setMangaMeta!.meta,
|
||||||
fragment: MANGA_META_FIELDS,
|
fragment: MANGA_META_FIELDS,
|
||||||
@@ -2161,7 +2143,7 @@ export class RequestManager {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
update(cache) {
|
update(cache) {
|
||||||
cache.evict({ id: cache.identify({ __typename: 'MangaMetaType', id: mangaId, key }) });
|
cache.evict({ id: cache.identify({ __typename: 'MangaMetaType', mangaId, key }) });
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
@@ -2351,7 +2333,7 @@ export class RequestManager {
|
|||||||
id: cache.identify({ __typename: 'ChapterType', id: chapterId }),
|
id: cache.identify({ __typename: 'ChapterType', id: chapterId }),
|
||||||
fields: {
|
fields: {
|
||||||
meta(existingMetas, { readField }) {
|
meta(existingMetas, { readField }) {
|
||||||
return updateMetadata(key, existingMetas, readField, () =>
|
return updateMetadataList(key, existingMetas, readField, () =>
|
||||||
cache.writeFragment({
|
cache.writeFragment({
|
||||||
data: data!.setChapterMeta!.meta,
|
data: data!.setChapterMeta!.meta,
|
||||||
fragment: CHAPTER_META_FIELDS,
|
fragment: CHAPTER_META_FIELDS,
|
||||||
@@ -2389,7 +2371,7 @@ export class RequestManager {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
update(cache) {
|
update(cache) {
|
||||||
cache.evict({ id: cache.identify({ __typename: 'ChapterMetaType', id: chapterId, key }) });
|
cache.evict({ id: cache.identify({ __typename: 'ChapterMetaType', chapterId, key }) });
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
@@ -2630,7 +2612,7 @@ export class RequestManager {
|
|||||||
id: cache.identify({ __typename: 'CategoryType', id: categoryId }),
|
id: cache.identify({ __typename: 'CategoryType', id: categoryId }),
|
||||||
fields: {
|
fields: {
|
||||||
meta(existingMetas, { readField }) {
|
meta(existingMetas, { readField }) {
|
||||||
return updateMetadata(key, existingMetas, readField, () =>
|
return updateMetadataList(key, existingMetas, readField, () =>
|
||||||
cache.writeFragment({
|
cache.writeFragment({
|
||||||
data: data!.setCategoryMeta!.meta,
|
data: data!.setCategoryMeta!.meta,
|
||||||
fragment: CATEGORY_META_FIELDS,
|
fragment: CATEGORY_META_FIELDS,
|
||||||
@@ -2668,7 +2650,7 @@ export class RequestManager {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
update(cache) {
|
update(cache) {
|
||||||
cache.evict({ id: cache.identify({ __typename: 'CategoryMetaType', id: categoryId, key }) });
|
cache.evict({ id: cache.identify({ __typename: 'CategoryMetaType', categoryId, key }) });
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,3 +22,20 @@ export type ValueToDisplayData<Value extends string | number> = Record<
|
|||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export interface MultiValueButtonBaseProps<Value extends string | number> {
|
||||||
|
value: Value;
|
||||||
|
values: Value[];
|
||||||
|
setValue: (value: Value) => void;
|
||||||
|
valueToDisplayData: ValueToDisplayData<Value>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultiValueButtonDefaultableProps<Value extends string | number>
|
||||||
|
extends OptionalProperty<MultiValueButtonBaseProps<Value>, 'value'> {
|
||||||
|
isDefaultable?: boolean;
|
||||||
|
onDefault?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MultiValueButtonProps<Value extends string | number> =
|
||||||
|
| (MultiValueButtonBaseProps<Value> & PropertiesNever<MultiValueButtonDefaultableProps<Value>>)
|
||||||
|
| MultiValueButtonDefaultableProps<Value>;
|
||||||
|
|||||||
@@ -9,23 +9,25 @@
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
import { MultiValueButtonProps } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export const ButtonSelect = <Value extends string | number>({
|
export const ButtonSelect = <Value extends string | number>({
|
||||||
value,
|
value,
|
||||||
values,
|
values,
|
||||||
setValue,
|
setValue,
|
||||||
valueToDisplayData,
|
valueToDisplayData,
|
||||||
}: {
|
isDefaultable,
|
||||||
value: Value;
|
onDefault,
|
||||||
values: Value[];
|
}: MultiValueButtonProps<Value>) => {
|
||||||
setValue: (value: Value) => void;
|
|
||||||
valueToDisplayData: ValueToDisplayData<Value>;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ flexDirection: 'row', flexWrap: 'wrap', gap: 1 }}>
|
<Stack sx={{ flexDirection: 'row', flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
{isDefaultable && (
|
||||||
|
<Button key="default" onClick={onDefault} variant={value === undefined ? 'contained' : 'outlined'}>
|
||||||
|
{t('global.label.default')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{values.map((displayValue) => (
|
{values.map((displayValue) => (
|
||||||
<Button
|
<Button
|
||||||
key={displayValue}
|
key={displayValue}
|
||||||
|
|||||||
@@ -7,28 +7,58 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
import { MultiValueButtonProps } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
export const ValueRotationButton = <Value extends string | number>({
|
export const ValueRotationButton = <Value extends string | number>({
|
||||||
value,
|
value,
|
||||||
values,
|
values,
|
||||||
setValue,
|
setValue,
|
||||||
valueToDisplayData,
|
valueToDisplayData,
|
||||||
}: {
|
isDefaultable,
|
||||||
value: Value;
|
onDefault,
|
||||||
values: Value[];
|
defaultIcon,
|
||||||
setValue: (value: Value) => void;
|
}: MultiValueButtonProps<Value> & { defaultIcon?: ReactNode }) => {
|
||||||
valueToDisplayData: ValueToDisplayData<Value>;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const indexOfValue = useMemo(() => values.indexOf(value), [value, values]);
|
const isDefault = value === undefined;
|
||||||
|
const indexOfValue = useMemo(() => {
|
||||||
|
if (isDefault) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.indexOf(value);
|
||||||
|
}, [value, values]);
|
||||||
|
|
||||||
|
if (isDefault) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => setValue(values[0])}
|
||||||
|
sx={{ justifyContent: 'start', textTransform: 'unset', flexGrow: 1 }}
|
||||||
|
variant="contained"
|
||||||
|
startIcon={defaultIcon}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{t('global.label.default')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setValue(values[(indexOfValue + 1) % values.length])}
|
onClick={() => {
|
||||||
|
const nextValueIndex = (indexOfValue + 1) % values.length;
|
||||||
|
const wasLastValue = nextValueIndex === 0;
|
||||||
|
|
||||||
|
const isDefaultNextValue = isDefaultable && wasLastValue;
|
||||||
|
if (isDefaultNextValue) {
|
||||||
|
onDefault?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(values[(indexOfValue + 1) % values.length]);
|
||||||
|
}}
|
||||||
sx={{ justifyContent: 'start', textTransform: 'unset', flexGrow: 1 }}
|
sx={{ justifyContent: 'start', textTransform: 'unset', flexGrow: 1 }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={valueToDisplayData[value].icon}
|
startIcon={valueToDisplayData[value].icon}
|
||||||
|
|||||||
37
src/modules/core/components/inputs/SliderInput.tsx
Normal file
37
src/modules/core/components/inputs/SliderInput.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 Slider, { SliderProps } from '@mui/material/Slider';
|
||||||
|
import Typography, { TypographyProps } from '@mui/material/Typography';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
|
export const SliderInput = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
slotProps,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value: number | string;
|
||||||
|
slotProps?: {
|
||||||
|
label?: TypographyProps;
|
||||||
|
value?: TypographyProps;
|
||||||
|
slider?: SliderProps;
|
||||||
|
};
|
||||||
|
}) => (
|
||||||
|
<Stack sx={{ flexDirection: 'row', gap: 1, alignItems: 'center' }}>
|
||||||
|
<Stack sx={{ flexBasis: '25%' }}>
|
||||||
|
<Typography {...slotProps?.label} sx={{ ...slotProps?.label?.sx }}>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
<Typography {...slotProps?.value} sx={{ ...slotProps?.value?.sx }}>
|
||||||
|
{value}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Slider {...slotProps?.slider} sx={{ flexBasis: '75%', ...slotProps?.slider?.sx }} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
@@ -6,20 +6,21 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Box, { BoxProps } from '@mui/material/Box';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps extends BoxProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
index: any;
|
index: any;
|
||||||
currentIndex: any;
|
currentIndex: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TabPanel(props: IProps) {
|
export function TabPanel(props: IProps) {
|
||||||
const { children, index, currentIndex } = props;
|
const { children, index, currentIndex, ...boxProps } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div role="tabpanel" hidden={index !== currentIndex} id={`simple-tabpanel-${index}`}>
|
<Box {...boxProps} role="tabpanel" hidden={index !== currentIndex} id={`simple-tabpanel-${index}`}>
|
||||||
{currentIndex === index && children}
|
{currentIndex === index && children}
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const TabsMenu = forwardRef(
|
|||||||
indicatorColor="primary"
|
indicatorColor="primary"
|
||||||
textColor="primary"
|
textColor="primary"
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
scrollButtons
|
scrollButtons="auto"
|
||||||
allowScrollButtonsMobile
|
allowScrollButtonsMobile
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type TupleUnion<U extends string | number | symbol, R extends any[] = []> = {
|
|
||||||
[S in U]: Exclude<U, S> extends never ? [...R, S] : TupleUnion<Exclude<U, S>, [...R, S]>;
|
|
||||||
}[U];
|
|
||||||
|
|
||||||
export const shouldForwardProp =
|
export const shouldForwardProp =
|
||||||
<TCustomProps extends Record<string, unknown>>(customProps: TupleUnion<keyof TCustomProps>) =>
|
<TCustomProps extends Record<string, unknown>>(customProps: TupleUnion<keyof TCustomProps>) =>
|
||||||
(prop: string): boolean =>
|
(prop: string): boolean =>
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export function Library() {
|
|||||||
<TabsMenu value={activeTab.order} onChange={(e, newTab) => handleTabChange(newTab)}>
|
<TabsMenu value={activeTab.order} onChange={(e, newTab) => handleTabChange(newTab)}>
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<Tab
|
<Tab
|
||||||
sx={{ display: 'flex' }}
|
sx={{ flexGrow: 1, maxWidth: 'unset' }}
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
label={
|
label={
|
||||||
<TitleWithSizeTag>
|
<TitleWithSizeTag>
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ import { ReaderPageScaleMode, ReadingMode } from '@/modules/reader/types/Reader.
|
|||||||
export const APP_METADATA_KEY_PREFIX = 'webUI_';
|
export const APP_METADATA_KEY_PREFIX = 'webUI_';
|
||||||
|
|
||||||
const APP_METADATA_OBJECT: Record<AppMetadataKeys, undefined> = {
|
const APP_METADATA_OBJECT: Record<AppMetadataKeys, undefined> = {
|
||||||
staticNav: undefined,
|
|
||||||
showPageNumber: undefined,
|
|
||||||
loadNextOnEnding: undefined,
|
|
||||||
skipDupChapters: undefined,
|
|
||||||
migration: undefined,
|
migration: undefined,
|
||||||
deleteChaptersManuallyMarkedRead: undefined,
|
deleteChaptersManuallyMarkedRead: undefined,
|
||||||
deleteChaptersWhileReading: undefined,
|
deleteChaptersWhileReading: undefined,
|
||||||
@@ -60,8 +56,16 @@ const APP_METADATA_OBJECT: Record<AppMetadataKeys, undefined> = {
|
|||||||
progressBarPosition: undefined,
|
progressBarPosition: undefined,
|
||||||
readingMode: undefined,
|
readingMode: undefined,
|
||||||
pageScaleMode: undefined,
|
pageScaleMode: undefined,
|
||||||
shouldScalePage: undefined,
|
|
||||||
shouldOffsetDoubleSpreads: undefined,
|
shouldOffsetDoubleSpreads: undefined,
|
||||||
|
exitMode: undefined,
|
||||||
|
customFilter: undefined,
|
||||||
|
shouldSkipDupChapters: undefined,
|
||||||
|
isStaticNav: undefined,
|
||||||
|
overlayMode: undefined,
|
||||||
|
shouldStretchPage: undefined,
|
||||||
|
shouldShowPageNumber: undefined,
|
||||||
|
backgroundColor: undefined,
|
||||||
|
pageGap: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_OBJECT);
|
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_OBJECT);
|
||||||
@@ -122,6 +126,11 @@ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
|||||||
// themes
|
// themes
|
||||||
'customThemes',
|
'customThemes',
|
||||||
'mangaThumbnailBackdrop',
|
'mangaThumbnailBackdrop',
|
||||||
|
|
||||||
|
// reader
|
||||||
|
'exitMode',
|
||||||
|
'customFilter',
|
||||||
|
'shouldSkipDupChapters',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,7 +212,19 @@ export const METADATA_MIGRATIONS: IMetadataMigration[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
oldKey: 'scalePage',
|
oldKey: 'scalePage',
|
||||||
newKey: 'shouldScalePage',
|
newKey: 'shouldStretchPage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oldKey: 'skipDupChapters',
|
||||||
|
newKey: 'shouldSkipDupChapters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oldKey: 'showPageNumber',
|
||||||
|
newKey: 'shouldShowPageNumber',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oldKey: 'staticNav',
|
||||||
|
newKey: 'isStaticNav',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
values: [
|
values: [
|
||||||
|
|||||||
@@ -31,14 +31,19 @@ export const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = A
|
|||||||
return value as T;
|
return value as T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const convertFromGqlMeta = (gqlMetadata?: MetaType[]): Metadata | undefined => {
|
export const convertFromGqlMeta = (
|
||||||
|
gqlMetadata?: MetaType[],
|
||||||
|
filter: (key: string) => boolean = () => true,
|
||||||
|
): Metadata | undefined => {
|
||||||
if (!gqlMetadata) {
|
if (!gqlMetadata) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata: Metadata = {};
|
const metadata: Metadata = {};
|
||||||
gqlMetadata.forEach(({ key, value }) => {
|
gqlMetadata.forEach(({ key, value }) => {
|
||||||
metadata[key] = value;
|
if (filter(key)) {
|
||||||
|
metadata[key] = value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
|||||||
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
||||||
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
import { SourceType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
|
|
||||||
export const extractOriginalKey = (key: string): string => key.split('_').slice(-1)[0];
|
export const extractOriginalKey = (key: string) => key.split('_').slice(-1)[0];
|
||||||
|
|
||||||
export const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => {
|
export const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => {
|
||||||
const isGlobalMetadataKey = GLOBAL_METADATA_KEYS.includes(key as AppMetadataKeys);
|
const isGlobalMetadataKey = GLOBAL_METADATA_KEYS.includes(key as AppMetadataKeys);
|
||||||
|
|||||||
@@ -16,18 +16,19 @@ import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/Rea
|
|||||||
import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx';
|
import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx';
|
||||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx';
|
import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx';
|
||||||
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx';
|
||||||
|
|
||||||
export const ReaderPageNumber = () => {
|
export const ReaderPageNumber = () => {
|
||||||
const isMobileWidth = MediaQuery.useIsMobileWidth();
|
const { isDesktop } = ReaderService.useOverlayMode();
|
||||||
|
const { scrollbarXSize } = useReaderScrollbarContext();
|
||||||
const { readerNavBarWidth } = useNavBarContext();
|
const { readerNavBarWidth } = useNavBarContext();
|
||||||
const { isMaximized } = useReaderProgressBarContext();
|
const { isMaximized } = useReaderProgressBarContext();
|
||||||
const { currentPageIndex, pages, totalPages } = userReaderStatePagesContext();
|
const { currentPageIndex, pages, totalPages } = userReaderStatePagesContext();
|
||||||
const { progressBarType, showPageNumber } = ReaderService.useSettings();
|
const { progressBarType, shouldShowPageNumber } = ReaderService.useSettings();
|
||||||
|
|
||||||
const pageName = useMemo(() => getPage(currentPageIndex, pages).name, [currentPageIndex, pages]);
|
const pageName = useMemo(() => getPage(currentPageIndex, pages).name, [currentPageIndex, pages]);
|
||||||
|
|
||||||
if (!showPageNumber) {
|
if (!shouldShowPageNumber) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ export const ReaderPageNumber = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMobileWidth && progressBarType === ProgressBarType.STANDARD) {
|
if (isDesktop && progressBarType === ProgressBarType.STANDARD) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ export const ReaderPageNumber = () => {
|
|||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
left: readerNavBarWidth,
|
left: readerNavBarWidth,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: (theme) => theme.spacing(1),
|
bottom: (theme) => `calc(${theme.spacing(1)} + ${scrollbarXSize}px)`,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
transition: (theme) => `left 0.${theme.transitions.duration.shortest}s`,
|
transition: (theme) => `left 0.${theme.transitions.duration.shortest}s`,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ export const TapZoneLayout = () => {
|
|||||||
}, [tapZoneLayoutElement]),
|
}, [tapZoneLayoutElement]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const canvas = ReaderTapZoneService.getOrCreateCanvas(tapZoneLayout, width, height, theme.typography.h3, {
|
const canvas = ReaderTapZoneService.getOrCreateCanvas(tapZoneLayout.value, width, height, theme.typography.h3, {
|
||||||
vertical: tapZoneInvertMode.vertical,
|
vertical: tapZoneInvertMode.value.vertical,
|
||||||
horizontal: tapZoneInvertMode.horizontal,
|
horizontal: tapZoneInvertMode.value.horizontal,
|
||||||
isRTL: readingDirection === ReadingDirection.RTL,
|
isRTL: readingDirection.value === ReadingDirection.RTL,
|
||||||
});
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
|||||||
@@ -7,36 +7,39 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import { useState } from 'react';
|
||||||
import { BaseReaderOverlayProps, MobileHeaderProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
import { BaseReaderOverlayProps, MobileHeaderProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
||||||
|
import { ReaderSettings } from '@/modules/reader/components/settings/ReaderSettings.tsx';
|
||||||
import { ReaderPageNumber } from '@/modules/reader/components/ReaderPageNumber.tsx';
|
import { ReaderPageNumber } from '@/modules/reader/components/ReaderPageNumber.tsx';
|
||||||
import { StandardReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx';
|
import { StandardReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx';
|
||||||
import { ReaderNavBarDesktop } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktop.tsx';
|
import { ReaderNavBarDesktop } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktop.tsx';
|
||||||
import { ReaderOverlayHeaderMobile } from '@/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx';
|
import { ReaderOverlayHeaderMobile } from '@/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx';
|
||||||
import { ReaderBottomBarMobile } from '@/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx';
|
import { ReaderBottomBarMobile } from '@/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx';
|
||||||
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
|
||||||
export const ReaderOverlay = ({ isVisible }: BaseReaderOverlayProps & MobileHeaderProps) => {
|
export const ReaderOverlay = ({ isVisible }: BaseReaderOverlayProps & MobileHeaderProps) => {
|
||||||
const isTouchDevice = MediaQuery.useIsTouchDevice();
|
const { isDesktop, isMobile } = ReaderService.useOverlayMode();
|
||||||
|
|
||||||
const isDesktop = !isTouchDevice;
|
const [areSettingsOpen, setAreSettingsOpen] = useState(false);
|
||||||
const isMobile = isTouchDevice;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'absolute', width: '100%', height: '100%', pointerEvents: 'none', zIndex: 1 }}>
|
<Box sx={{ position: 'absolute', width: '100%', height: '100%', pointerEvents: 'none', zIndex: 1 }}>
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<>
|
<>
|
||||||
<StandardReaderProgressBar />
|
<StandardReaderProgressBar />
|
||||||
<ReaderNavBarDesktop isVisible={isVisible} openSettings={() => {}} />
|
<ReaderNavBarDesktop isVisible={isVisible} openSettings={() => setAreSettingsOpen(true)} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<>
|
<>
|
||||||
<ReaderOverlayHeaderMobile isVisible={isVisible} />
|
<ReaderOverlayHeaderMobile isVisible={isVisible} />
|
||||||
<ReaderBottomBarMobile openSettings={() => {}} isVisible={isVisible} />
|
<ReaderBottomBarMobile openSettings={() => setAreSettingsOpen(true)} isVisible={isVisible} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ReaderSettings isOpen={areSettingsOpen} close={() => setAreSettingsOpen(false)} />
|
||||||
|
|
||||||
<ReaderPageNumber />
|
<ReaderPageNumber />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|||||||
import Drawer from '@mui/material/Drawer';
|
import Drawer from '@mui/material/Drawer';
|
||||||
import { useGetOptionForDirection } from '@/theme.tsx';
|
import { useGetOptionForDirection } from '@/theme.tsx';
|
||||||
import { ReaderNavBarDesktopProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
import { ReaderNavBarDesktopProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
||||||
import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
|
|
||||||
import { ReaderNavContainer } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavContainer.tsx';
|
import { ReaderNavContainer } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavContainer.tsx';
|
||||||
import { ReaderNavBarDesktopMetadata } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopMetadata.tsx';
|
import { ReaderNavBarDesktopMetadata } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopMetadata.tsx';
|
||||||
import { ReaderNavBarDesktopPageNavigation } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopPageNavigation.tsx';
|
import { ReaderNavBarDesktopPageNavigation } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopPageNavigation.tsx';
|
||||||
@@ -28,31 +27,31 @@ import { ReaderNavBarDesktopActions } from '@/modules/reader/components/overlay/
|
|||||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
|
||||||
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
||||||
import { createUpdateReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
|
||||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
|
||||||
import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx';
|
import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx';
|
||||||
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
|
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
|
||||||
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
|
||||||
const useGetPreviousNavBarStaticValue = (isVisible: boolean, staticNav: boolean) => {
|
const useGetPreviousNavBarStaticValue = (isVisible: boolean, isStaticNav: boolean) => {
|
||||||
const wasNavBarStaticRef = useRef(staticNav);
|
const wasNavBarStaticRef = useRef(isStaticNav);
|
||||||
const wasNavBarStaticPreviousRef = useRef(staticNav);
|
const wasNavBarStaticPreviousRef = useRef(isStaticNav);
|
||||||
|
|
||||||
const resetWasNavBarStaticValue = wasNavBarStaticPreviousRef.current !== wasNavBarStaticRef.current && !isVisible;
|
const resetWasNavBarStaticValue = wasNavBarStaticPreviousRef.current !== wasNavBarStaticRef.current && !isVisible;
|
||||||
if (resetWasNavBarStaticValue) {
|
if (resetWasNavBarStaticValue) {
|
||||||
wasNavBarStaticRef.current = false;
|
wasNavBarStaticRef.current = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const didNavBarStaticValueChange = wasNavBarStaticPreviousRef.current !== staticNav;
|
const didNavBarStaticValueChange = wasNavBarStaticPreviousRef.current !== isStaticNav;
|
||||||
if (didNavBarStaticValueChange) {
|
if (didNavBarStaticValueChange) {
|
||||||
wasNavBarStaticRef.current = wasNavBarStaticPreviousRef.current;
|
wasNavBarStaticRef.current = wasNavBarStaticPreviousRef.current;
|
||||||
wasNavBarStaticPreviousRef.current = staticNav;
|
wasNavBarStaticPreviousRef.current = isStaticNav;
|
||||||
}
|
}
|
||||||
|
|
||||||
return wasNavBarStaticRef.current;
|
return wasNavBarStaticRef.current;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_MANGA: MangaIdInfo = { id: -1 };
|
||||||
export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDesktopProps) => {
|
export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDesktopProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setReaderNavBarWidth } = useNavBarContext();
|
const { setReaderNavBarWidth } = useNavBarContext();
|
||||||
@@ -60,36 +59,33 @@ export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDes
|
|||||||
const { chapters, currentChapter, nextChapter, previousChapter } = useReaderStateChaptersContext();
|
const { chapters, currentChapter, nextChapter, previousChapter } = useReaderStateChaptersContext();
|
||||||
const { pages, currentPageIndex, setCurrentPageIndex } = userReaderStatePagesContext();
|
const { pages, currentPageIndex, setCurrentPageIndex } = userReaderStatePagesContext();
|
||||||
|
|
||||||
const handleBack = useBackButton();
|
|
||||||
const getOptionForDirection = useGetOptionForDirection();
|
const getOptionForDirection = useGetOptionForDirection();
|
||||||
|
|
||||||
const updateReaderSettings = createUpdateReaderSettings(manga ?? { id: -1 }, () =>
|
const exit = ReaderService.useExit();
|
||||||
makeToast(t('reader.settings.error.label.failed_to_save_settings')),
|
const updateReaderSettings = ReaderService.useCreateUpdateSetting(manga ?? DEFAULT_MANGA);
|
||||||
);
|
|
||||||
|
|
||||||
const settings = ReaderService.useSettings();
|
const settings = ReaderService.useSettings();
|
||||||
|
|
||||||
const [navBarElement, setNavBarElement] = useState<HTMLDivElement | null>();
|
const [navBarElement, setNavBarElement] = useState<HTMLDivElement | null>();
|
||||||
useResizeObserver(
|
useResizeObserver(
|
||||||
navBarElement,
|
navBarElement,
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
if (!settings?.staticNav) {
|
if (!settings?.isStaticNav) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setReaderNavBarWidth(navBarElement!.offsetWidth);
|
setReaderNavBarWidth(navBarElement!.offsetWidth);
|
||||||
}, [navBarElement, settings?.staticNav]),
|
}, [navBarElement, settings?.isStaticNav]),
|
||||||
);
|
);
|
||||||
useLayoutEffect(() => () => setReaderNavBarWidth(0), []);
|
useLayoutEffect(() => () => setReaderNavBarWidth(0), []);
|
||||||
|
|
||||||
const wasNavBarStatic = useGetPreviousNavBarStaticValue(isVisible, settings.staticNav);
|
const wasNavBarStatic = useGetPreviousNavBarStaticValue(isVisible, settings.isStaticNav);
|
||||||
const changedNavBarStaticValue = wasNavBarStatic && isVisible;
|
const changedNavBarStaticValue = wasNavBarStatic && isVisible;
|
||||||
const drawerTransitionDuration = changedNavBarStaticValue ? 0 : undefined;
|
const drawerTransitionDuration = changedNavBarStaticValue ? 0 : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
variant={settings.staticNav ? 'permanent' : 'persistent'}
|
variant={settings.isStaticNav ? 'permanent' : 'persistent'}
|
||||||
open={isVisible || settings.staticNav}
|
open={isVisible || settings.isStaticNav}
|
||||||
transitionDuration={drawerTransitionDuration}
|
transitionDuration={drawerTransitionDuration}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
ref: (ref: HTMLDivElement | null) => setNavBarElement(ref),
|
ref: (ref: HTMLDivElement | null) => setNavBarElement(ref),
|
||||||
@@ -99,7 +95,7 @@ export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDes
|
|||||||
<Stack sx={{ p: 2, gap: 2, backgroundColor: 'action.hover' }}>
|
<Stack sx={{ p: 2, gap: 2, backgroundColor: 'action.hover' }}>
|
||||||
<Stack sx={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Stack sx={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Tooltip title={t('reader.button.exit')}>
|
<Tooltip title={t('reader.button.exit')}>
|
||||||
<IconButton onClick={handleBack} color="inherit">
|
<IconButton onClick={exit} color="inherit">
|
||||||
{getOptionForDirection(<ArrowBack />, <ArrowForwardIcon />)}
|
{getOptionForDirection(<ArrowBack />, <ArrowForwardIcon />)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -107,9 +103,9 @@ export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDes
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setReaderNavBarWidth(0);
|
setReaderNavBarWidth(0);
|
||||||
updateReaderSettings('staticNav', !settings.staticNav);
|
updateReaderSettings('isStaticNav', !settings.isStaticNav);
|
||||||
}}
|
}}
|
||||||
color={settings.staticNav ? 'primary' : 'inherit'}
|
color={settings.isStaticNav ? 'primary' : 'inherit'}
|
||||||
>
|
>
|
||||||
<PushPinIcon />
|
<PushPinIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -147,6 +143,8 @@ export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDes
|
|||||||
settings={settings}
|
settings={settings}
|
||||||
updateSetting={updateReaderSettings}
|
updateSetting={updateReaderSettings}
|
||||||
openSettings={openSettings}
|
openSettings={openSettings}
|
||||||
|
isDefaultable
|
||||||
|
onDefault={(...args) => manga && ReaderService.deleteSetting(manga, ...args)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</ReaderNavContainer>
|
</ReaderNavContainer>
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { OffsetDoubleSpreadIcon } from '@/assets/icons/svg/OffsetDoubleSpreadIcon.tsx';
|
import { OffsetDoubleSpreadIcon } from '@/assets/icons/svg/OffsetDoubleSpreadIcon.tsx';
|
||||||
import { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { isOffsetDoubleSpreadPagesEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
|
|
||||||
export const ReaderNavBarDesktopOffsetDoubleSpread = ({
|
export const ReaderNavBarDesktopOffsetDoubleSpread = ({
|
||||||
readingMode,
|
readingMode,
|
||||||
@@ -20,7 +21,7 @@ export const ReaderNavBarDesktopOffsetDoubleSpread = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (readingMode !== ReadingMode.DOUBLE_PAGE) {
|
if (!isOffsetDoubleSpreadPagesEditable(readingMode)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,65 +9,52 @@
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ZoomOutMapIcon from '@mui/icons-material/ZoomOutMap';
|
|
||||||
import ExpandIcon from '@mui/icons-material/Expand';
|
|
||||||
import CropOriginalIcon from '@mui/icons-material/CropOriginal';
|
|
||||||
import FitScreenIcon from '@mui/icons-material/FitScreen';
|
import FitScreenIcon from '@mui/icons-material/FitScreen';
|
||||||
import { CustomIconButton } from '@/modules/core/components/buttons/CustomIconButton.tsx';
|
import { CustomIconButton } from '@/modules/core/components/buttons/CustomIconButton.tsx';
|
||||||
import { ValueRotationButton } from '@/modules/core/components/buttons/ValueRotationButton.tsx';
|
import { ValueRotationButton } from '@/modules/core/components/buttons/ValueRotationButton.tsx';
|
||||||
import { IReaderSettings, ReaderPageScaleMode } from '@/modules/reader/types/Reader.types.ts';
|
import {
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types';
|
IReaderSettings,
|
||||||
|
IReaderSettingsWithDefaultFlag,
|
||||||
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderPageScaleMode> = {
|
ReaderPageScaleMode,
|
||||||
[ReaderPageScaleMode.WIDTH]: {
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
title: 'reader.settings.page_scale.width',
|
import {
|
||||||
icon: <ExpandIcon sx={{ transform: 'rotate(90deg)' }} />,
|
PAGE_SCALE_VALUE_TO_DISPLAY_DATA,
|
||||||
},
|
READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED,
|
||||||
[ReaderPageScaleMode.HEIGHT]: {
|
READER_PAGE_SCALE_MODE_VALUES,
|
||||||
title: 'reader.settings.page_scale.height',
|
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
icon: <ExpandIcon />,
|
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
|
||||||
},
|
|
||||||
[ReaderPageScaleMode.SCREEN]: {
|
|
||||||
title: 'reader.settings.page_scale.screen',
|
|
||||||
icon: <ZoomOutMapIcon />,
|
|
||||||
},
|
|
||||||
[ReaderPageScaleMode.ORIGINAL]: {
|
|
||||||
title: 'reader.settings.page_scale.original',
|
|
||||||
icon: <CropOriginalIcon />,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const READER_PAGE_SCALE_MODE_VALUES = Object.values(ReaderPageScaleMode).filter((value) => typeof value === 'number');
|
|
||||||
|
|
||||||
export const ReaderNavBarDesktopPageScale = ({
|
export const ReaderNavBarDesktopPageScale = ({
|
||||||
pageScaleMode,
|
pageScaleMode,
|
||||||
shouldScalePage,
|
shouldStretchPage,
|
||||||
updateSetting,
|
updateSetting,
|
||||||
}: Pick<IReaderSettings, 'pageScaleMode' | 'shouldScalePage'> & {
|
...buttonSelectInputProps
|
||||||
updateSetting: <Setting extends keyof Pick<IReaderSettings, 'pageScaleMode' | 'shouldScalePage'>>(
|
}: Pick<IReaderSettingsWithDefaultFlag, 'pageScaleMode' | 'shouldStretchPage'> &
|
||||||
setting: Setting,
|
Pick<MultiValueButtonDefaultableProps<ReaderPageScaleMode>, 'isDefaultable' | 'onDefault'> & {
|
||||||
value: IReaderSettings[Setting],
|
updateSetting: <Setting extends keyof Pick<IReaderSettings, 'pageScaleMode' | 'shouldStretchPage'>>(
|
||||||
) => void;
|
setting: Setting,
|
||||||
}) => {
|
value: IReaderSettings[Setting],
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isPageScalingPossible = pageScaleMode !== ReaderPageScaleMode.ORIGINAL;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ flexDirection: 'row', gap: 1 }}>
|
<Stack sx={{ flexDirection: 'row', gap: 1 }}>
|
||||||
<ValueRotationButton
|
<ValueRotationButton
|
||||||
value={pageScaleMode}
|
{...buttonSelectInputProps}
|
||||||
|
value={pageScaleMode.isDefault ? undefined : pageScaleMode.value}
|
||||||
values={READER_PAGE_SCALE_MODE_VALUES}
|
values={READER_PAGE_SCALE_MODE_VALUES}
|
||||||
setValue={(value) => updateSetting('pageScaleMode', value)}
|
setValue={(value) => updateSetting('pageScaleMode', value)}
|
||||||
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
valueToDisplayData={PAGE_SCALE_VALUE_TO_DISPLAY_DATA}
|
||||||
|
defaultIcon={PAGE_SCALE_VALUE_TO_DISPLAY_DATA[pageScaleMode.value].icon}
|
||||||
/>
|
/>
|
||||||
{isPageScalingPossible && (
|
{READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED[pageScaleMode.value] && (
|
||||||
<Tooltip title={t('reader.settings.label.scale_page')}>
|
<Tooltip title={t('reader.settings.label.scale_page')}>
|
||||||
<CustomIconButton
|
<CustomIconButton
|
||||||
onClick={() => updateSetting('shouldScalePage', !shouldScalePage)}
|
onClick={() => updateSetting('shouldStretchPage', !shouldStretchPage.value)}
|
||||||
sx={{ minWidth: 0 }}
|
sx={{ minWidth: 0 }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color={shouldScalePage ? 'secondary' : 'primary'}
|
color={shouldStretchPage.value ? 'secondary' : 'primary'}
|
||||||
>
|
>
|
||||||
<FitScreenIcon />
|
<FitScreenIcon />
|
||||||
</CustomIconButton>
|
</CustomIconButton>
|
||||||
|
|||||||
@@ -14,17 +14,16 @@ import { ReaderNavBarDesktopPageScale } from '@/modules/reader/components/overla
|
|||||||
import { ReaderNavBarDesktopReadingMode } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopReadingMode.tsx';
|
import { ReaderNavBarDesktopReadingMode } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopReadingMode.tsx';
|
||||||
import { ReaderNavBarDesktopOffsetDoubleSpread } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopOffsetDoubleSpread.tsx';
|
import { ReaderNavBarDesktopOffsetDoubleSpread } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopOffsetDoubleSpread.tsx';
|
||||||
import { ReaderNavBarDesktopReadingDirection } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopReadingDirection.tsx';
|
import { ReaderNavBarDesktopReadingDirection } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopReadingDirection.tsx';
|
||||||
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
import { ReaderSettingsTypeProps } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { ReaderNavBarDesktopProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
import { ReaderNavBarDesktopProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
||||||
|
|
||||||
export const ReaderNavBarDesktopQuickSettings = ({
|
export const ReaderNavBarDesktopQuickSettings = ({
|
||||||
settings: { readingMode, shouldOffsetDoubleSpreads, pageScaleMode, shouldScalePage, readingDirection },
|
settings: { readingMode, shouldOffsetDoubleSpreads, pageScaleMode, shouldStretchPage, readingDirection },
|
||||||
updateSetting,
|
updateSetting,
|
||||||
openSettings,
|
openSettings,
|
||||||
}: {
|
isDefaultable,
|
||||||
settings: IReaderSettings;
|
onDefault,
|
||||||
updateSetting: <Setting extends keyof IReaderSettings>(setting: Setting, value: IReaderSettings[Setting]) => void;
|
}: ReaderSettingsTypeProps & Pick<ReaderNavBarDesktopProps, 'openSettings'>) => {
|
||||||
} & Pick<ReaderNavBarDesktopProps, 'openSettings'>) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,19 +31,26 @@ export const ReaderNavBarDesktopQuickSettings = ({
|
|||||||
<ReaderNavBarDesktopReadingMode
|
<ReaderNavBarDesktopReadingMode
|
||||||
readingMode={readingMode}
|
readingMode={readingMode}
|
||||||
setReadingMode={(value) => updateSetting('readingMode', value)}
|
setReadingMode={(value) => updateSetting('readingMode', value)}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => onDefault?.('readingMode')}
|
||||||
/>
|
/>
|
||||||
<ReaderNavBarDesktopOffsetDoubleSpread
|
<ReaderNavBarDesktopOffsetDoubleSpread
|
||||||
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads}
|
readingMode={readingMode.value}
|
||||||
|
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads.value}
|
||||||
setShouldOffsetDoubleSpreads={(value) => updateSetting('shouldOffsetDoubleSpreads', value)}
|
setShouldOffsetDoubleSpreads={(value) => updateSetting('shouldOffsetDoubleSpreads', value)}
|
||||||
/>
|
/>
|
||||||
<ReaderNavBarDesktopPageScale
|
<ReaderNavBarDesktopPageScale
|
||||||
pageScaleMode={pageScaleMode}
|
pageScaleMode={pageScaleMode}
|
||||||
shouldScalePage={shouldScalePage}
|
shouldStretchPage={shouldStretchPage}
|
||||||
updateSetting={updateSetting}
|
updateSetting={updateSetting}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => onDefault?.('pageScaleMode')}
|
||||||
/>
|
/>
|
||||||
<ReaderNavBarDesktopReadingDirection
|
<ReaderNavBarDesktopReadingDirection
|
||||||
readingDirection={readingDirection}
|
readingDirection={readingDirection}
|
||||||
setReadingDirection={(value) => updateSetting('readingDirection', value)}
|
setReadingDirection={(value) => updateSetting('readingDirection', value)}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => onDefault?.('readingDirection')}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => openSettings()}
|
onClick={() => openSettings()}
|
||||||
|
|||||||
@@ -6,35 +6,28 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
|
|
||||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
|
||||||
import { ValueRotationButton } from '@/modules/core/components/buttons/ValueRotationButton.tsx';
|
import { ValueRotationButton } from '@/modules/core/components/buttons/ValueRotationButton.tsx';
|
||||||
import { IReaderSettings, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
import { IReaderSettingsWithDefaultFlag, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
import {
|
||||||
|
READING_DIRECTION_VALUES,
|
||||||
const READING_MODE_VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingDirection> = {
|
READING_DIRECTION_VALUE_TO_DISPLAY_DATA,
|
||||||
[ReadingDirection.LTR]: {
|
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
title: 'reader.settings.reading_direction.ltr',
|
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
|
||||||
icon: <ArrowCircleRightIcon />,
|
|
||||||
},
|
|
||||||
[ReadingDirection.RTL]: {
|
|
||||||
title: 'reader.settings.reading_direction.rtl',
|
|
||||||
icon: <ArrowCircleLeftIcon />,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number');
|
|
||||||
|
|
||||||
export const ReaderNavBarDesktopReadingDirection = ({
|
export const ReaderNavBarDesktopReadingDirection = ({
|
||||||
readingDirection,
|
readingDirection,
|
||||||
setReadingDirection,
|
setReadingDirection,
|
||||||
}: Pick<IReaderSettings, 'readingDirection'> & {
|
...buttonSelectInputProps
|
||||||
setReadingDirection: (readingDirection: ReadingDirection) => void;
|
}: Pick<IReaderSettingsWithDefaultFlag, 'readingDirection'> &
|
||||||
}) => (
|
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setReadingDirection: (readingDirection: ReadingDirection) => void;
|
||||||
|
}) => (
|
||||||
<ValueRotationButton
|
<ValueRotationButton
|
||||||
value={readingDirection}
|
{...buttonSelectInputProps}
|
||||||
|
value={readingDirection.isDefault ? undefined : readingDirection.value}
|
||||||
values={READING_DIRECTION_VALUES}
|
values={READING_DIRECTION_VALUES}
|
||||||
setValue={setReadingDirection}
|
setValue={setReadingDirection}
|
||||||
valueToDisplayData={READING_MODE_VALUE_TO_DISPLAY_DATA}
|
valueToDisplayData={READING_DIRECTION_VALUE_TO_DISPLAY_DATA}
|
||||||
|
defaultIcon={READING_DIRECTION_VALUE_TO_DISPLAY_DATA[readingDirection.value].icon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,45 +6,28 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SinglePageIcon } from '@/assets/icons/svg/SinglePageIcon.tsx';
|
|
||||||
import { DoublePageIcon } from '@/assets/icons/svg/DoublePageIcon.tsx';
|
|
||||||
import { ContinuousVerticalPageIcon } from '@/assets/icons/svg/ContinuousVerticalPageIcon.tsx';
|
|
||||||
import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx';
|
|
||||||
import { ValueRotationButton } from '@/modules/core/components/buttons/ValueRotationButton.tsx';
|
import { ValueRotationButton } from '@/modules/core/components/buttons/ValueRotationButton.tsx';
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types.tsx';
|
import { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
import {
|
||||||
|
READING_MODE_VALUE_TO_DISPLAY_DATA,
|
||||||
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingMode> = {
|
READING_MODE_VALUES,
|
||||||
[ReadingMode.SINGLE_PAGE]: {
|
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
title: 'reader.settings.reader_type.label.single_page',
|
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
|
||||||
icon: <SinglePageIcon />,
|
|
||||||
},
|
|
||||||
[ReadingMode.DOUBLE_PAGE]: {
|
|
||||||
title: 'reader.settings.reader_type.label.double_page',
|
|
||||||
icon: <DoublePageIcon />,
|
|
||||||
},
|
|
||||||
[ReadingMode.CONTINUOUS_VERTICAL]: {
|
|
||||||
title: 'reader.settings.reader_type.label.continuous_vertical',
|
|
||||||
icon: <ContinuousVerticalPageIcon />,
|
|
||||||
},
|
|
||||||
[ReadingMode.CONTINUOUS_HORIZONTAL]: {
|
|
||||||
title: 'reader.settings.reader_type.label.continuous_horizontal',
|
|
||||||
icon: <ContinuousHorizontalPageIcon />,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number');
|
|
||||||
|
|
||||||
export const ReaderNavBarDesktopReadingMode = ({
|
export const ReaderNavBarDesktopReadingMode = ({
|
||||||
readingMode,
|
readingMode,
|
||||||
setReadingMode,
|
setReadingMode,
|
||||||
}: Pick<IReaderSettings, 'readingMode'> & {
|
...buttonSelectInputProps
|
||||||
setReadingMode: (mode: ReadingMode) => void;
|
}: Pick<IReaderSettingsWithDefaultFlag, 'readingMode'> &
|
||||||
}) => (
|
Pick<MultiValueButtonDefaultableProps<ReadingMode>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setReadingMode: (mode: ReadingMode) => void;
|
||||||
|
}) => (
|
||||||
<ValueRotationButton
|
<ValueRotationButton
|
||||||
value={readingMode}
|
{...buttonSelectInputProps}
|
||||||
|
value={readingMode.isDefault ? undefined : readingMode.value}
|
||||||
values={READING_MODE_VALUES}
|
values={READING_MODE_VALUES}
|
||||||
setValue={setReadingMode}
|
setValue={setReadingMode}
|
||||||
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
valueToDisplayData={READING_MODE_VALUE_TO_DISPLAY_DATA}
|
||||||
|
defaultIcon={READING_MODE_VALUE_TO_DISPLAY_DATA[readingMode.value].icon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { useLayoutEffect } from 'react';
|
|||||||
import { ReaderBottomBarMobileProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
import { ReaderBottomBarMobileProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
|
||||||
import { MobileReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx';
|
import { MobileReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx';
|
||||||
import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx';
|
import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx';
|
||||||
import { ReaderBottomBarMobileQuickSettings } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx';
|
import { ReaderBottomBarMobileQuickSettings } from '@/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobileQuickSettings.tsx';
|
||||||
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
|
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
|
||||||
import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx';
|
import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { ReaderSettingReadingMode } from '@/modules/reader/components/settings/layout/ReaderSettingReadingMode.tsx';
|
||||||
|
import { ReaderSettingReadingDirection } from '@/modules/reader/components/settings/layout/ReaderSettingReadingDirection.tsx';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
||||||
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
|
||||||
|
const DEFAULT_MANGA: MangaIdInfo = { id: -1 };
|
||||||
|
export const ReaderBottomBarMobileQuickSettings = () => {
|
||||||
|
const { manga } = useReaderStateMangaContext();
|
||||||
|
const { readingMode, readingDirection } = ReaderService.useSettings();
|
||||||
|
const deleteSetting = ReaderService.useCreateDeleteSetting(manga ?? DEFAULT_MANGA);
|
||||||
|
|
||||||
|
if (!manga) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ gap: 2 }}>
|
||||||
|
<ReaderSettingReadingMode
|
||||||
|
readingMode={readingMode}
|
||||||
|
setReadingMode={(value) => ReaderService.updateSetting(manga, 'readingMode', value)}
|
||||||
|
isDefaultable
|
||||||
|
onDefault={() => deleteSetting('readingMode')}
|
||||||
|
/>
|
||||||
|
<ReaderSettingReadingDirection
|
||||||
|
readingDirection={readingDirection}
|
||||||
|
setReadingDirection={(value) => ReaderService.updateSetting(manga, 'readingDirection', value)}
|
||||||
|
isDefaultable
|
||||||
|
onDefault={() => deleteSetting('readingDirection')}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 Stack from '@mui/material/Stack';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { ReaderBottomBarMobileReadingMode } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx';
|
|
||||||
import { ReaderBottomBarMobileReadingDirection } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx';
|
|
||||||
import { createUpdateReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
|
||||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
|
||||||
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
|
||||||
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
|
||||||
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
|
||||||
|
|
||||||
export const ReaderBottomBarMobileQuickSettings = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { manga } = useReaderStateMangaContext();
|
|
||||||
const { readingMode, readingDirection } = ReaderService.useSettings();
|
|
||||||
|
|
||||||
const updateReaderSettings = createUpdateReaderSettings<
|
|
||||||
keyof Pick<IReaderSettings, 'readingMode' | 'readingDirection'>
|
|
||||||
>(manga ?? { id: -1 }, () => makeToast(t('reader.settings.error.label.failed_to_save_settings')));
|
|
||||||
|
|
||||||
if (!manga) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack sx={{ gap: 2 }}>
|
|
||||||
<ReaderBottomBarMobileReadingMode
|
|
||||||
readingMode={readingMode}
|
|
||||||
setReadingMode={(value) => updateReaderSettings('readingMode', value)}
|
|
||||||
/>
|
|
||||||
<ReaderBottomBarMobileReadingDirection
|
|
||||||
readingDirection={readingDirection}
|
|
||||||
setReadingDirection={(value) => updateReaderSettings('readingDirection', value)}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { useTranslation } from 'react-i18next';
|
|
||||||
import { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
|
||||||
import { SinglePageIcon } from '@/assets/icons/svg/SinglePageIcon.tsx';
|
|
||||||
import { DoublePageIcon } from '@/assets/icons/svg/DoublePageIcon.tsx';
|
|
||||||
import { ContinuousVerticalPageIcon } from '@/assets/icons/svg/ContinuousVerticalPageIcon.tsx';
|
|
||||||
import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx';
|
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
|
||||||
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
|
||||||
|
|
||||||
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingMode> = {
|
|
||||||
[ReadingMode.SINGLE_PAGE]: {
|
|
||||||
title: 'reader.settings.reader_type.label.single_page',
|
|
||||||
icon: <SinglePageIcon />,
|
|
||||||
},
|
|
||||||
[ReadingMode.DOUBLE_PAGE]: {
|
|
||||||
title: 'reader.settings.reader_type.label.double_page',
|
|
||||||
icon: <DoublePageIcon />,
|
|
||||||
},
|
|
||||||
[ReadingMode.CONTINUOUS_VERTICAL]: {
|
|
||||||
title: 'reader.settings.reader_type.label.continuous_vertical',
|
|
||||||
icon: <ContinuousVerticalPageIcon />,
|
|
||||||
},
|
|
||||||
[ReadingMode.CONTINUOUS_HORIZONTAL]: {
|
|
||||||
title: 'reader.settings.reader_type.label.continuous_horizontal',
|
|
||||||
icon: <ContinuousHorizontalPageIcon />,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number');
|
|
||||||
|
|
||||||
export const ReaderBottomBarMobileReadingMode = ({
|
|
||||||
readingMode,
|
|
||||||
setReadingMode,
|
|
||||||
}: Pick<IReaderSettings, 'readingMode'> & {
|
|
||||||
setReadingMode: (mode: ReadingMode) => void;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ButtonSelectInput
|
|
||||||
label={t('reader.settings.label.reading_mode')}
|
|
||||||
value={readingMode}
|
|
||||||
values={READING_MODE_VALUES}
|
|
||||||
setValue={setReadingMode}
|
|
||||||
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -18,6 +18,7 @@ export const ReaderProgressBarContainer = styled(Stack, {
|
|||||||
shouldForwardProp: shouldForwardProp<ReaderProgressBarContainerProps>(['progressBarPosition']),
|
shouldForwardProp: shouldForwardProp<ReaderProgressBarContainerProps>(['progressBarPosition']),
|
||||||
})<ReaderProgressBarContainerProps>(({ theme, progressBarPosition }) => ({
|
})<ReaderProgressBarContainerProps>(({ theme, progressBarPosition }) => ({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
pointerEvents: 'all',
|
||||||
...applyStyles(getProgressBarPositionInfo(progressBarPosition).isHorizontal, {
|
...applyStyles(getProgressBarPositionInfo(progressBarPosition).isHorizontal, {
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import Tooltip from '@mui/material/Tooltip';
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { CurrentPageSlotProps } from '@/modules/reader/types/ReaderProgressBar.types.ts';
|
import { CurrentPageSlotProps } from '@/modules/reader/types/ReaderProgressBar.types.ts';
|
||||||
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
||||||
import { READER_PROGRESS_BAR_POSITION_TO_PLACEMENT } from '@/modules/reader/constants/ReaderProgressBar.constants.ts';
|
|
||||||
import { getProgressBarPositionInfo } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx';
|
import { getProgressBarPositionInfo } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx';
|
||||||
|
import { READER_PROGRESS_BAR_POSITION_TO_PLACEMENT } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
export const ReaderProgressBarCurrentPageSlot = ({
|
export const ReaderProgressBarCurrentPageSlot = ({
|
||||||
pageName,
|
pageName,
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import Box from '@mui/material/Box';
|
|||||||
import Tooltip from '@mui/material/Tooltip';
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { ReaderProgressBarSlotProps } from '@/modules/reader/types/ReaderProgressBar.types.ts';
|
import { ReaderProgressBarSlotProps } from '@/modules/reader/types/ReaderProgressBar.types.ts';
|
||||||
import { READER_PROGRESS_BAR_POSITION_TO_PLACEMENT } from '@/modules/reader/constants/ReaderProgressBar.constants.ts';
|
|
||||||
|
import { READER_PROGRESS_BAR_POSITION_TO_PLACEMENT } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
export const ReaderProgressBarSlot = ({
|
export const ReaderProgressBarSlot = ({
|
||||||
pageName,
|
pageName,
|
||||||
|
|||||||
51
src/modules/reader/components/settings/ReaderSettings.tsx
Normal file
51
src/modules/reader/components/settings/ReaderSettings.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
import { ReaderSettingsTabs } from '@/modules/reader/components/settings/ReaderSettingsTabs.tsx';
|
||||||
|
import { ReaderSettingTab } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettings = ({ isOpen, close }: { isOpen: boolean; close: () => void }) => {
|
||||||
|
const { manga } = useReaderStateMangaContext();
|
||||||
|
const settings = ReaderService.useSettings();
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
|
if (!manga) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth
|
||||||
|
onClose={close}
|
||||||
|
hideBackdrop={activeTab === ReaderSettingTab.FILTER}
|
||||||
|
>
|
||||||
|
<DialogContent sx={{ p: 0 }}>
|
||||||
|
<ReaderSettingsTabs
|
||||||
|
activeTab={activeTab}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
|
settings={settings}
|
||||||
|
updateSetting={(...args) => ReaderService.updateSetting(manga, ...args)}
|
||||||
|
deleteSetting={(...args) => ReaderService.deleteSetting(manga, ...args)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
159
src/modules/reader/components/settings/ReaderSettingsTabs.tsx
Normal file
159
src/modules/reader/components/settings/ReaderSettingsTabs.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* 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 Box from '@mui/material/Box';
|
||||||
|
import Tab from '@mui/material/Tab';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TabsMenu } from '@/modules/core/components/tabs/TabsMenu.tsx';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
||||||
|
import { IReaderSettings, IReaderSettingsWithDefaultFlag } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { useReaderTapZoneContext } from '@/modules/reader/contexts/ReaderTapZoneContext.tsx';
|
||||||
|
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
||||||
|
import { READER_SETTING_TABS, ReaderSettingTab } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
import { TabPanel } from '@/modules/core/components/tabs/TabPanel.tsx';
|
||||||
|
import { ReaderLayoutSettings } from '@/modules/reader/components/settings/layout/ReaderLayoutSettings.tsx';
|
||||||
|
import { ReaderGeneralSettings } from '@/modules/reader/components/settings/general/ReaderGeneralSettings.tsx';
|
||||||
|
import { ReaderFilterSettings } from '@/modules/reader/components/settings/filters/ReaderFilterSettings.tsx';
|
||||||
|
import { ReaderBehaviourSettings } from '@/modules/reader/components/settings/behaviour/ReaderBehaviourSettings.tsx';
|
||||||
|
|
||||||
|
interface BaseProps {
|
||||||
|
activeTab: number;
|
||||||
|
setActiveTab: (tab: number) => void;
|
||||||
|
settings: IReaderSettingsWithDefaultFlag;
|
||||||
|
updateSetting: (...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultSettingsProps extends BaseProps {
|
||||||
|
areDefaultSettings: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MangaSettingsProps extends BaseProps {
|
||||||
|
deleteSetting: (setting: keyof IReaderSettings) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props =
|
||||||
|
| (DefaultSettingsProps & PropertiesNever<Omit<MangaSettingsProps, keyof BaseProps>>)
|
||||||
|
| (PropertiesNever<Omit<DefaultSettingsProps, keyof BaseProps>> & MangaSettingsProps);
|
||||||
|
|
||||||
|
export const ReaderSettingsTabs = ({
|
||||||
|
activeTab,
|
||||||
|
setActiveTab,
|
||||||
|
areDefaultSettings,
|
||||||
|
settings,
|
||||||
|
updateSetting,
|
||||||
|
deleteSetting,
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setShowPreview } = useReaderTapZoneContext();
|
||||||
|
const isTouchDevice = MediaQuery.useIsTouchDevice();
|
||||||
|
|
||||||
|
const { mode: overlayMode } = ReaderService.useOverlayMode();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabsMenu
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(_, newTab) => setActiveTab(newTab)}
|
||||||
|
sx={{
|
||||||
|
...applyStyles(!!areDefaultSettings, { zIndex: 2 }),
|
||||||
|
...applyStyles(!areDefaultSettings, {
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
backgroundImage: 'var(--Paper-overlay)',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(READER_SETTING_TABS).map(({ id, label, supportsTouchDevices }) => {
|
||||||
|
if (!supportsTouchDevices && isTouchDevice) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tab
|
||||||
|
key={id}
|
||||||
|
value={id}
|
||||||
|
label={t(label)}
|
||||||
|
sx={{ flexGrow: 1, maxWidth: 'unset', textTransform: 'none' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TabsMenu>
|
||||||
|
<Box sx={{ p: areDefaultSettings ? undefined : 2, overflowX: 'hidden' }}>
|
||||||
|
{Object.values(READER_SETTING_TABS).map(({ id, supportsTouchDevices }) => {
|
||||||
|
if (!supportsTouchDevices && isTouchDevice) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (id as ReaderSettingTab) {
|
||||||
|
case ReaderSettingTab.LAYOUT:
|
||||||
|
return (
|
||||||
|
<TabPanel key={id} index={id} currentIndex={activeTab}>
|
||||||
|
<ReaderLayoutSettings
|
||||||
|
settings={settings}
|
||||||
|
updateSetting={(...args) => updateSetting(...args)}
|
||||||
|
setShowPreview={setShowPreview!}
|
||||||
|
// @ts-expect-error - TS2322: Type boolean is not assignable to type true
|
||||||
|
isDefaultable={!areDefaultSettings}
|
||||||
|
onDefault={(...args) => deleteSetting?.(...args)}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
case ReaderSettingTab.GENERAL:
|
||||||
|
return (
|
||||||
|
<TabPanel
|
||||||
|
key={id}
|
||||||
|
index={id}
|
||||||
|
currentIndex={activeTab}
|
||||||
|
sx={{ p: areDefaultSettings ? 2 : undefined }}
|
||||||
|
>
|
||||||
|
<ReaderGeneralSettings
|
||||||
|
overlayMode={overlayMode}
|
||||||
|
settings={settings}
|
||||||
|
updateSetting={(...args) => updateSetting(...args)}
|
||||||
|
// @ts-expect-error - TS2322: Type boolean is not assignable to type true
|
||||||
|
isDefaultable={!areDefaultSettings}
|
||||||
|
onDefault={(...args) => deleteSetting?.(...args)}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
case ReaderSettingTab.FILTER:
|
||||||
|
return (
|
||||||
|
<TabPanel
|
||||||
|
key={id}
|
||||||
|
index={id}
|
||||||
|
currentIndex={activeTab}
|
||||||
|
sx={{ p: areDefaultSettings ? 2 : undefined }}
|
||||||
|
>
|
||||||
|
<ReaderFilterSettings
|
||||||
|
settings={settings}
|
||||||
|
updateSetting={(...args) => updateSetting(...args)}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
case ReaderSettingTab.BEHAVIOUR:
|
||||||
|
return (
|
||||||
|
<TabPanel
|
||||||
|
key={id}
|
||||||
|
index={id}
|
||||||
|
currentIndex={activeTab}
|
||||||
|
sx={{ p: areDefaultSettings ? 2 : undefined }}
|
||||||
|
>
|
||||||
|
<ReaderBehaviourSettings
|
||||||
|
settings={settings}
|
||||||
|
updateSetting={(...args) => updateSetting(...args)}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "ReaderSettingTab" (${id})`);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
import { IReaderSettingsWithDefaultFlag } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { ReaderSettingExitMode } from '@/modules/reader/components/settings/behaviour/ReaderSettingExitMode.tsx';
|
||||||
|
import { isOffsetDoubleSpreadPagesEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
|
|
||||||
|
export const ReaderBehaviourSettings = ({
|
||||||
|
settings,
|
||||||
|
updateSetting,
|
||||||
|
}: {
|
||||||
|
settings: IReaderSettingsWithDefaultFlag;
|
||||||
|
updateSetting: (
|
||||||
|
...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>
|
||||||
|
) => ReturnType<typeof ReaderService.updateSetting>;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ gap: 2 }}>
|
||||||
|
<ReaderSettingExitMode
|
||||||
|
exitMode={settings.exitMode}
|
||||||
|
setExitMode={(value) => updateSetting('exitMode', value)}
|
||||||
|
/>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.label.skip_dup_chapters')}
|
||||||
|
checked={settings.shouldSkipDupChapters}
|
||||||
|
onChange={(_, checked) => updateSetting('shouldSkipDupChapters', checked)}
|
||||||
|
/>
|
||||||
|
{isOffsetDoubleSpreadPagesEditable(settings.readingMode.value) && (
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.label.offset_double_spread')}
|
||||||
|
checked={settings.shouldOffsetDoubleSpreads.value}
|
||||||
|
onChange={(_, checked) => updateSetting('shouldOffsetDoubleSpreads', checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings, ReaderExitMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderExitMode> = {
|
||||||
|
[ReaderExitMode.PREVIOUS]: {
|
||||||
|
title: 'global.label.previous',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[ReaderExitMode.MANGA]: {
|
||||||
|
title: 'manga.title_one',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const READER_EXIT_MODE_VALUES = Object.values(ReaderExitMode).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export const ReaderSettingExitMode = ({
|
||||||
|
exitMode,
|
||||||
|
setExitMode,
|
||||||
|
}: Pick<IReaderSettings, 'exitMode'> & {
|
||||||
|
setExitMode: (mode: ReaderExitMode) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
label={t('reader.settings.exit_mode')}
|
||||||
|
value={exitMode}
|
||||||
|
values={READER_EXIT_MODE_VALUES}
|
||||||
|
setValue={setExitMode}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 Stack from '@mui/material/Stack';
|
||||||
|
import { ReaderSettingBrightness } from '@/modules/reader/components/settings/filters/ReaderSettingBrightness.tsx';
|
||||||
|
import { ReaderSettingContrast } from '@/modules/reader/components/settings/filters/ReaderSettingContrast.tsx';
|
||||||
|
import { ReaderSettingSaturate } from '@/modules/reader/components/settings/filters/ReaderSettingSaturate.tsx';
|
||||||
|
import { ReaderSettingHue } from '@/modules/reader/components/settings/filters/ReaderSettingHue.tsx';
|
||||||
|
import { ReaderSettingRGBA } from '@/modules/reader/components/settings/filters/ReaderSettingRGBA.tsx';
|
||||||
|
import { ReaderSettingSepia } from '@/modules/reader/components/settings/filters/ReaderSettingSepia.tsx';
|
||||||
|
import { ReaderSettingGrayscale } from '@/modules/reader/components/settings/filters/ReaderSettingGrayscale.tsx';
|
||||||
|
import { ReaderSettingInvert } from '@/modules/reader/components/settings/filters/ReaderSettingInvert.tsx';
|
||||||
|
import { ReaderSettingsTypeProps } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
|
||||||
|
export const ReaderFilterSettings = ({ settings: { customFilter }, updateSetting }: ReaderSettingsTypeProps) => (
|
||||||
|
<Stack sx={{ gap: 2 }}>
|
||||||
|
<ReaderSettingBrightness
|
||||||
|
brightness={customFilter.brightness}
|
||||||
|
updateSetting={(key, value, commit) =>
|
||||||
|
updateSetting(
|
||||||
|
'customFilter',
|
||||||
|
{
|
||||||
|
...customFilter,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
commit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingContrast
|
||||||
|
contrast={customFilter.contrast}
|
||||||
|
updateSetting={(key, value, commit) =>
|
||||||
|
updateSetting(
|
||||||
|
'customFilter',
|
||||||
|
{
|
||||||
|
...customFilter,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
commit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingSaturate
|
||||||
|
saturate={customFilter.saturate}
|
||||||
|
updateSetting={(key, value, commit) =>
|
||||||
|
updateSetting(
|
||||||
|
'customFilter',
|
||||||
|
{
|
||||||
|
...customFilter,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
commit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingHue
|
||||||
|
hue={customFilter.hue}
|
||||||
|
updateSetting={(key, value, commit) =>
|
||||||
|
updateSetting(
|
||||||
|
'customFilter',
|
||||||
|
{
|
||||||
|
...customFilter,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
commit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingRGBA
|
||||||
|
rgba={customFilter.rgba}
|
||||||
|
updateSetting={(key, value, commit) =>
|
||||||
|
updateSetting(
|
||||||
|
'customFilter',
|
||||||
|
{
|
||||||
|
...customFilter,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
commit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingSepia
|
||||||
|
sepia={customFilter.sepia}
|
||||||
|
updateSetting={(value) =>
|
||||||
|
updateSetting('customFilter', {
|
||||||
|
...customFilter,
|
||||||
|
sepia: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingGrayscale
|
||||||
|
grayscale={customFilter.grayscale}
|
||||||
|
updateSetting={(value) =>
|
||||||
|
updateSetting('customFilter', {
|
||||||
|
...customFilter,
|
||||||
|
grayscale: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ReaderSettingInvert
|
||||||
|
invert={customFilter.invert}
|
||||||
|
updateSetting={(value) =>
|
||||||
|
updateSetting('customFilter', {
|
||||||
|
...customFilter,
|
||||||
|
invert: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingBrightness = ({
|
||||||
|
brightness,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'brightness'> & {
|
||||||
|
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
|
||||||
|
filter: Filter,
|
||||||
|
value: IReaderSettings['customFilter'][Filter],
|
||||||
|
commit: boolean,
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.brightness.title')}
|
||||||
|
checked={brightness.enabled}
|
||||||
|
onChange={(_, checked) => updateSetting('brightness', { ...brightness, enabled: checked }, true)}
|
||||||
|
/>
|
||||||
|
{brightness.enabled && (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.custom_filter.brightness.value')}
|
||||||
|
value={brightness.value}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.customFilter.brightness.value,
|
||||||
|
value: brightness.value,
|
||||||
|
step: 1,
|
||||||
|
min: 5,
|
||||||
|
max: 200,
|
||||||
|
onChange: (_, value) => {
|
||||||
|
updateSetting('brightness', { ...brightness, value: value as number }, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, value) => {
|
||||||
|
updateSetting('brightness', { ...brightness, value: value as number }, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingContrast = ({
|
||||||
|
contrast,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'contrast'> & {
|
||||||
|
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
|
||||||
|
filter: Filter,
|
||||||
|
value: IReaderSettings['customFilter'][Filter],
|
||||||
|
commit: boolean,
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.contrast.title')}
|
||||||
|
checked={contrast.enabled}
|
||||||
|
onChange={(_, checked) => updateSetting('contrast', { ...contrast, enabled: checked }, true)}
|
||||||
|
/>
|
||||||
|
{contrast.enabled && (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.custom_filter.contrast.value')}
|
||||||
|
value={contrast.value}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.customFilter.contrast.value,
|
||||||
|
value: contrast.value,
|
||||||
|
step: 1,
|
||||||
|
min: 5,
|
||||||
|
max: 200,
|
||||||
|
onChange: (_, newValue) => {
|
||||||
|
updateSetting('contrast', { ...contrast, value: newValue as number }, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, newValue) => {
|
||||||
|
updateSetting('contrast', { ...contrast, value: newValue as number }, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingGrayscale = ({
|
||||||
|
grayscale,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'grayscale'> & {
|
||||||
|
updateSetting: (grayscale: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.grayscale')}
|
||||||
|
checked={grayscale}
|
||||||
|
onChange={(_, checked) => updateSetting(checked)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingHue = ({
|
||||||
|
hue,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'hue'> & {
|
||||||
|
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
|
||||||
|
filter: Filter,
|
||||||
|
value: IReaderSettings['customFilter'][Filter],
|
||||||
|
commit: boolean,
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.hue.title')}
|
||||||
|
checked={hue.enabled}
|
||||||
|
onChange={(_, checked) => updateSetting('hue', { ...hue, enabled: checked }, true)}
|
||||||
|
/>
|
||||||
|
{hue.enabled && (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.custom_filter.hue.value')}
|
||||||
|
value={hue.value}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.customFilter.hue.value,
|
||||||
|
value: hue.value,
|
||||||
|
step: 1,
|
||||||
|
min: 0,
|
||||||
|
max: 200,
|
||||||
|
onChange: (_, newValue) => {
|
||||||
|
updateSetting('hue', { ...hue, value: newValue as number }, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, newValue) => {
|
||||||
|
updateSetting('hue', { ...hue, value: newValue as number }, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingInvert = ({
|
||||||
|
invert,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'invert'> & {
|
||||||
|
updateSetting: (invert: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.invert')}
|
||||||
|
checked={invert}
|
||||||
|
onChange={(_, checked) => updateSetting(checked)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
type RGBAType = keyof IReaderSettings['customFilter']['rgba']['value'];
|
||||||
|
|
||||||
|
const RGBA_TYPE_TO_TRANSLATION_KEY: Record<RGBAType, TranslationKey> = {
|
||||||
|
red: 'reader.settings.custom_filter.rgba.red',
|
||||||
|
green: 'reader.settings.custom_filter.rgba.green',
|
||||||
|
blue: 'reader.settings.custom_filter.rgba.blue',
|
||||||
|
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 = ({
|
||||||
|
rgba,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'rgba'> & {
|
||||||
|
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
|
||||||
|
filter: Filter,
|
||||||
|
value: IReaderSettings['customFilter'][Filter],
|
||||||
|
commit: boolean,
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.rgba.title')}
|
||||||
|
checked={rgba.enabled}
|
||||||
|
onChange={(_, checked) => updateSetting('rgba', { ...rgba, enabled: checked }, true)}
|
||||||
|
/>
|
||||||
|
{rgba.enabled && (
|
||||||
|
<Stack sx={{ gap: 1 }}>
|
||||||
|
{Object.entries(rgba.value).map(([key, value]) => (
|
||||||
|
<SliderInput
|
||||||
|
key={key}
|
||||||
|
label={t(RGBA_TYPE_TO_TRANSLATION_KEY[key as RGBAType])}
|
||||||
|
value={value}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
value,
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType],
|
||||||
|
step: 1,
|
||||||
|
min: 0,
|
||||||
|
max: RGBA_TYPE_TO_MAX_VALUE[key as RGBAType],
|
||||||
|
onChange: (_, newValue) => {
|
||||||
|
updateSetting(
|
||||||
|
'rgba',
|
||||||
|
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, newValue) => {
|
||||||
|
updateSetting(
|
||||||
|
'rgba',
|
||||||
|
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingSaturate = ({
|
||||||
|
saturate,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'saturate'> & {
|
||||||
|
updateSetting: <Filter extends keyof IReaderSettings['customFilter']>(
|
||||||
|
filter: Filter,
|
||||||
|
value: IReaderSettings['customFilter'][Filter],
|
||||||
|
commit: boolean,
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.saturate.title')}
|
||||||
|
checked={saturate.enabled}
|
||||||
|
onChange={(_, checked) => updateSetting('saturate', { ...saturate, enabled: checked }, true)}
|
||||||
|
/>
|
||||||
|
{saturate.enabled && (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.custom_filter.saturate.value')}
|
||||||
|
value={saturate.value}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.customFilter.saturate.value,
|
||||||
|
value: saturate.value,
|
||||||
|
step: 1,
|
||||||
|
min: 0,
|
||||||
|
max: 200,
|
||||||
|
onChange: (_, value) => {
|
||||||
|
updateSetting('saturate', { ...saturate, value: value as number }, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, value) => {
|
||||||
|
updateSetting('saturate', { ...saturate, value: value as number }, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingSepia = ({
|
||||||
|
sepia,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings['customFilter'], 'sepia'> & {
|
||||||
|
updateSetting: (sepia: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.custom_filter.sepia')}
|
||||||
|
checked={sepia}
|
||||||
|
onChange={(_, checked) => updateSetting(checked)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ReaderSettingProgressBarType } from '@/modules/reader/components/settings/general/ReaderSettingProgressBarType.tsx';
|
||||||
|
import { ReaderSettingProgressBarSize } from '@/modules/reader/components/settings/general/ReaderSettingProgressBarSize.tsx';
|
||||||
|
import { ReaderSettingProgressBarPosition } from '@/modules/reader/components/settings/general/ReaderSettingProgressBarPosition.tsx';
|
||||||
|
import {
|
||||||
|
IReaderSettings,
|
||||||
|
ProgressBarType,
|
||||||
|
ReaderOverlayMode,
|
||||||
|
ReaderSettingsTypeProps,
|
||||||
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ReaderSettingOverlayMode } from '@/modules/reader/components/settings/general/ReaderSettingOverlayMode.tsx';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { ReaderSettingBackgroundColor } from '@/modules/reader/components/settings/general/ReaderSettingBackgroundColor.tsx';
|
||||||
|
|
||||||
|
export const ReaderGeneralSettings = ({
|
||||||
|
overlayMode,
|
||||||
|
settings,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings, 'overlayMode'> & ReaderSettingsTypeProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack sx={{ gap: 2 }}>
|
||||||
|
<ReaderSettingOverlayMode
|
||||||
|
overlayMode={settings.overlayMode}
|
||||||
|
setOverlayMode={(value) => updateSetting('overlayMode', value)}
|
||||||
|
/>
|
||||||
|
<ReaderSettingBackgroundColor
|
||||||
|
backgroundColor={settings.backgroundColor}
|
||||||
|
updateSetting={(value) => updateSetting('backgroundColor', value)}
|
||||||
|
/>
|
||||||
|
<ReaderSettingProgressBarType
|
||||||
|
overlayMode={overlayMode}
|
||||||
|
progressBarType={settings.progressBarType}
|
||||||
|
setProgressBarType={(value) => updateSetting('progressBarType', value)}
|
||||||
|
/>
|
||||||
|
<ReaderSettingProgressBarSize
|
||||||
|
overlayMode={overlayMode}
|
||||||
|
progressBarType={settings.progressBarType}
|
||||||
|
progressBarSize={settings.progressBarSize}
|
||||||
|
setProgressBarSize={(...args) => updateSetting('progressBarSize', ...args)}
|
||||||
|
/>
|
||||||
|
<ReaderSettingProgressBarPosition
|
||||||
|
overlayMode={overlayMode}
|
||||||
|
progressBarPosition={settings.progressBarPosition}
|
||||||
|
setProgressBarPosition={(value) => updateSetting('progressBarPosition', value)}
|
||||||
|
/>
|
||||||
|
{(settings.progressBarType === ProgressBarType.HIDDEN || overlayMode === ReaderOverlayMode.MOBILE) && (
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.label.show_page_number')}
|
||||||
|
checked={settings.shouldShowPageNumber}
|
||||||
|
onChange={(_, checked) => updateSetting('shouldShowPageNumber', checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
import { IReaderSettingsWithDefaultFlag, ReaderBackgroundColor } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderBackgroundColor> = {
|
||||||
|
[ReaderBackgroundColor.THEME]: {
|
||||||
|
title: 'settings.appearance.theme.title',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[ReaderBackgroundColor.BLACK]: {
|
||||||
|
title: 'global.colors.black',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[ReaderBackgroundColor.GRAY]: {
|
||||||
|
title: 'global.colors.gray',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[ReaderBackgroundColor.WHITE]: {
|
||||||
|
title: 'global.colors.white',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const READER_BACKGROUND_COLOR_VALUES = Object.values(ReaderBackgroundColor).filter(
|
||||||
|
(value) => typeof value === 'number',
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ReaderSettingBackgroundColor = ({
|
||||||
|
backgroundColor,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettingsWithDefaultFlag, 'backgroundColor'> & {
|
||||||
|
updateSetting: (color: ReaderBackgroundColor) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
label={t('reader.settings.background_color')}
|
||||||
|
value={backgroundColor}
|
||||||
|
values={READER_BACKGROUND_COLOR_VALUES}
|
||||||
|
setValue={updateSetting}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import PhoneIphoneIcon from '@mui/icons-material/PhoneIphone';
|
||||||
|
import ComputerIcon from '@mui/icons-material/Computer';
|
||||||
|
import AutoModeIcon from '@mui/icons-material/AutoMode';
|
||||||
|
import { IReaderSettings, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderOverlayMode> = {
|
||||||
|
[ReaderOverlayMode.AUTO]: {
|
||||||
|
title: 'global.label.auto',
|
||||||
|
icon: <AutoModeIcon />,
|
||||||
|
},
|
||||||
|
[ReaderOverlayMode.DESKTOP]: {
|
||||||
|
title: 'global.label.desktop',
|
||||||
|
icon: <ComputerIcon />,
|
||||||
|
},
|
||||||
|
[ReaderOverlayMode.MOBILE]: {
|
||||||
|
title: 'global.label.mobile',
|
||||||
|
icon: <PhoneIphoneIcon />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const READING_MODE_VALUES = Object.values(ReaderOverlayMode).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export const ReaderSettingOverlayMode = ({
|
||||||
|
overlayMode,
|
||||||
|
setOverlayMode,
|
||||||
|
}: Pick<IReaderSettings, 'overlayMode'> & {
|
||||||
|
setOverlayMode: (mode: ReaderOverlayMode) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
label={t('reader.settings.overlay_mode')}
|
||||||
|
value={overlayMode}
|
||||||
|
values={READING_MODE_VALUES}
|
||||||
|
setValue={setOverlayMode}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||||
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
|
import { IReaderSettings, ProgressBarPosition, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ProgressBarPosition> = {
|
||||||
|
[ProgressBarPosition.BOTTOM]: {
|
||||||
|
title: 'global.label.bottom',
|
||||||
|
icon: <ArrowBackIosNewIcon sx={{ transform: 'rotate(90deg)' }} />,
|
||||||
|
},
|
||||||
|
[ProgressBarPosition.LEFT]: {
|
||||||
|
title: 'global.label.left',
|
||||||
|
icon: <ArrowForwardIosIcon />,
|
||||||
|
},
|
||||||
|
[ProgressBarPosition.RIGHT]: {
|
||||||
|
title: 'global.label.right',
|
||||||
|
icon: <ArrowBackIosNewIcon />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROGRESS_BAR_POSITION_VALUES = Object.values(ProgressBarPosition).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export const ReaderSettingProgressBarPosition = ({
|
||||||
|
overlayMode,
|
||||||
|
progressBarPosition,
|
||||||
|
setProgressBarPosition,
|
||||||
|
}: Pick<IReaderSettings, 'progressBarPosition' | 'overlayMode'> & {
|
||||||
|
setProgressBarPosition: (position: ProgressBarPosition) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (overlayMode !== ReaderOverlayMode.DESKTOP) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
label={t('reader.settings.progress_bar.position')}
|
||||||
|
value={progressBarPosition}
|
||||||
|
values={PROGRESS_BAR_POSITION_VALUES}
|
||||||
|
setValue={setProgressBarPosition}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings, ProgressBarType, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingProgressBarSize = ({
|
||||||
|
overlayMode,
|
||||||
|
progressBarType,
|
||||||
|
progressBarSize,
|
||||||
|
setProgressBarSize,
|
||||||
|
}: Pick<IReaderSettings, 'progressBarType' | 'progressBarSize' | 'overlayMode'> & {
|
||||||
|
setProgressBarSize: (size: number, commit: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isChangeable = overlayMode === ReaderOverlayMode.DESKTOP && progressBarType === ProgressBarType.STANDARD;
|
||||||
|
|
||||||
|
if (!isChangeable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.progress_bar.size')}
|
||||||
|
value={t('global.value', { value: progressBarSize, unit: t('global.unit.px') })}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.progressBarSize,
|
||||||
|
value: progressBarSize,
|
||||||
|
step: 1,
|
||||||
|
min: 2,
|
||||||
|
max: 20,
|
||||||
|
onChange: (_, value) => {
|
||||||
|
setProgressBarSize(value as number, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, value) => {
|
||||||
|
setProgressBarSize(value as number, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings, ProgressBarType, ReaderOverlayMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import { HiddenProgressBarIcon } from '@/assets/icons/svg/HiddenProgressBarIcon.tsx';
|
||||||
|
import { StandardProgressBarIcon } from '@/assets/icons/svg/StandardProgressBarIcon.tsx';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ProgressBarType> = {
|
||||||
|
[ProgressBarType.HIDDEN]: {
|
||||||
|
title: 'global.label.hidden',
|
||||||
|
icon: <HiddenProgressBarIcon />,
|
||||||
|
},
|
||||||
|
[ProgressBarType.STANDARD]: {
|
||||||
|
title: 'global.label.standard',
|
||||||
|
icon: <StandardProgressBarIcon />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROGRESS_BAR_TYPE_VALUES = Object.values(ProgressBarType).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export const ReaderSettingProgressBarType = ({
|
||||||
|
overlayMode,
|
||||||
|
progressBarType,
|
||||||
|
setProgressBarType,
|
||||||
|
}: Pick<IReaderSettings, 'progressBarType' | 'overlayMode'> & {
|
||||||
|
setProgressBarType: (progressBarType: ProgressBarType) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (overlayMode !== ReaderOverlayMode.DESKTOP) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
label={t('reader.settings.progress_bar.style')}
|
||||||
|
value={progressBarType}
|
||||||
|
values={PROGRESS_BAR_TYPE_VALUES}
|
||||||
|
setValue={setProgressBarType}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { ContextType } from 'react';
|
||||||
|
import { ReaderSettingReadingMode } from '@/modules/reader/components/settings/layout/ReaderSettingReadingMode.tsx';
|
||||||
|
import { ReaderSettingReadingDirection } from '@/modules/reader/components/settings/layout/ReaderSettingReadingDirection.tsx';
|
||||||
|
import { ReaderSettingTapZoneLayout } from '@/modules/reader/components/settings/layout/ReaderSettingTapZoneLayout.tsx';
|
||||||
|
import { ReaderTapZoneContext } from '@/modules/reader/contexts/ReaderTapZoneContext.tsx';
|
||||||
|
import { ReaderSettingTapZoneInvertMode } from '@/modules/reader/components/settings/layout/ReaderSettingTapZoneInvertMode.tsx';
|
||||||
|
import { ReaderSettingPageScaleMode } from '@/modules/reader/components/settings/layout/ReaderSettingPageScaleMode.tsx';
|
||||||
|
import { ReaderSettingStretchPage } from '@/modules/reader/components/settings/layout/ReaderSettingStretchPage.tsx';
|
||||||
|
import { ReaderSettingsTypeProps } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ReaderSettingPageGap } from '@/modules/reader/components/settings/layout/ReaderSettingPageGap.tsx';
|
||||||
|
import { ReaderSettingWidth } from '@/modules/reader/components/settings/layout/ReaderSettingWidth.tsx';
|
||||||
|
|
||||||
|
export const ReaderLayoutSettings = ({
|
||||||
|
setShowPreview,
|
||||||
|
settings,
|
||||||
|
updateSetting,
|
||||||
|
isDefaultable,
|
||||||
|
onDefault,
|
||||||
|
}: ReaderSettingsTypeProps & {
|
||||||
|
setShowPreview: ContextType<typeof ReaderTapZoneContext>['setShowPreview'];
|
||||||
|
}) => (
|
||||||
|
<Stack sx={{ gap: 2 }}>
|
||||||
|
<ReaderSettingReadingMode
|
||||||
|
readingMode={settings.readingMode}
|
||||||
|
setReadingMode={(value) => updateSetting('readingMode', value)}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => onDefault?.('readingMode')}
|
||||||
|
/>
|
||||||
|
<ReaderSettingPageGap
|
||||||
|
pageGap={settings.pageGap}
|
||||||
|
readingMode={settings.readingMode}
|
||||||
|
updateSetting={(...args) => updateSetting('pageGap', ...args)}
|
||||||
|
/>
|
||||||
|
<ReaderSettingReadingDirection
|
||||||
|
readingDirection={settings.readingDirection}
|
||||||
|
setReadingDirection={(value) => updateSetting('readingDirection', value)}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => onDefault?.('readingDirection')}
|
||||||
|
/>
|
||||||
|
<ReaderSettingTapZoneLayout
|
||||||
|
tapZoneLayout={settings.tapZoneLayout}
|
||||||
|
setTapZoneLayout={(value) => {
|
||||||
|
setShowPreview(true);
|
||||||
|
updateSetting('tapZoneLayout', value);
|
||||||
|
}}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => {
|
||||||
|
setShowPreview(true);
|
||||||
|
onDefault?.('tapZoneLayout');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ReaderSettingTapZoneInvertMode
|
||||||
|
tapZoneInvertMode={settings.tapZoneInvertMode}
|
||||||
|
setTapZoneInvertMode={(value) => {
|
||||||
|
setShowPreview(true);
|
||||||
|
updateSetting('tapZoneInvertMode', value);
|
||||||
|
}}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => {
|
||||||
|
setShowPreview(true);
|
||||||
|
onDefault?.('tapZoneInvertMode');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ReaderSettingPageScaleMode
|
||||||
|
pageScaleMode={settings.pageScaleMode}
|
||||||
|
setPageScaleMode={(value) => updateSetting('pageScaleMode', value)}
|
||||||
|
isDefaultable={isDefaultable}
|
||||||
|
onDefault={() => onDefault?.('pageScaleMode')}
|
||||||
|
/>
|
||||||
|
<ReaderSettingStretchPage
|
||||||
|
pageScaleMode={settings.pageScaleMode.value}
|
||||||
|
shouldStretchPage={settings.shouldStretchPage.value}
|
||||||
|
setShouldStretchPage={(value) => updateSetting('shouldStretchPage', value)}
|
||||||
|
/>
|
||||||
|
<ReaderSettingWidth
|
||||||
|
readerWidth={settings.readerWidth.value}
|
||||||
|
pageScaleMode={settings.pageScaleMode.value}
|
||||||
|
updateSetting={(...args) => updateSetting(...args)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingPageGap = ({
|
||||||
|
pageGap,
|
||||||
|
readingMode,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettingsWithDefaultFlag, 'pageGap' | 'readingMode'> & {
|
||||||
|
updateSetting: (gap: number, commit: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
{[ReadingMode.CONTINUOUS_HORIZONTAL, ReadingMode.CONTINUOUS_VERTICAL].includes(readingMode.value) && (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.label.page_gap')}
|
||||||
|
value={t('global.value', { value: pageGap.value, unit: t('global.unit.px') })}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.readerWidth.value,
|
||||||
|
value: pageGap.value,
|
||||||
|
step: 1,
|
||||||
|
min: 0,
|
||||||
|
max: 20,
|
||||||
|
onChange: (_, value) => {
|
||||||
|
updateSetting(value as number, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, value) => {
|
||||||
|
updateSetting(value as number, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
IReaderSettings,
|
||||||
|
IReaderSettingsWithDefaultFlag,
|
||||||
|
ReadingDirection,
|
||||||
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import {
|
||||||
|
PAGE_SCALE_VALUE_TO_DISPLAY_DATA,
|
||||||
|
READER_PAGE_SCALE_MODE_VALUES,
|
||||||
|
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
|
export const ReaderSettingPageScaleMode = ({
|
||||||
|
pageScaleMode,
|
||||||
|
setPageScaleMode,
|
||||||
|
...buttonSelectInputProps
|
||||||
|
}: Pick<IReaderSettingsWithDefaultFlag, 'pageScaleMode'> &
|
||||||
|
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setPageScaleMode: (mode: IReaderSettings['pageScaleMode']) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
{...buttonSelectInputProps}
|
||||||
|
label={t('reader.settings.page_scale.title')}
|
||||||
|
value={pageScaleMode.isDefault ? undefined : pageScaleMode.value}
|
||||||
|
values={READER_PAGE_SCALE_MODE_VALUES}
|
||||||
|
setValue={setPageScaleMode}
|
||||||
|
valueToDisplayData={PAGE_SCALE_VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
|
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
|
||||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IReaderSettings, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
import { IReaderSettingsWithDefaultFlag, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
import { MultiValueButtonDefaultableProps, 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';
|
||||||
|
|
||||||
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingDirection> = {
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingDirection> = {
|
||||||
@@ -26,18 +26,21 @@ const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingDirection> = {
|
|||||||
|
|
||||||
const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number');
|
const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
export const ReaderBottomBarMobileReadingDirection = ({
|
export const ReaderSettingReadingDirection = ({
|
||||||
readingDirection,
|
readingDirection,
|
||||||
setReadingDirection,
|
setReadingDirection,
|
||||||
}: Pick<IReaderSettings, 'readingDirection'> & {
|
...buttonSelectInputProps
|
||||||
setReadingDirection: (readingDirection: ReadingDirection) => void;
|
}: Pick<IReaderSettingsWithDefaultFlag, 'readingDirection'> &
|
||||||
}) => {
|
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setReadingDirection: (readingDirection: ReadingDirection) => void;
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonSelectInput
|
<ButtonSelectInput
|
||||||
|
{...buttonSelectInputProps}
|
||||||
label={t('reader.settings.label.reading_direction')}
|
label={t('reader.settings.label.reading_direction')}
|
||||||
value={readingDirection}
|
value={readingDirection.isDefault ? undefined : readingDirection.value}
|
||||||
values={READING_DIRECTION_VALUES}
|
values={READING_DIRECTION_VALUES}
|
||||||
setValue={setReadingDirection}
|
setValue={setReadingDirection}
|
||||||
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import {
|
||||||
|
READING_MODE_VALUE_TO_DISPLAY_DATA,
|
||||||
|
READING_MODE_VALUES,
|
||||||
|
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
import { MultiValueButtonDefaultableProps } from '@/modules/core/Core.types.ts';
|
||||||
|
|
||||||
|
export const ReaderSettingReadingMode = ({
|
||||||
|
readingMode,
|
||||||
|
setReadingMode,
|
||||||
|
...buttonSelectInputProps
|
||||||
|
}: Pick<IReaderSettingsWithDefaultFlag, 'readingMode'> &
|
||||||
|
Pick<MultiValueButtonDefaultableProps<ReadingMode>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setReadingMode: (mode: ReadingMode) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
{...buttonSelectInputProps}
|
||||||
|
label={t('reader.settings.label.reading_mode')}
|
||||||
|
value={readingMode.isDefault ? undefined : readingMode.value}
|
||||||
|
values={READING_MODE_VALUES}
|
||||||
|
setValue={setReadingMode}
|
||||||
|
valueToDisplayData={READING_MODE_VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingStretchPage = ({
|
||||||
|
pageScaleMode,
|
||||||
|
shouldStretchPage,
|
||||||
|
setShouldStretchPage,
|
||||||
|
}: Pick<IReaderSettings, 'pageScaleMode' | 'shouldStretchPage'> & {
|
||||||
|
setShouldStretchPage: (mode: IReaderSettings['shouldStretchPage']) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED[pageScaleMode]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShouldStretchPage(!shouldStretchPage)}
|
||||||
|
variant={shouldStretchPage ? 'contained' : 'outlined'}
|
||||||
|
>
|
||||||
|
{t('reader.settings.page_scale.stretch')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { MultiValueButtonDefaultableProps, ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import {
|
||||||
|
IReaderSettings,
|
||||||
|
IReaderSettingsWithDefaultFlag,
|
||||||
|
ReadingDirection,
|
||||||
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { TapZoneInvertMode } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
|
||||||
|
enum TapZonesInvertOption {
|
||||||
|
NONE,
|
||||||
|
HORIZONTAL,
|
||||||
|
VERTICAL,
|
||||||
|
BOTH,
|
||||||
|
}
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<TapZonesInvertOption> = {
|
||||||
|
[TapZonesInvertOption.NONE]: {
|
||||||
|
title: 'global.label.none',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[TapZonesInvertOption.HORIZONTAL]: {
|
||||||
|
title: 'global.label.horizontal',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[TapZonesInvertOption.VERTICAL]: {
|
||||||
|
title: 'global.label.vertical',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[TapZonesInvertOption.BOTH]: {
|
||||||
|
title: 'global.label.both',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const TAP_ZONES_INVERT_OPTION_VALUES = Object.values(TapZonesInvertOption).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
const TAP_ZONES_INVERT_OPTION_TO_SETTING: Record<TapZonesInvertOption, TapZoneInvertMode> = {
|
||||||
|
[TapZonesInvertOption.NONE]: {
|
||||||
|
vertical: false,
|
||||||
|
horizontal: false,
|
||||||
|
},
|
||||||
|
[TapZonesInvertOption.HORIZONTAL]: {
|
||||||
|
vertical: false,
|
||||||
|
horizontal: true,
|
||||||
|
},
|
||||||
|
[TapZonesInvertOption.VERTICAL]: {
|
||||||
|
vertical: true,
|
||||||
|
horizontal: false,
|
||||||
|
},
|
||||||
|
[TapZonesInvertOption.BOTH]: {
|
||||||
|
vertical: true,
|
||||||
|
horizontal: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertTapZoneInvertModeToOption = ({ vertical, horizontal }: TapZoneInvertMode): TapZonesInvertOption => {
|
||||||
|
if (vertical && horizontal) {
|
||||||
|
return TapZonesInvertOption.BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertical) {
|
||||||
|
return TapZonesInvertOption.VERTICAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizontal) {
|
||||||
|
return TapZonesInvertOption.HORIZONTAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TapZonesInvertOption.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReaderSettingTapZoneInvertMode = ({
|
||||||
|
tapZoneInvertMode,
|
||||||
|
setTapZoneInvertMode,
|
||||||
|
...buttonSelectInputProps
|
||||||
|
}: Pick<IReaderSettingsWithDefaultFlag, 'tapZoneInvertMode'> &
|
||||||
|
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setTapZoneInvertMode: (invert: IReaderSettings['tapZoneInvertMode']) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
{...buttonSelectInputProps}
|
||||||
|
label={t('reader.settings.tap_zones.invert')}
|
||||||
|
value={tapZoneInvertMode.isDefault ? undefined : convertTapZoneInvertModeToOption(tapZoneInvertMode.value)}
|
||||||
|
values={TAP_ZONES_INVERT_OPTION_VALUES}
|
||||||
|
setValue={(value) => setTapZoneInvertMode(TAP_ZONES_INVERT_OPTION_TO_SETTING[value])}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
||||||
|
import { MultiValueButtonDefaultableProps, ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import { IReaderSettingsWithDefaultFlag, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
|
||||||
|
|
||||||
|
const VALUE_TO_DISPLAY_DATA: ValueToDisplayData<TapZoneLayouts> = {
|
||||||
|
[TapZoneLayouts.EDGE]: {
|
||||||
|
title: 'reader.settings.tap_zones.edge',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[TapZoneLayouts.KINDLE]: {
|
||||||
|
title: 'reader.settings.tap_zones.kindle',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[TapZoneLayouts.L_SHAPE]: {
|
||||||
|
title: 'reader.settings.tap_zones.l_shape',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
[TapZoneLayouts.RIGHT_LEFT]: {
|
||||||
|
title: 'reader.settings.tap_zones.right_left',
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const READER_TAP_ZONE_LAYOUT_VALUES = Object.values(TapZoneLayouts).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export const ReaderSettingTapZoneLayout = ({
|
||||||
|
tapZoneLayout,
|
||||||
|
setTapZoneLayout,
|
||||||
|
...buttonSelectInputProps
|
||||||
|
}: Pick<IReaderSettingsWithDefaultFlag, 'tapZoneLayout'> &
|
||||||
|
Pick<MultiValueButtonDefaultableProps<ReadingDirection>, 'isDefaultable' | 'onDefault'> & {
|
||||||
|
setTapZoneLayout: (layout: TapZoneLayouts) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonSelectInput
|
||||||
|
{...buttonSelectInputProps}
|
||||||
|
label={t('reader.settings.tap_zones.title')}
|
||||||
|
value={tapZoneLayout.isDefault ? undefined : tapZoneLayout.value}
|
||||||
|
values={READER_TAP_ZONE_LAYOUT_VALUES}
|
||||||
|
setValue={setTapZoneLayout}
|
||||||
|
valueToDisplayData={VALUE_TO_DISPLAY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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 Stack from '@mui/material/Stack';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
import { isReaderWidthEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
|
|
||||||
|
export const ReaderSettingWidth = ({
|
||||||
|
readerWidth,
|
||||||
|
pageScaleMode,
|
||||||
|
updateSetting,
|
||||||
|
}: Pick<IReaderSettings, 'readerWidth' | 'pageScaleMode'> & {
|
||||||
|
updateSetting: (...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!isReaderWidthEditable(pageScaleMode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<CheckboxInput
|
||||||
|
label={t('reader.settings.label.limit_reader_width')}
|
||||||
|
checked={readerWidth.enabled}
|
||||||
|
onChange={(_, checked) => updateSetting('readerWidth', { ...readerWidth, enabled: checked }, true)}
|
||||||
|
/>
|
||||||
|
{readerWidth.enabled && (
|
||||||
|
<SliderInput
|
||||||
|
label={t('reader.settings.label.reader_width')}
|
||||||
|
value={t('global.value', { value: readerWidth.value, unit: '%' })}
|
||||||
|
slotProps={{
|
||||||
|
slider: {
|
||||||
|
defaultValue: DEFAULT_READER_SETTINGS.readerWidth.value,
|
||||||
|
value: readerWidth.value,
|
||||||
|
step: 1,
|
||||||
|
min: 10,
|
||||||
|
max: 100,
|
||||||
|
onChange: (_, value) => {
|
||||||
|
updateSetting('readerWidth', { ...readerWidth, value: value as number }, false);
|
||||||
|
},
|
||||||
|
onChangeCommitted: (_, value) => {
|
||||||
|
updateSetting('readerWidth', { ...readerWidth, value: value as number }, true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 {
|
|
||||||
IReaderSettings,
|
|
||||||
ProgressBarPosition,
|
|
||||||
ProgressBarType,
|
|
||||||
ReaderPageScaleMode,
|
|
||||||
ReadingDirection,
|
|
||||||
ReadingMode,
|
|
||||||
} from '@/modules/reader/types/Reader.types.ts';
|
|
||||||
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
|
||||||
|
|
||||||
export const DEFAULT_READER_SETTINGS: IReaderSettings = {
|
|
||||||
staticNav: false,
|
|
||||||
showPageNumber: true,
|
|
||||||
loadNextOnEnding: false,
|
|
||||||
skipDupChapters: true,
|
|
||||||
readerWidth: 50,
|
|
||||||
tapZoneLayout: TapZoneLayouts.RIGHT_LEFT,
|
|
||||||
tapZoneInvertMode: { vertical: false, horizontal: false },
|
|
||||||
progressBarType: ProgressBarType.STANDARD,
|
|
||||||
progressBarSize: 4,
|
|
||||||
progressBarPosition: ProgressBarPosition.BOTTOM,
|
|
||||||
pageScaleMode: ReaderPageScaleMode.ORIGINAL,
|
|
||||||
shouldScalePage: false,
|
|
||||||
shouldOffsetDoubleSpreads: false,
|
|
||||||
readingDirection: ReadingDirection.LTR,
|
|
||||||
readingMode: ReadingMode.SINGLE_PAGE,
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { TooltipProps } from '@mui/material/Tooltip';
|
|
||||||
import { ProgressBarPosition } from '@/modules/reader/types/Reader.types.ts';
|
|
||||||
|
|
||||||
export const READER_PROGRESS_BAR_POSITION_TO_PLACEMENT: Record<ProgressBarPosition, TooltipProps['placement']> = {
|
|
||||||
[ProgressBarPosition.BOTTOM]: 'top',
|
|
||||||
[ProgressBarPosition.LEFT]: 'right',
|
|
||||||
[ProgressBarPosition.RIGHT]: 'left',
|
|
||||||
};
|
|
||||||
@@ -6,10 +6,206 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft';
|
||||||
|
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||||
|
import ZoomOutMapIcon from '@mui/icons-material/ZoomOutMap';
|
||||||
|
import ExpandIcon from '@mui/icons-material/Expand';
|
||||||
|
import CropOriginalIcon from '@mui/icons-material/CropOriginal';
|
||||||
|
import { TooltipProps } from '@mui/material/Tooltip';
|
||||||
import { Direction } from '@mui/material/styles';
|
import { Direction } from '@mui/material/styles';
|
||||||
import { ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
import { ValueToDisplayData } from '@/modules/core/Core.types.ts';
|
||||||
|
import {
|
||||||
|
IReaderSettings,
|
||||||
|
IReaderSettingsGlobal,
|
||||||
|
ProgressBarPosition,
|
||||||
|
ProgressBarType,
|
||||||
|
ReaderBackgroundColor,
|
||||||
|
ReaderExitMode,
|
||||||
|
ReaderOverlayMode,
|
||||||
|
ReaderPageScaleMode,
|
||||||
|
ReadingDirection,
|
||||||
|
ReadingMode,
|
||||||
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { SinglePageIcon } from '@/assets/icons/svg/SinglePageIcon.tsx';
|
||||||
|
import { DoublePageIcon } from '@/assets/icons/svg/DoublePageIcon.tsx';
|
||||||
|
import { ContinuousVerticalPageIcon } from '@/assets/icons/svg/ContinuousVerticalPageIcon.tsx';
|
||||||
|
import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx';
|
||||||
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
|
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
||||||
|
|
||||||
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',
|
||||||
[ReadingDirection.RTL]: 'rtl',
|
[ReadingDirection.RTL]: 'rtl',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GLOBAL_READER_SETTING_OBJECT: Record<keyof IReaderSettingsGlobal, undefined> = {
|
||||||
|
overlayMode: undefined,
|
||||||
|
exitMode: undefined,
|
||||||
|
customFilter: undefined,
|
||||||
|
shouldSkipDupChapters: undefined,
|
||||||
|
progressBarType: undefined,
|
||||||
|
progressBarSize: undefined,
|
||||||
|
progressBarPosition: undefined,
|
||||||
|
shouldShowPageNumber: undefined,
|
||||||
|
isStaticNav: undefined,
|
||||||
|
backgroundColor: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GLOBAL_READER_SETTING_KEYS = Object.keys(GLOBAL_READER_SETTING_OBJECT);
|
||||||
|
|
||||||
|
export const DEFAULT_READER_SETTINGS: IReaderSettings = {
|
||||||
|
readerWidth: { value: 50, enabled: false },
|
||||||
|
overlayMode: ReaderOverlayMode.AUTO,
|
||||||
|
tapZoneLayout: TapZoneLayouts.RIGHT_LEFT,
|
||||||
|
tapZoneInvertMode: { vertical: false, horizontal: false },
|
||||||
|
progressBarType: ProgressBarType.STANDARD,
|
||||||
|
progressBarSize: 4,
|
||||||
|
progressBarPosition: ProgressBarPosition.BOTTOM,
|
||||||
|
pageScaleMode: ReaderPageScaleMode.ORIGINAL,
|
||||||
|
shouldStretchPage: false,
|
||||||
|
shouldOffsetDoubleSpreads: false,
|
||||||
|
shouldSkipDupChapters: true,
|
||||||
|
shouldShowPageNumber: true,
|
||||||
|
isStaticNav: false,
|
||||||
|
readingDirection: ReadingDirection.LTR,
|
||||||
|
readingMode: ReadingMode.SINGLE_PAGE,
|
||||||
|
exitMode: ReaderExitMode.PREVIOUS,
|
||||||
|
backgroundColor: ReaderBackgroundColor.THEME,
|
||||||
|
customFilter: {
|
||||||
|
brightness: {
|
||||||
|
value: 100,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
contrast: {
|
||||||
|
value: 100,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
saturate: {
|
||||||
|
value: 100,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
hue: {
|
||||||
|
value: 0,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
rgba: {
|
||||||
|
value: {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 0,
|
||||||
|
alpha: 0,
|
||||||
|
},
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
sepia: false,
|
||||||
|
grayscale: false,
|
||||||
|
invert: false,
|
||||||
|
},
|
||||||
|
pageGap: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const READER_PROGRESS_BAR_POSITION_TO_PLACEMENT: Record<ProgressBarPosition, TooltipProps['placement']> = {
|
||||||
|
[ProgressBarPosition.BOTTOM]: 'top',
|
||||||
|
[ProgressBarPosition.LEFT]: 'right',
|
||||||
|
[ProgressBarPosition.RIGHT]: 'left',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const READING_DIRECTION_VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingDirection> = {
|
||||||
|
[ReadingDirection.LTR]: {
|
||||||
|
title: 'reader.settings.reading_direction.ltr',
|
||||||
|
icon: <ArrowCircleRightIcon />,
|
||||||
|
},
|
||||||
|
[ReadingDirection.RTL]: {
|
||||||
|
title: 'reader.settings.reading_direction.rtl',
|
||||||
|
icon: <ArrowCircleLeftIcon />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export const PAGE_SCALE_VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReaderPageScaleMode> = {
|
||||||
|
[ReaderPageScaleMode.WIDTH]: {
|
||||||
|
title: 'reader.settings.page_scale.width',
|
||||||
|
icon: <ExpandIcon sx={{ transform: 'rotate(90deg)' }} />,
|
||||||
|
},
|
||||||
|
[ReaderPageScaleMode.HEIGHT]: {
|
||||||
|
title: 'reader.settings.page_scale.height',
|
||||||
|
icon: <ExpandIcon />,
|
||||||
|
},
|
||||||
|
[ReaderPageScaleMode.SCREEN]: {
|
||||||
|
title: 'reader.settings.page_scale.screen',
|
||||||
|
icon: <ZoomOutMapIcon />,
|
||||||
|
},
|
||||||
|
[ReaderPageScaleMode.ORIGINAL]: {
|
||||||
|
title: 'reader.settings.page_scale.original',
|
||||||
|
icon: <CropOriginalIcon />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const READER_PAGE_SCALE_MODE_VALUES = Object.values(ReaderPageScaleMode).filter(
|
||||||
|
(value) => typeof value === 'number',
|
||||||
|
);
|
||||||
|
|
||||||
|
export const READER_PAGE_SCALE_MODE_TO_SCALING_ALLOWED: Record<ReaderPageScaleMode, boolean> = {
|
||||||
|
[ReaderPageScaleMode.WIDTH]: true,
|
||||||
|
[ReaderPageScaleMode.HEIGHT]: true,
|
||||||
|
[ReaderPageScaleMode.SCREEN]: true,
|
||||||
|
[ReaderPageScaleMode.ORIGINAL]: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const READING_MODE_VALUE_TO_DISPLAY_DATA: ValueToDisplayData<ReadingMode> = {
|
||||||
|
[ReadingMode.SINGLE_PAGE]: {
|
||||||
|
title: 'reader.settings.reader_type.label.single_page',
|
||||||
|
icon: <SinglePageIcon />,
|
||||||
|
},
|
||||||
|
[ReadingMode.DOUBLE_PAGE]: {
|
||||||
|
title: 'reader.settings.reader_type.label.double_page',
|
||||||
|
icon: <DoublePageIcon />,
|
||||||
|
},
|
||||||
|
[ReadingMode.CONTINUOUS_VERTICAL]: {
|
||||||
|
title: 'reader.settings.reader_type.label.continuous_vertical',
|
||||||
|
icon: <ContinuousVerticalPageIcon />,
|
||||||
|
},
|
||||||
|
[ReadingMode.CONTINUOUS_HORIZONTAL]: {
|
||||||
|
title: 'reader.settings.reader_type.label.continuous_horizontal',
|
||||||
|
icon: <ContinuousHorizontalPageIcon />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number');
|
||||||
|
|
||||||
|
export enum ReaderSettingTab {
|
||||||
|
LAYOUT,
|
||||||
|
GENERAL,
|
||||||
|
FILTER,
|
||||||
|
BEHAVIOUR,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const READER_SETTING_TABS: Record<
|
||||||
|
ReaderSettingTab,
|
||||||
|
{
|
||||||
|
id: ReaderSettingTab;
|
||||||
|
label: TranslationKey;
|
||||||
|
supportsTouchDevices: boolean;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
[ReaderSettingTab.LAYOUT]: {
|
||||||
|
id: ReaderSettingTab.LAYOUT,
|
||||||
|
label: 'reader.settings.label.layout',
|
||||||
|
supportsTouchDevices: true,
|
||||||
|
},
|
||||||
|
[ReaderSettingTab.GENERAL]: {
|
||||||
|
id: ReaderSettingTab.GENERAL,
|
||||||
|
label: 'global.label.general',
|
||||||
|
supportsTouchDevices: true,
|
||||||
|
},
|
||||||
|
[ReaderSettingTab.FILTER]: {
|
||||||
|
id: ReaderSettingTab.FILTER,
|
||||||
|
label: 'reader.settings.custom_filter.title',
|
||||||
|
supportsTouchDevices: true,
|
||||||
|
},
|
||||||
|
[ReaderSettingTab.BEHAVIOUR]: {
|
||||||
|
id: ReaderSettingTab.BEHAVIOUR,
|
||||||
|
label: 'reader.settings.label.behaviour',
|
||||||
|
supportsTouchDevices: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
72
src/modules/reader/screens/DefaultReaderSettings.tsx
Normal file
72
src/modules/reader/screens/DefaultReaderSettings.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import { useLayoutEffect, useState } from 'react';
|
||||||
|
import { useDefaultReaderSettingsWithDefaultFlag } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
||||||
|
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
|
||||||
|
import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
|
||||||
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
|
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
import { ReaderSettingsTabs } from '@/modules/reader/components/settings/ReaderSettingsTabs.tsx';
|
||||||
|
|
||||||
|
export const DefaultReaderSettings = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setTitle, setAction } = useNavBarContext();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setTitle(t('reader.settings.title.default_reader_settings'));
|
||||||
|
setAction(null);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setTitle('');
|
||||||
|
setAction(null);
|
||||||
|
};
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings,
|
||||||
|
request: { loading, error, refetch },
|
||||||
|
} = useDefaultReaderSettingsWithDefaultFlag();
|
||||||
|
|
||||||
|
const updateSetting = <Setting extends keyof IReaderSettings>(
|
||||||
|
key: Setting,
|
||||||
|
value: IReaderSettings[Setting],
|
||||||
|
commit?: boolean,
|
||||||
|
) => {
|
||||||
|
ReaderService.updateSetting({ id: -1 }, key, value, commit, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingPlaceholder />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<EmptyViewAbsoluteCentered
|
||||||
|
message={t('global.error.label.failed_to_load_data')}
|
||||||
|
messageExtra={error.message}
|
||||||
|
retry={() => refetch().catch(defaultPromiseErrorHandler('DefaultReaderSettings::refetch'))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReaderSettingsTabs
|
||||||
|
activeTab={activeTab}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
|
areDefaultSettings
|
||||||
|
settings={settings}
|
||||||
|
updateSetting={(setting, value, commit) => updateSetting(setting, value, commit)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,14 +6,35 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Direction, useTheme } from '@mui/material/styles';
|
import { Direction, useTheme } from '@mui/material/styles';
|
||||||
|
import { t as translate } from 'i18next';
|
||||||
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
||||||
import { Chapters } from '@/modules/chapter/services/Chapters.ts';
|
import { Chapters } from '@/modules/chapter/services/Chapters.ts';
|
||||||
import { IReaderSettings, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
import {
|
||||||
|
IReaderSettings,
|
||||||
|
IReaderSettingsWithDefaultFlag,
|
||||||
|
ReaderExitMode,
|
||||||
|
ReaderOverlayMode,
|
||||||
|
ReadingDirection,
|
||||||
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
||||||
import { getReaderSettingsFor, useDefaultReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
import {
|
||||||
|
updateReaderSettings,
|
||||||
|
useDefaultReaderSettings,
|
||||||
|
useGetReaderSettingsFor,
|
||||||
|
} from '@/modules/reader/services/ReaderSettingsMetadata.ts';
|
||||||
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import { getMetadataKey } from '@/modules/metadata/services/MetadataReader.ts';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { MANGA_META_FIELDS } from '@/lib/graphql/fragments/MangaFragments.ts';
|
||||||
|
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||||
|
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
|
||||||
|
import { GLOBAL_METADATA } from '@/lib/graphql/fragments/Fragments.ts';
|
||||||
|
import { updateMetadataList } from '@/modules/metadata/services/MetadataApolloCacheHandler.ts';
|
||||||
|
import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
|
||||||
|
import { GLOBAL_READER_SETTING_KEYS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
|
||||||
const DIRECTION_TO_INVERTED: Record<Direction, Direction> = {
|
const DIRECTION_TO_INVERTED: Record<Direction, Direction> = {
|
||||||
ltr: 'rtl',
|
ltr: 'rtl',
|
||||||
@@ -25,6 +46,8 @@ const DIRECTION_TO_READING_DIRECTION: Record<Direction, ReadingDirection> = {
|
|||||||
rtl: ReadingDirection.RTL,
|
rtl: ReadingDirection.RTL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_MANGA: MangaIdInfo = { id: -1 };
|
||||||
|
|
||||||
export class ReaderService {
|
export class ReaderService {
|
||||||
static useNavigateToChapter(chapter?: TChapterReader): () => void {
|
static useNavigateToChapter(chapter?: TChapterReader): () => void {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -32,21 +55,170 @@ export class ReaderService {
|
|||||||
return useCallback(() => chapter && navigate(Chapters.getReaderUrl(chapter), { replace: true }), [chapter]);
|
return useCallback(() => chapter && navigate(Chapters.getReaderUrl(chapter), { replace: true }), [chapter]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static useSettings(): IReaderSettings {
|
static useSettings(): IReaderSettingsWithDefaultFlag {
|
||||||
const { manga } = useReaderStateMangaContext();
|
const { manga } = useReaderStateMangaContext();
|
||||||
const defaultReaderSettings = useDefaultReaderSettings();
|
const defaultReaderSettings = useDefaultReaderSettings();
|
||||||
return useMemo(
|
|
||||||
() => getReaderSettingsFor(manga ?? { id: -1 }, defaultReaderSettings.settings),
|
return useGetReaderSettingsFor(manga ?? DEFAULT_MANGA, defaultReaderSettings.settings);
|
||||||
[manga, defaultReaderSettings],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static useGetThemeDirection(): Direction {
|
static useGetThemeDirection(): Direction {
|
||||||
const { direction } = useTheme();
|
const { direction } = useTheme();
|
||||||
const { readingDirection } = ReaderService.useSettings();
|
const { readingDirection } = ReaderService.useSettings();
|
||||||
|
|
||||||
return DIRECTION_TO_READING_DIRECTION[direction] === readingDirection
|
return DIRECTION_TO_READING_DIRECTION[direction] === readingDirection.value
|
||||||
? direction
|
? direction
|
||||||
: DIRECTION_TO_INVERTED[direction];
|
: DIRECTION_TO_INVERTED[direction];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the change immediately to the cache and sends a mutation in case "commit" is true.
|
||||||
|
*/
|
||||||
|
static updateSetting<Setting extends keyof IReaderSettings>(
|
||||||
|
manga: MangaIdInfo,
|
||||||
|
setting: Setting,
|
||||||
|
value: IReaderSettings[Setting],
|
||||||
|
commit: boolean = true,
|
||||||
|
isGlobal: boolean = false,
|
||||||
|
): void {
|
||||||
|
if (!manga) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = getMetadataKey(setting);
|
||||||
|
|
||||||
|
const { cache } = requestManager.graphQLClient.client;
|
||||||
|
|
||||||
|
const isGlobalSetting = isGlobal || GLOBAL_READER_SETTING_KEYS.includes(setting);
|
||||||
|
if (isGlobalSetting) {
|
||||||
|
const reference = cache.writeFragment({
|
||||||
|
fragment: GLOBAL_METADATA,
|
||||||
|
data: {
|
||||||
|
__typename: 'GlobalMetaType',
|
||||||
|
key,
|
||||||
|
value: JSON.stringify(value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
cache.modify({
|
||||||
|
fields: {
|
||||||
|
metas(existingMetas, { readField }) {
|
||||||
|
return {
|
||||||
|
...existingMetas,
|
||||||
|
nodes: updateMetadataList(key, existingMetas?.nodes, readField, () => reference),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const reference = cache.writeFragment({
|
||||||
|
fragment: MANGA_META_FIELDS,
|
||||||
|
data: {
|
||||||
|
__typename: 'MangaMetaType',
|
||||||
|
mangaId: manga.id,
|
||||||
|
key,
|
||||||
|
value: JSON.stringify(value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
cache.modify({
|
||||||
|
id: cache.identify({ __typename: 'MangaType', id: manga.id }),
|
||||||
|
fields: {
|
||||||
|
meta(existingMetas, { readField }) {
|
||||||
|
return updateMetadataList(key, existingMetas, readField, () => reference);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commit) {
|
||||||
|
updateReaderSettings(manga, setting, value, isGlobal).catch(() =>
|
||||||
|
makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static deleteSetting<Setting extends keyof IReaderSettings>(
|
||||||
|
manga: MangaIdInfo,
|
||||||
|
setting: Setting,
|
||||||
|
isGlobal: boolean = false,
|
||||||
|
): void {
|
||||||
|
if (!manga) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = getMetadataKey(setting);
|
||||||
|
|
||||||
|
const { cache } = requestManager.graphQLClient.client;
|
||||||
|
|
||||||
|
const isGlobalSetting = isGlobal || GLOBAL_READER_SETTING_KEYS.includes(setting);
|
||||||
|
if (isGlobalSetting) {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'GlobalMetaType', key }) });
|
||||||
|
} else {
|
||||||
|
cache.evict({ id: cache.identify({ __typename: 'MangaMetaType', mangaId: manga.id, key }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSetting = isGlobalSetting
|
||||||
|
? () => requestManager.deleteGlobalMeta(key).response
|
||||||
|
: () => requestManager.deleteMangaMeta(manga.id, key).response;
|
||||||
|
deleteSetting().catch(() =>
|
||||||
|
makeToast(translate('reader.settings.error.label.failed_to_save_settings'), 'error'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useCreateUpdateSetting<Setting extends keyof IReaderSettings>(
|
||||||
|
manga: MangaIdInfo,
|
||||||
|
): (
|
||||||
|
...args: OmitFirst<Parameters<typeof this.updateSetting<Setting>>>
|
||||||
|
) => ReturnType<typeof this.updateSetting<Setting>> {
|
||||||
|
return useCallback((...args) => this.updateSetting<Setting>(manga, ...args), [manga]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useCreateDeleteSetting<Setting extends keyof IReaderSettings>(
|
||||||
|
manga: MangaIdInfo,
|
||||||
|
): (
|
||||||
|
...args: OmitFirst<Parameters<typeof this.deleteSetting<Setting>>>
|
||||||
|
) => ReturnType<typeof this.deleteSetting<Setting>> {
|
||||||
|
return useCallback((...args) => this.deleteSetting<Setting>(manga, ...args), [manga]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static useOverlayMode(): { mode: ReaderOverlayMode; isDesktop: boolean; isMobile: boolean } {
|
||||||
|
const isTouchDevice = MediaQuery.useIsTouchDevice();
|
||||||
|
const { overlayMode } = this.useSettings();
|
||||||
|
|
||||||
|
const isAutoModeSelected = overlayMode === ReaderOverlayMode.AUTO;
|
||||||
|
const isDesktopModeSelected = overlayMode === ReaderOverlayMode.DESKTOP;
|
||||||
|
const isMobileModeSelected = overlayMode === ReaderOverlayMode.MOBILE;
|
||||||
|
|
||||||
|
const isDesktop = isDesktopModeSelected || (isAutoModeSelected && !isTouchDevice);
|
||||||
|
const isMobile = isMobileModeSelected || (isAutoModeSelected && isTouchDevice);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: isDesktop ? ReaderOverlayMode.DESKTOP : ReaderOverlayMode.MOBILE,
|
||||||
|
isDesktop,
|
||||||
|
isMobile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static useExit(): () => void {
|
||||||
|
const { manga } = useReaderStateMangaContext();
|
||||||
|
const { exitMode } = this.useSettings();
|
||||||
|
const handleBack = useBackButton();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const openMangaPage = useCallback(() => {
|
||||||
|
if (!manga) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return navigate(`/manga/${manga.id}`);
|
||||||
|
}, [manga]);
|
||||||
|
|
||||||
|
switch (exitMode) {
|
||||||
|
case ReaderExitMode.PREVIOUS:
|
||||||
|
return handleBack;
|
||||||
|
case ReaderExitMode.MANGA:
|
||||||
|
return openMangaPage;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected "exitMode" (${exitMode})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
|
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import {
|
||||||
|
requestUpdateMangaMetadata,
|
||||||
|
requestUpdateServerMetadata,
|
||||||
|
} from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
|
||||||
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/Reader.constants.ts';
|
import { IReaderSettings, IReaderSettingsWithDefaultFlag } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
|
|
||||||
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
import { getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
|
import { extractOriginalKey, getMetadataFrom } from '@/modules/metadata/services/MetadataReader.ts';
|
||||||
import {
|
import {
|
||||||
AllowedMetadataValueTypes,
|
AllowedMetadataValueTypes,
|
||||||
AppMetadataKeys,
|
AppMetadataKeys,
|
||||||
@@ -25,14 +27,69 @@ import {
|
|||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
|
import {
|
||||||
|
DEFAULT_READER_SETTINGS,
|
||||||
|
GLOBAL_READER_SETTING_KEYS,
|
||||||
|
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
|
import { DEFAULT_DEVICE, getActiveDevice } from '@/modules/device/services/Device.ts';
|
||||||
|
import { APP_METADATA_KEY_PREFIX } from '@/modules/metadata/Metadata.constants.ts';
|
||||||
|
|
||||||
|
const convertToSettingsWithDefaultFlag = (
|
||||||
|
type: Extract<MetadataHolderType, 'global' | 'manga'>,
|
||||||
|
settings: IReaderSettings,
|
||||||
|
metadataHolder: MetadataHolder,
|
||||||
|
): IReaderSettingsWithDefaultFlag => {
|
||||||
|
const activeDevice = getActiveDevice();
|
||||||
|
const istDefaultDevice = activeDevice === DEFAULT_DEVICE;
|
||||||
|
const existingSettings = Object.keys(metadataHolder.meta ?? {})
|
||||||
|
// settings that are not for the active device need to be filtered out, otherwise, they mess up the "isDefault" flag
|
||||||
|
.filter((metaKey) => {
|
||||||
|
// the default device is not added as a prefix to the key, thus, there should only be the app prefix for these reader settings
|
||||||
|
if (istDefaultDevice) {
|
||||||
|
return metaKey.match(/_/g)?.length === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metaKey.startsWith(`${APP_METADATA_KEY_PREFIX}_${activeDevice}_`);
|
||||||
|
})
|
||||||
|
.map((metaKey) => extractOriginalKey(metaKey));
|
||||||
|
const settingsWithDefault = Object.fromEntries(
|
||||||
|
(Object.entries(settings) as [keyof IReaderSettings, IReaderSettings[keyof IReaderSettings]][]).map(
|
||||||
|
([key, value]) => {
|
||||||
|
const isGlobalSetting = GLOBAL_READER_SETTING_KEYS.includes(key);
|
||||||
|
if (isGlobalSetting) {
|
||||||
|
return [key, value];
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDefaultSetting = type === 'manga' && !existingSettings.includes(key);
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
isDefault: isDefaultSetting,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) as IReaderSettingsWithDefaultFlag;
|
||||||
|
|
||||||
|
return settingsWithDefault;
|
||||||
|
};
|
||||||
|
|
||||||
const convertSettingsToMetadata = (
|
const convertSettingsToMetadata = (
|
||||||
settings: Partial<IReaderSettings>,
|
settings: Partial<IReaderSettings>,
|
||||||
): Metadata<string, AllowedMetadataValueTypes> => ({
|
): Metadata<string, AllowedMetadataValueTypes> => ({
|
||||||
...settings,
|
...settings,
|
||||||
tapZoneInvertMode: JSON.stringify(settings.tapZoneInvertMode),
|
tapZoneInvertMode: JSON.stringify(settings.tapZoneInvertMode),
|
||||||
|
customFilter: JSON.stringify(settings.customFilter),
|
||||||
|
readerWidth: JSON.stringify(settings.readerWidth),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG = convertToSettingsWithDefaultFlag(
|
||||||
|
'global',
|
||||||
|
DEFAULT_READER_SETTINGS,
|
||||||
|
{ meta: convertSettingsToMetadata(DEFAULT_READER_SETTINGS) as Metadata },
|
||||||
|
);
|
||||||
|
|
||||||
const convertMetadataToSettings = (
|
const convertMetadataToSettings = (
|
||||||
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
||||||
defaultSettings: IReaderSettings,
|
defaultSettings: IReaderSettings,
|
||||||
@@ -41,27 +98,21 @@ const convertMetadataToSettings = (
|
|||||||
tapZoneInvertMode:
|
tapZoneInvertMode:
|
||||||
jsonSaveParse<IReaderSettings['tapZoneInvertMode']>((metadata.tapZoneInvertMode as string) ?? '') ??
|
jsonSaveParse<IReaderSettings['tapZoneInvertMode']>((metadata.tapZoneInvertMode as string) ?? '') ??
|
||||||
defaultSettings.tapZoneInvertMode,
|
defaultSettings.tapZoneInvertMode,
|
||||||
|
customFilter:
|
||||||
|
jsonSaveParse<IReaderSettings['customFilter']>((metadata.customFilter as string) ?? '') ??
|
||||||
|
defaultSettings.customFilter,
|
||||||
|
readerWidth:
|
||||||
|
jsonSaveParse<IReaderSettings['readerWidth']>((metadata.readerWidth as string) ?? '') ??
|
||||||
|
defaultSettings.readerWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IReaderSettings>(
|
const getReaderMetadata = (
|
||||||
type: 'global',
|
|
||||||
metadataHolder: MetadataHolder,
|
|
||||||
defaultSettings?: DefaultSettings,
|
|
||||||
useEffectFn?: typeof useEffect,
|
|
||||||
): DefaultSettings;
|
|
||||||
function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IReaderSettings>(
|
|
||||||
type: 'manga',
|
|
||||||
metadataHolder: MangaIdInfo & MetadataHolder,
|
|
||||||
defaultSettings?: DefaultSettings,
|
|
||||||
useEffectFn?: typeof useEffect,
|
|
||||||
): DefaultSettings;
|
|
||||||
function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IReaderSettings>(
|
|
||||||
type: Extract<MetadataHolderType, 'global' | 'manga'>,
|
type: Extract<MetadataHolderType, 'global' | 'manga'>,
|
||||||
metadataHolder: (MangaIdInfo & MetadataHolder) | MetadataHolder,
|
metadataHolder: (MangaIdInfo & MetadataHolder) | MetadataHolder,
|
||||||
defaultSettings: DefaultSettings = DEFAULT_READER_SETTINGS as DefaultSettings,
|
defaultSettings: IReaderSettings = DEFAULT_READER_SETTINGS,
|
||||||
useEffectFn?: typeof useEffect,
|
useEffectFn?: typeof useEffect,
|
||||||
): DefaultSettings {
|
) =>
|
||||||
return convertMetadataToSettings(
|
convertMetadataToSettings(
|
||||||
getMetadataFrom(
|
getMetadataFrom(
|
||||||
type as Parameters<typeof getMetadataFrom>[0],
|
type as Parameters<typeof getMetadataFrom>[0],
|
||||||
metadataHolder as Parameters<typeof getMetadataFrom>[1],
|
metadataHolder as Parameters<typeof getMetadataFrom>[1],
|
||||||
@@ -70,7 +121,28 @@ function getReaderSettingsWithDefaultValueFallback<DefaultSettings extends IRead
|
|||||||
useEffectFn,
|
useEffectFn,
|
||||||
),
|
),
|
||||||
defaultSettings as IReaderSettings,
|
defaultSettings as IReaderSettings,
|
||||||
) as DefaultSettings;
|
);
|
||||||
|
|
||||||
|
function getReaderSettingsWithDefaultValueFallback(
|
||||||
|
type: 'global',
|
||||||
|
metadataHolder: MetadataHolder,
|
||||||
|
defaultSettings?: IReaderSettings,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): IReaderSettingsWithDefaultFlag;
|
||||||
|
function getReaderSettingsWithDefaultValueFallback(
|
||||||
|
type: 'manga',
|
||||||
|
metadataHolder: MangaIdInfo & MetadataHolder,
|
||||||
|
defaultSettings?: IReaderSettings,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): IReaderSettingsWithDefaultFlag;
|
||||||
|
function getReaderSettingsWithDefaultValueFallback(
|
||||||
|
type: Extract<MetadataHolderType, 'global' | 'manga'>,
|
||||||
|
metadataHolder: (MangaIdInfo & MetadataHolder) | MetadataHolder,
|
||||||
|
defaultSettings: IReaderSettings = DEFAULT_READER_SETTINGS,
|
||||||
|
useEffectFn?: typeof useEffect,
|
||||||
|
): IReaderSettingsWithDefaultFlag {
|
||||||
|
const settings = getReaderMetadata(type, metadataHolder, defaultSettings, useEffectFn);
|
||||||
|
return convertToSettingsWithDefaultFlag(type, settings, metadataHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSettings = (
|
const getSettings = (
|
||||||
@@ -82,7 +154,10 @@ const getSettings = (
|
|||||||
'manga',
|
'manga',
|
||||||
{
|
{
|
||||||
...metaHolder,
|
...metaHolder,
|
||||||
meta: convertFromGqlMeta(metaHolder.meta),
|
meta: convertFromGqlMeta(
|
||||||
|
metaHolder.meta,
|
||||||
|
(key) => !GLOBAL_READER_SETTING_KEYS.includes(extractOriginalKey(key)),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
defaultSettings,
|
defaultSettings,
|
||||||
useEffectFn,
|
useEffectFn,
|
||||||
@@ -91,12 +166,12 @@ const getSettings = (
|
|||||||
export const getReaderSettingsFor = (
|
export const getReaderSettingsFor = (
|
||||||
metaHolder: MangaIdInfo & GqlMetaHolder,
|
metaHolder: MangaIdInfo & GqlMetaHolder,
|
||||||
defaultSettings: IReaderSettings,
|
defaultSettings: IReaderSettings,
|
||||||
): IReaderSettings => getSettings(metaHolder, defaultSettings);
|
): IReaderSettingsWithDefaultFlag => getSettings(metaHolder, defaultSettings);
|
||||||
|
|
||||||
export const useGetReaderSettingsFor = (
|
export const useGetReaderSettingsFor = (
|
||||||
metaHolder: MangaIdInfo & GqlMetaHolder,
|
metaHolder: MangaIdInfo & GqlMetaHolder,
|
||||||
defaultSettings: IReaderSettings,
|
defaultSettings: IReaderSettings,
|
||||||
): IReaderSettings => {
|
): IReaderSettingsWithDefaultFlag => {
|
||||||
const settings = getSettings(metaHolder, defaultSettings, useEffect);
|
const settings = getSettings(metaHolder, defaultSettings, useEffect);
|
||||||
return useMemo(() => settings, [metaHolder, defaultSettings]);
|
return useMemo(() => settings, [metaHolder, defaultSettings]);
|
||||||
};
|
};
|
||||||
@@ -106,6 +181,30 @@ export const useDefaultReaderSettings = (): {
|
|||||||
settings: IReaderSettings;
|
settings: IReaderSettings;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
request: ReturnType<typeof requestManager.useGetGlobalMeta>;
|
request: ReturnType<typeof requestManager.useGetGlobalMeta>;
|
||||||
|
} => {
|
||||||
|
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
||||||
|
const { data, loading } = request;
|
||||||
|
const metadata = useMemo(() => convertFromGqlMeta(data?.metas.nodes), [data?.metas.nodes]);
|
||||||
|
const metaHolder: MetadataHolder = useMemo(() => ({ meta: metadata }), [metadata]);
|
||||||
|
const tmpSettings = getReaderMetadata('global', metaHolder, undefined, useEffect);
|
||||||
|
const settings = useMemo(() => tmpSettings, [metaHolder]);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
metadata,
|
||||||
|
settings,
|
||||||
|
loading,
|
||||||
|
request,
|
||||||
|
}),
|
||||||
|
[metadata, settings, loading, request],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDefaultReaderSettingsWithDefaultFlag = (): {
|
||||||
|
metadata?: Metadata;
|
||||||
|
settings: IReaderSettingsWithDefaultFlag;
|
||||||
|
loading: boolean;
|
||||||
|
request: ReturnType<typeof requestManager.useGetGlobalMeta>;
|
||||||
} => {
|
} => {
|
||||||
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
const request = requestManager.useGetGlobalMeta({ notifyOnNetworkStatusChange: true });
|
||||||
const { data, loading } = request;
|
const { data, loading } = request;
|
||||||
@@ -129,13 +228,19 @@ export const updateReaderSettings = async <Setting extends keyof IReaderSettings
|
|||||||
manga: Pick<MangaType, 'id'> & GqlMetaHolder,
|
manga: Pick<MangaType, 'id'> & GqlMetaHolder,
|
||||||
setting: Setting,
|
setting: Setting,
|
||||||
value: IReaderSettings[Setting],
|
value: IReaderSettings[Setting],
|
||||||
): Promise<void[]> =>
|
isGlobal: boolean = false,
|
||||||
requestUpdateMangaMetadata(manga, [[setting, convertSettingsToMetadata({ [setting]: value })[setting]]]);
|
): Promise<void[]> => {
|
||||||
|
const isGlobalSetting = isGlobal || GLOBAL_READER_SETTING_KEYS.includes(setting);
|
||||||
|
if (isGlobalSetting) {
|
||||||
|
return requestUpdateServerMetadata([[setting, convertSettingsToMetadata({ [setting]: value })[setting]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestUpdateMangaMetadata(manga, [[setting, convertSettingsToMetadata({ [setting]: value })[setting]]]);
|
||||||
|
};
|
||||||
export const createUpdateReaderSettings =
|
export const createUpdateReaderSettings =
|
||||||
<Settings extends keyof IReaderSettings>(
|
<Settings extends keyof IReaderSettings>(
|
||||||
manga: Pick<MangaType, 'id'> & GqlMetaHolder,
|
manga: Pick<MangaType, 'id'> & GqlMetaHolder,
|
||||||
handleError: (error: any) => void = defaultPromiseErrorHandler('createUpdateReaderSettings'),
|
handleError: (error: any) => void = defaultPromiseErrorHandler('createUpdateReaderSettings'),
|
||||||
): ((...args: OmitFirst<Parameters<typeof updateReaderSettings<Settings>>>) => Promise<void | void[]>) =>
|
): ((...args: OmitFirst<Parameters<typeof updateReaderSettings<Settings>>>) => Promise<void | void[]>) =>
|
||||||
(setting, value) =>
|
(setting, value, isGlobal) =>
|
||||||
updateReaderSettings(manga, setting, value).catch(handleError);
|
updateReaderSettings(manga, setting, value, isGlobal).catch(handleError);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import { TapZoneInvertMode, TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
import { TapZoneInvertMode, TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
||||||
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
||||||
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
|
|
||||||
export enum ProgressBarType {
|
export enum ProgressBarType {
|
||||||
HIDDEN,
|
HIDDEN,
|
||||||
@@ -39,6 +40,112 @@ export enum ReaderPageScaleMode {
|
|||||||
ORIGINAL,
|
ORIGINAL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ReaderOverlayMode {
|
||||||
|
AUTO,
|
||||||
|
DESKTOP,
|
||||||
|
MOBILE,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ReaderExitMode {
|
||||||
|
PREVIOUS,
|
||||||
|
MANGA,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ReaderBackgroundColor {
|
||||||
|
THEME,
|
||||||
|
BLACK,
|
||||||
|
GRAY,
|
||||||
|
WHITE,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReaderFilterRGBA {
|
||||||
|
red: number;
|
||||||
|
green: number;
|
||||||
|
blue: number;
|
||||||
|
alpha: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReaderCustomFilter {
|
||||||
|
brightness: {
|
||||||
|
/**
|
||||||
|
* percentage
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
contrast: {
|
||||||
|
/**
|
||||||
|
* percentage
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
saturate: {
|
||||||
|
/**
|
||||||
|
* percentage
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
hue: {
|
||||||
|
/**
|
||||||
|
* degree
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
rgba: {
|
||||||
|
value: ReaderFilterRGBA;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
sepia: boolean;
|
||||||
|
grayscale: boolean;
|
||||||
|
invert: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReaderSettingsGlobal {
|
||||||
|
overlayMode: ReaderOverlayMode;
|
||||||
|
exitMode: ReaderExitMode;
|
||||||
|
customFilter: ReaderCustomFilter;
|
||||||
|
shouldSkipDupChapters: boolean;
|
||||||
|
progressBarType: ProgressBarType;
|
||||||
|
/**
|
||||||
|
* pixel
|
||||||
|
*/
|
||||||
|
progressBarSize: number;
|
||||||
|
progressBarPosition: ProgressBarPosition;
|
||||||
|
shouldShowPageNumber: boolean;
|
||||||
|
isStaticNav: boolean;
|
||||||
|
backgroundColor: ReaderBackgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReaderSettingsManga {
|
||||||
|
tapZoneLayout: TapZoneLayouts;
|
||||||
|
tapZoneInvertMode: TapZoneInvertMode;
|
||||||
|
pageScaleMode: ReaderPageScaleMode;
|
||||||
|
shouldStretchPage: boolean;
|
||||||
|
shouldOffsetDoubleSpreads: boolean;
|
||||||
|
readingDirection: ReadingDirection;
|
||||||
|
readingMode: ReadingMode;
|
||||||
|
/**
|
||||||
|
* pixel
|
||||||
|
*/
|
||||||
|
pageGap: number;
|
||||||
|
readerWidth: {
|
||||||
|
/**
|
||||||
|
* percentage
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReaderSettings extends IReaderSettingsGlobal, IReaderSettingsManga {}
|
||||||
|
|
||||||
|
export interface IReaderSettingsWithDefaultFlag
|
||||||
|
extends IReaderSettingsGlobal,
|
||||||
|
TransformRecordToWithDefaultFlag<IReaderSettingsManga> {}
|
||||||
|
|
||||||
export interface ReaderStateChapters {
|
export interface ReaderStateChapters {
|
||||||
chapters: TChapterReader[];
|
chapters: TChapterReader[];
|
||||||
currentChapter?: TChapterReader | null;
|
currentChapter?: TChapterReader | null;
|
||||||
@@ -47,20 +154,25 @@ export interface ReaderStateChapters {
|
|||||||
setReaderStateChapters: React.Dispatch<React.SetStateAction<Omit<ReaderStateChapters, 'setReaderStateChapters'>>>;
|
setReaderStateChapters: React.Dispatch<React.SetStateAction<Omit<ReaderStateChapters, 'setReaderStateChapters'>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReaderSettings {
|
interface ReaderSettingsTypeBaseProps {
|
||||||
staticNav: boolean;
|
settings: IReaderSettingsWithDefaultFlag;
|
||||||
showPageNumber: boolean;
|
updateSetting: (
|
||||||
loadNextOnEnding: boolean;
|
...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>
|
||||||
skipDupChapters: boolean;
|
) => ReturnType<typeof ReaderService.updateSetting>;
|
||||||
readerWidth: number;
|
|
||||||
tapZoneLayout: TapZoneLayouts;
|
|
||||||
tapZoneInvertMode: TapZoneInvertMode;
|
|
||||||
progressBarType: ProgressBarType;
|
|
||||||
progressBarSize: number;
|
|
||||||
progressBarPosition: ProgressBarPosition;
|
|
||||||
pageScaleMode: ReaderPageScaleMode;
|
|
||||||
shouldScalePage: boolean;
|
|
||||||
shouldOffsetDoubleSpreads: boolean;
|
|
||||||
readingDirection: ReadingDirection;
|
|
||||||
readingMode: ReadingMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReaderSettingsDefaultableProps {
|
||||||
|
isDefaultable: boolean;
|
||||||
|
onDefault: (setting: keyof IReaderSettings) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReaderSettingsTypeDefaultableProps extends ReaderSettingsTypeBaseProps, ReaderSettingsDefaultableProps {
|
||||||
|
isDefaultable: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReaderSettingsTypeNonDefaultableProps extends ReaderSettingsTypeBaseProps {}
|
||||||
|
|
||||||
|
export type ReaderSettingsTypeProps =
|
||||||
|
| ReaderSettingsTypeDefaultableProps
|
||||||
|
| (PropertiesNever<Omit<ReaderSettingsTypeDefaultableProps, keyof ReaderSettingsTypeBaseProps>> &
|
||||||
|
ReaderSettingsTypeNonDefaultableProps);
|
||||||
|
|||||||
12
src/modules/reader/utils/ReaderSettings.utils.tsx
Normal file
12
src/modules/reader/utils/ReaderSettings.utils.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* 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 { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
|
|
||||||
|
export const isOffsetDoubleSpreadPagesEditable = (readingMode: IReaderSettings['readingMode']): boolean =>
|
||||||
|
readingMode === ReadingMode.DOUBLE_PAGE;
|
||||||
@@ -33,4 +33,4 @@ export interface ISearchSettings {
|
|||||||
ignoreFilters: boolean;
|
ignoreFilters: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServerSettings = GetServerSettingsQuery['settings'];
|
export type ServerSettings = Omit<GetServerSettingsQuery['settings'], '__typename'>;
|
||||||
|
|||||||
Reference in New Issue
Block a user