Skip to content

Commit cbea0e0

Browse files
authored
feat: enhance task manage page (#205)
Simplify and arrange task details Add the feature to select tasks and retry/delete them in batches Add a regex matcher to filter tasks Support sorting tasks by name, creator, state or progress Support redirecting to paths of files operated Closes AlistGo/alist#7184, AlistGo/alist#7096
1 parent 110332f commit cbea0e0

File tree

10 files changed

+659
-82
lines changed

10 files changed

+659
-82
lines changed

src/lang/en/tasks.json

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,36 @@
1919
"7": "Failed",
2020
"8": "WaitingRetry",
2121
"9": "BeforeRetry"
22-
}
22+
},
23+
"retry_selected": "Retry Selected",
24+
"cancel_selected": "Cancel Selected",
25+
"delete_selected": "Delete Selected",
26+
"filter": "Filter",
27+
"expand": "Expand",
28+
"fold": "Fold",
29+
"expand_all": "Expand All",
30+
"fold_all": "Fold All",
31+
"attr": {
32+
"name": "Name",
33+
"creator": "Creator",
34+
"state": "State",
35+
"progress": "Progress",
36+
"operation": "Operation",
37+
"copy": {
38+
"src": "Source Path",
39+
"dst": "Destination Path"
40+
},
41+
"upload": {
42+
"path": "Path"
43+
},
44+
"offline_download": {
45+
"url": "URL",
46+
"path": "Destination Path",
47+
"transfer_src": "Source Path",
48+
"transfer_dst": "Destination Path"
49+
},
50+
"status": "Status",
51+
"err": "Error"
52+
},
53+
"show_only_mine": "Show only my tasks"
2354
}

src/pages/manage/tasks/Aria2.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { VStack } from "@hope-ui/solid"
22
import { useManageTitle } from "~/hooks"
33
import { TypeTasks } from "./Tasks"
4+
import {
5+
getOfflineDownloadNameAnalyzer,
6+
getOfflineDownloadTransferNameAnalyzer,
7+
} from "./helper"
48

9+
// deprecated
510
const Aria2 = () => {
611
useManageTitle("manage.sidemenu.aria2")
712
return (
813
<VStack w="$full" alignItems="start" spacing="$4">
9-
<TypeTasks type="aria2_down" canRetry />
10-
<TypeTasks type="aria2_transfer" />
14+
<TypeTasks
15+
type="aria2_down"
16+
canRetry
17+
nameAnalyzer={getOfflineDownloadNameAnalyzer()}
18+
/>
19+
<TypeTasks
20+
type="aria2_transfer"
21+
nameAnalyzer={getOfflineDownloadTransferNameAnalyzer()}
22+
/>
1123
</VStack>
1224
)
1325
}

src/pages/manage/tasks/Copy.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1-
import { useManageTitle } from "~/hooks"
1+
import { useManageTitle, useT } from "~/hooks"
22
import { TypeTasks } from "./Tasks"
3+
import { getPath } from "./helper"
34

45
const Copy = () => {
6+
const t = useT()
57
useManageTitle("manage.sidemenu.copy")
6-
return <TypeTasks type="copy" canRetry />
8+
return (
9+
<TypeTasks
10+
type="copy"
11+
canRetry
12+
nameAnalyzer={{
13+
regex: /^copy \[(.+)]\((.*\/([^\/]+))\) to \[(.+)]\((.+)\)$/,
14+
title: (matches) => matches[3],
15+
attrs: {
16+
[t(`tasks.attr.copy.src`)]: (matches) =>
17+
getPath(matches[1], matches[2]),
18+
[t(`tasks.attr.copy.dst`)]: (matches) =>
19+
getPath(matches[4], matches[5]),
20+
},
21+
}}
22+
/>
23+
)
724
}
825

926
export default Copy

src/pages/manage/tasks/Qbit.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { VStack } from "@hope-ui/solid"
22
import { useManageTitle } from "~/hooks"
33
import { TypeTasks } from "./Tasks"
4+
import {
5+
getOfflineDownloadNameAnalyzer,
6+
getOfflineDownloadTransferNameAnalyzer,
7+
} from "./helper"
48

9+
// deprecated
510
const Qbit = () => {
611
useManageTitle("manage.sidemenu.qbit")
712
return (
813
<VStack w="$full" alignItems="start" spacing="$4">
9-
<TypeTasks type="qbit_down" canRetry />
10-
<TypeTasks type="qbit_transfer" />
14+
<TypeTasks
15+
type="qbit_down"
16+
canRetry
17+
nameAnalyzer={getOfflineDownloadNameAnalyzer()}
18+
/>
19+
<TypeTasks
20+
type="qbit_transfer"
21+
nameAnalyzer={getOfflineDownloadTransferNameAnalyzer()}
22+
/>
1123
</VStack>
1224
)
1325
}

