mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-06-06 05:38:44 +08:00
213 lines
8.5 KiB
TypeScript
213 lines
8.5 KiB
TypeScript
import { Component, ContextType } from "preact";
|
|
import { useEffect, useRef, useState } from "preact/hooks";
|
|
import { signal } from "@preact/signals";
|
|
import { GlobalCtx } from "../components/GlobalContext.tsx";
|
|
import type { TaskDetail } from "../task.ts";
|
|
import { TaskStatus } from "../task.ts";
|
|
import { Sortable } from "sortable";
|
|
import type {
|
|
TaskClientSocketData,
|
|
TaskServerSocketData,
|
|
} from "../server/task.ts";
|
|
import { get_ws_host } from "../server/utils.ts";
|
|
import Task from "../components/Task.tsx";
|
|
import Fab from "preact-material-components/Fab.js";
|
|
import Icon from "preact-material-components/Icon.js";
|
|
import { set_state } from "../server/state.ts";
|
|
import t from "../server/i18n.ts";
|
|
import { TaskFilterBar, TaskStatusFlag } from "../components/TaskFilterBar.tsx";
|
|
|
|
export type TaskManagerProps = {
|
|
base: string;
|
|
show: boolean;
|
|
};
|
|
|
|
const tasks = signal(new Map<number, TaskDetail>());
|
|
const task_list = signal(new Array<number>());
|
|
export const task_ws = signal<WebSocket | undefined>(undefined);
|
|
export function sendTaskMessage(mes: TaskClientSocketData) {
|
|
const ws = task_ws.value;
|
|
if (ws && ws.readyState === ws.OPEN) {
|
|
ws.send(JSON.stringify(mes));
|
|
return true;
|
|
} else return false;
|
|
}
|
|
|
|
type SortableEvent = CustomEvent & {
|
|
oldIndex: number | undefined;
|
|
newIndex: number | undefined;
|
|
};
|
|
|
|
export default class TaskManager extends Component<TaskManagerProps> {
|
|
static contextType = GlobalCtx;
|
|
declare context: ContextType<typeof GlobalCtx>;
|
|
sortable?: Sortable;
|
|
render() {
|
|
const ul = useRef<HTMLDivElement>();
|
|
useEffect(() => {
|
|
if (!this.props.show) {
|
|
this.sortable?.destroy();
|
|
this.sortable = undefined;
|
|
return;
|
|
}
|
|
this.sortable = new Sortable(ul.current, {
|
|
handle: ".task_handle",
|
|
onSort: (evt: SortableEvent) => {
|
|
if (
|
|
evt.newIndex === undefined || evt.oldIndex === undefined
|
|
) return;
|
|
const tl = task_list.value;
|
|
tl.splice(evt.newIndex, 0, tl.splice(evt.oldIndex, 1)[0]);
|
|
},
|
|
});
|
|
}, [this.props.show]);
|
|
useEffect(() => {
|
|
const ws = new WebSocket(`${get_ws_host()}/api/task`);
|
|
console.log(ws);
|
|
task_ws.value = ws;
|
|
function sendMessage(mes: TaskClientSocketData) {
|
|
ws.send(JSON.stringify(mes));
|
|
}
|
|
ws.onopen = () => {
|
|
sendMessage({ type: "task_list" });
|
|
};
|
|
ws.onmessage = (e) => {
|
|
const t: TaskServerSocketData = JSON.parse(e.data);
|
|
function sendTaskChangedEvent(id: number) {
|
|
self.dispatchEvent(
|
|
new CustomEvent("task_changed", { detail: id }),
|
|
);
|
|
}
|
|
if (t.type == "close") {
|
|
ws.close();
|
|
} else if (t.type == "tasks") {
|
|
let running_index = -1;
|
|
t.tasks.forEach((ta) => {
|
|
const is_running = t.running.includes(ta.id);
|
|
tasks.value.set(ta.id, {
|
|
base: ta,
|
|
status: is_running
|
|
? TaskStatus.Running
|
|
: TaskStatus.Wait,
|
|
});
|
|
const tl = task_list.value;
|
|
if (!tl.includes(ta.id)) {
|
|
tl.push(ta.id);
|
|
if (is_running) {
|
|
if (tl.length) {
|
|
tl.splice(
|
|
running_index + 1,
|
|
0,
|
|
tl.splice(tl.length - 1, 1)[0],
|
|
);
|
|
}
|
|
running_index += 1;
|
|
}
|
|
}
|
|
});
|
|
this.forceUpdate();
|
|
} else if (t.type == "new_task") {
|
|
tasks.value.set(t.detail.id, {
|
|
base: t.detail,
|
|
status: TaskStatus.Wait,
|
|
});
|
|
if (!task_list.value.includes(t.detail.id)) {
|
|
task_list.value.push(t.detail.id);
|
|
}
|
|
this.forceUpdate();
|
|
} else if (t.type == "task_started") {
|
|
const task = tasks.value.get(t.detail.id);
|
|
if (task === undefined) {
|
|
tasks.value.set(t.detail.id, {
|
|
base: t.detail,
|
|
status: TaskStatus.Running,
|
|
});
|
|
const tl = task_list.value;
|
|
if (!tl.includes(t.detail.id)) {
|
|
tl.push(t.detail.id);
|
|
if (tl.length) {
|
|
tl.splice(0, 0, tl.splice(tl.length - 1, 1)[0]);
|
|
}
|
|
}
|
|
this.forceUpdate();
|
|
} else {
|
|
task.status = TaskStatus.Running;
|
|
sendTaskChangedEvent(t.detail.id);
|
|
const tl = task_list.value;
|
|
const ind = tl.indexOf(task.base.id);
|
|
if (ind > 0) {
|
|
tl.splice(0, 0, tl.splice(ind, 1)[0]);
|
|
this.sortable?.sort(tl.map((t) => t.toString()));
|
|
}
|
|
}
|
|
} else if (t.type == "task_finished") {
|
|
const task = tasks.value.get(t.detail.id);
|
|
if (task !== undefined) {
|
|
task.status = TaskStatus.Finished;
|
|
sendTaskChangedEvent(t.detail.id);
|
|
const tl = task_list.value;
|
|
const ind = tl.indexOf(task.base.id);
|
|
if (ind < tl.length - 1 && ind > -1) {
|
|
tl.splice(tl.length - 1, 0, tl.splice(ind, 1)[0]);
|
|
this.sortable?.sort(tl.map((t) => t.toString()));
|
|
}
|
|
}
|
|
} else if (t.type == "task_progress") {
|
|
const task = tasks.value.get(t.detail.task_id);
|
|
if (task !== undefined) {
|
|
task.progress = t.detail.detail;
|
|
sendTaskChangedEvent(t.detail.task_id);
|
|
}
|
|
} else if (t.type == "task_updated") {
|
|
const task = tasks.value.get(t.detail.id);
|
|
if (task) {
|
|
task.base = t.detail;
|
|
}
|
|
} else if (t.type === "task_error") {
|
|
const task = tasks.value.get(t.detail.task.id);
|
|
if (task) {
|
|
task.status = TaskStatus.Failed;
|
|
task.error = t.detail.error;
|
|
task.fataled = t.detail.fatal;
|
|
sendTaskChangedEvent(task.base.id);
|
|
}
|
|
}
|
|
};
|
|
self.addEventListener("beforeunload", () => {
|
|
sendMessage({ type: "close" });
|
|
});
|
|
}, []);
|
|
if (!this.props.show) return null;
|
|
const [flags, set_flags] = useState(TaskStatusFlag.All);
|
|
return (
|
|
<div class="task_manager">
|
|
<Fab
|
|
class="new_task"
|
|
onClick={() => {
|
|
set_state(`${this.props.base}/new`);
|
|
}}
|
|
>
|
|
<Icon>add</Icon>
|
|
</Fab>
|
|
<div class="task_amounts">
|
|
<TaskFilterBar value={flags} set_value={set_flags} />
|
|
</div>
|
|
<div
|
|
class="task_details"
|
|
// @ts-ignore Checked
|
|
ref={ul}
|
|
>
|
|
{task_list.value.map((k) => {
|
|
const t = tasks.value.get(k);
|
|
if (t) {
|
|
return <Task task={t} flags={flags} />;
|
|
} else {
|
|
return <div data-id={k}></div>;
|
|
}
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|