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

@@ -1,121 +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 React, { useContext, useEffect, useState } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import { useParams } from 'react-router-dom';
import MangaGrid from 'components/MangaGrid';
import NavbarContext from 'components/context/NavbarContext';
import client from 'util/client';
import { Box } from '@mui/system';
export default function SearchSingle() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('Search'); setAction(<></>); }, []);
const { sourceId } = useParams<{ sourceId: string }>();
const [error, setError] = useState<boolean>(false);
const [mangas, setMangas] = useState<IMangaCard[]>([]);
const [message, setMessage] = useState<string>('');
const [searchTerm, setSearchTerm] = useState<string>('');
const [hasNextPage, setHasNextPage] = useState<boolean>(false);
const [lastPageNum, setLastPageNum] = useState<number>(1);
const [isLoading, setIsLoading] = useState<boolean>(false);
const textInput = React.createRef<HTMLInputElement>();
useEffect(() => {
client.get(`/api/v1/source/${sourceId}`)
.then((response) => response.data)
.then((data: { name: string }) => setTitle(`Search: ${data.name}`));
}, []);
function resetSearch() {
setIsLoading(true);
setMangas([]);
setLastPageNum(1);
}
function processInput() {
if (textInput.current) {
const { value } = textInput.current;
if (value === '') {
setError(true);
} else {
setError(false);
setSearchTerm(value);
resetSearch();
}
}
}
useEffect(() => {
if (searchTerm.length > 0) {
client.get(`/api/v1/source/${sourceId}/search`, { params: { searchTerm, pageNum: lastPageNum } })
.then((response) => response.data)
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
if (data.mangaList.length > 0) {
setMangas([
...mangas,
...data.mangaList.map((it) => ({
title: it.title, thumbnailUrl: it.thumbnailUrl, id: it.id,
}))]);
setHasNextPage(data.hasNextPage);
} else {
setMessage('Search query returned nothing.');
}
setIsLoading(false);
});
}
}, [searchTerm, lastPageNum]);
return (
<>
<Box sx={{
margin: '20px 10px',
display: 'flex',
width: '400px',
maxWidth: 'calc(100% - 20px)',
}}
>
<TextField
inputRef={textInput}
error={error}
sx={{
mx: 1,
flexGrow: 1,
}}
id="filled-basic"
variant="filled"
size="small"
label="Search text.."
onKeyDown={(e) => e.key === 'Enter' && processInput()}
/>
<Button
variant="contained"
color="primary"
onClick={() => processInput()}
>
Search
</Button>
</Box>
{searchTerm.length > 0
&& (
<MangaGrid
mangas={mangas}
message={message}
hasNextPage={hasNextPage}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
isLoading={isLoading}
/>
)}
</>
);
}

View File

