app/panels/ProjectsPanel.tsx (266 lines of code) (raw):
import React, { useContext, useEffect, useState } from "react";
import {
Button,
Dialog,
DialogTitle,
Grid,
Icon,
IconButton,
Link,
Paper,
Typography,
} from "@material-ui/core";
import {
SystemNotifcationKind,
SystemNotification,
UserContext,
} from "@guardian/pluto-headers";
import PanelLauncher from "./PanelLauncher";
import { ProjectsPanelProps, usePanelStyles } from "./PanelsCommon";
import { useStyles as useCommonStyles } from "../CommonStyles";
import { GetMyRecentOpenProjects, openProject } from "../services/PlutoCore";
import { formatRelative, parseISO } from "date-fns";
import { CancelOutlined, Help, Launch } from "@material-ui/icons";
import PlutoCoreHealthcheck from "./PlutoCoreHealthcheck";
import clsx from "clsx";
const ProjectsPanel: React.FC<ProjectsPanelProps> = (props) => {
const userContext = useContext(UserContext);
const commonClasses = useCommonStyles();
const panelClasses = usePanelStyles();
const [lastOpenedProject, setLastOpenedProject] = useState<
PlutoProject | undefined
>(undefined);
const [lastOpenedAt, setLastOpenedAt] = useState<Date | undefined>(undefined);
const [
healthcheckStateChangeCount,
setHealthcheckStateChangeCount,
] = useState(0);
const [showingCreateHelp, setShowingCreateHelp] = useState(false);
useEffect(() => {
GetMyRecentOpenProjects(1)
.then((results) => {
const [projects, events] = results;
setLastOpenedProject(projects.length > 0 ? projects[0] : undefined);
try {
if (events.length > 0) {
const lastOpenedDate = parseISO(events[0].at);
setLastOpenedAt(lastOpenedDate);
}
} catch (err) {
console.error(
"Could not get time from ",
events[0],
" field 'at': ",
err
);
}
})
.catch((err) => {
console.error(
"Could not receive last opened project information: ",
err
);
SystemNotification.open(
SystemNotifcationKind.Warning,
"Could not get last opened project"
);
});
}, [healthcheckStateChangeCount]);
const launchLastProject = async () => {
if (lastOpenedProject) {
try {
await openProject(lastOpenedProject.id);
} catch (error) {
SystemNotification.open(
SystemNotifcationKind.Error,
"An error occurred when attempting to open the project."
);
console.error(error);
}
} else {
console.error("Can't open last opened project as there is none set");
}
};
return (
<>
<Paper className={clsx(props.className, panelClasses.panel)}>
<PanelLauncher
buttonLabel="Open Project"
onClick={launchLastProject}
caption={`Work on the last edit that I had open`}
disabled={!lastOpenedProject}
>
{lastOpenedProject ? (
<Typography className={commonClasses.secondaryPara}>
You last opened "{lastOpenedProject.title}"
{lastOpenedAt
? ` at ${formatRelative(lastOpenedAt, Date.now())}`
: ""}
<IconButton
onClick={() =>
(window.location.href = `/pluto-core/project/${lastOpenedProject?.id}`)
}
>
<Launch />
</IconButton>
</Typography>
) : undefined}
</PanelLauncher>
<PanelLauncher
buttonLabel="Project List"
onClick={() => window.location.assign("/pluto-core/project/?mine")}
caption="Search for another of my edits"
/>
<PanelLauncher
buttonLabel="Create Project"
onClick={() => window.location.assign("/pluto-core/project/new")}
caption="Create a new edit project in an existing piece of work"
/>
<PanelLauncher
buttonLabel="Create Commission"
onClick={() => window.location.assign("/pluto-core/commission/new")}
caption="Start a completely new piece of work"
/>
<span
className={commonClasses.clickable}
onClick={() => setShowingCreateHelp(true)}
style={{ marginTop: "0.2em" }}
>
<Help />
<Typography
className={commonClasses.secondaryPara}
style={{ verticalAlign: "top", display: "inline" }}
>
Should I create a new commission or project?
</Typography>
</span>
<PlutoCoreHealthcheck
onConnectionRestored={() =>
setHealthcheckStateChangeCount((prev) => prev + 1)
}
/>
</Paper>
<Dialog
open={showingCreateHelp}
onClose={() => setShowingCreateHelp(false)}
aria-labelledby="create-help-title"
maxWidth="lg"
fullWidth={true}
>
<DialogTitle style={{ textAlign: "center" }}>
Should I create a commission or project?
<IconButton
onClick={() => setShowingCreateHelp(false)}
style={{ float: "right" }}
>
<CancelOutlined />
</IconButton>
</DialogTitle>
<Typography
className={commonClasses.dialogPara}
style={{ textAlign: "center" }}
>
<b>
THINK BEFORE YOU TYPE!! - How would I find this in the future if I
had not worked on it.
</b>
</Typography>
<Typography className={commonClasses.dialogPara}>
The purpose of splitting our work into working groups, commissions and
projects is to make it easier for other people to find your work in
the future.
<br />
This could be for syndication or sales, or it could be for awards
entries, or to locate specific footage for another edit.
</Typography>
<Typography className={commonClasses.dialogPara}>
The job of a <b>Commission</b> is to hold together edit projects all
relating to the same content or content series.
<br />
For example, a documentary film may well have multiple edit projects,
graphics projects, audio sweetening.... All of these should live in
the same Commission, so we can find them later.
<br />
At the other extreme, all reactive news projects for a given month
live in the same commission. This is because every day has multiple
projects created and we need to keep them together.
</Typography>
<Typography className={commonClasses.dialogPara}>
The job of a <b>Project</b>, on the other hand, is to represent{" "}
<i>your current edit</i>. Normally, this will live{" "}
<b>within a pre-existing commission</b> and you should <b>always</b>{" "}
make an effort to find a relevant commission before you create a new
one. This will help avoid a situation where we have two, three or more
commissions all apparently relating to the same thing but with
slightly different spellings. You can use the Search function in the
commissions list when creating a project to try to find a relevant
one.
</Typography>
<Typography className={commonClasses.dialogPara}>
When choosing a name for a project, think how you would search for
that project if you did not know what it was called. Avoid acronyms
and try to ensure that key words are included in the title. This will
make it much more likely to surface when you are using the Search
function in the Projects list.
</Typography>
<Typography variant="h6">Examples</Typography>
<ul>
<li className={commonClasses.dialogPara}>
<Typography>
<b>I need to record a quick voiceover for a video project</b>
</Typography>
<Typography>
You{" "}
<b>
<i>must</i>
</b>{" "}
find the commission that the video project belongs to. If it has
not been created yet then create it as if you were the video
producer. You{" "}
<b>
<i>MUST NOT</i>
</b>
create a commission called e.g. "Voice-over for Jim" in
the "Multimedia Audio" working group because when
looking for the media nobody is going to look there.
</Typography>
</li>
<li className={commonClasses.dialogPara}>
<Typography>
<b>I need to ingest some footage that somebody just gave me</b>
</Typography>
<Typography>
Make sure you ask "somebody" if a project exists already
and if so where. There is nothing wrong with creating an
"ingest project" if an edit is already in-progress, but
make sure that it is in the same commission as the edit project.
That will ensure that the footage is easily findable.
</Typography>
</li>
<li className={commonClasses.dialogPara}>
<Typography>
<b>I have an edit that needs to be done NOW!!!!</b>
</Typography>
<Typography>
Spend 2 minutes in the Commissions page and search for something
relevant in your working group. If your edit is part of a longer
running series (e.g. Made in Britain, Anywhere But.... etc.) then
search for that and see how it has been managed in the past.
<br />
<b>Golden rule</b> is less commissions, more projects <i>in</i>{" "}
commissions.
</Typography>
</li>
</ul>
<Button
variant="contained"
onClick={() => setShowingCreateHelp(false)}
className={commonClasses.dialogButtonSingle}
>
Got it, thanks!
</Button>
</Dialog>
</>
);
};
export default ProjectsPanel;