Source filters, move search to SourceMangas (#142)

* source genre filter

much the same as the library version

* undo

* base

base + SelectFilter + CheckBoxFilter
haven't done useQueryParam yet

* gui done

left to do:
1.saving the input data (so it don't disappear when closing the drawer)
2.post to update
3.reload list after drawer close

* done ish

* done im tired

* ok, actually done now

worked out the breaking that was happening in the last commit

im probably using useState for more than is nessessary, only really needed to do triggerUpdate and Data

* key error/warning fix

* issues all done

* remove junk

* jank back button fix

i re-used the query querystring

* reove single search

* boi was that a pain to fix

* Update App.tsx
This commit is contained in:
robo
2022-03-04 01:25:10 +00:00
committed by GitHub
parent 06aa40630f
commit 964186ce42
16 changed files with 957 additions and 190 deletions

View 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 React from 'react';
import MangaGrid, { IMangaGridProps } from 'components/MangaGrid';
const FILTERED_OUT_MESSAGE = 'There are no Manga matching this filter';
function filterManga(mangas: IMangaCard[]): IMangaCard[] {
return mangas;
}
export default function SourceMangaGrid(props: IMangaGridProps) {
const {
mangas, isLoading, hasNextPage, lastPageNum, setLastPageNum, message, messageExtra,
} = props;
const filteredManga = filterManga(mangas);
const showFilteredOutMessage = filteredManga.length === 0 && mangas.length > 0;
return (
<MangaGrid
mangas={filteredManga}
isLoading={isLoading}
hasNextPage={hasNextPage}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
message={showFilteredOutMessage ? FILTERED_OUT_MESSAGE : message}
messageExtra={messageExtra}
/>
);
}

View File

@@ -0,0 +1,203 @@
/*
* 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 React from 'react';
import FilterListIcon from '@mui/icons-material/FilterList';
import {
Drawer, Button, Fab,
} from '@mui/material';
import { Box } from '@mui/system';
import SelectFilter from './filters/SelectFilter';
import CheckBoxFilter from './filters/CheckBoxFilter';
import HeaderFilter from './filters/HeaderFilter';
import SeperatorFilter from './filters/SeparatorFilter';
import SortFilter from './filters/SortFilter';
import TextFilter from './filters/TextFilter';
import TriStateFilter from './filters/TriStateFilter';
// this can only cycle once, so should be fine
// eslint-disable-next-line import/no-cycle
import GroupFilter from './filters/GroupFilter';
interface IFilters {
sourceFilter: ISourceFilters[]
updateFilterValue: Function
group: number | undefined
}
interface IFilters1 {
sourceFilter: ISourceFilters[]
updateFilterValue: Function
resetFilterValue: Function
setTriggerUpdate: Function
setSearch: Function
}
export function Options({
sourceFilter,
group,
updateFilterValue,
}: IFilters) {
return (
<Box key={`filters ${group}`}>
{ sourceFilter.map((e: ISourceFilters, index) => {
switch (e.type) {
case 'CheckBox':
return (
<CheckBoxFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={e.filter.state as boolean}
position={index}
group={group}
updateFilterValue={updateFilterValue}
/>
);
case 'Group':
return (
<GroupFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={e.filter.state as ISourceFilters[]}
position={index}
updateFilterValue={updateFilterValue}
/>
);
case 'Header':
return (
<HeaderFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
/>
);
case 'Select':
return (
<SelectFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
values={e.filter.values}
state={e.filter.state as number}
selected={e.filter.selected}
position={index}
group={group}
updateFilterValue={updateFilterValue}
/>
);
case 'Separator':
return (
<SeperatorFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
/>
);
case 'Sort':
return (
<SortFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
values={e.filter.values}
state={e.filter.state as IState}
position={index}
group={group}
updateFilterValue={updateFilterValue}
/>
);
case 'Text':
return (
<TextFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={e.filter.state as string}
position={index}
group={group}
updateFilterValue={updateFilterValue}
/>
);
case 'TriState':
return (
<TriStateFilter
key={`filters ${e.filter.name}`}
name={e.filter.name}
state={e.filter.state as number}
position={index}
group={group}
updateFilterValue={updateFilterValue}
/>
);
default:
return (<Box key={`${e.filter.name}null`} />);
}
})}
</Box>
);
}
export default function SourceOptions({
sourceFilter,
updateFilterValue,
resetFilterValue,
setTriggerUpdate,
setSearch,
}: IFilters1) {
const [FilterOptions, setFilterOptions] = React.useState(false);
function handleReset() {
resetFilterValue(0);
setFilterOptions(false);
}
function handleSubmit() {
setTriggerUpdate(0);
setSearch(true);
setFilterOptions(false);
}
return (
<>
<Fab
sx={{ position: 'fixed', bottom: '2em', right: '3em' }}
onClick={() => setFilterOptions(!FilterOptions)}
variant="extended"
color="primary"
>
<FilterListIcon />
Filter
</Fab>
<Drawer
anchor="bottom"
open={FilterOptions}
onClose={() => setFilterOptions(false)}
PaperProps={{
style: {
maxWidth: 600, padding: '1em', marginLeft: 'auto', marginRight: 'auto',
},
}}
>
<Box sx={{ display: 'flex' }}>
<Button
onClick={handleReset}
>
Reset
</Button>
<Button
sx={{ marginLeft: 'auto' }}
variant="contained"
onClick={handleSubmit}
>
Submit
</Button>
</Box>
<Options
sourceFilter={sourceFilter}
updateFilterValue={updateFilterValue}
group={undefined}
/>
</Drawer>
</>
);
}

View File

@@ -0,0 +1,53 @@
/*
* 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 React from 'react';
import { Box } from '@mui/system';
import { Checkbox, FormControlLabel } from '@mui/material';
interface Props {
state: boolean
name: string
position: number
group: number | undefined
updateFilterValue: Function
}
export default function CheckBoxFilter(props: Props) {
const {
state,
name,
position,
group,
updateFilterValue,
} = props;
const [val, setval] = React.useState(state);
const handleChange = (event: { target: { name: any; checked: any; }; }) => {
setval(event.target.checked);
updateFilterValue({ position, state: event.target.checked.toString(), group });
};
if (state !== undefined) {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', minWidth: 120 }}>
<FormControlLabel
key={name}
control={(
<Checkbox
name={name}
checked={val}
onChange={handleChange}
/>
)}
label={name}
/>
</Box>
);
}
return (<></>);
}

View File

@@ -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 {
Collapse, List, ListItemButton, ListItemText,
} from '@mui/material';
import React from 'react';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
// eslint-disable-next-line import/no-cycle
import { Options } from '../SourceOptions';
interface Props {
state: ISourceFilters[]
name: string
position: number
updateFilterValue: Function
}
export default function GroupFilter(props: Props) {
const {
state,
name,
position,
updateFilterValue,
} = props;
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(!open);
};
return (
<>
<ListItemButton onClick={handleClick}>
<ListItemText primary={name} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open}>
<List disablePadding>
<Options
sourceFilter={state}
group={position}
updateFilterValue={updateFilterValue}
/>
</List>
</Collapse>
</>
);
}

View File

@@ -0,0 +1,19 @@
/*
* 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 React from 'react';
import { Box } from '@mui/system';
interface Props {
name: string
}
export default function HeaderFilter(props: Props) {
const { name } = props;
return (<Box key={name}>{name}</Box>);
}

View File

@@ -0,0 +1,150 @@
/*
* 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 React from 'react';
import { Box } from '@mui/system';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
interface Props {
values: any
name: string
state: number
selected: Selected | undefined
position: number
updateFilterValue: Function
group: number | undefined
}
interface Selected {
displayname: string
value: string
_value: string
}
function hasSelect(
values: Selected[],
name: string,
state: number,
position: number,
updateFilterValue: Function,
group?: number,
) {
const [val, setval] = React.useState(state);
if (values) {
const handleChange = (event: { target: { name: any; value: any; }; }) => {
const vall = values.map((e) => e.displayname).indexOf(`${event.target.value}`);
setval(vall);
updateFilterValue({ position, state: vall.toString(), group });
};
const rett = values.map((e: Selected) => (
<MenuItem
key={`${name} ${e.displayname}`}
value={e.displayname}
>
{
e.displayname
}
</MenuItem>
));
return (
<Box key={name} sx={{ display: 'flex', flexDirection: 'column', minWidth: 120 }}>
<FormControl fullWidth>
<InputLabel sx={{ margin: '10px 0 10px 0' }}>
{name}
</InputLabel>
<Select
name={name}
value={values[val].displayname}
label={name}
onChange={handleChange}
autoWidth
sx={{ margin: '10px 0 10px 0' }}
>
{rett}
</Select>
</FormControl>
</Box>
);
}
return (<></>);
}
function noSelect(
values: string[],
name: string,
state: number,
position: number,
updateFilterValue: Function,
group?: number,
) {
const [val, setval] = React.useState(state);
if (values) {
const handleChange = (event: { target: { name: any; value: any; }; }) => {
const vall = values.indexOf(`${event.target.value}`);
setval(vall);
updateFilterValue({ position, state: vall.toString(), group });
};
const rett = values.map((value: string) => (<MenuItem key={`${name} ${value}`} value={value}>{value}</MenuItem>));
return (
<Box key={name} sx={{ display: 'flex', flexDirection: 'column', minWidth: 120 }}>
<FormControl fullWidth>
<InputLabel sx={{ margin: '10px 0 10px 0' }}>
{name}
</InputLabel>
<Select
name={name}
value={values[val]}
label={name}
onChange={handleChange}
autoWidth
sx={{ margin: '10px 0 10px 0' }}
>
{rett}
</Select>
</FormControl>
</Box>
);
}
return (<></>);
}
export default function SelectFilter({
values,
name,
state,
selected,
position,
updateFilterValue,
group,
}: Props) {
if (selected === undefined) {
return noSelect(
values,
name,
state,
position,
updateFilterValue,
group,
);
}
return hasSelect(
values,
name,
state,
position,
updateFilterValue,
group,
);
}

View File

@@ -0,0 +1,18 @@
/*
* 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 React from 'react';
import { Box } from '@mui/system';
interface Props {
name: string
}
export default function SeperatorFilter(props: Props) {
const { name } = props;
return (<Box key={name}>{name}</Box>);
}

View File

@@ -0,0 +1,95 @@
/*
* 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 React from 'react';
import { Box } from '@mui/system';
import FormControl from '@mui/material/FormControl';
import {
Collapse,
List,
ListItem,
ListItemButton, ListItemIcon, ListItemText,
} from '@mui/material';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
interface Props {
values: any
name: string
state: IState
position: number
group: number | undefined
updateFilterValue: Function
}
export default function SortFilter(props: Props) {
const {
values,
name,
state,
position,
group,
updateFilterValue,
} = props;
const [val, setval] = React.useState(state);
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(!open);
};
if (values) {
const handleChange = (event:
React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
const tmp = val;
tmp.index = index;
tmp.ascending = !tmp.ascending;
setval(tmp);
updateFilterValue({ position, state: JSON.stringify(tmp), group });
};
const ret = (
<FormControl fullWidth>
<ListItemButton onClick={handleClick}>
<ListItemText primary={name} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open}>
<List>
{values.map((value: string, index: number) => {
let icon;
if (val.index === index) {
icon = val.ascending ? (<ArrowUpwardIcon color="primary" />)
: (<ArrowDownwardIcon color="primary" />);
}
return (
<ListItem disablePadding key={`${name} ${value}`}>
<ListItemButton
onClick={(event) => handleChange(event, index)}
>
<ListItemIcon>
{icon}
</ListItemIcon>
<ListItemText primary={value} />
</ListItemButton>
</ListItem>
);
})}
</List>
</Collapse>
</FormControl>
);
return (
<Box key={name} sx={{ display: 'flex', flexDirection: 'column', minWidth: 120 }}>
{ret}
</Box>
);
}
return (<></>);
}

View 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 React from 'react';
import { Box } from '@mui/system';
import {
Input,
InputLabel,
FormControl,
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
interface Props {
state: string
name: string
position: number
group: number | undefined
updateFilterValue: Function
}
export default function TextFilter(props: Props) {
const {
state,
name,
position,
group,
updateFilterValue,
} = props;
const [Search, setsearch] = React.useState('');
let typingTimer: NodeJS.Timeout;
function doneTyping(e: React.ChangeEvent<HTMLInputElement>) {
updateFilterValue({ position, state: e.target.value === '' ? '' : e.target.value.toString(), group });
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setsearch(e.target.value === '' ? '' : e.target.value);
clearTimeout(typingTimer);
typingTimer = setTimeout(() => { doneTyping(e); }, 2500);
}
if (state !== undefined) {
return (
<Box key={`${name}`} sx={{ display: 'flex', flexDirection: 'row', minWidth: 120 }}>
<>
<SearchIcon
sx={{
margin: 'auto',
}}
/>
<FormControl fullWidth>
<InputLabel sx={{ margin: '10px 0 10px 0' }}>
{name}
</InputLabel>
<Input
name={name}
value={Search || ''}
onChange={handleChange}
sx={{ margin: '10px 0 10px 0' }}
/>
</FormControl>
</>
</Box>
);
}
return (<></>);
}

View File

@@ -0,0 +1,70 @@
/*
* 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 React from 'react';
import { Box } from '@mui/system';
import { FormControlLabel } from '@mui/material';
import ThreeStateCheckbox from 'components/util/ThreeStateCheckbox';
interface Props {
state: number
name: string
position: number
group: number | undefined
updateFilterValue: Function
}
export default function TriStateFilter(props: Props) {
const {
state,
name,
position,
group,
updateFilterValue,
} = props;
const [val, setval] = React.useState({
[name]: state,
});
const handleChange = (checked: boolean | null | undefined) => {
const tmp = val;
if (checked !== undefined) {
tmp[name] = checked ? 1 : 2;
} else {
delete tmp[name];
}
setval({
...tmp,
});
updateFilterValue({
position,
state: (tmp[name] === undefined ? 0 : tmp[name]).toString(),
group,
});
};
if (state !== undefined) {
let check;
if (val[name] !== 0) {
check = val[name] === 1;
} else {
check = undefined;
}
return (
<Box sx={{ marginLeft: 3 }}>
<FormControlLabel
key={name}
control={(
<ThreeStateCheckbox name="Unread" checked={check} onChange={(checked) => handleChange(checked)} />
)}
label={name}
/>
</Box>
);
}
return (<></>);
}