@@ -458,6 +458,7 @@
|
|||||||
"placeholder": "Placeholder",
|
"placeholder": "Placeholder",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"right": "Right",
|
"right": "Right",
|
||||||
|
"scanlator": "Scanlator",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"small": "Small",
|
"small": "Small",
|
||||||
"sort": "Sort",
|
"sort": "Sort",
|
||||||
@@ -965,6 +966,7 @@
|
|||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"behaviour": "Behaviour",
|
"behaviour": "Behaviour",
|
||||||
|
"custom_scroll_amount": "Custom scroll amount",
|
||||||
"fit_page_to_window": "Fit page to window",
|
"fit_page_to_window": "Fit page to window",
|
||||||
"layout": "Layout",
|
"layout": "Layout",
|
||||||
"limit_reader_width": "Limit reader width",
|
"limit_reader_width": "Limit reader width",
|
||||||
@@ -977,7 +979,6 @@
|
|||||||
"reading_mode": "Reading mode",
|
"reading_mode": "Reading mode",
|
||||||
"show_page_number": "Show page number",
|
"show_page_number": "Show page number",
|
||||||
"skip_dup_chapters": "Skip duplicate chapters",
|
"skip_dup_chapters": "Skip duplicate chapters",
|
||||||
"custom_scroll_amount": "Custom scroll amount",
|
|
||||||
"static_navigation": "Static navigation"
|
"static_navigation": "Static navigation"
|
||||||
},
|
},
|
||||||
"overlay_mode": "Overlay mode",
|
"overlay_mode": "Overlay mode",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const DEFAULT_CHAPTER_OPTIONS: ChapterListOptions = {
|
|||||||
reverse: true,
|
reverse: true,
|
||||||
sortBy: 'source',
|
sortBy: 'source',
|
||||||
showChapterNumber: false,
|
showChapterNumber: false,
|
||||||
|
excludedScanlators: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHAPTER_SORT_OPTIONS_TO_TRANSLATION_KEY: Record<ChapterSortMode, TranslationKey> = {
|
export const CHAPTER_SORT_OPTIONS_TO_TRANSLATION_KEY: Record<ChapterSortMode, TranslationKey> = {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface ChapterListOptions {
|
|||||||
reverse: boolean;
|
reverse: boolean;
|
||||||
sortBy: ChapterSortMode;
|
sortBy: ChapterSortMode;
|
||||||
showChapterNumber: boolean;
|
showChapterNumber: boolean;
|
||||||
|
excludedScanlators: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TChapterReader = ChapterReaderFieldsFragment;
|
export type TChapterReader = ChapterReaderFieldsFragment;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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 { bindTrigger, usePopupState } from 'material-ui-popup-state/hooks';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import PeopleAltOutlinedIcon from '@mui/icons-material/PeopleAltOutlined';
|
||||||
|
import { CheckboxListSetting } from '@/modules/core/components/settings/CheckboxListSetting.tsx';
|
||||||
|
import { updateChapterListOptions } from '@/modules/chapter/utils/ChapterList.util.tsx';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
|
||||||
|
export const ChapterExcludeSanlatorsFilter = ({
|
||||||
|
updateOption,
|
||||||
|
scanlators,
|
||||||
|
excludedScanlators,
|
||||||
|
}: {
|
||||||
|
updateOption: ReturnType<typeof updateChapterListOptions>;
|
||||||
|
scanlators: string[];
|
||||||
|
excludedScanlators: string[];
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const popupState = usePopupState({ variant: 'dialog', popupId: 'chapter-list-options-scanlator-filter-dialog' });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CheckboxInput
|
||||||
|
{...bindTrigger(popupState)}
|
||||||
|
label={t('global.label.scanlator')}
|
||||||
|
icon={<PeopleAltOutlinedIcon />}
|
||||||
|
checkedIcon={<PeopleAltOutlinedIcon color="warning" />}
|
||||||
|
checked={!!excludedScanlators.length}
|
||||||
|
/>
|
||||||
|
<CheckboxListSetting
|
||||||
|
open={popupState.isOpen}
|
||||||
|
onClose={(selectedScanlators) => {
|
||||||
|
if (selectedScanlators) {
|
||||||
|
updateOption('excludedScanlators', selectedScanlators);
|
||||||
|
}
|
||||||
|
popupState.close();
|
||||||
|
}}
|
||||||
|
items={scanlators}
|
||||||
|
getId={(scanlator) => scanlator}
|
||||||
|
getLabel={(scanlator) => scanlator}
|
||||||
|
isChecked={(scanlator) => excludedScanlators.includes(scanlator)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -216,6 +216,8 @@ export const ChapterList = ({
|
|||||||
options={options}
|
options={options}
|
||||||
updateOption={updateOption}
|
updateOption={updateOption}
|
||||||
unreadChapters={Chapters.getNonRead(chapters)}
|
unreadChapters={Chapters.getNonRead(chapters)}
|
||||||
|
scanlators={Chapters.getScanlators(chapters)}
|
||||||
|
excludeScanlators={options.excludedScanlators}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!!visibleChapterIds.length && (
|
{!!visibleChapterIds.length && (
|
||||||
|
|||||||
@@ -17,12 +17,15 @@ import { CHAPTER_SORT_OPTIONS_TO_TRANSLATION_KEY } from '@/modules/chapter/Chapt
|
|||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
import { ChapterListOptions } from '@/modules/chapter/Chapter.types.ts';
|
import { ChapterListOptions } from '@/modules/chapter/Chapter.types.ts';
|
||||||
import { updateChapterListOptions } from '@/modules/chapter/utils/ChapterList.util.tsx';
|
import { updateChapterListOptions } from '@/modules/chapter/utils/ChapterList.util.tsx';
|
||||||
|
import { ChapterExcludeSanlatorsFilter } from '@/modules/chapter/components/ChapterExcludeSanlatorsFilter.tsx';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
options: ChapterListOptions;
|
options: ChapterListOptions;
|
||||||
updateOption: ReturnType<typeof updateChapterListOptions>;
|
updateOption: ReturnType<typeof updateChapterListOptions>;
|
||||||
|
scanlators: string[];
|
||||||
|
excludedScanlators: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
|
const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
|
||||||
@@ -31,7 +34,14 @@ const TITLES: { [key in 'filter' | 'sort' | 'display']: TranslationKey } = {
|
|||||||
display: 'global.label.display',
|
display: 'global.label.display',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChapterOptions: React.FC<IProps> = ({ open, onClose, options, updateOption }) => {
|
export const ChapterOptions: React.FC<IProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
options,
|
||||||
|
updateOption,
|
||||||
|
scanlators,
|
||||||
|
excludedScanlators,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -60,6 +70,11 @@ export const ChapterOptions: React.FC<IProps> = ({ open, onClose, options, updat
|
|||||||
checked={options.bookmarked}
|
checked={options.bookmarked}
|
||||||
onChange={(c) => updateOption('bookmarked', c)}
|
onChange={(c) => updateOption('bookmarked', c)}
|
||||||
/>
|
/>
|
||||||
|
<ChapterExcludeSanlatorsFilter
|
||||||
|
scanlators={scanlators}
|
||||||
|
excludedScanlators={excludedScanlators}
|
||||||
|
updateOption={updateOption}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ interface IProps {
|
|||||||
options: ChapterListOptions;
|
options: ChapterListOptions;
|
||||||
updateOption: ReturnType<typeof updateChapterListOptions>;
|
updateOption: ReturnType<typeof updateChapterListOptions>;
|
||||||
unreadChapters: (ChapterIdInfo & ChapterDownloadInfo & ChapterBookmarkInfo)[];
|
unreadChapters: (ChapterIdInfo & ChapterDownloadInfo & ChapterBookmarkInfo)[];
|
||||||
|
scanlators: string[];
|
||||||
|
excludeScanlators: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChaptersToolbarMenu = ({
|
export const ChaptersToolbarMenu = ({
|
||||||
@@ -42,6 +44,8 @@ export const ChaptersToolbarMenu = ({
|
|||||||
options,
|
options,
|
||||||
updateOption,
|
updateOption,
|
||||||
unreadChapters,
|
unreadChapters,
|
||||||
|
scanlators,
|
||||||
|
excludeScanlators,
|
||||||
}: IProps) => {
|
}: IProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -90,7 +94,14 @@ export const ChaptersToolbarMenu = ({
|
|||||||
<FilterList color={isFiltered ? 'warning' : undefined} />
|
<FilterList color={isFiltered ? 'warning' : undefined} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
<ChapterOptions open={open} onClose={() => setOpen(false)} options={options} updateOption={updateOption} />
|
<ChapterOptions
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
options={options}
|
||||||
|
updateOption={updateOption}
|
||||||
|
scanlators={scanlators}
|
||||||
|
excludedScanlators={excludeScanlators}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -439,4 +439,12 @@ export class Chapters {
|
|||||||
|
|
||||||
return Math.max(0, Math.floor(higherChapterNumber) - Math.floor(lowerChapterNumber) - 1);
|
return Math.max(0, Math.floor(higherChapterNumber) - Math.floor(lowerChapterNumber) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getScanlators<Chapter extends ChapterScanlatorInfo>(chapters: Chapter[]): string[] {
|
||||||
|
return [
|
||||||
|
...new Set(
|
||||||
|
chapters.map((chapter) => chapter.scanlator).filter((scanlator) => typeof scanlator === 'string'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
ChapterDownloadInfo,
|
ChapterDownloadInfo,
|
||||||
ChapterListOptions,
|
ChapterListOptions,
|
||||||
ChapterReadInfo,
|
ChapterReadInfo,
|
||||||
|
ChapterScanlatorInfo,
|
||||||
} from '@/modules/chapter/Chapter.types.ts';
|
} from '@/modules/chapter/Chapter.types.ts';
|
||||||
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
|
||||||
import { GqlMetaHolder } from '@/modules/metadata/Metadata.types.ts';
|
import { GqlMetaHolder } from '@/modules/metadata/Metadata.types.ts';
|
||||||
@@ -56,6 +57,10 @@ function bookmarkedFilter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scanlatorFilter(excludedScanlators: string[], { scanlator }: ChapterScanlatorInfo): boolean {
|
||||||
|
return !!scanlator && !excludedScanlators.includes(scanlator);
|
||||||
|
}
|
||||||
|
|
||||||
type TChapterSort = Pick<ChapterType, 'sourceOrder' | 'fetchedAt' | 'chapterNumber' | 'uploadDate'>;
|
type TChapterSort = Pick<ChapterType, 'sourceOrder' | 'fetchedAt' | 'chapterNumber' | 'uploadDate'>;
|
||||||
const sortChapters = <T extends TChapterSort>(
|
const sortChapters = <T extends TChapterSort>(
|
||||||
chapters: T[],
|
chapters: T[],
|
||||||
@@ -87,7 +92,7 @@ const sortChapters = <T extends TChapterSort>(
|
|||||||
return sortedChapters;
|
return sortedChapters;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TChapterFilter = TChapterSort & ChapterReadInfo & ChapterDownloadInfo & ChapterBookmarkInfo;
|
type TChapterFilter = TChapterSort & ChapterReadInfo & ChapterDownloadInfo & ChapterBookmarkInfo & ChapterScanlatorInfo;
|
||||||
export function filterAndSortChapters<Chapters extends TChapterFilter>(
|
export function filterAndSortChapters<Chapters extends TChapterFilter>(
|
||||||
chapters: Chapters[],
|
chapters: Chapters[],
|
||||||
options: ChapterListOptions,
|
options: ChapterListOptions,
|
||||||
@@ -96,23 +101,25 @@ export function filterAndSortChapters<Chapters extends TChapterFilter>(
|
|||||||
(chp) =>
|
(chp) =>
|
||||||
unreadFilter(options.unread, chp) &&
|
unreadFilter(options.unread, chp) &&
|
||||||
downloadFilter(options.downloaded, chp) &&
|
downloadFilter(options.downloaded, chp) &&
|
||||||
bookmarkedFilter(options.bookmarked, chp),
|
bookmarkedFilter(options.bookmarked, chp) &&
|
||||||
|
scanlatorFilter(options.excludedScanlators, chp),
|
||||||
);
|
);
|
||||||
|
|
||||||
return sortChapters(filtered, options);
|
return sortChapters(filtered, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isFilterActive = (options: ChapterListOptions) => {
|
export const isFilterActive = (options: ChapterListOptions) => {
|
||||||
const { unread, downloaded, bookmarked } = options;
|
const { unread, downloaded, bookmarked, excludedScanlators } = options;
|
||||||
return unread != null || downloaded != null || bookmarked != null;
|
return unread != null || downloaded != null || bookmarked != null || !!excludedScanlators.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useChapterListOptions = (manga: MangaIdInfo & GqlMetaHolder): ChapterListOptions => {
|
export const useChapterListOptions = (manga: MangaIdInfo & GqlMetaHolder): ChapterListOptions => {
|
||||||
const { unread, downloaded, bookmarked, reverse, sortBy, showChapterNumber } = useGetMangaMetadata(manga);
|
const { unread, downloaded, bookmarked, reverse, sortBy, showChapterNumber, excludedScanlators } =
|
||||||
|
useGetMangaMetadata(manga);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({ unread, downloaded, bookmarked, reverse, sortBy, showChapterNumber }),
|
() => ({ unread, downloaded, bookmarked, reverse, sortBy, showChapterNumber, excludedScanlators }),
|
||||||
[unread, downloaded, bookmarked, reverse, sortBy, showChapterNumber],
|
[unread, downloaded, bookmarked, reverse, sortBy, showChapterNumber, excludedScanlators],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type SelectableCollectionReturnType<Id extends number | string, Key exten
|
|||||||
setSelectionForKey: (key: Key, ids: Id[]) => void;
|
setSelectionForKey: (key: Key, ids: Id[]) => void;
|
||||||
getSelectionForKey: (key: Key) => Id[];
|
getSelectionForKey: (key: Key) => Id[];
|
||||||
clearSelection: () => void;
|
clearSelection: () => void;
|
||||||
|
reset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSelectableCollection = <Id extends number | string, Key extends string = 'default'>(
|
export const useSelectableCollection = <Id extends number | string, Key extends string = 'default'>(
|
||||||
@@ -122,6 +123,11 @@ export const useSelectableCollection = <Id extends number | string, Key extends
|
|||||||
setKeyToSelectedItemIds({});
|
setKeyToSelectedItemIds({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const reset = useCallback(() => {
|
||||||
|
clearSelection();
|
||||||
|
setKeyToSelectedItemIds(initialState);
|
||||||
|
}, [clearSelection, initialState]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedItemIds,
|
selectedItemIds,
|
||||||
keySelectedItemIds,
|
keySelectedItemIds,
|
||||||
@@ -134,5 +140,6 @@ export const useSelectableCollection = <Id extends number | string, Key extends
|
|||||||
setSelectionForKey,
|
setSelectionForKey,
|
||||||
getSelectionForKey,
|
getSelectionForKey,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
|
reset,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
115
src/modules/core/components/settings/CheckboxListSetting.tsx
Normal file
115
src/modules/core/components/settings/CheckboxListSetting.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import FormGroup from '@mui/material/FormGroup';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.tsx';
|
||||||
|
import { useSelectableCollection } from '@/modules/collection/hooks/useSelectableCollection.ts';
|
||||||
|
|
||||||
|
export function CheckboxListSetting<Item>({
|
||||||
|
items,
|
||||||
|
getId,
|
||||||
|
getLabel,
|
||||||
|
isChecked,
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
items: Item[];
|
||||||
|
getId: (item: Item) => string;
|
||||||
|
getLabel: (item: Item) => string;
|
||||||
|
isChecked: (item: Item) => boolean;
|
||||||
|
open: boolean;
|
||||||
|
onClose: (selectedItems?: Item[]) => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const itemIds = useMemo(() => items.map(getId), [items]);
|
||||||
|
const currentSelectedItemIds = useMemo(() => items.filter(isChecked).map(getId), [items]);
|
||||||
|
|
||||||
|
const { selectedItemIds, handleSelection, handleSelectAll, reset } = useSelectableCollection(items.length, {
|
||||||
|
currentKey: 'default',
|
||||||
|
itemIds,
|
||||||
|
initialState: { default: currentSelectedItemIds },
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
onClose();
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = useCallback(() => {
|
||||||
|
const didSelectionChange =
|
||||||
|
selectedItemIds.length !== currentSelectedItemIds.length ||
|
||||||
|
selectedItemIds.some((id) => !currentSelectedItemIds.includes(id));
|
||||||
|
const selectedItems = items.filter((item) => selectedItemIds.includes(getId(item)));
|
||||||
|
|
||||||
|
onClose(didSelectionChange ? selectedItems : undefined);
|
||||||
|
}, [selectedItemIds]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
sx={{
|
||||||
|
'.MuiDialog-paper': {
|
||||||
|
maxHeight: 435,
|
||||||
|
width: '80%',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
maxWidth="xs"
|
||||||
|
open={open}
|
||||||
|
onClose={handleCancel}
|
||||||
|
>
|
||||||
|
<DialogTitle>{t('category.title.set_categories')}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<FormGroup>
|
||||||
|
{items.length === 0 && <span>{t('category.error.no_categories_found.label.info')}</span>}
|
||||||
|
{items.map((item) => (
|
||||||
|
<CheckboxInput
|
||||||
|
checked={selectedItemIds.includes(getId(item))}
|
||||||
|
onChange={(e, checked) => handleSelection(getId(item), checked)}
|
||||||
|
label={getLabel(item)}
|
||||||
|
key={getId(item)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Stack sx={{ width: '100%' }}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'end',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={() => handleSelectAll(!selectedItemIds.length, items.map(getId))}>
|
||||||
|
{t(selectedItemIds.length ? 'global.button.reset' : 'global.button.select_all')}
|
||||||
|
</Button>
|
||||||
|
<Stack direction="row">
|
||||||
|
<Button autoFocus onClick={handleCancel} color="primary">
|
||||||
|
{t('global.button.cancel')}
|
||||||
|
</Button>
|
||||||
|
{!!items.length && (
|
||||||
|
<Button onClick={handleOk} color="primary">
|
||||||
|
{t('global.button.ok')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
import { convertFromGqlMeta } from '@/modules/metadata/services/MetadataConverter.ts';
|
||||||
import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
|
||||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||||
|
import { jsonSaveParse } from '@/lib/HelperFunctions.ts';
|
||||||
|
|
||||||
const DEFAULT_MANGA_METADATA: MangaMetadata = {
|
const DEFAULT_MANGA_METADATA: MangaMetadata = {
|
||||||
...DEFAULT_CHAPTER_OPTIONS,
|
...DEFAULT_CHAPTER_OPTIONS,
|
||||||
@@ -29,12 +30,14 @@ const convertAppMetadataToGqlMetadata = (
|
|||||||
metadata: Partial<MangaMetadata>,
|
metadata: Partial<MangaMetadata>,
|
||||||
): Metadata<string, AllowedMetadataValueTypes> => ({
|
): Metadata<string, AllowedMetadataValueTypes> => ({
|
||||||
...metadata,
|
...metadata,
|
||||||
|
excludedScanlators: JSON.stringify(metadata.excludedScanlators),
|
||||||
});
|
});
|
||||||
|
|
||||||
const convertGqlMetadataToAppMetadata = (
|
const convertGqlMetadataToAppMetadata = (
|
||||||
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
metadata: Partial<Metadata<AppMetadataKeys, AllowedMetadataValueTypes>>,
|
||||||
): MangaMetadata => ({
|
): MangaMetadata => ({
|
||||||
...(metadata as unknown as MangaMetadata),
|
...(metadata as unknown as MangaMetadata),
|
||||||
|
excludedScanlators: jsonSaveParse<MangaMetadata['excludedScanlators']>(metadata.excludedScanlators as string) ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const getMangaMetadataWithDefaultValueFallback = (
|
const getMangaMetadataWithDefaultValueFallback = (
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export const APP_METADATA_KEY_TO_TYPE = {
|
|||||||
isPinned: 'boolean',
|
isPinned: 'boolean',
|
||||||
lastUsedSourceId: 'string',
|
lastUsedSourceId: 'string',
|
||||||
shouldShowOnlySourcesWithResults: 'boolean',
|
shouldShowOnlySourcesWithResults: 'boolean',
|
||||||
|
excludedScanlators: 'string', // string[]
|
||||||
} as const satisfies Record<AppMetadataKeys, 'auto' | 'string' | 'number' | 'boolean'>;
|
} as const satisfies Record<AppMetadataKeys, 'auto' | 'string' | 'number' | 'boolean'>;
|
||||||
|
|
||||||
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_KEY_TO_TYPE);
|
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_KEY_TO_TYPE);
|
||||||
|
|||||||
Reference in New Issue
Block a user