Skip to content

Commit 3700564

Browse files
committed
feat: multiselect mode - select all and deselect all
1 parent cba2764 commit 3700564

File tree

2 files changed

+78
-56
lines changed

2 files changed

+78
-56
lines changed

src/components/OperationList.tsx

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +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

910
import { Operation } from '../models/operation'
1011
import { NeoOperationCard, OperationCard } from './OperationCard'
1112
import { withSuspensable } from './Suspensable'
13+
import { AddToOperationSetButton } from './operation-set/AddToOperationSet'
1214

1315
interface OperationListProps extends UseOperationsParams {
1416
multiselect?: boolean
15-
selectedOperations?: Operation[]
16-
onSelect?: (operation: Operation, selected: boolean) => void
1717
onUpdate?: (params: { total: number }) => void
1818
}
1919

2020
export const OperationList: ComponentType<OperationListProps> = withSuspensable(
21-
({ multiselect, selectedOperations, onSelect, onUpdate, ...params }) => {
21+
({ multiselect, onUpdate, ...params }) => {
2222
const neoLayout = useAtomValue(neoLayoutAtom)
2323

2424
const { operations, total, setSize, isValidating, isReachingEnd } =
@@ -34,6 +34,25 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
3434
onUpdate?.({ total })
3535
}, [total, onUpdate])
3636

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+
3756
const items: ReactNode = neoLayout ? (
3857
<div
3958
className="grid gap-4"
@@ -59,6 +78,60 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
5978

6079
return (
6180
<>
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+
62135
{items}
63136

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

src/components/Operations.tsx

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
Button,
33
ButtonGroup,
4-
Callout,
54
Card,
65
Divider,
76
H6,
@@ -22,12 +21,10 @@ import { OperationList } from 'components/OperationList'
2221
import { OperationSetList } from 'components/OperationSetList'
2322
import { neoLayoutAtom } from 'store/pref'
2423

25-
import { Operation } from '../models/operation'
2624
import { LevelSelect } from './LevelSelect'
2725
import { OperatorFilter, useOperatorFilter } from './OperatorFilter'
2826
import { withSuspensable } from './Suspensable'
2927
import { UserFilter } from './UserFilter'
30-
import { AddToOperationSetButton } from './operation-set/AddToOperationSet'
3128

3229
export const Operations: ComponentType = withSuspensable(() => {
3330
const [queryParams, setQueryParams] = useState<
@@ -46,7 +43,6 @@ export const Operations: ComponentType = withSuspensable(() => {
4643
const [neoLayout, setNeoLayout] = useAtom(neoLayoutAtom)
4744
const [tab, setTab] = useState<'operation' | 'operationSet'>('operation')
4845
const [multiselect, setMultiselect] = useState(false)
49-
const [selectedOperations, setSelectedOperations] = useState<Operation[]>([])
5046

5147
return (
5248
<>
@@ -191,43 +187,6 @@ export const Operations: ComponentType = withSuspensable(() => {
191187
</ButtonGroup>
192188
</div>
193189
</div>
194-
{multiselect && (
195-
<Callout className="mt-2 p-0 select-none">
196-
<details>
197-
<summary className="px-2 py-4 cursor-pointer hover:bg-zinc-500 hover:bg-opacity-5">
198-
已选择 {selectedOperations.length} 份作业
199-
</summary>
200-
<div className="p-2 flex flex-wrap gap-1">
201-
{selectedOperations.map((operation) => (
202-
<Button
203-
key={operation.id}
204-
small
205-
minimal
206-
rightIcon="cross"
207-
onClick={() =>
208-
setSelectedOperations((old) =>
209-
old.filter((op) => op.id !== operation.id),
210-
)
211-
}
212-
>
213-
{operation.parsedContent.doc.title}
214-
</Button>
215-
))}
216-
</div>
217-
</details>
218-
<AddToOperationSetButton
219-
minimal
220-
outlined
221-
intent="primary"
222-
icon="add-to-folder"
223-
className="absolute top-2 right-2"
224-
disabled={selectedOperations.length === 0}
225-
operationIds={selectedOperations.map((op) => op.id)}
226-
>
227-
添加到作业集
228-
</AddToOperationSetButton>
229-
</Callout>
230-
)}
231190
</>
232191
)}
233192

@@ -269,16 +228,6 @@ export const Operations: ComponentType = withSuspensable(() => {
269228
<OperationList
270229
{...queryParams}
271230
multiselect={multiselect}
272-
selectedOperations={selectedOperations}
273-
onSelect={(operation, selected) =>
274-
setSelectedOperations((old) => {
275-
const newList = old.filter((op) => op.id !== operation.id)
276-
if (selected) {
277-
newList.push(operation)
278-
}
279-
return newList
280-
})
281-
}
282231
operator={operatorFilter.enabled ? operatorFilter : undefined}
283232
// 按热度排序时列表前几页的变化不会太频繁,可以不刷新第一页,节省点流量
284233
revalidateFirstPage={queryParams.orderBy !== 'hot'}

0 commit comments

Comments
 (0)