Skip to content

Commit 7c5e8ec

Browse files
authored
Merge pull request #257 from Gemini2035/feat/favOperators
新增收藏干员功能,并优化了一些类型约束
2 parents 1da5314 + b5adf84 commit 7c5e8ec

File tree

5 files changed

+228
-60
lines changed

5 files changed

+228
-60
lines changed

src/components/editor/operator/sheet/SheetGroup.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const SheetGroup = ({
158158
{ ...value },
159159
])
160160

161-
const groupAddHandle = (value: Group) => {
161+
const groupAddHandle: GroupListModifyProp['groupAddHandle'] = (value) => {
162162
if (checkGroupExisted(value.name)) {
163163
AppToaster.show({
164164
message: '干员组已存在!',
@@ -169,18 +169,20 @@ const SheetGroup = ({
169169
submitGroup(value, undefined, true)
170170
}
171171
}
172-
const groupRemoveHandle = (_id: string) => {
172+
const groupRemoveHandle: GroupListModifyProp['groupRemoveHandle'] = (_id) => {
173173
removeGroup(existedGroups.findIndex((item) => item._id === _id))
174174
}
175-
const groupPinHandle = (value: Group) => {
175+
const groupPinHandle: GroupListModifyProp['groupPinHandle'] = (value) => {
176176
if (checkGroupPinned(value))
177177
setFavGroups([...favGroups].filter(({ name }) => name !== value.name))
178178
else {
179179
if (checkSamePinned(value.name)) setCoverGroup(value)
180180
else updateFavGroup(value)
181181
}
182182
}
183-
const groupUpdateHandle = (value: Group) => {
183+
const groupUpdateHandle: GroupListModifyProp['groupUpdateHandle'] = (
184+
value,
185+
) => {
184186
changeOperatorOfOtherGroups(value.opers)
185187
submitGroup(value, undefined, true)
186188
}

src/components/editor/operator/sheet/SheetOperator.tsx

Lines changed: 146 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { Button, Divider, H4, H5, H6, Intent } from '@blueprintjs/core'
1+
import { Alert, Button, Divider, H4, H5, H6, Intent } from '@blueprintjs/core'
22

33
import clsx from 'clsx'
4+
import { useAtom } from 'jotai'
5+
import { isEqual, omit } from 'lodash'
46
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
57
import { UseFieldArrayRemove } from 'react-hook-form'
68

79
import { AppToaster } from 'components/Toaster'
10+
import { CopilotDocV1 } from 'models/copilot.schema'
811
import { OPERATORS, PROFESSIONS } from 'models/operator'
12+
import { ignoreKeyDic } from 'store/useFavGroups'
13+
import { favOperatorAtom } from 'store/useFavOperators'
914

1015
import { EditorPerformerOperatorProps } from '../EditorPerformerOperator'
1116
import { Group, Operator } from '../EditorSheet'
@@ -20,12 +25,23 @@ export interface SheetOperatorProps {
2025
removeOperator: UseFieldArrayRemove
2126
}
2227

28+
export interface OperatorModifyProps {
29+
operatorPinHandle?: (value: Operator) => void
30+
operatorSelectHandle?: (value: string) => void
31+
operatorSkillHandle?: (value: Operator) => void
32+
}
33+
2334
const defaultProf = [
2435
{
2536
id: 'all',
2637
name: '全部',
2738
sub: [],
2839
},
40+
{
41+
id: 'fav',
42+
name: '收藏',
43+
sub: [],
44+
},
2945
{
3046
id: 'others',
3147
name: '其它',
@@ -39,9 +55,9 @@ const defaultSubProf = [
3955
]
4056

4157
const formattedProfessions = [
42-
defaultProf[0],
58+
...defaultProf.slice(0, defaultProf.length - 1),
4359
...PROFESSIONS,
44-
...defaultProf.slice(1),
60+
...defaultProf.slice(defaultProf.length - 1),
4561
]
4662

4763
const paginationSize = 60
@@ -56,6 +72,12 @@ const SheetOperator = ({
5672

5773
const [selectedProf, setSelectedProf] = useState(formattedProfessions[0])
5874
const [selectedSubProf, setSelectedSubProf] = useState(defaultSubProf[0])
75+
const [favOperators, setFavOperators] = useAtom(favOperatorAtom)
76+
const [coverOperator, setCoverOperator] = useState<Operator>()
77+
78+
const favOperatorFindByName = (target: string) => {
79+
return !!favOperators.find(({ name }) => name === target)
80+
}
5981

6082
const [formattedSubProfessions, operatorsGroupedByProf] = useMemo(
6183
() => [
@@ -73,12 +95,14 @@ const SheetOperator = ({
7395
...OPERATORS,
7496
].filter((item) => {
7597
if (selectedProf.id === defaultProf[0].id) return true
76-
else if (selectedProf.id === defaultProf[1].id) {
98+
if (selectedProf.id === defaultProf[1].id)
99+
return favOperatorFindByName(item.name)
100+
else if (selectedProf.id === defaultProf[2].id) {
77101
return item.subProf === 'notchar1' || !item.subProf
78102
} else return !!selectedProf.sub?.find((op) => op.id === item.subProf)
79103
}),
80104
],
81-
[selectedProf, existedOperators],
105+
[selectedProf, existedOperators, favOperators],
82106
)
83107

84108
const checkOperatorSelected = useCallback(
@@ -93,6 +117,46 @@ const SheetOperator = ({
93117
[existedOperators, existedGroups],
94118
)
95119

120+
const checkOperatorPinned = (target: Operator, ignoreKey = ignoreKeyDic) =>
121+
isEqual(
122+
omit(target, [...ignoreKey]),
123+
omit(
124+
favOperators.find(({ name }) => name === target.name),
125+
[...ignoreKey],
126+
),
127+
)
128+
129+
const updateFavOperator = (value: Operator) => {
130+
const { skill, skillUsage, skillTimes, ...rest } = value
131+
const formattedValue = {
132+
...rest,
133+
skill: skill || 1,
134+
skillUsage: skillUsage || 0,
135+
skillTimes:
136+
skillUsage === CopilotDocV1.SkillUsageType.ReadyToUseTimes
137+
? skillTimes || 1
138+
: undefined,
139+
}
140+
setFavOperators([
141+
...[...favOperators].filter(({ name }) => name !== formattedValue.name),
142+
{ ...formattedValue },
143+
])
144+
submitOperator(formattedValue, undefined, true)
145+
}
146+
147+
const operatorPinHandle: OperatorModifyProps['operatorPinHandle'] = (
148+
value,
149+
) => {
150+
if (checkOperatorPinned(value))
151+
setFavOperators(
152+
[...favOperators].filter(({ name }) => name !== value.name),
153+
)
154+
else {
155+
if (favOperatorFindByName(value.name)) setCoverOperator(value)
156+
else updateFavOperator(value)
157+
}
158+
}
159+
96160
const operatorsGroupedBySubProf = useMemo(() => {
97161
if (selectedSubProf.id === 'all') return operatorsGroupedByProf
98162
else if (selectedSubProf.id === 'selected')
@@ -105,7 +169,9 @@ const SheetOperator = ({
105169
)
106170
}, [selectedSubProf, operatorsGroupedByProf, checkOperatorSelected])
107171

108-
const operatorSelectHandle = (operatorName: string) => {
172+
const operatorSelectHandle: OperatorModifyProps['operatorSelectHandle'] = (
173+
operatorName,
174+
) => {
109175
if (checkOperatorSelected(operatorName))
110176
if (existedOperators.find((item) => item.name === operatorName))
111177
removeOperator(
@@ -116,10 +182,19 @@ const SheetOperator = ({
116182
message: `干员 ${operatorName} 已被编组`,
117183
intent: Intent.DANGER,
118184
})
119-
else submitOperator({ name: operatorName }, undefined, true)
185+
else
186+
submitOperator(
187+
favOperators.find(({ name }) => name === operatorName) || {
188+
name: operatorName,
189+
},
190+
undefined,
191+
true,
192+
)
120193
}
121194

122-
const operatorSkillHandle = (value: Operator) => {
195+
const operatorSkillHandle: OperatorModifyProps['operatorSkillHandle'] = (
196+
value,
197+
) => {
123198
submitOperator(value, undefined, true)
124199
}
125200

@@ -259,44 +334,70 @@ const SheetOperator = ({
259334
)
260335

261336
return (
262-
<div className="flex h-full">
263-
<div className="flex-auto px-1" ref={operatorScrollRef}>
264-
{operatorsGroupedBySubProf.length ? (
265-
<>
266-
<div
267-
key="operatorContainer"
268-
className="flex flex-wrap items-start content-start overscroll-contain relative"
269-
>
270-
{operatorsGroupedBySubProf
271-
.slice(0, lastIndex)
272-
.map(({ name: operatorInfoName }, index) => {
273-
const operatorDetail = existedOperators.find(
274-
({ name }) => name === operatorInfoName,
275-
)
276-
return (
277-
<div
278-
className="flex items-center w-32 h-32 flex-0"
279-
key={index}
280-
>
281-
<OperatorItem
282-
selected={checkOperatorSelected(operatorInfoName)}
283-
onSkillChange={operatorSkillHandle}
284-
operator={operatorDetail}
285-
name={operatorInfoName}
286-
onClick={() => operatorSelectHandle(operatorInfoName)}
287-
/>
288-
</div>
289-
)
290-
})}
291-
</div>
292-
{ShowMoreButton}
293-
</>
294-
) : (
295-
OperatorNoData
296-
)}
337+
<>
338+
<div className="flex h-full">
339+
<div className="flex-auto px-1" ref={operatorScrollRef}>
340+
{operatorsGroupedBySubProf.length ? (
341+
<>
342+
<div
343+
key="operatorContainer"
344+
className="flex flex-wrap items-start content-start overscroll-contain relative"
345+
>
346+
{operatorsGroupedBySubProf
347+
.slice(0, lastIndex)
348+
.map(({ name: operatorInfoName }, index) => {
349+
const operatorDetail = existedOperators.find(
350+
({ name }) => name === operatorInfoName,
351+
)
352+
return (
353+
<div
354+
className="flex items-center w-32 h-32 flex-0"
355+
key={index}
356+
>
357+
<OperatorItem
358+
selected={checkOperatorSelected(operatorInfoName)}
359+
pinned={checkOperatorPinned(
360+
operatorDetail || { name: operatorInfoName },
361+
)}
362+
onSkillChange={operatorSkillHandle}
363+
operator={operatorDetail}
364+
name={operatorInfoName}
365+
onClick={() => operatorSelectHandle(operatorInfoName)}
366+
onPinHandle={
367+
existedOperators.find(
368+
({ name }) => name === operatorInfoName,
369+
)
370+
? operatorPinHandle
371+
: undefined
372+
}
373+
/>
374+
</div>
375+
)
376+
})}
377+
</div>
378+
{ShowMoreButton}
379+
</>
380+
) : (
381+
OperatorNoData
382+
)}
383+
</div>
384+
{ProfSelect}
297385
</div>
298-
{ProfSelect}
299-
</div>
386+
<Alert
387+
isOpen={!!coverOperator}
388+
confirmButtonText="是"
389+
cancelButtonText="否"
390+
icon="error"
391+
intent={Intent.DANGER}
392+
onConfirm={() => updateFavOperator(coverOperator as Group)}
393+
onClose={() => setCoverOperator(undefined)}
394+
>
395+
<div>
396+
<H5>收藏: </H5>
397+
<p>检测到同名的已收藏干员 {coverOperator?.name},是否覆盖?</p>
398+
</div>
399+
</Alert>
400+
</>
300401
)
301402
}
302403

src/components/editor/operator/sheet/SheetOperatorItem.tsx

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { Card, CardProps } from '@blueprintjs/core'
1+
import { Button, Card, CardProps, Icon } from '@blueprintjs/core'
2+
import { Popover2 } from '@blueprintjs/popover2'
23

34
import clsx from 'clsx'
45

56
import { OperatorAvatar } from '../EditorOperator'
7+
import { Operator } from '../EditorSheet'
8+
import { OperatorModifyProps } from './SheetOperator'
69
import { SkillAboutProps, SkillAboutTrigger } from './SheetOperatorSkillAbout'
710

811
export interface OperatorItemPorps extends CardProps, SkillAboutProps {
@@ -11,6 +14,8 @@ export interface OperatorItemPorps extends CardProps, SkillAboutProps {
1114
horizontal?: boolean
1215
scaleDisable?: boolean
1316
readOnly?: boolean
17+
pinned?: boolean
18+
onPinHandle?: OperatorModifyProps['operatorPinHandle']
1419
}
1520

1621
export const OperatorItem = ({
@@ -21,6 +26,8 @@ export const OperatorItem = ({
2126
scaleDisable,
2227
readOnly,
2328
onSkillChange,
29+
onPinHandle,
30+
pinned,
2431
...cardProps
2532
}: OperatorItemPorps) => (
2633
<Card
@@ -33,15 +40,45 @@ export const OperatorItem = ({
3340
{...cardProps}
3441
>
3542
<>
36-
<OperatorAvatar name={name} size="large" />
37-
<p
38-
className={clsx(
39-
'font-bold leading-none text-center mt-3 truncate',
40-
horizontal && 'mt-0 ml-1 mr-auto',
41-
)}
42-
>
43-
{name}
44-
</p>
43+
<>
44+
<OperatorAvatar name={name} size="large" />
45+
<p
46+
className={clsx(
47+
'font-bold leading-none text-center mt-3 truncate',
48+
horizontal && 'mt-0 ml-1 mr-auto',
49+
)}
50+
>
51+
{name}
52+
</p>
53+
</>
54+
{!horizontal && selected && !!onPinHandle && (
55+
<div
56+
className="absolute top-2 right-2"
57+
onClick={(e) => e.stopPropagation()}
58+
role="presentation"
59+
>
60+
<Popover2
61+
content={
62+
<Button
63+
minimal
64+
onClick={() => onPinHandle?.(operator as Operator)}
65+
>
66+
<Icon icon="pin" className="-rotate-45" />
67+
<span>移出收藏</span>
68+
</Button>
69+
}
70+
disabled={!pinned}
71+
>
72+
<Icon
73+
icon={pinned ? 'pin' : 'unpin'}
74+
className={clsx(pinned && '-rotate-45')}
75+
onClick={
76+
pinned ? undefined : () => onPinHandle?.(operator as Operator)
77+
}
78+
/>
79+
</Popover2>
80+
</div>
81+
)}
4582
</>
4683
{!readOnly && selected && (
4784
<SkillAboutTrigger {...{ operator, onSkillChange }} />

0 commit comments

Comments
 (0)