Files
stash/ui/v2.5/src/components/Settings/Tasks/JobTable.tsx
WithoutPants d176e9f192 Settings UI refactor (#2086)
* Full width settings page
* Group settings
* Make config fields optional
* auto save on change
* Add settings context
* Refactor stash library section
* Restructure settings
* Refactor tasks page
* Add collapse buttons for setting groups
* Add collapse buttons in library
* Add loading indicator
* Simplify task options. Add details to manual
* Add manual links to tasks page
* Add help tooltips
* Refactor about page
* Refactor log page
* Refactor tools panel
* Refactor plugin page
* Refactor task queue
* Improve disabled styling
2021-12-14 15:06:05 +11:00

219 lines
5.0 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { Button, Card, ProgressBar } from "react-bootstrap";
import {
mutateStopJob,
useJobQueue,
useJobsSubscribe,
} from "src/core/StashService";
import * as GQL from "src/core/generated-graphql";
import { Icon } from "src/components/Shared";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { useIntl } from "react-intl";
type JobFragment = Pick<
GQL.Job,
"id" | "status" | "subTasks" | "description" | "progress"
>;
interface IJob {
job: JobFragment;
}
const Task: React.FC<IJob> = ({ job }) => {
const [stopping, setStopping] = useState(false);
const [className, setClassName] = useState("");
useEffect(() => {
setTimeout(() => setClassName("fade-in"));
}, []);
useEffect(() => {
if (
job.status === GQL.JobStatus.Cancelled ||
job.status === GQL.JobStatus.Finished
) {
// fade out around 10 seconds
setTimeout(() => {
setClassName("fade-out");
}, 9800);
}
}, [job]);
async function stopJob() {
setStopping(true);
await mutateStopJob(job.id);
}
function canStop() {
return (
!stopping &&
(job.status === GQL.JobStatus.Ready ||
job.status === GQL.JobStatus.Running)
);
}
function getStatusClass() {
switch (job.status) {
case GQL.JobStatus.Ready:
return "ready";
case GQL.JobStatus.Running:
return "running";
case GQL.JobStatus.Stopping:
return "stopping";
case GQL.JobStatus.Finished:
return "finished";
case GQL.JobStatus.Cancelled:
return "cancelled";
}
}
function getStatusIcon() {
let icon: IconProp = "circle";
let iconClass = "";
switch (job.status) {
case GQL.JobStatus.Ready:
icon = "hourglass-start";
break;
case GQL.JobStatus.Running:
icon = "cog";
iconClass = "fa-spin";
break;
case GQL.JobStatus.Stopping:
icon = "cog";
iconClass = "fa-spin";
break;
case GQL.JobStatus.Finished:
icon = "check";
break;
case GQL.JobStatus.Cancelled:
icon = "ban";
break;
}
return <Icon icon={icon} className={`fa-fw ${iconClass}`} />;
}
function maybeRenderProgress() {
if (
job.status === GQL.JobStatus.Running &&
job.progress !== undefined &&
job.progress !== null
) {
const progress = job.progress * 100;
return (
<ProgressBar
animated
now={progress}
label={`${progress.toFixed(0)}%`}
/>
);
}
}
function maybeRenderSubTasks() {
if (
job.status === GQL.JobStatus.Running ||
job.status === GQL.JobStatus.Stopping
) {
return (
<div>
{/* eslint-disable react/no-array-index-key */}
{(job.subTasks ?? []).map((t, i) => (
<div className="job-subtask" key={i}>
{t}
</div>
))}
{/* eslint-enable react/no-array-index-key */}
</div>
);
}
}
return (
<li className={`job ${className}`}>
<div>
<Button
className="minimal stop"
size="sm"
onClick={() => stopJob()}
disabled={!canStop()}
>
<Icon icon="times" />
</Button>
<div className={`job-status ${getStatusClass()}`}>
<div>
{getStatusIcon()}
<span>{job.description}</span>
</div>
<div>{maybeRenderProgress()}</div>
{maybeRenderSubTasks()}
</div>
</div>
</li>
);
};
export const JobTable: React.FC = () => {
const intl = useIntl();
const jobStatus = useJobQueue();
const jobsSubscribe = useJobsSubscribe();
const [queue, setQueue] = useState<JobFragment[]>([]);
useEffect(() => {
setQueue(jobStatus.data?.jobQueue ?? []);
}, [jobStatus]);
useEffect(() => {
if (!jobsSubscribe.data) {
return;
}
const event = jobsSubscribe.data.jobsSubscribe;
function updateJob() {
setQueue((q) =>
q.map((j) => {
if (j.id === event.job.id) {
return event.job;
}
return j;
})
);
}
switch (event.type) {
case GQL.JobStatusUpdateType.Add:
// add to the end of the queue
setQueue((q) => q.concat([event.job]));
break;
case GQL.JobStatusUpdateType.Remove:
// update the job then remove after a timeout
updateJob();
setTimeout(() => {
setQueue((q) => q.filter((j) => j.id !== event.job.id));
}, 10000);
break;
case GQL.JobStatusUpdateType.Update:
updateJob();
break;
}
}, [jobsSubscribe.data]);
return (
<Card className="job-table">
<ul>
{!queue?.length ? (
<span className="empty-queue-message">
{intl.formatMessage({ id: "config.tasks.empty_queue" })}
</span>
) : undefined}
{(queue ?? []).map((j) => (
<Task job={j} key={j.id} />
))}
</ul>
</Card>
);
};