@@ -51,10 +51,17 @@ import { i18nDefer, useTranslation } from '../../i18n/i18n'
51
51
import { CopilotDocV1 } from '../../models/copilot.schema'
52
52
import { createCustomLevel , findLevelByStageName } from '../../models/level'
53
53
import { Level } from '../../models/operation'
54
- import { OPERATORS , useLocalizedOperatorName } from '../../models/operator'
54
+ import {
55
+ OPERATORS ,
56
+ defaultSkills ,
57
+ getEliteIconUrl ,
58
+ useLocalizedOperatorName ,
59
+ withDefaultRequirements ,
60
+ } from '../../models/operator'
55
61
import { formatError } from '../../utils/error'
56
62
import { ActionCard } from '../ActionCard'
57
63
import { Confirm } from '../Confirm'
64
+ import { MasteryIcon } from '../MasteryIcon'
58
65
import { ReLink } from '../ReLink'
59
66
import { UserName } from '../UserName'
60
67
import { CommentArea } from './comment/CommentArea'
@@ -329,24 +336,98 @@ export const OperationViewer: ComponentType<{
329
336
330
337
const OperatorCard : FC < {
331
338
operator : CopilotDocV1 . Operator
332
- } > = ( { operator } ) => {
339
+ version ?: number
340
+ } > = ( { operator, version = 1 } ) => {
333
341
const t = useTranslation ( )
334
- const { name, skill } = operator
335
- const info = OPERATORS . find ( ( o ) => o . name === name )
342
+ const displayName = useLocalizedOperatorName ( operator . name )
343
+ const info = OPERATORS . find ( ( o ) => o . name === operator . name )
344
+ const skills = info ? info . skills : defaultSkills
345
+ const { level, elite, skillLevel, module } = withDefaultRequirements (
346
+ operator . requirements ,
347
+ info ?. rarity ,
348
+ )
336
349
337
350
return (
338
- < div className = "min-w-24 flex flex-col items-center" >
339
- < OperatorAvatar
340
- id = { info ?. id }
341
- rarity = { info ?. rarity }
342
- className = "w-16 h-16 mb-1"
343
- />
344
- < span className = { clsx ( 'mb-1 font-bold' ) } >
345
- { useLocalizedOperatorName ( name ) }
346
- </ span >
347
- < span className = "text-xs text-zinc-300" >
348
- { t . models . operator . skill_number ( { count : skill ?? 1 } ) }
349
- </ span >
351
+ < div className = "relative flex items-start" >
352
+ < div className = "relative w-20" >
353
+ < div className = "relative rounded-lg overflow-hidden shadow-md" >
354
+ < OperatorAvatar
355
+ id = { info ?. id }
356
+ rarity = { info ?. rarity }
357
+ className = "w-20 h-20"
358
+ fallback = { displayName }
359
+ />
360
+ { info ?. equips && module !== 0 && (
361
+ < div
362
+ title = { t . components . editor2 . label . opers . requirements . module }
363
+ className = "absolute -bottom-1 right-1 font-serif font-bold text-lg text-white [text-shadow:0_0_3px_#a855f7,0_0_5px_#a855f7]"
364
+ >
365
+ { info . equips [ module ] }
366
+ </ div >
367
+ ) }
368
+ </ div >
369
+ < h4 className = "mt-1 -mx-2 font-semibold tracking-tighter text-center" >
370
+ { displayName }
371
+ </ h4 >
372
+ { info && info . prof !== 'TOKEN' && (
373
+ < img
374
+ className = "absolute top-0 right-0 w-5 h-5 p-px bg-gray-600 rounded-tr-md"
375
+ src = { '/assets/prof-icons/' + info . prof + '.png' }
376
+ alt = { info . prof }
377
+ />
378
+ ) }
379
+ </ div >
380
+ { version >= 2 && info ?. prof !== 'TOKEN' && (
381
+ < >
382
+ < div className = "absolute top-1 -left-5 ml-[2px] px-3 py-4 rounded-full bg-[radial-gradient(rgba(0,0,0,0.6)_10%,rgba(0,0,0,0.08)_30%,rgba(0,0,0,0)_45%)] pointer-events-none" >
383
+ < img
384
+ className = "w-7 h-6 object-contain"
385
+ src = { getEliteIconUrl ( elite ) }
386
+ alt = { t . models . operator . elite ( { level : elite } ) }
387
+ />
388
+ </ div >
389
+ < div className = "absolute -top-2 -left-2 w-8 h-8 pr-px leading-7 rounded-full border-2 border-yellow-300 bg-black/50 text-lg text-white font-semibold text-center shadow-[0_1px_2px_rgba(0,0,0,0.9)]" >
390
+ { level }
391
+ </ div >
392
+ </ >
393
+ ) }
394
+
395
+ < ul className = "flex flex-col gap-1 ml-1" >
396
+ { skills . map ( ( _ , index ) => {
397
+ const skillNumber = index + 1
398
+ const selected = operator . skill === skillNumber
399
+ return (
400
+ < li
401
+ key = { index }
402
+ className = { clsx (
403
+ 'relative' ,
404
+ selected
405
+ ? 'bg-purple-100 dark:bg-purple-900 dark:text-purple-200 text-purple-800'
406
+ : 'bg-gray-300 dark:bg-gray-600 opacity-15 dark:opacity-25' ,
407
+ ) }
408
+ title = { t . models . operator . skill_number ( { count : skillNumber } ) }
409
+ >
410
+ < div className = "w-6 h-6 flex items-center justify-center font-bold text-xl border-2 border-current" >
411
+ { version >= 2 ? (
412
+ selected ? (
413
+ skillLevel <= 7 ? (
414
+ skillLevel
415
+ ) : (
416
+ < MasteryIcon
417
+ className = "w-4 h-4"
418
+ mastery = { skillLevel - 7 }
419
+ subClassName = "fill-gray-300 dark:fill-gray-500"
420
+ />
421
+ )
422
+ ) : undefined
423
+ ) : (
424
+ skillNumber
425
+ ) }
426
+ </ div >
427
+ </ li >
428
+ )
429
+ } ) }
430
+ </ ul >
350
431
</ div >
351
432
)
352
433
}
@@ -531,7 +612,7 @@ function OperationViewerInnerDetails({ operation }: { operation: Operation }) {
531
612
/>
532
613
</ H4 >
533
614
< Collapse isOpen = { showOperators } >
534
- < div className = "mt-2 flex flex-wrap -ml-4 gap-y-2 " >
615
+ < div className = "mt-2 flex flex-wrap gap-6 " >
535
616
{ ! operation . parsedContent . opers ?. length &&
536
617
! operation . parsedContent . groups ?. length && (
537
618
< NonIdealState
@@ -545,7 +626,11 @@ function OperationViewerInnerDetails({ operation }: { operation: Operation }) {
545
626
/>
546
627
) }
547
628
{ operation . parsedContent . opers ?. map ( ( operator ) => (
548
- < OperatorCard key = { operator . name } operator = { operator } />
629
+ < OperatorCard
630
+ key = { operator . name }
631
+ operator = { operator }
632
+ version = { operation . parsedContent . version }
633
+ />
549
634
) ) }
550
635
</ div >
551
636
< div className = "flex flex-wrap gap-4 mt-4" >
@@ -555,12 +640,16 @@ function OperationViewerInnerDetails({ operation }: { operation: Operation }) {
555
640
className = "!p-2 flex flex-col items-center"
556
641
key = { group . name }
557
642
>
558
- < H6 className = "text-gray-800" > { group . name } </ H6 >
559
- < div className = "flex flex-wrap gap-y-2 " >
643
+ < H6 className = "mb-3 text-gray-800" > { group . name } </ H6 >
644
+ < div className = "flex flex-wrap px-2 gap-6 " >
560
645
{ group . opers
561
646
?. filter ( Boolean )
562
647
. map ( ( operator ) => (
563
- < OperatorCard key = { operator . name } operator = { operator } />
648
+ < OperatorCard
649
+ key = { operator . name }
650
+ operator = { operator }
651
+ version = { operation . parsedContent . version }
652
+ />
564
653
) ) }
565
654
566
655
{ group . opers ?. filter ( Boolean ) . length === 0 && (
0 commit comments