Skip to content

Commit 8c57b1b

Browse files
committed
✨ feat: 新增作业集编辑的作业排序功能 #298
1 parent 116f183 commit 8c57b1b

File tree

6 files changed

+137
-44
lines changed

6 files changed

+137
-44
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: 121 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ 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 {
@@ -20,11 +32,21 @@ import {
2032
useRefreshOperationSets,
2133
} from 'apis/operation-set'
2234
import clsx from 'clsx'
23-
import { Ref, useCallback, useImperativeHandle, useRef, useState } from 'react'
35+
import { UpdateCopilotSetRequest } from 'maa-copilot-client'
36+
import {
37+
Ref,
38+
useCallback,
39+
useEffect,
40+
useImperativeHandle,
41+
useRef,
42+
useState,
43+
} from 'react'
2444
import { Controller, UseFormSetError, useForm } from 'react-hook-form'
2545

2646
import { FormField } from 'components/FormField'
2747
import { AppToaster } from 'components/Toaster'
48+
import { Sortable } from 'components/dnd'
49+
import { Operation } from 'models/operation'
2850
import { OperationSet } from 'models/operation-set'
2951
import { formatError } from 'utils/error'
3052

@@ -67,15 +89,20 @@ export function OperationSetEditorDialog({
6789
status,
6890
idsToAdd,
6991
idsToRemove,
92+
sortIds,
7093
}) => {
7194
const updateInfo = async () => {
7295
if (isEdit) {
73-
await updateOperationSet({
96+
const params: UpdateCopilotSetRequest['copilotSetUpdateReq'] = {
7497
id: operationSet!.id,
7598
name,
7699
description,
77100
status,
78-
})
101+
}
102+
103+
if (sortIds?.length) params.copilotIds = sortIds
104+
105+
await updateOperationSet(params)
79106

80107
AppToaster.show({
81108
intent: 'success',
@@ -168,6 +195,7 @@ interface FormValues {
168195

169196
idsToAdd?: number[]
170197
idsToRemove?: number[]
198+
sortIds?: number[]
171199
}
172200

173201
function OperationSetForm({ operationSet, onSubmit }: FormProps) {
@@ -207,7 +235,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
207235
return (
208236
<form
209237
className={clsx(
210-
'p-4 w-[500px] max-w-[100vw] max-h-[calc(100vh-20rem)] overflow-y-auto',
238+
'p-4 w-[500px] max-w-[100vw] max-h-[calc(100vh-20rem)] min-h-[18rem] overflow-y-auto',
211239
isEdit && 'lg:w-[1000px]',
212240
)}
213241
onSubmit={localOnSubmit}
@@ -219,6 +247,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
219247
<>
220248
<div className="grow">
221249
<OperationSelector
250+
key={operationSet.id}
222251
operationSet={operationSet}
223252
selectorRef={operationSelectorRef}
224253
/>
@@ -330,7 +359,11 @@ interface OperationSelectorProps {
330359
}
331360

332361
interface OperationSelectorRef {
333-
getValues(): { idsToAdd: number[]; idsToRemove: number[] }
362+
getValues(): {
363+
idsToAdd: number[]
364+
idsToRemove: number[]
365+
sortIds?: number[]
366+
}
334367
}
335368

336369
function OperationSelector({
@@ -341,6 +374,14 @@ function OperationSelector({
341374
operationIds: operationSet.copilotIds,
342375
})
343376

377+
const [isSorting, setIsSorting] = useState(false)
378+
379+
const [renderedOperations, setRenderedOperations] = useState<Operation[]>([])
380+
useEffect(() => {
381+
setRenderedOperations([...(operations ?? [])])
382+
// eslint-disable-next-line react-hooks/exhaustive-deps
383+
}, [operations.length])
384+
344385
const [checkboxOverrides, setCheckboxOverrides] = useState(
345386
{} as Record<number, boolean>,
346387
)
@@ -356,7 +397,7 @@ function OperationSelector({
356397
getValues() {
357398
const idsToAdd: number[] = []
358399
const idsToRemove: number[] = []
359-
400+
const sortIds: number[] = []
360401
Object.entries(checkboxOverrides).forEach(([idKey, checked]) => {
361402
const id = +idKey
362403
if (isNaN(id)) return
@@ -367,13 +408,37 @@ function OperationSelector({
367408
idsToRemove.push(id)
368409
}
369410
})
370-
371-
return { idsToAdd, idsToRemove }
411+
if (isSorting) {
412+
sortIds.push(
413+
...renderedOperations
414+
.map(({ id }) => (checkboxOverrides[id] === false ? 0 : id))
415+
.filter((id) => !!id),
416+
)
417+
}
418+
419+
return { idsToAdd, idsToRemove, sortIds }
372420
},
373421
}),
374-
[checkboxOverrides, alreadyAdded],
422+
[checkboxOverrides, isSorting, alreadyAdded, renderedOperations],
375423
)
376424

425+
const sensors = useSensors(useSensor(PointerSensor))
426+
427+
const handleDragEnd = (event: DragEndEvent) => {
428+
const { active, over } = event
429+
430+
if (active.id !== over?.id) {
431+
setRenderedOperations((items) => {
432+
const oldIndex = items.findIndex((v) => v.id === active.id)
433+
const newIndex = items.findIndex((v) => v.id === over?.id)
434+
435+
return arrayMove(items, oldIndex, newIndex)
436+
})
437+
438+
setIsSorting(true)
439+
}
440+
}
441+
377442
return (
378443
<div className="py-2">
379444
{error && (
@@ -382,28 +447,53 @@ function OperationSelector({
382447
</Callout>
383448
)}
384449

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-
))}
450+
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
451+
<SortableContext
452+
items={(renderedOperations ?? []).map(({ id }) => id)}
453+
strategy={verticalListSortingStrategy}
454+
>
455+
{renderedOperations?.map(({ id, parsedContent }) => (
456+
<Sortable key={id} id={id}>
457+
{({ listeners, attributes }) => (
458+
<div
459+
key={id}
460+
className="flex items-center hover:bg-slate-200 dark:hover:bg-slate-800"
461+
>
462+
<Icon
463+
className="cursor-grab active:cursor-grabbing p-1 -my-1 -ml-2 -mr-1 rounded-[1px]"
464+
icon="drag-handle-vertical"
465+
{...listeners}
466+
{...attributes}
467+
/>
468+
<Checkbox
469+
className={clsx(
470+
'flex items-center m-0 p-2 !pl-10 flex-1',
471+
checkboxOverrides[id] !== undefined &&
472+
checkboxOverrides[id] !== alreadyAdded(id) &&
473+
'font-bold',
474+
)}
475+
checked={checkboxOverrides[id] ?? alreadyAdded(id)}
476+
onChange={(e) => {
477+
const checked = (e.target as HTMLInputElement).checked
478+
setCheckboxOverrides((prev) => ({
479+
...prev,
480+
[id]: checked,
481+
}))
482+
}}
483+
>
484+
<div className="tabular-nums text-slate-500">
485+
{id}:&nbsp;
486+
</div>
487+
<div className="truncate text-ellipsis">
488+
{parsedContent.doc.title}
489+
</div>
490+
</Checkbox>
491+
</div>
492+
)}
493+
</Sortable>
494+
))}
495+
</SortableContext>
496+
</DndContext>
407497
</div>
408498
)
409499
}

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)