frontend/app/Lightbox/BulkSelectionsScroll.tsx (245 lines of code) (raw):
import React, {useEffect, useState} from 'react';
import TimestampFormatter from "../common/TimestampFormatter";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import axios from 'axios';
import LoadingThrobber from "../common/LoadingThrobber.jsx";
import {LightboxBulk, LightboxBulkResponse} from "../types";
import {formatError} from "../common/ErrorViewComponent";
import {Grid, IconButton, LinearProgress, makeStyles, Tooltip, Typography} from "@material-ui/core";
import clsx from "clsx";
import {AirportShuttle, DeleteOutline, GetApp, Timelapse, WarningRounded} from "@material-ui/icons";
interface BulkSelectionsScrollProps {
currentSelection?: string;
onSelected: (newId:string|undefined)=>void;
forUser: string;
isAdmin: boolean;
expiryDays: number;
onError?: (desc:string)=>void;
}
const useStyles = makeStyles((theme)=>({
dealWithLongNames: {
"& p": {
width: "282px",
overflow: "hidden"
}
},
entryView: {
border: "1px solid black",
overflow: "hidden",
width: "140px",
height: "150px",
borderRadius: "10px",
backgroundColor: "white",
display: "inline-block",
marginRight: "2em",
marginBottom: "2em",
padding: "0.4em",
paddingTop: "0.2em"
},
entryTitle: {
marginTop: "0",
marginBottom: "0.2em",
color: theme.palette.secondary.dark,
fontSize: "0.8em",
fontWeight: "bold",
backgroundColor: "inherit",
marginLeft: "0.2em",
marginRight: "0.2em",
height: "2.5em",
overflow: "hidden",
},
bulkSelectionView: {
height: "130px",
width: "280px",
overflow: "hidden",
backgroundColor: theme.palette.primary.main,
whiteSpace: "nowrap"
},
bulkSelectionScroll: {
overflowY: "hidden",
overflowX: "auto",
width: "max-content"
},
clickable: {
cursor: "pointer",
},
entryThumbnailShadow: {
boxShadow: "2px 2px 6px black"
},
entryViewSelected: {
borderColor: "white !important",
boxShadow: "3px 3px 5px black"
},
dontExpand: {
marginBottom: 0,
marginTop: 0,
},
black: {
color: "black",
backgroundColor: "inherit"
},
small: {
"& p": {
fontSize: "0.7em"
}
},
bulkDownloadLink: {
fontSize: "0.8em",
zIndex: 999,
float: "left"
},
redoRestoreLink: {
fontSize: "0.8em",
zIndex: 999,
float: "right"
},
runOnText: {
display: "inline"
},
warningIcon: {
color: theme.palette.warning.dark
}
}));
const BulkSelectionsScroll:React.FC<BulkSelectionsScrollProps> = (props) => {
const [bulkSelections, setBulkSelections] = useState<LightboxBulk[]>([]);
const [loading, setLoading] = useState(true);
const classes = useStyles();
const nameExtractor = /^([^:]+):(.*)$/;
const extractNameAndPathArray = (str:string) => {
const result = nameExtractor.exec(str);
if(result){
return ({name: result[1], pathArray: result[2].split("/")})
} else {
return ({name: str, pathArray: []})
}
}
const bulkSearchDeleteRequested = async (entryId:string) => {
try {
await axios.delete("/api/lightbox/"+props.forUser+"/bulk/" + entryId);
console.log("lightbox entry " + entryId + " deleted.");
//if we are deleting the current selection, the update the selection to undefined otherwise do a no-op update
//to trugger reload
const updatedSelected = props.currentSelection===entryId ? undefined : props.currentSelection;
setBulkSelections((prevState) => prevState.filter(entry=>entry.id!==entryId));
props.onSelected(updatedSelected);
} catch(err) {
console.error(err);
if(props.onError) props.onError(formatError(err, false));
}
}
const loadData = async () => {
setLoading(true);
try {
const bulkSelections = await axios.get<LightboxBulkResponse>("/api/lightbox/" + props.forUser + "/bulks");
setBulkSelections(bulkSelections.data.entries);
setLoading(false);
} catch(err) {
setLoading(false);
console.error("Could not load in bulks: ", err);
if(props.onError) props.onError(formatError(err, false));
}
}
useEffect(()=>{
loadData();
}, [props.forUser]);
const initiateDownloadInApp = (entryId:string) => {
axios.get("/api/lightbox/bulk/appDownload/" + entryId, )
.then(result=>{
window.location.href = result.data.objectId;
}).catch(err=>{
console.error(err);
if(props.onError) props.onError(formatError(err,false));
})
}
const initiateRedoBulk = (entryId:string) => {
axios.put("/api/lightbox/" + props.forUser + "/bulk/redoRestore/" + entryId).then(response=>{
console.log(response.data);
}).catch(err=>{
console.error(err);
if(props.onError) props.onError(formatError(err, false));
})
}
const showExpiryWarning = (addedAt:string) => {
try {
let date = Date.now();
let addedTime = Date.parse(addedAt);
let setting = props.expiryDays * 86400000;
return ((date - addedTime) > setting);
} catch (err) {
console.error("could not set expiry warning: ", err);
if(props.onError) props.onError("An internal error occurred calculating expiry time");
return false
}
}
return <div className={classes.bulkSelectionScroll}>
{
loading ? <LinearProgress/> : bulkSelections.map((entry,idx)=>{
const bulkInfo = extractNameAndPathArray(entry.description);
const baseClasses = [
classes.entryView,
classes.bulkSelectionView,
classes.clickable
];
const classList = props.currentSelection === entry.id ? baseClasses : baseClasses.concat(classes.entryThumbnailShadow);
return <div className={clsx(classList)} onClick={()=>props.onSelected(entry.id)} key={idx}>
<Typography className={clsx(classes.entryTitle, classes.dontExpand)}>
<FontAwesomeIcon style={{marginRight: "0.5em"}} icon="hdd"/>{bulkInfo.name}
</Typography>
<Typography className={clsx(classes.black, classes.small, classes.dontExpand, classes.dealWithLongNames)}>
<FontAwesomeIcon style={{marginRight: "0.5em"}} icon="folder"/>
{bulkInfo.pathArray.length>0 ? bulkInfo.pathArray.slice(-1) : ""}
</Typography>
<Typography className={clsx(classes.black, classes.small, classes.dontExpand)}>
<TimestampFormatter relative={true} value={entry.addedAt} className={classes.runOnText}/>
</Typography>
<Grid container justify="space-between" alignItems="center">
<Grid item>
<Typography className={clsx(classes.black, classes.small, classes.dontExpand)}>
<FontAwesomeIcon style={{marginRight: "0.5em"}} icon="list-ol"/>{entry.availCount} items
</Typography>
</Grid>
<Grid>
<Grid container justify="flex-end" alignItems="center">
{
showExpiryWarning(entry.addedAt) ?
<Tooltip title="Some or all of the media may have returned to the deep archive and therefore be unavailable.">
<WarningRounded className={classes.warningIcon}/>
</Tooltip> : null
}
<Grid item>
<Tooltip title="Download in app">
<IconButton onClick={(evt)=>{
evt.preventDefault();
initiateDownloadInApp(entry.id)
}}>
<GetApp/>
</IconButton>
</Tooltip>
</Grid>
{
props.isAdmin ? <Grid item>
<Tooltip title="Re-do restore of folder">
<IconButton onClick={(evt)=>{
evt.preventDefault();
initiateRedoBulk(entry.id);
}}>
<AirportShuttle/>
</IconButton>
</Tooltip>
</Grid> : null
}
<Grid item>
<Tooltip title="Remove this bulk from your lightbox">
<IconButton style={{float: "right"}} onClick={(evt)=>{
evt.stopPropagation();
bulkSearchDeleteRequested(entry.id);
}}>
<DeleteOutline style={{color: "red"}}/>
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Grid>
</Grid>
</div>
})
}
</div>
}
export default BulkSelectionsScroll;