2021-03-26 04:17:02 +04:30
|
|
|
/*
|
|
|
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
|
|
|
*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
2021-02-20 01:23:52 +03:30
|
|
|
* 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/. */
|
|
|
|
|
|
2023-02-06 10:06:33 +01:00
|
|
|
import React, { useMemo, useState, useContext, useEffect } from 'react';
|
|
|
|
|
import { List, ListItem, ListItemText, ListItemIcon, IconButton } from '@mui/material';
|
2021-02-20 01:23:52 +03:30
|
|
|
import {
|
2023-02-06 10:06:33 +01:00
|
|
|
DragDropContext,
|
|
|
|
|
Droppable,
|
|
|
|
|
Draggable,
|
|
|
|
|
DropResult,
|
|
|
|
|
DraggingStyle,
|
|
|
|
|
NotDraggingStyle,
|
2021-05-30 04:01:49 +04:30
|
|
|
} from 'react-beautiful-dnd';
|
2021-09-09 17:51:22 +04:30
|
|
|
import DragHandleIcon from '@mui/icons-material/DragHandle';
|
|
|
|
|
import EditIcon from '@mui/icons-material/Edit';
|
|
|
|
|
import { useTheme, Palette } from '@mui/material/styles';
|
|
|
|
|
import Fab from '@mui/material/Fab';
|
|
|
|
|
import AddIcon from '@mui/icons-material/Add';
|
|
|
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
|
|
|
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';
|
2021-10-30 16:10:34 +03:30
|
|
|
import NavbarContext from 'components/context/NavbarContext';
|
2022-11-02 21:48:22 +01:00
|
|
|
import client, { useQuery } from 'util/client';
|
2023-02-24 16:40:37 +01:00
|
|
|
import { ICategory } from 'typings';
|
2023-03-23 13:59:32 +01:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2023-05-15 18:41:43 +02:00
|
|
|
import { DEFAULT_FULL_FAB_HEIGHT } from 'components/util/StyledFab';
|
2021-02-20 01:23:52 +03:30
|
|
|
|
2023-02-06 10:06:33 +01:00
|
|
|
const getItemStyle = (
|
|
|
|
|
isDragging: boolean,
|
|
|
|
|
draggableStyle: DraggingStyle | NotDraggingStyle | undefined,
|
|
|
|
|
palette: Palette,
|
|
|
|
|
) => ({
|
2021-02-20 01:23:52 +03:30
|
|
|
// styles we need to apply on draggables
|
|
|
|
|
...draggableStyle,
|
|
|
|
|
|
|
|
|
|
...(isDragging && {
|
2021-09-09 17:51:22 +04:30
|
|
|
background: palette.mode === 'dark' ? '#424242' : 'rgb(235,235,235)',
|
2021-02-20 01:23:52 +03:30
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default function Categories() {
|
2023-03-23 13:59:32 +01:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
2021-03-09 16:44:09 +03:30
|
|
|
const { setTitle, setAction } = useContext(NavbarContext);
|
2023-02-06 10:06:33 +01:00
|
|
|
useEffect(() => {
|
2023-03-23 13:59:32 +01:00
|
|
|
setTitle(t('category.title.categories'));
|
2023-03-11 18:05:58 +01:00
|
|
|
setAction(null);
|
2023-03-28 23:14:03 +02:00
|
|
|
}, [t]);
|
2021-03-09 16:44:09 +03:30
|
|
|
|
2022-11-02 21:48:22 +01:00
|
|
|
const { data, mutate } = useQuery<ICategory[]>('/api/v1/category/');
|
|
|
|
|
const categories = useMemo(() => {
|
2023-02-06 10:06:33 +01:00
|
|
|
const res = [...(data ?? [])];
|
2022-11-02 21:48:22 +01:00
|
|
|
if (res.length > 0 && res[0].name === 'Default') {
|
|
|
|
|
res.shift();
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}, [data]);
|
|
|
|
|
|
2021-05-30 04:01:49 +04:30
|
|
|
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);
|
2021-02-20 01:23:52 +03:30
|
|
|
const theme = useTheme();
|
|
|
|
|
|
2021-05-30 04:01:49 +04:30
|
|
|
const categoryReorder = (list: ICategory[], from: number, to: number) => {
|
2022-11-02 21:48:22 +01:00
|
|
|
const newData = [...list];
|
|
|
|
|
const [removed] = newData.splice(from, 1);
|
|
|
|
|
newData.splice(to, 0, removed);
|
|
|
|
|
mutate(newData, { revalidate: false });
|
|
|
|
|
|
2021-02-20 01:23:52 +03:30
|
|
|
const formData = new FormData();
|
2021-05-30 04:01:49 +04:30
|
|
|
formData.append('from', `${from + 1}`);
|
|
|
|
|
formData.append('to', `${to + 1}`);
|
2022-11-02 21:48:22 +01:00
|
|
|
client.patch('/api/v1/category/reorder', formData).finally(() => mutate());
|
2021-02-20 01:23:52 +03:30
|
|
|
};
|
|
|
|
|
|
2021-05-30 04:01:49 +04:30
|
|
|
const onDragEnd = (result: DropResult) => {
|
2021-02-20 01:23:52 +03:30
|
|
|
// dropped outside the list?
|
|
|
|
|
if (!result.destination) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-06 10:06:33 +01:00
|
|
|
categoryReorder(categories, result.source.index, result.destination.index);
|
2021-02-20 01:23:52 +03:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetDialog = () => {
|
2021-05-23 15:28:46 +04:30
|
|
|
setDialogName('');
|
|
|
|
|
setDialogDefault(false);
|
2021-02-20 01:23:52 +03:30
|
|
|
setCategoryToEdit(-1);
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-07 22:25:29 +03:30
|
|
|
const handleDialogOpen = () => {
|
2021-02-20 01:23:52 +03:30
|
|
|
resetDialog();
|
2021-03-07 22:25:29 +03:30
|
|
|
setDialogOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-06 10:06:33 +01:00
|
|
|
const handleEditDialogOpen = (index: number) => {
|
2021-05-23 15:28:46 +04:30
|
|
|
setDialogName(categories[index].name);
|
|
|
|
|
setDialogDefault(categories[index].default);
|
|
|
|
|
setCategoryToEdit(index);
|
|
|
|
|
setDialogOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-07 22:25:29 +03:30
|
|
|
const handleDialogCancel = () => {
|
|
|
|
|
setDialogOpen(false);
|
2021-02-20 01:23:52 +03:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDialogSubmit = () => {
|
2021-03-07 22:25:29 +03:30
|
|
|
setDialogOpen(false);
|
2021-02-20 01:23:52 +03:30
|
|
|
|
|
|
|
|
const formData = new FormData();
|
2021-05-23 15:28:46 +04:30
|
|
|
formData.append('name', dialogName);
|
2021-05-30 04:01:49 +04:30
|
|
|
formData.append('default', dialogDefault.toString());
|
2021-02-20 01:23:52 +03:30
|
|
|
|
|
|
|
|
if (categoryToEdit === -1) {
|
2023-02-06 10:06:33 +01:00
|
|
|
client.post('/api/v1/category/', formData).finally(() => mutate());
|
2021-02-20 01:23:52 +03:30
|
|
|
} else {
|
|
|
|
|
const category = categories[categoryToEdit];
|
2023-02-06 10:06:33 +01:00
|
|
|
client.patch(`/api/v1/category/${category.id}`, formData).finally(() => mutate());
|
2021-02-20 01:23:52 +03:30
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-06 10:06:33 +01:00
|
|
|
const deleteCategory = (index: number) => {
|
2021-02-20 01:23:52 +03:30
|
|
|
const category = categories[index];
|
2023-02-06 10:06:33 +01:00
|
|
|
client.delete(`/api/v1/category/${category.id}`).finally(() => mutate());
|
2021-02-20 01:23:52 +03:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<DragDropContext onDragEnd={onDragEnd}>
|
|
|
|
|
<Droppable droppableId="droppable">
|
2023-05-11 18:00:37 +02:00
|
|
|
{(droppableProvided) => (
|
2023-05-15 18:41:43 +02:00
|
|
|
<List ref={droppableProvided.innerRef} sx={{ paddingBottom: DEFAULT_FULL_FAB_HEIGHT }}>
|
2021-02-20 01:23:52 +03:30
|
|
|
{categories.map((item, index) => (
|
2023-02-08 18:19:26 +01:00
|
|
|
<Draggable key={item.id} draggableId={item.id.toString()} index={index}>
|
2023-05-11 18:00:37 +02:00
|
|
|
{(draggableProvided, snapshot) => (
|
2021-02-20 01:23:52 +03:30
|
|
|
<ListItem
|
2023-05-11 18:00:37 +02:00
|
|
|
ContainerProps={{ ref: draggableProvided.innerRef } as any}
|
|
|
|
|
{...draggableProvided.draggableProps}
|
|
|
|
|
{...draggableProvided.dragHandleProps}
|
2021-02-20 01:23:52 +03:30
|
|
|
style={getItemStyle(
|
|
|
|
|
snapshot.isDragging,
|
2023-05-11 18:00:37 +02:00
|
|
|
draggableProvided.draggableProps.style,
|
2021-02-20 01:23:52 +03:30
|
|
|
theme.palette,
|
|
|
|
|
)}
|
2023-05-11 18:00:37 +02:00
|
|
|
ref={draggableProvided.innerRef}
|
2021-02-20 01:23:52 +03:30
|
|
|
>
|
|
|
|
|
<ListItemIcon>
|
|
|
|
|
<DragHandleIcon />
|
|
|
|
|
</ListItemIcon>
|
2023-02-06 10:06:33 +01:00
|
|
|
<ListItemText primary={item.name} />
|
2021-02-20 01:23:52 +03:30
|
|
|
<IconButton
|
|
|
|
|
onClick={() => {
|
2021-05-23 15:28:46 +04:30
|
|
|
handleEditDialogOpen(index);
|
2021-02-20 01:23:52 +03:30
|
|
|
}}
|
2021-09-09 17:51:22 +04:30
|
|
|
size="large"
|
2021-02-20 01:23:52 +03:30
|
|
|
>
|
|
|
|
|
<EditIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
<IconButton
|
|
|
|
|
onClick={() => {
|
|
|
|
|
deleteCategory(index);
|
|
|
|
|
}}
|
2021-09-09 17:51:22 +04:30
|
|
|
size="large"
|
2021-02-20 01:23:52 +03:30
|
|
|
>
|
|
|
|
|
<DeleteIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</ListItem>
|
|
|
|
|
)}
|
|
|
|
|
</Draggable>
|
|
|
|
|
))}
|
2023-05-11 18:00:37 +02:00
|
|
|
{droppableProvided.placeholder}
|
2021-02-20 01:23:52 +03:30
|
|
|
</List>
|
|
|
|
|
)}
|
|
|
|
|
</Droppable>
|
|
|
|
|
</DragDropContext>
|
|
|
|
|
<Fab
|
|
|
|
|
color="primary"
|
|
|
|
|
aria-label="add"
|
|
|
|
|
style={{
|
2023-04-12 18:36:52 +02:00
|
|
|
position: 'fixed',
|
2021-02-20 01:23:52 +03:30
|
|
|
bottom: theme.spacing(2),
|
|
|
|
|
right: theme.spacing(2),
|
|
|
|
|
}}
|
|
|
|
|
onClick={handleDialogOpen}
|
|
|
|
|
>
|
|
|
|
|
<AddIcon />
|
|
|
|
|
</Fab>
|
2021-03-07 22:25:29 +03:30
|
|
|
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
|
2021-02-20 01:23:52 +03:30
|
|
|
<DialogTitle id="form-dialog-title">
|
2023-03-23 13:59:32 +01:00
|
|
|
{categoryToEdit === -1
|
|
|
|
|
? t('category.dialog.title.new_category')
|
|
|
|
|
: t('category.dialog.title.edit_category')}
|
2021-02-20 01:23:52 +03:30
|
|
|
</DialogTitle>
|
|
|
|
|
<DialogContent>
|
|
|
|
|
<TextField
|
|
|
|
|
autoFocus
|
|
|
|
|
margin="dense"
|
|
|
|
|
id="name"
|
2023-03-23 13:59:32 +01:00
|
|
|
label={t('category.label.category_name')}
|
2021-02-20 01:23:52 +03:30
|
|
|
type="text"
|
|
|
|
|
fullWidth
|
2021-05-23 15:28:46 +04:30
|
|
|
value={dialogName}
|
|
|
|
|
onChange={(e) => setDialogName(e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
<FormControlLabel
|
2023-02-06 10:06:33 +01:00
|
|
|
control={
|
2021-05-23 15:28:46 +04:30
|
|
|
<Checkbox
|
|
|
|
|
checked={dialogDefault}
|
|
|
|
|
onChange={(e) => setDialogDefault(e.target.checked)}
|
|
|
|
|
color="default"
|
|
|
|
|
/>
|
2023-02-06 10:06:33 +01:00
|
|
|
}
|
2023-03-23 13:59:32 +01:00
|
|
|
label={t('category.label.use_as_default_category')}
|
2021-02-20 01:23:52 +03:30
|
|
|
/>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
<DialogActions>
|
|
|
|
|
<Button onClick={handleDialogCancel} color="primary">
|
2023-03-23 13:59:32 +01:00
|
|
|
{t('global.button.cancel')}
|
2021-02-20 01:23:52 +03:30
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleDialogSubmit} color="primary">
|
2023-03-23 13:59:32 +01:00
|
|
|
{t('global.button.submit')}
|
2021-02-20 01:23:52 +03:30
|
|
|
</Button>
|
|
|
|
|
</DialogActions>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|