Skip to content

Commit

Permalink
feat: enhance task manage page (#205)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
KirCute authored Dec 9, 2024
1 parent 110332f commit cbea0e0
Show file tree
Hide file tree
Showing 10 changed files with 659 additions and 82 deletions.
33 changes: 32 additions & 1 deletion src/lang/en/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,36 @@
"7": "Failed",
"8": "WaitingRetry",
"9": "BeforeRetry"
}
},
"retry_selected": "Retry Selected",
"cancel_selected": "Cancel Selected",
"delete_selected": "Delete Selected",
"filter": "Filter",
"expand": "Expand",
"fold": "Fold",
"expand_all": "Expand All",
"fold_all": "Fold All",
"attr": {
"name": "Name",
"creator": "Creator",
"state": "State",
"progress": "Progress",
"operation": "Operation",
"copy": {
"src": "Source Path",
"dst": "Destination Path"
},
"upload": {
"path": "Path"
},
"offline_download": {
"url": "URL",
"path": "Destination Path",
"transfer_src": "Source Path",
"transfer_dst": "Destination Path"
},
"status": "Status",
"err": "Error"
},
"show_only_mine": "Show only my tasks"
}
16 changes: 14 additions & 2 deletions src/pages/manage/tasks/Aria2.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { VStack } from "@hope-ui/solid"
import { useManageTitle } from "~/hooks"
import { TypeTasks } from "./Tasks"
import {
getOfflineDownloadNameAnalyzer,
getOfflineDownloadTransferNameAnalyzer,
} from "./helper"

// deprecated
const Aria2 = () => {
useManageTitle("manage.sidemenu.aria2")
return (
<VStack w="$full" alignItems="start" spacing="$4">
<TypeTasks type="aria2_down" canRetry />
<TypeTasks type="aria2_transfer" />
<TypeTasks
type="aria2_down"
canRetry
nameAnalyzer={getOfflineDownloadNameAnalyzer()}
/>
<TypeTasks
type="aria2_transfer"
nameAnalyzer={getOfflineDownloadTransferNameAnalyzer()}
/>
</VStack>
)
}
Expand Down
21 changes: 19 additions & 2 deletions src/pages/manage/tasks/Copy.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import { useManageTitle } from "~/hooks"
import { useManageTitle, useT } from "~/hooks"
import { TypeTasks } from "./Tasks"
import { getPath } from "./helper"

const Copy = () => {
const t = useT()
useManageTitle("manage.sidemenu.copy")
return <TypeTasks type="copy" canRetry />
return (
<TypeTasks
type="copy"
canRetry
nameAnalyzer={{
regex: /^copy \[(.+)]\((.*\/([^\/]+))\) to \[(.+)]\((.+)\)$/,
title: (matches) => matches[3],
attrs: {
[t(`tasks.attr.copy.src`)]: (matches) =>
getPath(matches[1], matches[2]),
[t(`tasks.attr.copy.dst`)]: (matches) =>
getPath(matches[4], matches[5]),
},
}}
/>
)
}

export default Copy
16 changes: 14 additions & 2 deletions src/pages/manage/tasks/Qbit.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { VStack } from "@hope-ui/solid"
import { useManageTitle } from "~/hooks"
import { TypeTasks } from "./Tasks"
import {
getOfflineDownloadNameAnalyzer,
getOfflineDownloadTransferNameAnalyzer,
} from "./helper"

// deprecated
const Qbit = () => {
useManageTitle("manage.sidemenu.qbit")
return (
<VStack w="$full" alignItems="start" spacing="$4">
<TypeTasks type="qbit_down" canRetry />
<TypeTasks type="qbit_transfer" />
<TypeTasks
type="qbit_down"
canRetry
nameAnalyzer={getOfflineDownloadNameAnalyzer()}
/>
<TypeTasks
type="qbit_transfer"
nameAnalyzer={getOfflineDownloadTransferNameAnalyzer()}
/>
</VStack>
)
}
Expand Down
201 changes: 151 additions & 50 deletions src/pages/manage/tasks/Task.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import {
Badge,
Button,
Center,
Checkbox,
Divider,
Flex,
Grid,
GridItem,
Heading,
HStack,
Progress,
ProgressIndicator,
Stack,
Text,
useColorModeValue,
Spacer,
VStack,
} from "@hope-ui/solid"
import { createSignal, Show } from "solid-js"
import { createSignal, For, Show } from "solid-js"
import { useT, useFetch } from "~/hooks"
import { PEmptyResp, TaskInfo } from "~/types"
import { handleResp, notify, r } from "~/utils"
import { TasksProps } from "./Tasks"
import { me } from "~/store"