@@ -8,10 +8,19 @@
import React, { useContext, useEffect, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import IconButton from '@mui/material/IconButton';
import MangaGrid from 'components/MangaGrid';
import SourceMangaGrid from 'components/source/SourceMangaGrid';
import NavbarContext from 'components/context/NavbarContext';
import client from 'util/client';
import SettingsIcon from '@mui/icons-material/Settings';
import SourceOptions from 'components/source/SourceOptions';
import AppbarSearch from 'components/util/AppbarSearch';
import { useQueryParam, StringParam } from 'use-query-params';
interface IPos {
position: number
state: any
group?: number
}
export default function SourceMangas(props: { popular: boolean }) {
const { setTitle, setAction } = useContext(NavbarContext);
@@ -24,28 +33,28 @@ export default function SourceMangas(props: { popular: boolean }) {
const [lastPageNum, setLastPageNum] = useState<number>(1);
const [fetched, setFetched] = useState<boolean>(false);
const [Search, setSearch] = useState<boolean>();
const [query, setquery] = useQueryParam('query', StringParam);
const [reset, setReset] = React.useState(2);
const [update, setUpdate] = React.useState();
const [triggerUpdate, setTriggerUpdate] = useState<number>(2);
const [Data, SetData] = useState<ISourceFilters[]>();
const [Init, setInit] = useState<undefined | null>();
const [Noreset, setNoreset] = useQueryParam('R');
function makeFilters() {
client.get(`/api/v1/source/${sourceId}/filters`)
.then((response) => response.data)
.then((data: ISourceFilters[]) => {
SetData(data);
});
}
useEffect(() => {
setTitle('Source'); // title is later set after a fetch but we set it here once
}, []);
useEffect(() => {
setAction(
<>
{isConfigurable && (
<IconButton
onClick={() => history.push(`/sources/${sourceId}/configure/`)}
aria-label="display more actions"
edge="end"
color="inherit"
size="large"
>
<SettingsIcon />
</IconButton>
)}
</>,
);
}, [isConfigurable]);
useEffect(() => {
client.get(`/api/v1/source/${sourceId}`)
.then((response) => response.data)
@@ -56,18 +65,113 @@ export default function SourceMangas(props: { popular: boolean }) {
}, []);
useEffect(() => {
const sourceType = props.popular ? 'popular' : 'latest';
client.get(`/api/v1/source/${sourceId}/${sourceType}/${lastPageNum}`)
.then((response) => response.data)
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
setMangas([
...mangas,
...data.mangaList.map((it) => ({
title: it.title, thumbnailUrl: it.thumbnailUrl, id: it.id,
}))]);
setHasNextPage(data.hasNextPage);
setFetched(true);
});
if (triggerUpdate === 2) {
return;
}
if (triggerUpdate === 0) {
setTriggerUpdate(1);
return;
}
setFetched(false);
setMangas([]);
setLastPageNum(0);
if (Noreset === undefined && Search) { setNoreset(null); }
}, [triggerUpdate]);
useEffect(() => {
if (update !== undefined) {
const { position, state, group }: IPos = update;
client.post(`/api/v1/source/${sourceId}/filters`,
JSON.stringify(group === undefined ? {
position,
state,
} : {
position: group,
state: JSON.stringify({
position,
state,
}),
}))
.then(() => {
makeFilters();
});
}
}, [update]);
useEffect(() => {
if (reset === 0) {
setquery(undefined);
setNoreset(undefined);
setReset(1);
} else if (Noreset === undefined) {
client.get(`/api/v1/source/${sourceId}/filters?reset=true`)
.then(() => {
makeFilters();
setSearch(false);
if (reset === 1) {
setTriggerUpdate(0);
}
});
return;
}
makeFilters();
}, [reset]);
useEffect(() => {
setAction(
<>
<AppbarSearch />
{isConfigurable && (
<IconButton
onClick={() => history.push(`/sources/${sourceId}/configure/`)}
aria-label="display more actions"
edge="end"
color="inherit"
size="large"
>
<SettingsIcon />
</IconButton>
)}
</>
,
);
}, [isConfigurable]);
useEffect(() => {
if (query) {
setSearch(true);
} else { setSearch(false); }
if (Noreset === undefined) { setInit(null); }
}, [query]);
useEffect(() => {
if (Search !== undefined && query !== undefined && Init === null) {
const delayDebounceFn = setTimeout(() => {
setTriggerUpdate(0);
}, 1000);
return () => clearTimeout(delayDebounceFn);
}
if (Search !== undefined) { setInit(null); }
return () => {};
}, [Search, query]);
useEffect(() => {
if (lastPageNum !== 0) {
const sourceType = props.popular ? 'popular' : 'latest';
client.get(`/api/v1/source/${sourceId}/${query !== undefined || Search || Noreset === null ? 'search' : sourceType}${query !== undefined || Search || Noreset === null ? `?searchTerm=${query || ''}&pageNum=${lastPageNum}` : `/${lastPageNum}`}`)
.then((response) => response.data)
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
setMangas([
...mangas,
...data.mangaList.map((it) => ({
title: it.title,
thumbnailUrl: it.thumbnailUrl,
id: it.id,
}))]);
setHasNextPage(data.hasNextPage);
setFetched(true);
});
} else { setLastPageNum(1); }
}, [lastPageNum]);
let message;
@@ -86,14 +190,25 @@ export default function SourceMangas(props: { popular: boolean }) {
}
return (
<MangaGrid
mangas={mangas}
hasNextPage={hasNextPage}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
message={message}
messageExtra={messageExtra}
isLoading={!fetched}
/>
<>
<SourceMangaGrid
mangas={mangas}
hasNextPage={hasNextPage}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
message={message}
messageExtra={messageExtra}
isLoading={!fetched}
/>
{Data !== undefined && (
<SourceOptions
sourceFilter={Data}
updateFilterValue={setUpdate}
resetFilterValue={setReset}
setTriggerUpdate={setTriggerUpdate}
setSearch={setSearch}
/>
)}
</>
);
}