@@ -6,9 +6,12 @@ import {
6
6
DialogProps ,
7
7
Icon ,
8
8
InputGroup ,
9
+ Menu ,
10
+ MenuItem ,
9
11
NonIdealState ,
10
12
TextArea ,
11
13
} from '@blueprintjs/core'
14
+ import { Popover2 } from '@blueprintjs/popover2'
12
15
import {
13
16
DndContext ,
14
17
DragEndEvent ,
@@ -44,10 +47,13 @@ import { Controller, UseFormSetError, useForm } from 'react-hook-form'
44
47
import { FormField } from 'components/FormField'
45
48
import { AppToaster } from 'components/Toaster'
46
49
import { Sortable } from 'components/dnd'
47
- import { Operation } from 'models/operation'
50
+ import { Level , Operation } from 'models/operation'
48
51
import { OperationSet } from 'models/operation-set'
49
52
import { formatError } from 'utils/error'
50
53
54
+ import { useLevels } from '../../apis/level'
55
+ import { findLevelByStageName } from '../../models/level'
56
+
51
57
export function OperationSetEditorLauncher ( ) {
52
58
const [ isOpen , setIsOpen ] = useState ( false )
53
59
@@ -202,27 +208,22 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
202
208
return (
203
209
< form
204
210
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 ' ,
206
212
isEdit && 'lg:w-[1000px]' ,
207
213
) }
208
214
onSubmit = { localOnSubmit }
209
215
>
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 " >
211
217
{ 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" >
213
219
{ 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
+ />
223
225
) : (
224
226
< NonIdealState
225
- className = "grow"
226
227
icon = "helicopter"
227
228
description = {
228
229
< >
@@ -236,7 +237,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
236
237
</ div >
237
238
) }
238
239
239
- < div className = "grow basis-full" >
240
+ < div className = "grow basis-full lg:overflow-y-auto " >
240
241
< FormField
241
242
label = "标题"
242
243
field = "name"
@@ -261,6 +262,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
261
262
ControllerProps = { {
262
263
render : ( renderProps ) => (
263
264
< TextArea
265
+ rows = { 6 }
264
266
{ ...renderProps . field }
265
267
value = { renderProps . field . value || '' }
266
268
/>
@@ -290,7 +292,7 @@ function OperationSetForm({ operationSet, onSubmit }: FormProps) {
290
292
</ div >
291
293
</ div >
292
294
293
- < div className = "mt-6 flex items-end" >
295
+ < div className = "flex items-end" >
294
296
{ isEdit && (
295
297
< div className = "text-xs text-gray-500" >
296
298
< Icon icon = "info-sign" /> 修改后请点击保存按钮
@@ -338,6 +340,11 @@ function OperationSelector({
338
340
const { operations, error } = useOperations ( {
339
341
operationIds : operationSet . copilotIds ,
340
342
} )
343
+ const {
344
+ data : levels ,
345
+ isLoading : levelLoading ,
346
+ error : levelError ,
347
+ } = useLevels ( )
341
348
342
349
const [ renderedOperations , setRenderedOperations ] = useState < Operation [ ] > ( [ ] )
343
350
useEffect ( ( ) => {
@@ -386,8 +393,88 @@ function OperationSelector({
386
393
}
387
394
}
388
395
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
+
389
432
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
+
391
478
{ error && (
392
479
< Callout intent = "danger" icon = "error" title = "错误" >
393
480
{ formatError ( error ) }
0 commit comments