enum TaskStateEnum {
Pending,
Expand Down Expand Up @@ -47,7 +53,18 @@ const StateMap: Record<
const Creator = (props: { name: string; role: number }) => {
if (props.role < 0) return null
const roleColors = ["info", "neutral", "accent"]
return <Badge colorScheme={roleColors[props.role] as any}>{props.name}</Badge>
return (
<Badge
colorScheme={roleColors[props.role] as any}
css={{
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
}}
>
{props.name}
</Badge>
)
}

export const TaskState = (props: { state: number }) => {
Expand All @@ -59,7 +76,32 @@ export const TaskState = (props: { state: number }) => {
)
}

export const Task = (props: TaskInfo & TasksProps) => {
export type TaskOrderBy = "name" | "creator" | "state" | "progress"

export interface TaskCol {
name: TaskOrderBy | "operation"
textAlign: "left" | "right" | "center"
w: any
}

export interface TaskControlCallback {
setSelected: (id: string, v: boolean) => void
setExpanded: (id: string, v: boolean) => void
}

export const cols: TaskCol[] = [
{
name: "name",
textAlign: "left",
w: me().role === 2 ? "calc(100% - 660px)" : "calc(100% - 540px)",
},
{ name: "creator", textAlign: "center", w: me().role === 2 ? "120px" : "0" },
{ name: "state", textAlign: "center", w: "100px" },
{ name: "progress", textAlign: "left", w: "160px" },
{ name: "operation", textAlign: "right", w: "280px" },
]

export const Task = (props: TaskInfo & TasksProps & TaskControlCallback) => {
const t = useT()
const operateName = props.done === "undone" ? "cancel" : "delete"
const canRetry = props.done === "done" && props.state === TaskStateEnum.Failed
Expand All @@ -71,58 +113,56 @@ export const Task = (props: TaskInfo & TasksProps) => {
(): PEmptyResp => r.post(`/task/${props.type}/retry?tid=${props.id}`),
)
const [deleted, setDeleted] = createSignal(false)
const matches: RegExpMatchArray | null = props.name.match(
props.nameAnalyzer.regex,
)
const title =
matches === null ? props.name : props.nameAnalyzer.title(matches)
return (
<Show when={!deleted()}>
<Stack
bgColor={useColorModeValue("$background", "$neutral3")()}
w="$full"
overflowX="auto"
shadow="$md"
rounded="$lg"
p="$2"
direction={{ "@initial": "column", "@xl": "row" }}
spacing="$2"
>
<VStack w="$full" alignItems="start" spacing="$1">
<HStack w="$full" p="$2">
<HStack w={cols[0].w} spacing="$1">
<Checkbox
// colorScheme="neutral"
on:click={(e: MouseEvent) => {
e.stopPropagation()
}}
checked={props.selected}
onChange={(e: any) => {
props.setSelected(props.id, e.target.checked as boolean)
}}
/>
<Heading
size="sm"
css={{
wordBreak: "break-all",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{props.name}
{title}
</Heading>
<Creator name={props.creator} role={props.creator_role} />
</HStack>
<Show when={me().role === 2}>
<Center w={cols[1].w}>
<Creator name={props.creator} role={props.creator_role} />
</Center>
</Show>
<Center w={cols[2].w}>
<TaskState state={props.state} />
<Text
css={{
wordBreak: "break-all",
}}
>
{props.status}
</Text>
<Show when={props.error}>
<Text color="$danger9" css={{ wordBreak: "break-all" }}>
{props.error}
</Text>
</Show>
<Progress
w="$full"
trackColor="$info3"
rounded="$full"
size="sm"
value={props.progress}
>
<ProgressIndicator color="$info8" rounded="$md" />
{/* <ProgressLabel /> */}
</Progress>
</VStack>

<Stack
direction={{ "@initial": "row", "@xl": "column" }}
justifyContent={{ "@xl": "center" }}
spacing="$1"
</Center>
<Progress
w={cols[3].w}
trackColor="$info3"
rounded="$full"
size="sm"
value={props.progress}
>
<ProgressIndicator color="$info8" rounded="$md" />
{/* <ProgressLabel /> */}
</Progress>
<Flex w={cols[4].w} gap="$1">
<Spacer />
<Show when={props.canRetry}>
<Button
disabled={!canRetry}
Expand Down Expand Up @@ -152,8 +192,69 @@ export const Task = (props: TaskInfo & TasksProps) => {
>
{t(`global.${operateName}`)}
</Button>
</Stack>
</Stack>
<Button
colorScheme="neutral"
onClick={() => {
props.setExpanded(props.id, !props.expanded)
}}
>
{props.expanded ? t(`tasks.fold`) : t(`tasks.expand`)}
</Button>
</Flex>
</HStack>
<Show when={props.expanded}>
<VStack
css={{ wordBreak: "break-all", fontSize: "0.8em" }}
w="$full"
pl="$2"
pr="$2"
>
<Grid
templateColumns="min-content 1fr"
w="$full"
columnGap="$4"
mb="$2"
>
<Show when={matches !== null}>
<For each={Object.entries(props.nameAnalyzer.attrs)}>
{(entry) => (
<>
<GridItem
color="$neutral9"
textAlign="right"
css={{ whiteSpace: "nowrap" }}
>
{entry[0]}
</GridItem>
<GridItem color="$neutral9">
{entry[1](matches as RegExpMatchArray)}
</GridItem>
</>
)}
</For>
</Show>
<GridItem
color="$neutral9"
textAlign="right"
css={{ whiteSpace: "nowrap" }}
>
{t(`tasks.attr.status`)}
</GridItem>
<GridItem color="$neutral9">{props.status}</GridItem>
<Show when={props.error}>
<GridItem
color="$danger9"
textAlign="right"
css={{ whiteSpace: "nowrap" }}
>
{t(`tasks.attr.err`)}
</GridItem>
<GridItem color="$danger9">{props.error} </GridItem>
</Show>
</Grid>
<Divider />
</VStack>
</Show>
</Show>
)
}
Loading

0 comments on commit cbea0e0

Please sign in to comment.