import { Button, Checkbox, Flex, Heading, HStack, Input, Spacer, Text, VStack, } from "@hope-ui/solid" import { batch, createEffect, createMemo, createSignal, For, JSX, onCleanup, Show, } from "solid-js" import { Paginator } from "~/components" import { useFetch, useT } from "~/hooks" import { PEmptyResp, PResp, TaskInfo } from "~/types" import { handleResp, notify, r } from "~/utils" import { TaskCol, cols, Task, TaskOrderBy, TaskLocal } from "./Task" import { me } from "~/store" export interface TaskNameAnalyzer { regex: RegExp title: (matches: RegExpMatchArray, task?: TaskInfo) => string attrs: { [attr: string]: ( matches: RegExpMatchArray, task?: TaskInfo, ) => JSX.Element | undefined } statusText?: (task: TaskInfo) => JSX.Element | string | undefined } export interface TasksProps { type: string done: string nameAnalyzer: TaskNameAnalyzer canRetry?: boolean } export interface TaskViewAttribute { curFetchTime: number prevFetchTime?: number prevProgress?: number } export interface TaskLocalContainer { local: TaskLocal } export interface TaskLocalSetter { setLocal: (l: TaskLocal) => void } export type TaskAttribute = TaskInfo & TaskViewAttribute & TaskLocalContainer export const Tasks = (props: TasksProps) => { const t = useT() const [loading, get] = useFetch( (): PResp => r.get(`/task/${props.type}/${props.done}`), ) const [tasks, setTasks] = createSignal([]) const [orderBy, setOrderBy] = createSignal("name") const [orderReverse, setOrderReverse] = createSignal(false) const sorter: Record number> = { name: (a, b) => (a.name > b.name ? 1 : -1), creator: (a, b) => a.creator === b.creator ? a.id > b.id ? 1 : -1 : a.creator > b.creator ? 1 : -1, state: (a, b) => a.state === b.state ? (a.id > b.id ? 1 : -1) : a.state > b.state ? 1 : -1, progress: (a, b) => a.progress === b.progress ? a.id > b.id ? 1 : -1 : a.progress < b.progress ? 1 : -1, } const curSorter = createMemo(() => { return (a: TaskInfo, b: TaskInfo) => (orderReverse() ? -1 : 1) * sorter[orderBy()](a, b) }) const refresh = async () => { const resp = await get() handleResp(resp, (data) => { const fetchTime = new Date().getTime() const curFetchTimeMap: Record = {} const prevFetchTimeMap: Record = {} const curProgressMap: Record = {} const prevProgressMap: Record = {} const taskLocalMap: Record = {} for (const task of tasks()) { curFetchTimeMap[task.id] = task.curFetchTime prevFetchTimeMap[task.id] = task.prevFetchTime curProgressMap[task.id] = task.progress prevProgressMap[task.id] = task.prevProgress taskLocalMap[task.id] = task.local } setTasks( data ?.map((task) => { let prevFetchTime: number | undefined let prevProgress: number | undefined if (task.progress === curProgressMap[task.id]) { prevFetchTime = prevFetchTimeMap[task.id] // may be undefined prevProgress = prevProgressMap[task.id] // may be undefined } else { prevFetchTime = curFetchTimeMap[task.id] prevProgress = curProgressMap[task.id] } const taskLocal = taskLocalMap[task.id] ?? { selected: false, expanded: false, } return { ...task, curFetchTime: fetchTime, prevFetchTime: prevFetchTime, prevProgress: prevProgress, local: taskLocal, } }) .sort(curSorter()) ?? [], ) }) } refresh() if (props.done === "undone") { const interval = setInterval(refresh, 2000) onCleanup(() => clearInterval(interval)) } const [clearDoneLoading, clearDone] = useFetch( (): PEmptyResp => r.post(`/task/${props.type}/clear_done`), ) const [clearSucceededLoading, clearSucceeded] = useFetch( (): PEmptyResp => r.post(`/task/${props.type}/clear_succeeded`), ) const [retryFailedLoading, retryFailed] = useFetch( (): PEmptyResp => r.post(`/task/${props.type}/retry_failed`), ) console.log("props", props.type) const [regexFilterValue, setRegexFilterValue] = createSignal("") const [regexFilter, setRegexFilter] = createSignal(new RegExp("")) const [regexCompileFailed, setRegexCompileFailed] = createSignal(false) createEffect(() => { try { setRegexFilter(new RegExp(regexFilterValue())) setRegexCompileFailed(false) } catch (_) { setRegexCompileFailed(true) } }) const [showOnlyMine, setShowOnlyMine] = createSignal(me().role !== 2) const taskFilter = createMemo(() => { const regex = regexFilter() const mine = showOnlyMine() return (task: TaskInfo): boolean => regex.test(task.name) && (!mine || task.creator === me().username) }) const filteredTask = createMemo(() => { return tasks().filter(taskFilter()) }) const allSelected = createMemo(() => filteredTask() .map((task) => task.local.selected) .every(Boolean), ) const isIndeterminate = createMemo( () => filteredTask() .map((task) => task.local.selected) .some(Boolean) && !allSelected(), ) const selectAll = (v: boolean) => setTasks( tasks().map((task) => { if (taskFilter()(task)) { task.local.selected = v } return task }), ) const allExpanded = createMemo(() => filteredTask() .map((task) => task.local.expanded) .every(Boolean), ) const expandAll = (v: boolean) => setTasks( tasks().map((task) => { if (taskFilter()(task)) { task.local.expanded = v } return task }), ) const getSelectedId = () => filteredTask() .filter((task) => task.local.selected) .map((task) => task.id) const [retrySelectedLoading, retrySelected] = useFetch( (): PEmptyResp => r.post(`/task/${props.type}/retry_some`, getSelectedId()), ) const [operateSelectedLoading, operateSelected] = useFetch( (): PEmptyResp => r.post(`/task/${props.type}/${operateName}_some`, getSelectedId()), ) const notifyIndividualError = (msg: Record) => { Object.entries(msg).forEach(([key, value]) => { notify.error(`${key}: ${value}`) }) } const [page, setPage] = createSignal(1) const pageSize = 20 const operateName = props.done === "undone" ? "cancel" : "delete" const curTasks = createMemo(() => { const start = (page() - 1) * pageSize const end = start + pageSize return filteredTask().slice(start, end) }) const itemProps = (col: TaskCol) => { return { fontWeight: "bold", fontSize: "$sm", color: "$neutral11", textAlign: col.textAlign as any, } } const itemPropsSort = (col: TaskCol) => { return { cursor: "pointer", onClick: () => { if (orderBy() === col.name) { setOrderReverse(!orderReverse()) } else { batch(() => { setOrderBy(col.name as TaskOrderBy) setOrderReverse(false) }) } refresh() }, } } const getLocalSetter = (id: string) => { return (l: TaskLocal) => setTasks( tasks().map((t) => { if (t.id === id) { t.local = l } return t }), ) } return ( {t(`tasks.${props.done}`)} setRegexFilterValue(e.target.value as string)} invalid={regexCompileFailed()} /> setShowOnlyMine(e.target.checked as boolean)} > {t(`tasks.show_only_mine`)} selectAll(e.target.checked as boolean)} /> {t(`tasks.attr.${cols[0].name}`)} {t(`tasks.attr.${cols[1].name}`)} {t(`tasks.attr.${cols[2].name}`)} {t(`tasks.attr.${cols[3].name}`)} {t(`tasks.attr.${cols[4].name}`)} {t(`tasks.attr.${cols[5].name}`)} {curTasks().map((task) => ( ))} { setPage(p) }} /> ) } export const TypeTasks = (props: { type: string nameAnalyzer: TaskNameAnalyzer canRetry?: boolean }) => { const t = useT() return ( {t(`tasks.${props.type}`)} {(done) => ( )} ) }