Skip to content

Commit 67e3dc3

Browse files
committed
feat: operation set auto sorting
1 parent 3700564 commit 67e3dc3

File tree

1 file changed

+104
-17
lines changed

1 file changed

+104
-17
lines changed

src/components/operation-set/OperationSetEditor.tsx

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import {
66
DialogProps,
77
Icon,
88
InputGroup,
9+
Menu,
10+
MenuItem,
911
NonIdealState,
1012
TextArea,
1113
} from '@blueprintjs/core'
14+
import { Popover2 } from '@blueprintjs/popover2'
1215
import {
1316
DndContext,
1417
DragEndEvent,
@@ -44,10 +47,13 @@ import { Controller, UseFormSetError, useForm } from 'react-hook-form'
4447
import { FormField } from 'components/FormField'
4548
import { AppToaster } from 'components/Toaster'
4649
import { Sortable } from 'components/dnd'
47-
import { Operation } from 'models/operation'
50+
import { Level, Operation } from 'models/operation'
4851
import { OperationSet } from 'models/operation-set'
4952
import { formatError } from 'utils/error'
5053

54+
import { useLevels } from '../../apis/level'
55+
import { findLevelByStageName } from '../../models/level'
56+
5157
export function OperationSetEditorLauncher() {
5258
const [isOpen, setIsOpen] = useState(false)
5359

@@ -202,27 +208,22 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
202208
return (
203209
<form
204210
className={clsx(
205-
'p-4 w-[500px] max-w-[100vw] max-h-[calc(100vh-20rem)] min-h-[18rem] overflow-y-auto',
211+
'p-4 w-[500px] max-w-[100vw] max-h-[calc(100vh-20rem)] min-h-[18rem] flex flex-col overflow-auto lg:overflow-hidden',
206212
isEdit && 'lg:w-[1000px]',
207213
)}
208214
onSubmit={localOnSubmit}
209215
>
210-
<div className="gap-4 flex flex-wrap-reverse lg:flex-nowrap">
216+
<div className="gap-4 flex flex-wrap-reverse lg:flex-nowrap lg:overflow-hidden">
211217
{isEdit && (
212-
<div className="grow basis-full flex flex-col border-t lg:border-t-0 lg:border-r border-slate-200">
218+
<div className="grow basis-full lg:overflow-y-auto border-t lg:border-t-0 lg:border-r border-slate-200">
213219
{operationSet.copilotIds.length > 0 ? (
214-
<>
215-
<div className="grow">
216-
<OperationSelector
217-
key={operationSet.id}
218-
operationSet={operationSet}
219-
selectorRef={operationSelectorRef}
220-
/>
221-
</div>
222-
</>
220+
<OperationSelector
221+
key={operationSet.id}
222+
operationSet={operationSet}
223+
selectorRef={operationSelectorRef}
224+
/>
223225
) : (
224226
<NonIdealState
225-
className="grow"
226227
icon="helicopter"
227228
description={
228229
<>
@@ -236,7 +237,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
236237
</div>
237238
)}
238239

239-
<div className="grow basis-full">
240+
<div className="grow basis-full lg:overflow-y-auto">
240241
<FormField
241242
label="标题"
242243
field="name"
@@ -261,6 +262,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
261262
ControllerProps={{
262263
render: (renderProps) => (
263264
<TextArea
265+
rows={6}
264266
{...renderProps.field}
265267
value={renderProps.field.value || ''}
266268
/>
@@ -290,7 +292,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
290292
</div>
291293
</div>
292294

293-
<div className="mt-6 flex items-end">
295+
<div className="flex items-end">
294296
{isEdit && (
295297
<div className="text-xs text-gray-500">
296298
<Icon icon="info-sign" /> 修改后请点击保存按钮
@@ -338,6 +340,11 @@ function OperationSelector({
338340
const { operations, error } = useOperations({
339341
operationIds: operationSet.copilotIds,
340342
})
343+
const {
344+
data: levels,
345+
isLoading: levelLoading,
346+
error: levelError,
347+
} = useLevels()
341348

342349
const [renderedOperations, setRenderedOperations] = useState<Operation[]>([])
343350
useEffect(() => {
@@ -386,8 +393,88 @@ function OperationSelector({
386393
}
387394
}
388395

396+
const sort = (type: 'title' | 'level' | 'id' | 'reverse') => {
397+
const levelCache: Record<string, Level | undefined> = {}
398+
setRenderedOperations((items) => {
399+
if (type === 'reverse') {
400+
return [...items].reverse()
401+
}
402+
return [...items].sort((a, b) => {
403+
if (type === 'title') {
404+
return a.parsedContent.doc.title.localeCompare(
405+
b.parsedContent.doc.title,
406+
)
407+
} else if (type === 'level') {
408+
const aLevel = (levelCache[a.parsedContent.stageName] ??=
409+
findLevelByStageName(levels, a.parsedContent.stageName))
410+
const bLevel = (levelCache[b.parsedContent.stageName] ??=
411+
findLevelByStageName(levels, b.parsedContent.stageName))
412+
413+
if (aLevel && bLevel) {
414+
return aLevel.catThree.localeCompare(bLevel.catThree)
415+
} else if (!aLevel && !bLevel) {
416+
// 如果两个都是未知关卡,可能是自定义关卡,或者关卡列表加载失败,直接按 stageName 排序
417+
return a.parsedContent.stageName.localeCompare(
418+
b.parsedContent.stageName,
419+
)
420+
} else if (!aLevel) {
421+
// 未知关卡排最后面
422+
return 1
423+
} else if (!bLevel) {
424+
return -1
425+
}
426+
}
427+
return a.id - b.id
428+
})
429+
})
430+
}
431+
389432
return (
390-
<div className="py-2">
433+
<div>
434+
<div className="mb-2 flex">
435+
<Popover2
436+
minimal
437+
captureDismiss
438+
placement="bottom-start"
439+
content={
440+
<Menu>
441+
<MenuItem
442+
disabled={levelLoading}
443+
icon="sort-alphabetical"
444+
text={
445+
'按关卡' +
446+
(levelLoading
447+
? ' (加载中...)'
448+
: levelError
449+
? ' (关卡加载失败,使用备用排序)'
450+
: '')
451+
}
452+
onClick={() => sort('level')}
453+
/>
454+
<MenuItem
455+
icon="sort-alphabetical"
456+
text="按标题"
457+
onClick={() => sort('title')}
458+
/>
459+
<MenuItem
460+
icon="sort-numerical"
461+
text="按 ID"
462+
onClick={() => sort('id')}
463+
/>
464+
</Menu>
465+
}
466+
>
467+
<Button small minimal icon="sort" text="一键排序..." />
468+
</Popover2>
469+
<Button
470+
small
471+
minimal
472+
icon="reset"
473+
text="反转列表"
474+
onClick={() => sort('reverse')}
475+
/>
476+
</div>
477+
391478
{error && (
392479
<Callout intent="danger" icon="error" title="错误">
393480
{formatError(error)}

0 commit comments

Comments
 (0)