1
- import { Button , NonIdealState } from '@blueprintjs/core'
1
+ import { Button , Callout , NonIdealState } from '@blueprintjs/core'
2
+ import { Tooltip2 } from '@blueprintjs/popover2'
2
3
3
4
import { UseOperationsParams , useOperations } from 'apis/operation'
4
5
import { useAtomValue } from 'jotai'
5
- import { ComponentType , ReactNode , useEffect } from 'react'
6
+ import { ComponentType , ReactNode , useEffect , useState } from 'react'
6
7
7
8
import { neoLayoutAtom } from 'store/pref'
8
9
9
10
import { Operation } from '../models/operation'
10
11
import { NeoOperationCard , OperationCard } from './OperationCard'
11
12
import { withSuspensable } from './Suspensable'
13
+ import { AddToOperationSetButton } from './operation-set/AddToOperationSet'
12
14
13
15
interface OperationListProps extends UseOperationsParams {
14
16
multiselect ?: boolean
15
- selectedOperations ?: Operation [ ]
16
- onSelect ?: ( operation : Operation , selected : boolean ) => void
17
17
onUpdate ?: ( params : { total : number } ) => void
18
18
}
19
19
20
20
export const OperationList : ComponentType < OperationListProps > = withSuspensable (
21
- ( { multiselect, selectedOperations , onSelect , onUpdate, ...params } ) => {
21
+ ( { multiselect, onUpdate, ...params } ) => {
22
22
const neoLayout = useAtomValue ( neoLayoutAtom )
23
23
24
24
const { operations, total, setSize, isValidating, isReachingEnd } =
@@ -34,6 +34,25 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
34
34
onUpdate ?.( { total } )
35
35
} , [ total , onUpdate ] )
36
36
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
+
37
56
const items : ReactNode = neoLayout ? (
38
57
< div
39
58
className = "grid gap-4"
@@ -59,6 +78,60 @@ export const OperationList: ComponentType<OperationListProps> = withSuspensable(
59
78
60
79
return (
61
80
< >
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
+
62
135
{ items }
63
136
64
137
{ isReachingEnd && operations . length === 0 && (
0 commit comments