parent
f89495c206
commit
dcd0974ad2
12
frontend/package-lock.json
generated
12
frontend/package-lock.json
generated
@ -1383,6 +1383,18 @@
|
||||
"@babel/runtime": "^7.4.4"
|
||||
}
|
||||
},
|
||||
"@material-ui/lab": {
|
||||
"version": "4.0.0-alpha.56",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz",
|
||||
"integrity": "sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@material-ui/utils": "^4.10.2",
|
||||
"clsx": "^1.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.0"
|
||||
}
|
||||
},
|
||||
"@material-ui/styles": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.10.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.56",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
|
@ -5,8 +5,13 @@ import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import Pagination from '@material-ui/lab/Pagination';
|
||||
|
||||
import { useSearch } from './utils/useSearch';
|
||||
import {
|
||||
useLocationSearch,
|
||||
useLocationSearchState
|
||||
} from './utils/routing';
|
||||
import { SearchHit } from './SearchHit';
|
||||
|
||||
|
||||
@ -22,11 +27,15 @@ const classes = makeStyles((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
export const SearchPage = () => {
|
||||
const [titleQuery, setTitleQuery] = useState("");
|
||||
const pageSize = 20;
|
||||
const [query, setQuery] = useLocationSearchState('query', '');
|
||||
const [pageNumber, setPageNumber] = useLocationSearchState('page', 1, parseInt);
|
||||
const [_, setLocationSearch] = useLocationSearch();
|
||||
|
||||
const [results, resultsAreLoading] = useSearch(titleQuery);
|
||||
|
||||
const [results, numberOfHits, error, resultsAreLoading] = useSearch(query, pageSize, pageNumber);
|
||||
const totalPages = Math.ceil(numberOfHits/pageSize);
|
||||
|
||||
let resultsDisplay="No rule found...";
|
||||
if (resultsAreLoading) {
|
||||
@ -36,6 +45,14 @@ export const SearchPage = () => {
|
||||
resultsDisplay = results.map(result => <SearchHit key={result.id} data={result}/>)
|
||||
}
|
||||
|
||||
function handleQueryUpdate(event) {
|
||||
if (pageNumber > 1) {
|
||||
setLocationSearch({query: event.target.value, page: 1});
|
||||
} else {
|
||||
setQuery(event.target.value, {push: false});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Paper className={classes.languagesBar}>
|
||||
@ -52,15 +69,21 @@ export const SearchPage = () => {
|
||||
shrink: true,
|
||||
}}
|
||||
variant="outlined"
|
||||
value={titleQuery}
|
||||
onChange={e => setTitleQuery(e.target.value)}
|
||||
value={query}
|
||||
onChange={handleQueryUpdate}
|
||||
error={error}
|
||||
helperText={error}
|
||||
/>
|
||||
</Container>
|
||||
</Paper>
|
||||
<h1>Results</h1>
|
||||
<Typography variant="h5" className={classes.searchBar}>Number of rules found: {numberOfHits}</Typography>
|
||||
<ul>
|
||||
{resultsDisplay}
|
||||
</ul>
|
||||
<Pagination count={totalPages} page={pageNumber} siblingCount={2}
|
||||
onChange={(event, value) => setPageNumber(value)}
|
||||
/>
|
||||
<Paper/>
|
||||
</div>
|
||||
)
|
||||
}
|
53
frontend/src/utils/routing.js
Normal file
53
frontend/src/utils/routing.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useState } from "react";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
export function useLocationSearch() {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
function setLocationSearch(searchParams, push=true) {
|
||||
const search = new URLSearchParams(location.search);
|
||||
|
||||
for (const [key, value] of Object.entries(searchParams)) {
|
||||
search.set(key, value);
|
||||
}
|
||||
|
||||
if (push) {
|
||||
history.push(`${location.pathname}?${search.toString()}`);
|
||||
} else {
|
||||
history.replace(`${location.pathname}?${search.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
return [new URLSearchParams(location.search), setLocationSearch];
|
||||
}
|
||||
|
||||
export function useLocationSearchState(name, defaultValue, convert=value=>value) {
|
||||
const [state, setState] = useState(defaultValue);
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
React.useEffect(() => {
|
||||
const search = new URLSearchParams(location.search);
|
||||
if (search.has(name) && search.get(name) != state) {
|
||||
setState(convert(search.get(name)));
|
||||
} else if (!search.has(name) && state != defaultValue) {
|
||||
setState(defaultValue);
|
||||
}
|
||||
}, [location, history]);
|
||||
|
||||
function setSearchParam(value, {push=true, skipURI=false} = {}) {
|
||||
const search = new URLSearchParams(location.search);
|
||||
|
||||
search.set(name, value);
|
||||
setState(value);
|
||||
if (push) {
|
||||
history.push(`${location.pathname}?${search.toString()}`);
|
||||
} else {
|
||||
history.replace(`${location.pathname}?${search.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
return [state, setSearchParam];
|
||||
}
|
@ -4,7 +4,7 @@ import * as lunr from 'lunr'
|
||||
|
||||
import { useFetch } from './useFetch';
|
||||
|
||||
export function useSearch(query) {
|
||||
export function useSearch(query, pageSize, pageNumber) {
|
||||
let indexDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index.json`;
|
||||
let storeDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index-store.json`;
|
||||
|
||||
@ -12,6 +12,8 @@ export function useSearch(query) {
|
||||
const [storeData, storeDataError, storeDataIsLoading] = useFetch(storeDataUrl);
|
||||
|
||||
const [results, setResults] = useState([]);
|
||||
const [numberOfHits, setNumberOfHits] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [resultsAreloading, setResultsAreLoading] = useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -23,11 +25,25 @@ export function useSearch(query) {
|
||||
if (query) {
|
||||
finalQuery = `titles:${query}`
|
||||
}
|
||||
const hits = index.search(finalQuery);
|
||||
setResults(hits.map(({ ref }) => storeData[ref]));
|
||||
|
||||
let hits = []
|
||||
setError(null);
|
||||
try {
|
||||
hits = index.search(finalQuery);
|
||||
} catch (exception) {
|
||||
if (exception instanceof lunr.QueryParseError) {
|
||||
setError(exception.message);
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
setNumberOfHits(hits.length)
|
||||
// const pageResults = hits;
|
||||
const pageResults = hits.slice(pageSize*(pageNumber - 1), pageSize*(pageNumber));
|
||||
setResults(pageResults.map(({ ref }) => storeData[ref]));
|
||||
setResultsAreLoading(false);
|
||||
}
|
||||
}, [query, indexData, storeData, indexDataError, storeDataError, indexDataIsLoading, storeDataIsLoading]);
|
||||
}, [query, pageSize, pageNumber, error, indexData, storeData, indexDataError, storeDataError, indexDataIsLoading, storeDataIsLoading]);
|
||||
|
||||
return [results, resultsAreloading];
|
||||
return [results, numberOfHits, error, resultsAreloading];
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user