Skip to content

Commit 0a50390

Browse files
authored
Merge pull request #398 from MaaAssistantArknights/dev
Release to Production
2 parents 4f9c77b + 0b38211 commit 0a50390

File tree

8 files changed

+357
-91
lines changed

8 files changed

+357
-91
lines changed

src/components/OperationCard.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ import { UserName } from './UserName'
1818
import { EDifficulty } from './entity/EDifficulty'
1919
import { EDifficultyLevel, NeoELevel } from './entity/ELevel'
2020

21-
export const NeoOperationCard = ({ operation }: { operation: Operation }) => {
21+
export const NeoOperationCard = ({
22+
operation,
23+
selected,
24+
selectable,
25+
onSelect,
26+
}: {
27+
operation: Operation
28+
selectable?: boolean
29+
selected?: boolean
30+
onSelect?: (operation: Operation, selected: boolean) => void
31+
}) => {
2232
const { data: levels } = useLevels()
2333

2434
return (
@@ -113,7 +123,13 @@ export const NeoOperationCard = ({ operation }: { operation: Operation }) => {
113123
</div>
114124
</ReLinkDiv>
115125

116-
<CardActions className="absolute top-4 right-4" operation={operation} />
126+
<CardActions
127+
className="absolute top-4 right-4"
128+
operation={operation}
129+
selectable={selectable}
130+
selected={selected}
131+
onSelect={onSelect}
132+
/>
117133
</Card>
118134
)
119135
}
@@ -247,11 +263,27 @@ const OperatorTags = ({ operation }: { operation: Operation }) => {
247263
const CardActions = ({
248264
className,
249265
operation,
266+
selected,
267+
selectable,
268+
onSelect,
250269
}: {
251270
className?: string
252271
operation: Operation
272+
selectable?: boolean
273+
selected?: boolean
274+
onSelect?: (operation: Operation, selected: boolean) => void
253275
}) => {
254-
return (
276+
return selectable ? (
277+
<Button
278+
small
279+
minimal={!selected}
280+
outlined={!selected}
281+
intent="primary"
282+
className="absolute top-4 right-4"
283+
icon={selected ? 'tick' : 'blank'}
284+
onClick={() => onSelect?.(operation, !selected)}
285+
/>
286+
) : (
255287
<div className={clsx('flex gap-1', className)}>
256288
<Tooltip2
257289
placement="bottom"
@@ -288,7 +320,11 @@ const CardActions = ({
288320
<div className="max-w-sm dark:text-slate-900">添加到作业集</div>
289321
}
290322
>
291-
<AddToOperationSetButton small icon="plus" operationId={operation.id} />
323+
<AddToOperationSetButton
324+
small
325+
icon="plus"
326+
operationIds={[operation.id]}
327+
/>
292328
</Tooltip2>
293329
</div>
294330
)

src/components/OperationList.tsx

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
import { Button, NonIdealState } from '@blueprintjs/core'
1+
import { Button, Callout, NonIdealState } from '@blueprintjs/core'
2+
import { Tooltip2 } from '@blueprintjs/popover2'
23

34
import { UseOperationsParams, useOperations } from 'apis/operation'
45
import { useAtomValue } from 'jotai'
5-
import { ComponentType, ReactNode, useEffect } from 'react'
6+
import { ComponentType, ReactNode, useEffect, useState } from 'react'
67

78
import { neoLayoutAtom } from 'store/pref'
89

10+
import { Operation } from '../models/operation'
911
import { NeoOperationCard, OperationCard } from './OperationCard'
1012
import { withSuspensable } from './Suspensable'
13+
import { AddToOperationSetButton } from './operation-set/AddToOperationSet'
1114

1215
interface OperationListProps extends UseOperationsParams {
16+
multiselect?: boolean
1317
onUpdate?: (params: { total: number }) => void
1418
}
1519

1620
export const OperationList: ComponentType<OperationListProps> = withSuspensable(
17-
({ onUpdate, ...params }) => {
21+
({ multiselect, onUpdate, ...params }) => {
1822
const neoLayout = useAtomValue(neoLayoutAtom)
1923

2024
const { operations, total, setSize, isValidating, isReachingEnd } =
@@ -30,6 +34,25 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
3034
onUpdate?.({ total })
3135
}, [total, onUpdate])
3236

37+
const [selectedOperations, setSelectedOperations] = useState<Operation[]>(
38+
[],
39+
)
40+
const updateSelection = (add: Operation[], remove: Operation[]) => {
41+
setSelectedOperations((old) => {
42+
return [
43+
...old.filter((op) => !remove.some((o) => o.id === op.id)),
44+
...add.filter((op) => !old.some((o) => o.id === op.id)),
45+
]
46+
})
47+
}
48+
const onSelect = (operation: Operation, selected: boolean) => {
49+
if (selected) {
50+
updateSelection([operation], [])
51+
} else {
52+
updateSelection([], [operation])
53+
}
54+
}
55+
3356
const items: ReactNode = neoLayout ? (
3457
<div
3558
className="grid gap-4"
@@ -38,7 +61,13 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
3861
}}
3962
>
4063
{operations.map((operation) => (
41-
<NeoOperationCard operation={operation} key={operation.id} />
64+
<NeoOperationCard
65+
operation={operation}
66+
key={operation.id}
67+
selectable={multiselect}
68+
selected={selectedOperations?.some((op) => op.id === operation.id)}
69+
onSelect={onSelect}
70+
/>
4271
))}
4372
</div>
4473
) : (
@@ -49,6 +78,60 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
4978

5079
return (
5180
<>
81+
{multiselect && (
82+
<Callout className="mb-4 p-0 select-none">
83+
<details>
84+
<summary className="px-2 py-4 cursor-pointer hover:bg-zinc-500 hover:bg-opacity-5">
85+
已选择 {selectedOperations.length} 份作业
86+
</summary>
87+
<div className="p-2 flex flex-wrap gap-1">
88+
{selectedOperations.map((operation) => (
89+
<Button
90+
key={operation.id}
91+
small
92+
minimal
93+
outlined
94+
rightIcon="cross"
95+
onClick={() => updateSelection([], [operation])}
96+
>
97+
{operation.parsedContent.doc.title}
98+
</Button>
99+
))}
100+
</div>
101+
</details>
102+
<div className="absolute top-2 right-2 flex">
103+
<Tooltip2 content="只能选择已加载的项目" placement="top">
104+
<Button
105+
minimal
106+
icon="tick"
107+
onClick={() => updateSelection(operations, [])}
108+
>
109+
全选
110+
</Button>
111+
</Tooltip2>
112+
<Button
113+
minimal
114+
intent="danger"
115+
icon="trash"
116+
onClick={() => setSelectedOperations([])}
117+
>
118+
清空
119+
</Button>
120+
<AddToOperationSetButton
121+
minimal
122+
outlined
123+
intent="primary"
124+
icon="add-to-folder"
125+
className="ml-2"
126+
disabled={selectedOperations.length === 0}
127+
operationIds={selectedOperations.map((op) => op.id)}
128+
>
129+
添加到作业集
130+
</AddToOperationSetButton>
131+
</div>
132+
</Callout>
133+
)}
134+
52135
{items}
53136

54137
{isReachingEnd && operations.length === 0 && (

src/components/Operations.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const Operations: ComponentType = withSuspensable(() => {
4242
const [selectedUser, setSelectedUser] = useState<MaaUserInfo>()
4343
const [neoLayout, setNeoLayout] = useAtom(neoLayoutAtom)
4444
const [tab, setTab] = useState<'operation' | 'operationSet'>('operation')
45+
const [multiselect, setMultiselect] = useState(false)
4546

4647
return (
4748
<>
@@ -74,7 +75,15 @@ export const Operations: ComponentType = withSuspensable(() => {
7475
title="作业集"
7576
/>
7677
</Tabs>
77-
<ButtonGroup className="ml-auto">
78+
<Button
79+
minimal
80+
icon="multi-select"
81+
title="启动多选"
82+
className="ml-auto mr-2"
83+
active={multiselect}
84+
onClick={() => setMultiselect((v) => !v)}
85+
/>
86+
<ButtonGroup>
7887
<Button
7988
icon="grid-view"
8089
active={neoLayout}
@@ -218,6 +227,7 @@ export const Operations: ComponentType = withSuspensable(() => {
218227
{tab === 'operation' && (
219228
<OperationList
220229
{...queryParams}
230+
multiselect={multiselect}
221231
operator={operatorFilter.enabled ? operatorFilter : undefined}
222232
// 按热度排序时列表前几页的变化不会太频繁,可以不刷新第一页,节省点流量
223233
revalidateFirstPage={queryParams.orderBy !== 'hot'}

0 commit comments

Comments
 (0)