src/pages/manage/tasks/Task.tsx

Lines changed: 151 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import {
22
Badge,
33
Button,
4+
Center,
5+
Checkbox,
6+
Divider,
7+
Flex,
8+
Grid,
9+
GridItem,
410
Heading,
11+
HStack,
512
Progress,
613
ProgressIndicator,
7-
Stack,
8-
Text,
9-
useColorModeValue,
14+
Spacer,
1015
VStack,
1116
} from "@hope-ui/solid"
12-
import { createSignal, Show } from "solid-js"
17+
import { createSignal, For, Show } from "solid-js"
1318
import { useT, useFetch } from "~/hooks"
1419
import { PEmptyResp, TaskInfo } from "~/types"
1520
import { handleResp, notify, r } from "~/utils"
1621
import { TasksProps } from "./Tasks"
22+
import { me } from "~/store"
1723

1824
enum TaskStateEnum {
1925
Pending,
@@ -47,7 +53,18 @@ const StateMap: Record<
4753
const Creator = (props: { name: string; role: number }) => {
4854
if (props.role < 0) return null
4955
const roleColors = ["info", "neutral", "accent"]
50-
return <Badge colorScheme={roleColors[props.role] as any}>{props.name}</Badge>
56+
return (
57+
<Badge
58+
colorScheme={roleColors[props.role] as any}
59+
css={{
60+
overflow: "hidden",
61+
whiteSpace: "nowrap",
62+
textOverflow: "ellipsis",
63+
}}
64+
>
65+
{props.name}
66+
</Badge>
67+
)
5168
}
5269

5370
export const TaskState = (props: { state: number }) => {
@@ -59,7 +76,32 @@ export const TaskState = (props: { state: number }) => {
5976
)
6077
}
6178

62-
export const Task = (props: TaskInfo & TasksProps) => {
79+
export type TaskOrderBy = "name" | "creator" | "state" | "progress"
80+
81+
export interface TaskCol {
82+
name: TaskOrderBy | "operation"
83+
textAlign: "left" | "right" | "center"
84+
w: any
85+
}
86+
87+
export interface TaskControlCallback {
88+
setSelected: (id: string, v: boolean) => void
89+
setExpanded: (id: string, v: boolean) => void
90+
}
91+
92+
export const cols: TaskCol[] = [
93+
{
94+
name: "name",
95+
textAlign: "left",
96+
w: me().role === 2 ? "calc(100% - 660px)" : "calc(100% - 540px)",
97+
},
98+
{ name: "creator", textAlign: "center", w: me().role === 2 ? "120px" : "0" },
99+
{ name: "state", textAlign: "center", w: "100px" },
100+
{ name: "progress", textAlign: "left", w: "160px" },
101+
{ name: "operation", textAlign: "right", w: "280px" },
102+
]
103+
104+
export const Task = (props: TaskInfo & TasksProps & TaskControlCallback) => {
63105
const t = useT()
64106
const operateName = props.done === "undone" ? "cancel" : "delete"
65107
const canRetry = props.done === "done" && props.state === TaskStateEnum.Failed
@@ -71,58 +113,56 @@ export const Task = (props: TaskInfo & TasksProps) => {
71113
(): PEmptyResp => r.post(`/task/${props.type}/retry?tid=${props.id}`),
72114
)
73115
const [deleted, setDeleted] = createSignal(false)
116+
const matches: RegExpMatchArray | null = props.name.match(
117+
props.nameAnalyzer.regex,
118+
)
119+
const title =
120+
matches === null ? props.name : props.nameAnalyzer.title(matches)
74121
return (
75122
<Show when={!deleted()}>
76-
<Stack
77-
bgColor={useColorModeValue("$background", "$neutral3")()}
78-
w="$full"
79-
overflowX="auto"
80-
shadow="$md"
81-
rounded="$lg"
82-
p="$2"
83-
direction={{ "@initial": "column", "@xl": "row" }}
84-
spacing="$2"
85-
>
86-
<VStack w="$full" alignItems="start" spacing="$1">
123+
<HStack w="$full" p="$2">
124+
<HStack w={cols[0].w} spacing="$1">
125+
<Checkbox
126+
// colorScheme="neutral"
127+
on:click={(e: MouseEvent) => {
128+
e.stopPropagation()
129+
}}
130+
checked={props.selected}
131+
onChange={(e: any) => {
132+
props.setSelected(props.id, e.target.checked as boolean)
133+
}}
134+
/>
87135
<Heading
88136
size="sm"
89137
css={{
90-
wordBreak: "break-all",
138+
whiteSpace: "nowrap",
139+
overflow: "hidden",
140+
textOverflow: "ellipsis",
91141
}}
92142
>
93-
{props.name}
143+
{title}
94144
</Heading>
95-
<Creator name={props.creator} role={props.creator_role} />
145+
</HStack>
146+
<Show when={me().role === 2}>
147+
<Center w={cols[1].w}>
148+
<Creator name={props.creator} role={props.creator_role} />
149+
</Center>
150+
</Show>
151+
<Center w={cols[2].w}>
96152
<TaskState state={props.state} />
97-
<Text
98-
css={{
99-
wordBreak: "break-all",
100-
}}
101-
>
102-
{props.status}
103-
</Text>
104-
<Show when={props.error}>
105-
<Text color="$danger9" css={{ wordBreak: "break-all" }}>
106-
{props.error}
107-
</Text>
108-
</Show>
109-
<Progress
110-
w="$full"
111-
trackColor="$info3"
112-
rounded="$full"
113-
size="sm"
114-
value={props.progress}
115-
>
116-
<ProgressIndicator color="$info8" rounded="$md" />
117-
{/* <ProgressLabel /> */}
118-
</Progress>
119-
</VStack>
120-
121-
<Stack
122-
direction={{ "@initial": "row", "@xl": "column" }}
123-
justifyContent={{ "@xl": "center" }}
124-
spacing="$1"
153+
</Center>
154+
<Progress
155+
w={cols[3].w}
156+
trackColor="$info3"
157+
rounded="$full"
158+
size="sm"
159+
value={props.progress}
125160
>
161+
<ProgressIndicator color="$info8" rounded="$md" />
162+
{/* <ProgressLabel /> */}
163+
</Progress>
164+
<Flex w={cols[4].w} gap="$1">
165+
<Spacer />
126166
<Show when={props.canRetry}>
127167
<Button
128168
disabled={!canRetry}
@@ -152,8 +192,69 @@ export const Task = (props: TaskInfo & TasksProps) => {
152192
>
153193
{t(`global.${operateName}`)}
154194
</Button>
155-
</Stack>
156-
</Stack>
195+
<Button
196+
colorScheme="neutral"
197+
onClick={() => {
198+
props.setExpanded(props.id, !props.expanded)
199+
}}
200+
>
201+
{props.expanded ? t(`tasks.fold`) : t(`tasks.expand`)}
202+
</Button>
203+
</Flex>
204+
</HStack>
205+
<Show when={props.expanded}>
206+
<VStack
207+
css={{ wordBreak: "break-all", fontSize: "0.8em" }}
208+
w="$full"
209+
pl="$2"
210+
pr="$2"
211+
>
212+
<Grid
213+
templateColumns="min-content 1fr"
214+
w="$full"
215+
columnGap="$4"
216+
mb="$2"
217+
>
218+
<Show when={matches !== null}>
219+
<For each={Object.entries(props.nameAnalyzer.attrs)}>
220+
{(entry) => (
221+
<>
222+
<GridItem
223+
color="$neutral9"
224+
textAlign="right"
225+
css={{ whiteSpace: "nowrap" }}
226+
>
227+
{entry[0]}
228+
</GridItem>
229+
<GridItem color="$neutral9">
230+
{entry[1](matches as RegExpMatchArray)}
231+
</GridItem>
232+
</>
233+
)}
234+
</For>
235+
</Show>
236+
<GridItem
237+
color="$neutral9"
238+
textAlign="right"
239+
css={{ whiteSpace: "nowrap" }}
240+
>
241+
{t(`tasks.attr.status`)}
242+
</GridItem>
243+
<GridItem color="$neutral9">{props.status}</GridItem>
244+
<Show when={props.error}>
245+
<GridItem
246+
color="$danger9"
247+
textAlign="right"
248+
css={{ whiteSpace: "nowrap" }}
249+
>
250+
{t(`tasks.attr.err`)}
251+
</GridItem>
252+
<GridItem color="$danger9">{props.error} </GridItem>
253+
</Show>
254+
</Grid>
255+
<Divider />
256+
</VStack>
257+
</Show>
157258
</Show>
158259
)
159260
}

0 commit comments

Comments
 (0)