frontend/app/ProjectEntryList/ProjectBackups.tsx (191 lines of code) (raw):
import React, { useEffect, useState } from "react";
import { RouteComponentProps, useHistory } from "react-router";
import { Helmet } from "react-helmet";
import {
Button,
Dialog,
DialogActions,
DialogContentText,
DialogTitle,
Grid,
IconButton,
List,
Paper,
Tooltip,
Typography,
} from "@material-ui/core";
import { Breadcrumb } from "@guardian/pluto-headers";
import { ArrowBack, PermMedia, WarningRounded } from "@material-ui/icons";
import { getFileStorageMetadata, getProject, getProjectFiles } from "./helpers";
import { Alert } from "@material-ui/lab";
import { format, parseISO } from "date-fns";
import clsx from "clsx";
import { DEFAULT_DATE_FORMAT } from "../../types/constants";
import BackupEntry from "./BackupEntry";
import SizeFormatter from "../common/SizeFormatter";
import PremiereVersionTranslationView from "../EntryViews/PremiereVersionTranslationView";
import { useGuardianStyles } from "~/misc/utils";
import { isLoggedIn } from "~/utils/api";
declare var deploymentRootPath: string;
const PrimaryFilesIndicator: React.FC<{
primaryFiles: FileEntry[];
meta: Map<string, string>;
}> = (props) => {
const [timeString, setTimeString] = useState("");
const classes = useGuardianStyles();
useEffect(() => {
if (props.primaryFiles.length >= 1) {
try {
const date = parseISO(props.primaryFiles[0].mtime);
setTimeString(format(date, DEFAULT_DATE_FORMAT));
} catch (err) {
console.error(
"Could not format date ",
props.primaryFiles[0].mtime,
": ",
err
);
setTimeString("(invalid date)");
}
}
}, [props.primaryFiles]);
if (props.primaryFiles.length == 0) {
return (
<Alert severity="error">There is no openable file on this project!</Alert>
);
} else if (props.primaryFiles.length == 1) {
return (
<Alert severity="info">
<ul style={{ listStyle: "none" }}>
<li className={classes.noSpacing}>
The main file is {props.primaryFiles[0].filepath} which was created
at {timeString}
</li>
<li className={classes.noSpacing}>
{props.primaryFiles[0].premiereVersion ? (
<span>
This is a Premiere project;{" "}
<PremiereVersionTranslationView
internalVersion={props.primaryFiles[0].premiereVersion}
/>
</span>
) : (
<span>
This is no Premiere version information on this project, maybe
it's not Premiere
</span>
)}
</li>
<li>
<>
{props.meta.get("size") ? (
<span>
The file size is{" "}
{<SizeFormatter bytes={props.meta.get("size")} />}
</span>
) : undefined}
{props.meta.get("lastModified") ? (
<span>
{" "}
and it was last modified at {props.meta.get("lastModified")}
</span>
) : undefined}
</>
</li>
</ul>
</Alert>
);
} else {
return (
<Alert severity="warning">
There are {props.primaryFiles.length} potential primary files on this
project
</Alert>
);
}
};
const ProjectBackups: React.FC<RouteComponentProps<{ itemid: string }>> = (
props
) => {
const [project, setProject] = useState<Project | undefined>(undefined);
const [dialogErrString, setDialogErrString] = useState<string | undefined>(
undefined
);
const [primaryFiles, setPrimaryFiles] = useState<FileEntry[]>([]);
const [backupFiles, setBackupFiles] = useState<FileEntry[]>([]);
const [primaryFileMetadata, setPrimaryFileMetadata] = useState<
Map<string, string>
>(new Map());
const history = useHistory();
const classes = useGuardianStyles();
useEffect(() => {
if (primaryFiles.length > 0) {
getFileStorageMetadata(primaryFiles[0].id)
.then((info) => setPrimaryFileMetadata(info))
.catch((err) => {
console.error(
"Can't get metadata for file id ",
primaryFiles[0].id,
": ",
err
);
});
}
}, [primaryFiles]);
useEffect(() => {
const loadProject = async () => {
const p = await getProject(Number(props.match.params.itemid));
setProject(p);
};
loadProject().catch((err) => {
console.error(err);
setDialogErrString(
`Could not load backups for project ${props.match.params.itemid}, it probably does not exist. See the javascript console for more details.`
);
});
}, [props.match.params.itemid]);
const sortFileEntryFunc = (a: FileEntry, b: FileEntry) =>
a.mtime.localeCompare(b.mtime);
useEffect(() => {
if (project) {
getProjectFiles(project.id).then((fileList) => {
setPrimaryFiles(fileList.filter((f) => !f.backupOf));
setBackupFiles(
fileList.filter((f) => !!f.backupOf).sort(sortFileEntryFunc)
);
});
}
}, [project]);
return (
<>
{project ? (
<Helmet>
<title>[{project.title}] Backups</title>
</Helmet>
) : null}
<Grid
container
justifyContent="space-between"
style={{ marginBottom: "0.8em" }}
>
<Grid item>
{project ? (
<Breadcrumb
projectId={project?.id}
plutoCoreBaseUri={`${deploymentRootPath.replace(/\/+$/, "")}`}
/>
) : undefined}
</Grid>
<Grid item>
<Grid container spacing={2}>
<Grid item>
<Tooltip title="Back to project page">
<IconButton
onClick={() => history.push(`/project/${project?.id}`)}
>
<ArrowBack />
</IconButton>
</Tooltip>
</Grid>
<Grid item>
<Tooltip title="See project's media">
<IconButton
onClick={() =>
window.location.assign(`/vs/project/${project?.id}`)
}
>
<PermMedia />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Grid>
</Grid>
{project ? (
<Paper elevation={3}>
<div>
<PrimaryFilesIndicator
primaryFiles={primaryFiles}
meta={primaryFileMetadata}
/>
</div>
<List>
{backupFiles.map((f, idx) => (
<BackupEntry
key={idx}
fileId={f.id}
filepath={f.filepath}
version={f.version}
premiereVersion={f.premiereVersion}
projectId={project?.id}
/>
))}
</List>
<Grid
container
className={classes.centeredDiv}
justifyContent="space-around"
>
<Grid item>
{backupFiles.length == 0 ? (
project.status == "New" || project.status == "Killed" ? (
<Typography className={classes.emphasised}>
There are no backups of this project file because it is{" "}
{project.status}
</Typography>
) : (
<Typography className={classes.emphasised}>
<WarningRounded
className={clsx(classes.warningIcon, classes.inlineIcon)}
/>
This project has not been backed up yet
</Typography>
)
) : undefined}
</Grid>
</Grid>
</Paper>
) : undefined}
{dialogErrString ? (
<Dialog open={!!dialogErrString}>
<DialogTitle>Could not load backups information</DialogTitle>
<DialogContentText>{dialogErrString}</DialogContentText>
<DialogActions>
<Button variant="contained" onClick={() => history.goBack()}>
Go back
</Button>
</DialogActions>
</Dialog>
) : undefined}
</>
);
};
export default ProjectBackups;