@@ -9,22 +9,42 @@ import {
9
9
NonIdealState ,
10
10
TextArea ,
11
11
} from '@blueprintjs/core'
12
+ import {
13
+ DndContext ,
14
+ DragEndEvent ,
15
+ PointerSensor ,
16
+ useSensor ,
17
+ useSensors ,
18
+ } from '@dnd-kit/core'
19
+ import {
20
+ SortableContext ,
21
+ arrayMove ,
22
+ verticalListSortingStrategy ,
23
+ } from '@dnd-kit/sortable'
12
24
13
25
import { useOperations } from 'apis/operation'
14
26
import {
15
- addToOperationSet ,
16
27
createOperationSet ,
17
- removeFromOperationSet ,
18
28
updateOperationSet ,
19
29
useRefreshOperationSet ,
20
30
useRefreshOperationSets ,
21
31
} from 'apis/operation-set'
22
32
import clsx from 'clsx'
23
- import { Ref , useCallback , useImperativeHandle , useRef , useState } from 'react'
33
+ import { UpdateCopilotSetRequest } from 'maa-copilot-client'
34
+ import {
35
+ Ref ,
36
+ useCallback ,
37
+ useEffect ,
38
+ useImperativeHandle ,
39
+ useRef ,
40
+ useState ,
41
+ } from 'react'
24
42
import { Controller , UseFormSetError , useForm } from 'react-hook-form'
25
43
26
44
import { FormField } from 'components/FormField'
27
45
import { AppToaster } from 'components/Toaster'
46
+ import { Sortable } from 'components/dnd'
47
+ import { Operation } from 'models/operation'
28
48
import { OperationSet } from 'models/operation-set'
29
49
import { formatError } from 'utils/error'
30
50
@@ -65,17 +85,19 @@ export function OperationSetEditorDialog({
65
85
name,
66
86
description,
67
87
status,
68
- idsToAdd,
69
- idsToRemove,
88
+ copilotIds,
70
89
} ) => {
71
90
const updateInfo = async ( ) => {
72
91
if ( isEdit ) {
73
- await updateOperationSet ( {
92
+ const params : UpdateCopilotSetRequest [ 'copilotSetUpdateReq' ] = {
74
93
id : operationSet ! . id ,
75
94
name,
76
95
description,
77
96
status,
78
- } )
97
+ copilotIds,
98
+ }
99
+
100
+ await updateOperationSet ( params )
79
101
80
102
AppToaster . show ( {
81
103
intent : 'success' ,
@@ -102,35 +124,7 @@ export function OperationSetEditorDialog({
102
124
}
103
125
}
104
126
105
- const addOperations = async ( ) => {
106
- if ( operationSet && idsToAdd ?. length ) {
107
- await addToOperationSet ( {
108
- operationSetId : operationSet . id ,
109
- operationIds : idsToAdd ,
110
- } )
111
-
112
- AppToaster . show ( {
113
- intent : 'success' ,
114
- message : `添加作业成功` ,
115
- } )
116
- }
117
- }
118
-
119
- const removeOperations = async ( ) => {
120
- if ( operationSet && idsToRemove ?. length ) {
121
- await removeFromOperationSet ( {
122
- operationSetId : operationSet . id ,
123
- operationIds : idsToRemove ,
124
- } )
125
-
126
- AppToaster . show ( {
127
- intent : 'success' ,
128
- message : `移除作业成功` ,
129
- } )
130
- }
131
- }
132
-
133
- await Promise . all ( [ updateInfo ( ) , addOperations ( ) , removeOperations ( ) ] )
127
+ await updateInfo ( )
134
128
135
129
onClose ( )
136
130
}
@@ -168,6 +162,7 @@ interface FormValues {
168
162
169
163
idsToAdd ?: number [ ]
170
164
idsToRemove ?: number [ ]
165
+ copilotIds ?: number [ ]
171
166
}
172
167
173
168
function OperationSetForm ( { operationSet, onSubmit } : FormProps ) {
@@ -207,7 +202,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
207
202
return (
208
203
< form
209
204
className = { clsx (
210
- 'p-4 w-[500px] max-w-[100vw] max-h-[calc(100vh-20rem)] overflow-y-auto' ,
205
+ 'p-4 w-[500px] max-w-[100vw] max-h-[calc(100vh-20rem)] min-h-[18rem] overflow-y-auto' ,
211
206
isEdit && 'lg:w-[1000px]' ,
212
207
) }
213
208
onSubmit = { localOnSubmit }
@@ -219,6 +214,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
219
214
< >
220
215
< div className = "grow" >
221
216
< OperationSelector
217
+ key = { operationSet . id }
222
218
operationSet = { operationSet }
223
219
selectorRef = { operationSelectorRef }
224
220
/>
@@ -330,7 +326,9 @@ interface OperationSelectorProps {
330
326
}
331
327
332
328
interface OperationSelectorRef {
333
- getValues ( ) : { idsToAdd : number [ ] ; idsToRemove : number [ ] }
329
+ getValues ( ) : {
330
+ copilotIds ?: number [ ]
331
+ }
334
332
}
335
333
336
334
function OperationSelector ( {
@@ -341,6 +339,12 @@ function OperationSelector({
341
339
operationIds : operationSet . copilotIds ,
342
340
} )
343
341
342
+ const [ renderedOperations , setRenderedOperations ] = useState < Operation [ ] > ( [ ] )
343
+ useEffect ( ( ) => {
344
+ setRenderedOperations ( [ ...( operations ?? [ ] ) ] )
345
+ // eslint-disable-next-line react-hooks/exhaustive-deps
346
+ } , [ operations . length ] )
347
+
344
348
const [ checkboxOverrides , setCheckboxOverrides ] = useState (
345
349
{ } as Record < number , boolean > ,
346
350
)
@@ -354,26 +358,34 @@ function OperationSelector({
354
358
selectorRef ,
355
359
( ) => ( {
356
360
getValues ( ) {
357
- const idsToAdd : number [ ] = [ ]
358
- const idsToRemove : number [ ] = [ ]
359
-
360
- Object . entries ( checkboxOverrides ) . forEach ( ( [ idKey , checked ] ) => {
361
- const id = + idKey
362
- if ( isNaN ( id ) ) return
363
-
364
- if ( checked && ! alreadyAdded ( id ) ) {
365
- idsToAdd . push ( id )
366
- } else if ( ! checked && alreadyAdded ( id ) ) {
367
- idsToRemove . push ( id )
368
- }
369
- } )
370
-
371
- return { idsToAdd, idsToRemove }
361
+ const copilotIds : number [ ] = [ ]
362
+ copilotIds . push (
363
+ ...renderedOperations
364
+ . map ( ( { id } ) => ( checkboxOverrides [ id ] === false ? 0 : id ) )
365
+ . filter ( ( id ) => ! ! id ) ,
366
+ )
367
+
368
+ return { copilotIds }
372
369
} ,
373
370
} ) ,
374
- [ checkboxOverrides , alreadyAdded ] ,
371
+ [ checkboxOverrides , renderedOperations ] ,
375
372
)
376
373
374
+ const sensors = useSensors ( useSensor ( PointerSensor ) )
375
+
376
+ const handleDragEnd = ( event : DragEndEvent ) => {
377
+ const { active, over } = event
378
+
379
+ if ( active . id !== over ?. id ) {
380
+ setRenderedOperations ( ( items ) => {
381
+ const oldIndex = items . findIndex ( ( v ) => v . id === active . id )
382
+ const newIndex = items . findIndex ( ( v ) => v . id === over ?. id )
383
+
384
+ return arrayMove ( items , oldIndex , newIndex )
385
+ } )
386
+ }
387
+ }
388
+
377
389
return (
378
390
< div className = "py-2" >
379
391
{ error && (
@@ -382,28 +394,53 @@ function OperationSelector({
382
394
</ Callout >
383
395
) }
384
396
385
- { operations ?. map ( ( { id, parsedContent } ) => (
386
- < div key = { id } >
387
- < Checkbox
388
- className = { clsx (
389
- 'flex items-center m-0 p-2 !pl-10 hover:bg-slate-200' ,
390
- checkboxOverrides [ id ] !== undefined &&
391
- checkboxOverrides [ id ] !== alreadyAdded ( id ) &&
392
- 'font-bold' ,
393
- ) }
394
- checked = { checkboxOverrides [ id ] ?? alreadyAdded ( id ) }
395
- onChange = { ( e ) => {
396
- const checked = ( e . target as HTMLInputElement ) . checked
397
- setCheckboxOverrides ( ( prev ) => ( { ...prev , [ id ] : checked } ) )
398
- } }
399
- >
400
- < div className = "tabular-nums text-slate-500" > { id } : </ div >
401
- < div className = "truncate text-ellipsis" >
402
- { parsedContent . doc . title }
403
- </ div >
404
- </ Checkbox >
405
- </ div >
406
- ) ) }
397
+ < DndContext sensors = { sensors } onDragEnd = { handleDragEnd } >
398
+ < SortableContext
399
+ items = { ( renderedOperations ?? [ ] ) . map ( ( { id } ) => id ) }
400
+ strategy = { verticalListSortingStrategy }
401
+ >
402
+ { renderedOperations ?. map ( ( { id, parsedContent } ) => (
403
+ < Sortable key = { id } id = { id } >
404
+ { ( { listeners, attributes } ) => (
405
+ < div
406
+ key = { id }
407
+ className = "flex items-center hover:bg-slate-200 dark:hover:bg-slate-800"
408
+ >
409
+ < Icon
410
+ className = "cursor-grab active:cursor-grabbing p-1 -my-1 -ml-2 -mr-1 rounded-[1px]"
411
+ icon = "drag-handle-vertical"
412
+ { ...listeners }
413
+ { ...attributes }
414
+ />
415
+ < Checkbox
416
+ className = { clsx (
417
+ 'flex items-center m-0 p-2 !pl-10 flex-1' ,
418
+ checkboxOverrides [ id ] !== undefined &&
419
+ checkboxOverrides [ id ] !== alreadyAdded ( id ) &&
420
+ 'font-bold' ,
421
+ ) }
422
+ checked = { checkboxOverrides [ id ] ?? alreadyAdded ( id ) }
423
+ onChange = { ( e ) => {
424
+ const checked = ( e . target as HTMLInputElement ) . checked
425
+ setCheckboxOverrides ( ( prev ) => ( {
426
+ ...prev ,
427
+ [ id ] : checked ,
428
+ } ) )
429
+ } }
430
+ >
431
+ < div className = "tabular-nums text-slate-500" >
432
+ { id } :
433
+ </ div >
434
+ < div className = "truncate text-ellipsis" >
435
+ { parsedContent . doc . title }
436
+ </ div >
437
+ </ Checkbox >
438
+ </ div >
439
+ ) }
440
+ </ Sortable >
441
+ ) ) }
442
+ </ SortableContext >
443
+ </ DndContext >
407
444
</ div >
408
445
)
409
446
}
0 commit comments