Cleanup category settings
This commit is contained in:
9
src/modules/category/Category.constants.ts
Normal file
9
src/modules/category/Category.constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
export const CREATE_NEW_CATEGORY_ID = -1;
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 { useState } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { CategoryDefaultInfo, CategoryIdInfo, CategoryNameInfo } from '@/modules/category/Category.types.ts';
|
||||
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
|
||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||
|
||||
export const CreateOrEditCategoryDialog = ({
|
||||
category,
|
||||
onClose,
|
||||
}: {
|
||||
category: (CategoryIdInfo & CategoryNameInfo & CategoryDefaultInfo) | undefined;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const isEditMode = !!category;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [dialogName, setDialogName] = useState<string>(category?.name ?? '');
|
||||
const [dialogDefault, setDialogDefault] = useState<boolean>(!!category?.default);
|
||||
|
||||
const handleDialogSubmit = () => {
|
||||
onClose();
|
||||
|
||||
if (isEditMode) {
|
||||
requestManager
|
||||
.updateCategory(category.id, { name: dialogName, default: dialogDefault })
|
||||
.response.catch((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
requestManager
|
||||
.createCategory({ name: dialogName, default: dialogDefault })
|
||||
.response.catch((e) => makeToast(t('category.error.label.create_failure'), 'error', getErrorMessage(e)));
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open onClose={onClose}>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{isEditMode ? t('category.dialog.title.edit_category_one') : t('category.dialog.title.new_category')}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
label={t('category.label.category_name')}
|
||||
type="text"
|
||||
fullWidth
|
||||
value={dialogName}
|
||||
onChange={(e) => setDialogName(e.target.value)}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={dialogDefault} onChange={(e) => setDialogDefault(e.target.checked)} />}
|
||||
label={t('category.label.use_as_default_category')}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleDialogSubmit} color="primary">
|
||||
{t('global.button.submit')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -7,17 +7,8 @@
|
||||
*/
|
||||
|
||||
import { ComponentProps, useMemo, useState } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Fab from '@mui/material/Fab';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Box from '@mui/material/Box';
|
||||
import { closestCenter, DndContext, DragEndEvent } from '@dnd-kit/core';
|
||||
@@ -32,14 +23,16 @@ import { GET_CATEGORIES_SETTINGS } from '@/lib/graphql/queries/CategoryQuery.ts'
|
||||
import { CategorySettingsCard } from '@/modules/category/components/CategorySettingsCard.tsx';
|
||||
import { CategoryIdInfo } from '@/modules/category/Category.types.ts';
|
||||
import { getErrorMessage, noOp } from '@/lib/HelperFunctions.ts';
|
||||
import { makeToast } from '@/modules/core/utils/Toast.ts';
|
||||
import { DndSortableItem } from '@/lib/dnd-kit/DndSortableItem.tsx';
|
||||
import { DndKitUtil } from '@/lib/dnd-kit/DndKitUtil.ts';
|
||||
import { DndOverlayItem } from '@/lib/dnd-kit/DndOverlayItem.tsx';
|
||||
import { useAppTitle } from '@/modules/navigation-bar/hooks/useAppTitle.ts';
|
||||
import { CREATE_NEW_CATEGORY_ID } from '@/modules/category/Category.constants.ts';
|
||||
import { CreateOrEditCategoryDialog } from '@/modules/category/components/CreateOrEditCategoryDialog.tsx';
|
||||
|
||||
export function CategorySettings() {
|
||||
const { t } = useTranslation();
|
||||
const dndSensors = DndKitUtil.useSensorsForDevice();
|
||||
|
||||
useAppTitle(t('category.dialog.title.edit_category_other'));
|
||||
|
||||
@@ -47,6 +40,14 @@ export function CategorySettings() {
|
||||
GetCategoriesSettingsQuery,
|
||||
GetCategoriesSettingsQueryVariables
|
||||
>(GET_CATEGORIES_SETTINGS, { notifyOnNetworkStatusChange: true });
|
||||
const [reorderCategory, { reset: revertReorder }] = requestManager.useReorderCategory();
|
||||
|
||||
const [categoryToEdit, setCategoryToEdit] = useState<number>(CREATE_NEW_CATEGORY_ID);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [dndActiveCategory, setDndActiveCategory] = useState<
|
||||
ComponentProps<typeof CategorySettingsCard>['category'] | null
|
||||
>(null);
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const res = [...(data?.categories.nodes ?? [])];
|
||||
if (res.length > 0 && res[0].name === 'Default') {
|
||||
@@ -55,18 +56,6 @@ export function CategorySettings() {
|
||||
return res;
|
||||
}, [data]);
|
||||
|
||||
const [categoryToEdit, setCategoryToEdit] = useState<number>(-1); // -1 means new category
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
const [dialogName, setDialogName] = useState<string>('');
|
||||
const [dialogDefault, setDialogDefault] = useState<boolean>(false);
|
||||
const [reorderCategory, { reset: revertReorder }] = requestManager.useReorderCategory();
|
||||
const theme = useTheme();
|
||||
|
||||
const dndSensors = DndKitUtil.useSensorsForDevice();
|
||||
const [dndActiveCategory, setDndActiveCategory] = useState<
|
||||
ComponentProps<typeof CategorySettingsCard>['category'] | null
|
||||
>(null);
|
||||
|
||||
const categoryReorder = (list: CategoryIdInfo[], from: number, to: number) => {
|
||||
const reorderedCategory = list[from];
|
||||
|
||||
@@ -90,47 +79,16 @@ export function CategorySettings() {
|
||||
categoryReorder(categories, oldIndex, newIndex);
|
||||
};
|
||||
|
||||
const resetDialog = () => {
|
||||
setDialogName('');
|
||||
setDialogDefault(false);
|
||||
setCategoryToEdit(-1);
|
||||
};
|
||||
|
||||
const handleDialogOpen = () => {
|
||||
resetDialog();
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleEditDialogOpen = (index: number) => {
|
||||
setDialogName(categories[index].name);
|
||||
setDialogDefault(categories[index].default);
|
||||
setCategoryToEdit(index);
|
||||
const handleDialogOpen = (categoryId?: CategoryIdInfo['id']) => {
|
||||
setCategoryToEdit(categoryId ?? CREATE_NEW_CATEGORY_ID);
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDialogCancel = () => {
|
||||
setCategoryToEdit(CREATE_NEW_CATEGORY_ID);
|
||||
setDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleDialogSubmit = () => {
|
||||
setDialogOpen(false);
|
||||
|
||||
if (categoryToEdit === -1) {
|
||||
requestManager
|
||||
.createCategory({ name: dialogName, default: dialogDefault })
|
||||
.response.catch((e) =>
|
||||
makeToast(t('category.error.label.create_failure'), 'error', getErrorMessage(e)),
|
||||
);
|
||||
} else {
|
||||
const category = categories[categoryToEdit];
|
||||
requestManager
|
||||
.updateCategory(category.id, { name: dialogName, default: dialogDefault })
|
||||
.response.catch((e) =>
|
||||
makeToast(t('global.error.label.failed_to_save_changes'), 'error', getErrorMessage(e)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <LoadingPlaceholder />;
|
||||
}
|
||||
@@ -165,7 +123,7 @@ export function CategorySettings() {
|
||||
id={category.id}
|
||||
isDragging={category.id === dndActiveCategory?.id}
|
||||
>
|
||||
<CategorySettingsCard category={category} onEdit={() => handleEditDialogOpen(index)} />
|
||||
<CategorySettingsCard category={category} onEdit={() => handleDialogOpen(index)} />
|
||||
</DndSortableItem>
|
||||
))}
|
||||
</SortableContext>
|
||||
@@ -177,48 +135,19 @@ export function CategorySettings() {
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add"
|
||||
style={{
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: theme.spacing(2),
|
||||
right: theme.spacing(2),
|
||||
bottom: (theme) => theme.spacing(2),
|
||||
right: (theme) => theme.spacing(2),
|
||||
}}
|
||||
onClick={handleDialogOpen}
|
||||
onClick={() => handleDialogOpen()}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
{categoryToEdit === -1
|
||||
? t('category.dialog.title.new_category')
|
||||
: t('category.dialog.title.edit_category_one')}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
label={t('category.label.category_name')}
|
||||
type="text"
|
||||
fullWidth
|
||||
value={dialogName}
|
||||
onChange={(e) => setDialogName(e.target.value)}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox checked={dialogDefault} onChange={(e) => setDialogDefault(e.target.checked)} />
|
||||
}
|
||||
label={t('category.label.use_as_default_category')}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleDialogCancel} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleDialogSubmit} color="primary">
|
||||
{t('global.button.submit')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{dialogOpen && (
|
||||
<CreateOrEditCategoryDialog category={categories[categoryToEdit]} onClose={handleDialogCancel} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user