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"
|
"@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": {
|
"@material-ui/styles": {
|
||||||
"version": "4.10.0",
|
"version": "4.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.10.2",
|
"@material-ui/core": "^4.10.2",
|
||||||
"@material-ui/icons": "^4.9.1",
|
"@material-ui/icons": "^4.9.1",
|
||||||
|
"@material-ui/lab": "^4.0.0-alpha.56",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^9.5.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@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 Typography from '@material-ui/core/Typography';
|
||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
import Container from '@material-ui/core/Container';
|
import Container from '@material-ui/core/Container';
|
||||||
|
import Pagination from '@material-ui/lab/Pagination';
|
||||||
|
|
||||||
import { useSearch } from './utils/useSearch';
|
import { useSearch } from './utils/useSearch';
|
||||||
|
import {
|
||||||
|
useLocationSearch,
|
||||||
|
useLocationSearchState
|
||||||
|
} from './utils/routing';
|
||||||
import { SearchHit } from './SearchHit';
|
import { SearchHit } from './SearchHit';
|
||||||
|
|
||||||
|
|
||||||
@ -22,11 +27,15 @@ const classes = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
export const SearchPage = () => {
|
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...";
|
let resultsDisplay="No rule found...";
|
||||||
if (resultsAreLoading) {
|
if (resultsAreLoading) {
|
||||||
@ -36,6 +45,14 @@ export const SearchPage = () => {
|
|||||||
resultsDisplay = results.map(result => <SearchHit key={result.id} data={result}/>)
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Paper className={classes.languagesBar}>
|
<Paper className={classes.languagesBar}>
|
||||||
@ -52,15 +69,21 @@ export const SearchPage = () => {
|
|||||||
shrink: true,
|
shrink: true,
|
||||||
}}
|
}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={titleQuery}
|
value={query}
|
||||||
onChange={e => setTitleQuery(e.target.value)}
|
onChange={handleQueryUpdate}
|
||||||
|
error={error}
|
||||||
|
helperText={error}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</Paper>
|
</Paper>
|
||||||
<h1>Results</h1>
|
<Typography variant="h5" className={classes.searchBar}>Number of rules found: {numberOfHits}</Typography>
|
||||||
<ul>
|
<ul>
|
||||||
{resultsDisplay}
|
{resultsDisplay}
|
||||||
</ul>
|
</ul>
|
||||||
|
<Pagination count={totalPages} page={pageNumber} siblingCount={2}
|
||||||
|
onChange={(event, value) => setPageNumber(value)}
|
||||||
|
/>
|
||||||
|
<Paper/>
|
||||||
</div>
|
</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';
|
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 indexDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index.json`;
|
||||||
let storeDataUrl = `${process.env.PUBLIC_URL}/rules/rule-index-store.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 [storeData, storeDataError, storeDataIsLoading] = useFetch(storeDataUrl);
|
||||||
|
|
||||||
const [results, setResults] = useState([]);
|
const [results, setResults] = useState([]);
|
||||||
|
const [numberOfHits, setNumberOfHits] = useState(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
const [resultsAreloading, setResultsAreLoading] = useState(true);
|
const [resultsAreloading, setResultsAreLoading] = useState(true);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -23,11 +25,25 @@ export function useSearch(query) {
|
|||||||
if (query) {
|
if (query) {
|
||||||
finalQuery = `titles:${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);
|
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