frontend/app/search/NewBasicSearch.tsx (123 lines of code) (raw):
import React, {useState, useEffect} from "react";
import {RouteComponentProps} from "react-router";
import {CircularProgress, Grid, Input, makeStyles, Snackbar, Select, MenuItem} from "@material-ui/core";
import {Search} from "@material-ui/icons";
import MuiAlert from "@material-ui/lab/Alert";
import NewSearchComponent from "../common/NewSearchComponent";
import {ArchiveEntry} from "../types";
import EntryDetails from "../Entry/EntryDetails";
import {Helmet} from "react-helmet";
const useStyles = makeStyles({
searchBox: {
marginLeft: "auto",
marginRight: "auto",
width: "60vw",
alignItems: "flex-start"
},
spinner: {
width: "16px",
height: "16px"
},
appArea: {
display: "grid",
gridTemplateColumns: "repeat(20, 5%)",
marginTop: "2em",
height: "90vh"
}
});
const NewBasicSearch:React.FC<RouteComponentProps> = (props) => {
const classes = useStyles();
const [typedSearch, setTypedSearch] = useState(""); //this state var stores the text as it is typed
const [activeSearch, setActiveSearch] = useState(""); //this state var is shared with searchComponent as the basicSearch param
const [showingAlert, setShowingAlert] = useState(false);
const [lastError, setLastError] = useState<string|undefined>(undefined);
const [selectedEntry, setSelectedEntry] = useState<ArchiveEntry|undefined>(undefined);
const [isLoading, setIsLoading] = useState(false);
const [dividerLocation, setDividerLocation] = useState(20);
const [newlyLightboxedList, setNewlyLightboxedList] = useState<string[]>([]);
const [typeString, setTypeString] = useState<string>("Any");
const closeAlert = ()=>setShowingAlert(false);
/**
* 1 second after the user finishes typing, update the search parameter to trigger the search
*/
useEffect(()=>{
const timerId = window.setTimeout(()=>setActiveSearch(typedSearch), 1000);
return ()=>{
console.log(`cancelling previous timer ${timerId}`);
window.clearTimeout(timerId);
}
}, [typedSearch]);
useEffect(()=>{
setActiveSearch("");
const timerId2 = window.setTimeout(()=>setActiveSearch(typedSearch), 10);
}, [typeString]);
/**
* if the user has selected an entry, then open the details panel; if s/he has deselected then close it.
*/
useEffect(()=>{
if(selectedEntry) {
setDividerLocation(16); //panel open
} else {
setDividerLocation(20); //panel closed
}
}, [selectedEntry]);
return <>
<Helmet>
<title>Search - ArchiveHunter</title>
</Helmet>
<Snackbar open={showingAlert} onClose={closeAlert} autoHideDuration={8000}>
<MuiAlert severity="error" onClose={closeAlert}>{lastError}</MuiAlert>
</Snackbar>
<Grid container className={classes.searchBox} spacing={2}>
<Grid item><Search/></Grid>
<Grid item style={{flexGrow: 1}}>
<Input onChange={(evt)=>setTypedSearch(evt.target.value)}
value={typedSearch}
style={{width: "100%"}}/>
</Grid>
<Grid item style={{flexGrow: 1}}>
Type: <Select id="filter-type" value={typeString} onChange={(evt) => setTypeString(evt.target.value as string)} >
<MenuItem value="Any">Any</MenuItem>
<MenuItem value="application/gzip">application/gzip</MenuItem>
<MenuItem value="application/javascript">application/javascript</MenuItem>
<MenuItem value="application/json">application/json</MenuItem>
<MenuItem value="application/msword">application/msword</MenuItem>
<MenuItem value="application/mxf">application/mxf</MenuItem>
<MenuItem value="application/octet-stream">application/octet-stream</MenuItem>
<MenuItem value="application/pdf">application/pdf</MenuItem>
<MenuItem value="application/photoshop">application/photoshop</MenuItem>
<MenuItem value="application/postscript">application/postscript</MenuItem>
<MenuItem value="application/psd">application/psd</MenuItem>
<MenuItem value="application/rtf">application/rtf</MenuItem>
<MenuItem value="application/x-7z-compressed">application/x-7z-compressed</MenuItem>
<MenuItem value="application/x-cdf">application/x-cdf</MenuItem>
<MenuItem value="application/x-gzip">application/x-gzip</MenuItem>
<MenuItem value="application/x-photoshop">application/x-photoshop</MenuItem>
<MenuItem value="application/x-tar">application/x-tar</MenuItem>
<MenuItem value="application/xml">application/xml</MenuItem>
<MenuItem value="application/zip">application/zip</MenuItem>
<MenuItem value="audio/aac">audio/aac</MenuItem>
<MenuItem value="audio/aiff">audio/aiff</MenuItem>
<MenuItem value="audio/midi">audio/midi</MenuItem>
<MenuItem value="audio/mpeg">audio/mpeg</MenuItem>
<MenuItem value="audio/ogg">audio/ogg</MenuItem>
<MenuItem value="audio/wav">audio/wav</MenuItem>
<MenuItem value="audio/x-aiff">audio/x-aiff</MenuItem>
<MenuItem value="audio/x-wav">audio/x-wav</MenuItem>
<MenuItem value="binary/octet-stream">binary/octet-stream</MenuItem>
<MenuItem value="image/bmp">image/bmp</MenuItem>
<MenuItem value="image/gif">image/gif</MenuItem>
<MenuItem value="image/jpeg">image/jpeg</MenuItem>
<MenuItem value="image/png">image/png</MenuItem>
<MenuItem value="image/psd">image/psd</MenuItem>
<MenuItem value="image/tiff">image/tiff</MenuItem>
<MenuItem value="image/vnd.adobe.photoshop">image/vnd.adobe.photoshop</MenuItem>
<MenuItem value="image/x-icon">image/x-icon</MenuItem>
<MenuItem value="text/css">text/css</MenuItem>
<MenuItem value="text/csv">text/csv</MenuItem>
<MenuItem value="text/html">text/html</MenuItem>
<MenuItem value="text/plain">text/plain</MenuItem>
<MenuItem value="text/richtext">text/richtext</MenuItem>
<MenuItem value="text/xml">text/xml</MenuItem>
<MenuItem value="video/mp4">video/mp4</MenuItem>
<MenuItem value="video/mpeg">video/mpeg</MenuItem>
<MenuItem value="video/ogg">video/ogg</MenuItem>
<MenuItem value="video/quicktime">video/quicktime</MenuItem>
<MenuItem value="video/webm">video/webm</MenuItem>
<MenuItem value="video/x-msvideo">video/x-msvideo</MenuItem>
<MenuItem value="video/x-sgi-movie">video/x-sgi-movie</MenuItem>
<MenuItem value="video/x-flv">video/x-flv</MenuItem>
</Select>
</Grid>
{
isLoading ? <Grid item className={classes.spinner}><CircularProgress/></Grid> : null
}
</Grid>
<div className={classes.appArea}>
<div style={{ gridColumnStart: 1, gridColumnEnd: dividerLocation}}>
<NewSearchComponent pageSize={25}
itemLimit={100}
onEntryClicked={(entry)=>setSelectedEntry(entry)}
onErrorOccurred={(error)=>{
setLastError(error);
setShowingAlert(true);
}}
basicQuery={activeSearch}
selectedEntry={selectedEntry}
onLoadingStarted={()=>setIsLoading(true)}
onLoadingFinished={()=>setIsLoading(false)}
newlyLightboxed={newlyLightboxedList}
typeQuery={typeString}
/>
</div>
<div style={{gridColumnStart: dividerLocation, gridColumnEnd: -1}}>
{ selectedEntry ? <EntryDetails entry={selectedEntry}
autoPlay={true}
showJobs={true}
loadJobs={false}
onError={(message:string)=>{
setLastError(message);
setShowingAlert(true);
}}
openClicked={(itemId:string)=>props.history.push(`/item/${encodeURIComponent(itemId)}`)}
//when the user adds to lightbox we record it here. This state var is bound to the NewSearchComponent
//which will then re-load data for the given entry (after a short delay)
lightboxedCb={(entryId:string)=>setNewlyLightboxedList((prevState) => prevState.concat(entryId))}
/> : undefined }
</div>
</div>
</>
}
export default NewBasicSearch;