Feature/add manga to library category select dialog (#519)
* Move "SearchSettings" into "LibrarySettings" * Change property "setOpen" to "onClose" for "CategorySelect" * Optionally show category selection when adding manga to library * Update "ignore filters" translation * Move "ignore_filters" setting to "metadataServerSettings"
This commit is contained in:
@@ -10,7 +10,7 @@ import { StringParam, useQueryParam } from 'use-query-params';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { LibrarySortMode, NullAndUndefined, TManga } from '@/typings.ts';
|
import { LibrarySortMode, NullAndUndefined, TManga } from '@/typings.ts';
|
||||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx';
|
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext.tsx';
|
||||||
import { useSearchSettings } from '@/util/searchSettings.ts';
|
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||||
|
|
||||||
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: TManga): boolean => {
|
const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: TManga): boolean => {
|
||||||
switch (unread) {
|
switch (unread) {
|
||||||
@@ -105,7 +105,7 @@ export const useGetVisibleLibraryMangas = (mangas: TManga[]) => {
|
|||||||
const [query] = useQueryParam('query', StringParam);
|
const [query] = useQueryParam('query', StringParam);
|
||||||
const { options } = useLibraryOptionsContext();
|
const { options } = useLibraryOptionsContext();
|
||||||
const { unread, downloaded } = options;
|
const { unread, downloaded } = options;
|
||||||
const { settings } = useSearchSettings();
|
const { settings } = useMetadataServerSettings();
|
||||||
|
|
||||||
const filteredMangas = useMemo(
|
const filteredMangas = useMemo(
|
||||||
() => filterManga(mangas, query, unread, downloaded, settings.ignoreFilters),
|
() => filterManga(mangas, query, unread, downloaded, settings.ignoreFilters),
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ export const MangaActionMenu = ({
|
|||||||
{isCategorySelectOpen && (
|
{isCategorySelectOpen && (
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
open={isCategorySelectOpen}
|
open={isCategorySelectOpen}
|
||||||
setOpen={(open) => {
|
onClose={() => {
|
||||||
setIsCategorySelectOpen(open);
|
setIsCategorySelectOpen(false);
|
||||||
bindMenuProps.onClose();
|
bindMenuProps.onClose();
|
||||||
}}
|
}}
|
||||||
mangaId={manga.id}
|
mangaId={manga.id}
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
|
|||||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
||||||
import PublicIcon from '@mui/icons-material/Public';
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { ISource, TManga } from '@/typings';
|
import { ISource, TManga } from '@/typings';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { makeToast } from '@/components/util/Toast';
|
import { makeToast } from '@/components/util/Toast';
|
||||||
|
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||||
|
import { CategorySelect } from '@/components/navbar/action/CategorySelect.tsx';
|
||||||
|
|
||||||
const DetailsWrapper = styled('div')(({ theme }) => ({
|
const DetailsWrapper = styled('div')(({ theme }) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -167,12 +169,13 @@ function getValueOrUnknown(val?: string | null) {
|
|||||||
|
|
||||||
export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: categoriesData, loading: areCategoriesLoading } = requestManager.useGetCategories();
|
|
||||||
const categories = categoriesData?.categories.nodes ?? [];
|
const {
|
||||||
const defaultCategoryIds = categories
|
settings: { showAddToLibraryCategorySelectDialog },
|
||||||
.filter((category) => category.default && category.id !== 0)
|
loading: areSettingsLoading,
|
||||||
.map((category) => category.id);
|
} = useMetadataServerSettings();
|
||||||
const [updateMangaCategories] = requestManager.useUpdateMangaCategories();
|
|
||||||
|
const [isCategorySelectOpen, setIsCategorySelectOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!manga.source) {
|
if (!manga.source) {
|
||||||
@@ -181,18 +184,23 @@ export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
}, [manga.source]);
|
}, [manga.source]);
|
||||||
|
|
||||||
const addToLibrary = () => {
|
const addToLibrary = () => {
|
||||||
Promise.all([
|
requestManager
|
||||||
requestManager.updateManga(manga.id, { inLibrary: true }).response,
|
.updateManga(manga.id, { inLibrary: true })
|
||||||
updateMangaCategories({
|
.response.then(() => makeToast(t('library.info.label.added_to_library'), 'success'))
|
||||||
variables: { input: { id: manga.id, patch: { addToCategories: defaultCategoryIds } } },
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
.then(() => makeToast(t('library.info.label.added_to_library'), 'success'))
|
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
makeToast(t('library.error.label.add_to_library'), 'error');
|
makeToast(t('library.error.label.add_to_library'), 'error');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddToLibraryClick = () => {
|
||||||
|
if (!showAddToLibraryCategorySelectDialog) {
|
||||||
|
addToLibrary();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsCategorySelectOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const removeFromLibrary = () => {
|
const removeFromLibrary = () => {
|
||||||
Promise.all([requestManager.updateManga(manga.id, { inLibrary: false }).response])
|
Promise.all([requestManager.updateManga(manga.id, { inLibrary: false }).response])
|
||||||
.then(() => makeToast(t('library.info.label.removed_from_library'), 'success'))
|
.then(() => makeToast(t('library.info.label.removed_from_library'), 'success'))
|
||||||
@@ -202,6 +210,7 @@ export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<DetailsWrapper>
|
<DetailsWrapper>
|
||||||
<TopContentWrapper>
|
<TopContentWrapper>
|
||||||
<ThumbnailMetadataWrapper>
|
<ThumbnailMetadataWrapper>
|
||||||
@@ -227,9 +236,9 @@ export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
<MangaButtonsContainer inLibrary={manga.inLibrary}>
|
<MangaButtonsContainer inLibrary={manga.inLibrary}>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
disabled={areCategoriesLoading}
|
disabled={areSettingsLoading}
|
||||||
startIcon={manga.inLibrary ? <FavoriteIcon /> : <FavoriteBorderIcon />}
|
startIcon={manga.inLibrary ? <FavoriteIcon /> : <FavoriteBorderIcon />}
|
||||||
onClick={manga.inLibrary ? removeFromLibrary : addToLibrary}
|
onClick={manga.inLibrary ? removeFromLibrary : handleAddToLibraryClick}
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
{manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')}
|
{manga.inLibrary ? t('manga.button.in_library') : t('manga.button.add_to_library')}
|
||||||
@@ -250,5 +259,20 @@ export const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
</Genres>
|
</Genres>
|
||||||
</BottomContentWrapper>
|
</BottomContentWrapper>
|
||||||
</DetailsWrapper>
|
</DetailsWrapper>
|
||||||
|
{isCategorySelectOpen && (
|
||||||
|
<CategorySelect
|
||||||
|
open={isCategorySelectOpen}
|
||||||
|
onClose={(didUpdateCategories) => {
|
||||||
|
setIsCategorySelectOpen(false);
|
||||||
|
|
||||||
|
if (didUpdateCategories) {
|
||||||
|
addToLibrary();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
mangaId={manga.id}
|
||||||
|
addToLibrary
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CategorySelect open={editCategories} setOpen={setEditCategories} mangaId={manga.id} />
|
<CategorySelect open={editCategories} onClose={() => setEditCategories(false)} mangaId={manga.id} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ export const MangasSelectionFABActionItems = ({
|
|||||||
{isCategorySelectOpen && (
|
{isCategorySelectOpen && (
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
open={isCategorySelectOpen}
|
open={isCategorySelectOpen}
|
||||||
setOpen={(open) => {
|
onClose={() => {
|
||||||
setIsCategorySelectOpen(open);
|
setIsCategorySelectOpen(false);
|
||||||
handleClose(true);
|
handleClose(true);
|
||||||
}}
|
}}
|
||||||
mangaIds={Mangas.getIds(selectedMangas)}
|
mangaIds={Mangas.getIds(selectedMangas)}
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|||||||
import { Mangas } from '@/lib/data/Mangas.ts';
|
import { Mangas } from '@/lib/data/Mangas.ts';
|
||||||
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput.tsx';
|
import { ThreeStateCheckboxInput } from '@/components/atoms/ThreeStateCheckboxInput.tsx';
|
||||||
|
import { TCategory } from '@/typings.ts';
|
||||||
|
|
||||||
type BaseProps = {
|
type BaseProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (value: boolean) => void;
|
onClose: (didUpdateCategories: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SingleMangaModeProps = {
|
type SingleMangaModeProps = {
|
||||||
mangaId: number;
|
mangaId: number;
|
||||||
|
addToLibrary?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MultiMangaModeProps = {
|
type MultiMangaModeProps = {
|
||||||
@@ -69,10 +71,13 @@ const getCategoryCheckedState = (
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDefaultCategoryIds = (categories: TCategory[]) =>
|
||||||
|
categories.filter(({ default: isDefault }) => isDefault).map(({ id }) => id);
|
||||||
|
|
||||||
export function CategorySelect(props: Props) {
|
export function CategorySelect(props: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { open, setOpen, mangaId, mangaIds: passedMangaIds } = props;
|
const { open, onClose, mangaId, mangaIds: passedMangaIds, addToLibrary = false } = props;
|
||||||
|
|
||||||
const isSingleSelectionMode = mangaId !== undefined;
|
const isSingleSelectionMode = mangaId !== undefined;
|
||||||
const mangaIds = passedMangaIds ?? [mangaId];
|
const mangaIds = passedMangaIds ?? [mangaId];
|
||||||
@@ -89,19 +94,24 @@ export function CategorySelect(props: Props) {
|
|||||||
return cats;
|
return cats;
|
||||||
}, [categoriesData]);
|
}, [categoriesData]);
|
||||||
|
|
||||||
|
const defaultCategoryIds = useMemo(
|
||||||
|
() => (addToLibrary ? getDefaultCategoryIds(allCategories) : []),
|
||||||
|
[allCategories],
|
||||||
|
);
|
||||||
|
|
||||||
const { handleSelection, setSelectionForKey, getSelectionForKey } = useSelectableCollection<
|
const { handleSelection, setSelectionForKey, getSelectionForKey } = useSelectableCollection<
|
||||||
number,
|
number,
|
||||||
'categoriesToAdd' | 'categoriesToRemove'
|
'categoriesToAdd' | 'categoriesToRemove'
|
||||||
>(allCategories.length, {
|
>(allCategories.length, {
|
||||||
currentKey: 'categoriesToAdd',
|
currentKey: 'categoriesToAdd',
|
||||||
initialState: {
|
initialState: {
|
||||||
categoriesToAdd: mangaCategoryIds,
|
categoriesToAdd: [...mangaCategoryIds, ...defaultCategoryIds],
|
||||||
categoriesToRemove: [],
|
categoriesToRemove: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectionForKey('categoriesToAdd', mangaCategoryIds);
|
setSelectionForKey('categoriesToAdd', [...mangaCategoryIds, ...defaultCategoryIds]);
|
||||||
setSelectionForKey('categoriesToRemove', []);
|
setSelectionForKey('categoriesToRemove', []);
|
||||||
}, [mangaCategoryIds]);
|
}, [mangaCategoryIds]);
|
||||||
|
|
||||||
@@ -111,11 +121,11 @@ export function CategorySelect(props: Props) {
|
|||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setSelectionForKey('categoriesToAdd', mangaCategoryIds);
|
setSelectionForKey('categoriesToAdd', mangaCategoryIds);
|
||||||
setSelectionForKey('categoriesToRemove', []);
|
setSelectionForKey('categoriesToRemove', []);
|
||||||
setOpen(false);
|
onClose(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
setOpen(false);
|
onClose(true);
|
||||||
|
|
||||||
const addToCategories = isSingleSelectionMode
|
const addToCategories = isSingleSelectionMode
|
||||||
? categoriesToAdd.filter((categoryId) => !mangaCategoryIds.includes(categoryId))
|
? categoriesToAdd.filter((categoryId) => !mangaCategoryIds.includes(categoryId))
|
||||||
|
|||||||
@@ -314,6 +314,7 @@
|
|||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"display": "Display",
|
"display": "Display",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
|
"general": "General",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"links": "Links",
|
"links": "Links",
|
||||||
"loading": "Loading…",
|
"loading": "Loading…",
|
||||||
@@ -402,6 +403,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"general": {
|
||||||
|
"add_to_library": {
|
||||||
|
"category_selection": {
|
||||||
|
"label": {
|
||||||
|
"description": "Show the category selection dialog when adding a manga to the library",
|
||||||
|
"title": "Category selection dialog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"ignore_filters": {
|
||||||
|
"label": {
|
||||||
|
"description": "Search results will include manga that do not match the current filters",
|
||||||
|
"title": "Ignore filters when searching"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"global_update": {
|
"global_update": {
|
||||||
"auto_update": {
|
"auto_update": {
|
||||||
"interval": {
|
"interval": {
|
||||||
@@ -447,6 +466,18 @@
|
|||||||
},
|
},
|
||||||
"manga": {
|
"manga": {
|
||||||
"action": {
|
"action": {
|
||||||
|
"category": {
|
||||||
|
"button": {
|
||||||
|
"selected": "Change categories of selected"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"action": "Change categories",
|
||||||
|
"error_one": "Could not change the categories of the manga",
|
||||||
|
"error_other": "Could not change the categories of the manga",
|
||||||
|
"success_one": "Changed categories of manga",
|
||||||
|
"success_other": "Changed categories of {{count}} manga"
|
||||||
|
}
|
||||||
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"remove": {
|
"remove": {
|
||||||
"button": {
|
"button": {
|
||||||
@@ -460,18 +491,6 @@
|
|||||||
"success_other": "Removed {{count}} manga from the library"
|
"success_other": "Removed {{count}} manga from the library"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"button": {
|
|
||||||
"selected": "Change categories of selected"
|
|
||||||
},
|
|
||||||
"label": {
|
|
||||||
"action": "Change categories",
|
|
||||||
"error_one": "Could not change the categories of the manga",
|
|
||||||
"error_other": "Could not change the categories of the manga",
|
|
||||||
"success_one": "Changed categories of manga",
|
|
||||||
"success_other": "Changed categories of {{count}} manga"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
@@ -558,9 +577,6 @@
|
|||||||
"source_search_failed": "Could not search source"
|
"source_search_failed": "Could not search source"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"label": {
|
|
||||||
"ignore_filters": "Ignore filters when searching"
|
|
||||||
},
|
|
||||||
"title": {
|
"title": {
|
||||||
"global_search": "Global Search",
|
"global_search": "Global Search",
|
||||||
"search": "Search"
|
"search": "Search"
|
||||||
|
|||||||
@@ -8,9 +8,14 @@
|
|||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { List, ListItem, ListItemText, Switch } from '@mui/material';
|
||||||
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
|
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
|
||||||
import { GlobalUpdateSettings } from '@/components/settings/globalUpdate/GlobalUpdateSettings.tsx';
|
import { GlobalUpdateSettings } from '@/components/settings/globalUpdate/GlobalUpdateSettings.tsx';
|
||||||
import { SearchSettings } from '@/screens/settings/SearchSettings.tsx';
|
import { MetadataServerSettingKeys, MetadataServerSettings } from '@/typings.ts';
|
||||||
|
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata.ts';
|
||||||
|
import { makeToast } from '@/components/util/Toast.tsx';
|
||||||
|
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||||
|
|
||||||
export function LibrarySettings() {
|
export function LibrarySettings() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -23,10 +28,50 @@ export function LibrarySettings() {
|
|||||||
|
|
||||||
useSetDefaultBackTo('settings');
|
useSetDefaultBackTo('settings');
|
||||||
|
|
||||||
|
const { metadata, settings } = useMetadataServerSettings();
|
||||||
|
|
||||||
|
const setSettingValue = <Setting extends MetadataServerSettingKeys>(
|
||||||
|
setting: Setting,
|
||||||
|
value: MetadataServerSettings[Setting],
|
||||||
|
) => {
|
||||||
|
requestUpdateServerMetadata(convertToGqlMeta(metadata)! ?? {}, [[setting, value]]).catch(() =>
|
||||||
|
makeToast(t('search.error.label.failed_to_save_settings'), 'warning'),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<List>
|
||||||
<SearchSettings />
|
<List
|
||||||
|
subheader={
|
||||||
|
<ListSubheader component="div" id="library-general-settings">
|
||||||
|
{t('global.label.general')}
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('library.settings.general.search.ignore_filters.label.title')}
|
||||||
|
secondary={t('library.settings.general.search.ignore_filters.label.description')}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={settings.ignoreFilters}
|
||||||
|
onChange={(e) => setSettingValue('ignoreFilters', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('library.settings.general.add_to_library.category_selection.label.title')}
|
||||||
|
secondary={t('library.settings.general.add_to_library.category_selection.label.description')}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={settings.showAddToLibraryCategorySelectDialog}
|
||||||
|
onChange={(e) => setSettingValue('showAddToLibraryCategorySelectDialog', e.target.checked)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
<GlobalUpdateSettings />
|
<GlobalUpdateSettings />
|
||||||
</>
|
</List>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +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 { List, ListItem, ListItemText, Switch } from '@mui/material';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ListSubheader from '@mui/material/ListSubheader';
|
|
||||||
import { SearchMetadataKeys } from '@/typings';
|
|
||||||
import { convertToGqlMeta, requestUpdateServerMetadata } from '@/util/metadata';
|
|
||||||
import { useSearchSettings } from '@/util/searchSettings';
|
|
||||||
import { makeToast } from '@/components/util/Toast';
|
|
||||||
import { useSetDefaultBackTo } from '@/components/context/NavbarContext';
|
|
||||||
|
|
||||||
export function SearchSettings() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { metadata, settings } = useSearchSettings();
|
|
||||||
|
|
||||||
useSetDefaultBackTo('settings');
|
|
||||||
|
|
||||||
const setSettingValue = (key: SearchMetadataKeys, value: boolean) => {
|
|
||||||
requestUpdateServerMetadata(convertToGqlMeta(metadata)! ?? {}, [[key, value]]).catch(() =>
|
|
||||||
makeToast(t('search.error.label.failed_to_save_settings'), 'warning'),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
subheader={
|
|
||||||
<ListSubheader component="div" id="library-search-filter">
|
|
||||||
{t('search.title.search')}
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItem>
|
|
||||||
<ListItemText primary={t('search.label.ignore_filters')} />
|
|
||||||
<Switch
|
|
||||||
edge="end"
|
|
||||||
checked={settings.ignoreFilters}
|
|
||||||
onChange={(e) => setSettingValue('ignoreFilters', e.target.checked)}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -213,9 +213,14 @@ export enum ChapterOffset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type MetadataServerSettings = {
|
export type MetadataServerSettings = {
|
||||||
|
// downloads
|
||||||
deleteChaptersManuallyMarkedRead: boolean;
|
deleteChaptersManuallyMarkedRead: boolean;
|
||||||
deleteChaptersWhileReading: number;
|
deleteChaptersWhileReading: number;
|
||||||
deleteChaptersWithBookmark: boolean;
|
deleteChaptersWithBookmark: boolean;
|
||||||
|
|
||||||
|
// library
|
||||||
|
showAddToLibraryCategorySelectDialog: boolean;
|
||||||
|
ignoreFilters: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ISearchSettings {
|
export interface ISearchSettings {
|
||||||
|
|||||||
@@ -11,9 +11,14 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|||||||
import { convertFromGqlMeta, getMetadataFrom } from '@/util/metadata';
|
import { convertFromGqlMeta, getMetadataFrom } from '@/util/metadata';
|
||||||
|
|
||||||
export const getDefaultSettings = (): MetadataServerSettings => ({
|
export const getDefaultSettings = (): MetadataServerSettings => ({
|
||||||
|
// downloads
|
||||||
deleteChaptersManuallyMarkedRead: false,
|
deleteChaptersManuallyMarkedRead: false,
|
||||||
deleteChaptersWhileReading: 0,
|
deleteChaptersWhileReading: 0,
|
||||||
deleteChaptersWithBookmark: false,
|
deleteChaptersWithBookmark: false,
|
||||||
|
|
||||||
|
// library
|
||||||
|
showAddToLibraryCategorySelectDialog: true,
|
||||||
|
ignoreFilters: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getMetadataServerSettingsWithDefaultFallback = (
|
const getMetadataServerSettingsWithDefaultFallback = (
|
||||||
|
|||||||
@@ -1,32 +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 { Metadata, ISearchSettings } from '@/typings';
|
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|
||||||
import { convertFromGqlMeta, getMetadataFrom } from '@/util/metadata';
|
|
||||||
|
|
||||||
export const getDefaultSettings = (): ISearchSettings => ({
|
|
||||||
ignoreFilters: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getSearchSettingsWithDefaultValueFallback = (
|
|
||||||
meta?: Metadata,
|
|
||||||
defaultSettings: ISearchSettings = getDefaultSettings(),
|
|
||||||
applyMetadataMigration: boolean = true,
|
|
||||||
): ISearchSettings => getMetadataFrom({ meta }, defaultSettings, applyMetadataMigration);
|
|
||||||
export const useSearchSettings = (): {
|
|
||||||
metadata?: Metadata;
|
|
||||||
settings: ISearchSettings;
|
|
||||||
loading: boolean;
|
|
||||||
} => {
|
|
||||||
const { data, loading } = requestManager.useGetGlobalMeta();
|
|
||||||
const metadata = convertFromGqlMeta(data?.metas.nodes);
|
|
||||||
const settings = getSearchSettingsWithDefaultValueFallback(metadata);
|
|
||||||
|
|
||||||
return { metadata, settings, loading };
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user