Skip to content

Commit 01c9bfd

Browse files
authored
Merge pull request #349 from hmydgz/dev
✨ feat: 新增作业集编辑的作业排序功能
2 parents 0492be0 + 32868ee commit 01c9bfd

File tree

6 files changed

+129
-89
lines changed

6 files changed

+129
-89
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"linkify-react": "^3.0.4",
4545
"linkifyjs": "^3.0.5",
4646
"lodash-es": "^4.17.21",
47-
"maa-copilot-client": "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.758.dbc2eb1",
47+
"maa-copilot-client": "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.784.58f96e5",
4848
"normalize.css": "^8.0.1",
4949
"prettier": "^3.2.5",
5050
"react": "^18.0.0",

src/apis/operation-set.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CopilotSetPageRes,
55
CopilotSetQuery,
66
CopilotSetStatus,
7+
CopilotSetUpdateReq,
78
} from 'maa-copilot-client'
89
import useSWR from 'swr'
910
import useSWRInfinite from 'swr/infinite'
@@ -184,12 +185,7 @@ export async function createOperationSet(req: {
184185
})
185186
}
186187

187-
export async function updateOperationSet(req: {
188-
id: number
189-
name: string
190-
description: string
191-
status: CopilotSetStatus
192-
}) {
188+
export async function updateOperationSet(req: CopilotSetUpdateReq) {
193189
await new OperationSetApi().updateCopilotSet({ copilotSetUpdateReq: req })
194190
}
195191

src/apis/operation.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,14 @@ export function useOperations({
114114

115115
const isReachingEnd = !!pages?.some((page) => !page.hasNext)
116116

117-
const operations = pages?.map((page) => page.data).flat()
117+
const _operations = pages?.map((page) => page.data).flat() ?? []
118+
119+
// 按 operationIds 的顺序排序
120+
const operations = operationIds?.length
121+
? operationIds
122+
?.map((id) => _operations?.find((v) => v.id === id))
123+
.filter((v) => !!v)
124+
: _operations
118125

119126
return {
120127
error,

src/components/operation-set/AddToOperationSet.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export function AddToOperationSet({
142142

143143
return (
144144
<>
145-
<div className="py-2">
145+
<div className="py-2 px-px">
146146
{error && (
147147
<Callout intent="danger" icon="error" title="错误">
148148
{formatError(error)}
@@ -165,7 +165,7 @@ export function AddToOperationSet({
165165
<div key={id}>
166166
<Checkbox
167167
className={clsx(
168-
'block m-0 p-2 !pl-10 hover:bg-slate-200',
168+
'block m-0 p-2 !pl-10 hover:bg-slate-200 dark:hover:bg-slate-800',
169169
checkboxOverrides[id] !== undefined &&
170170
checkboxOverrides[id] !== alreadyAdded(id) &&
171171
'font-bold',

src/components/operation-set/OperationSetEditor.tsx

Lines changed: 113 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,42 @@ import {
99
NonIdealState,
1010
TextArea,
1111
} 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'
1224

1325
import { useOperations } from 'apis/operation'
1426
import {
15-
addToOperationSet,
1627
createOperationSet,
17-
removeFromOperationSet,
1828
updateOperationSet,
1929
useRefreshOperationSet,
2030
useRefreshOperationSets,
2131
} from 'apis/operation-set'
2232
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'
2442
import { Controller, UseFormSetError, useForm } from 'react-hook-form'
2543

2644
import { FormField } from 'components/FormField'
2745
import { AppToaster } from 'components/Toaster'
46+
import { Sortable } from 'components/dnd'
47+
import { Operation } from 'models/operation'
2848
import { OperationSet } from 'models/operation-set'
2949
import { formatError } from 'utils/error'
3050

@@ -65,17 +85,19 @@ export function OperationSetEditorDialog({
6585
name,
6686
description,
6787
status,
68-
idsToAdd,
69-
idsToRemove,
88+
copilotIds,
7089
}) => {
7190
const updateInfo = async () => {
7291
if (isEdit) {
73-
await updateOperationSet({
92+
const params: UpdateCopilotSetRequest['copilotSetUpdateReq'] = {
7493
id: operationSet!.id,
7594
name,
7695
description,
7796
status,
78-
})
97+
copilotIds,
98+
}
99+
100+
await updateOperationSet(params)
79101

80102
AppToaster.show({
81103
intent: 'success',
@@ -102,35 +124,7 @@ export function OperationSetEditorDialog({
102124
}
103125
}
104126

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()
134128

135129
onClose()
136130
}
@@ -168,6 +162,7 @@ interface FormValues {
168162

169163
idsToAdd?: number[]
170164
idsToRemove?: number[]
165+
copilotIds?: number[]
171166
}
172167

173168
function OperationSetForm({ operationSet, onSubmit }: FormProps) {
@@ -207,7 +202,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
207202
return (
208203
<form
209204
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',
211206
isEdit && 'lg:w-[1000px]',
212207
)}
213208
onSubmit={localOnSubmit}
@@ -219,6 +214,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
219214
<>
220215
<div className="grow">
221216
<OperationSelector
217+
key={operationSet.id}
222218
operationSet={operationSet}
223219
selectorRef={operationSelectorRef}
224220
/>
@@ -330,7 +326,9 @@ interface OperationSelectorProps {
330326
}
331327

332328
interface OperationSelectorRef {
333-
getValues(): { idsToAdd: number[]; idsToRemove: number[] }
329+
getValues(): {
330+
copilotIds?: number[]
331+
}
334332
}
335333

336334
function OperationSelector({
@@ -341,6 +339,12 @@ function OperationSelector({
341339
operationIds: operationSet.copilotIds,
342340
})
343341

342+
const [renderedOperations, setRenderedOperations] = useState<Operation[]>([])
343+
useEffect(() => {
344+
setRenderedOperations([...(operations ?? [])])
345+
// eslint-disable-next-line react-hooks/exhaustive-deps
346+
}, [operations.length])
347+
344348
const [checkboxOverrides, setCheckboxOverrides] = useState(
345349
{} as Record<number, boolean>,
346350
)
@@ -354,26 +358,34 @@ function OperationSelector({
354358
selectorRef,
355359
() => ({
356360
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 }
372369
},
373370
}),
374-
[checkboxOverrides, alreadyAdded],
371+
[checkboxOverrides, renderedOperations],
375372
)
376373

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+
377389
return (
378390
<div className="py-2">
379391
{error && (
@@ -382,28 +394,53 @@ function OperationSelector({
382394
</Callout>
383395
)}
384396

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}:&nbsp;</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}:&nbsp;
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>
407444
</div>
408445
)
409446
}

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3642,9 +3642,9 @@ lru-cache@^6.0.0:
36423642
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
36433643
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
36443644

3645-
"maa-copilot-client@https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.758.dbc2eb1":
3646-
version "0.1.0-SNAPSHOT.758.dbc2eb1"
3647-
resolved "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#d6750070b90fec2cac02d3f3d718a3165fd5d55a"
3645+
"maa-copilot-client@https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#0.1.0-SNAPSHOT.784.58f96e5":
3646+
version "0.1.0-SNAPSHOT.784.58f96e5"
3647+
resolved "https://github.com/MaaAssistantArknights/maa-copilot-client-ts.git#cecb29af32fa36d6f11ed46469765abe56caafaf"
36483648

36493649
make-dir@^2.1.0:
36503650
version "2.1.0"

0 commit comments

Comments
 (0)