Skip to content

Commit a50d1db

Browse files
authored
Merge pull request #119 from MaaAssistantArknights/dev
2 parents 124d1d9 + 10ad78a commit a50d1db

File tree

9 files changed

+160
-67
lines changed

9 files changed

+160
-67
lines changed

src/apis/arknights.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Response } from 'models/network'
55
import type { Level } from 'models/operation'
66

77
import { withoutUnusedLevels } from '../models/level'
8+
import { request } from '../utils/fetcher'
89
import { enableCache } from '../utils/swr-cache'
910

1011
const ONE_DAY = 1000 * 60 * 60 * 24
@@ -25,19 +26,34 @@ export const useLevels = ({ suspense = true }: { suspense?: boolean } = {}) => {
2526
!!data?.data && Array.isArray(data.data) && 'stageId' in data.data[0],
2627
)
2728

28-
return useSWR<Response<Level[]>>(url, {
29+
const response = useSWR<Response<Level[]>>(url, {
2930
focusThrottleInterval: ONE_DAY,
3031
suspense,
3132
fetcher: async (input: string, init?: RequestInit) => {
32-
// TODO: remove this when backend is ready
33-
const res = await requestBuiltInLevels(init)
34-
// const res = await request<Response<Level[]>>(input, init)
33+
let res: Response<Level[]>
34+
35+
try {
36+
res = await request<Response<Level[]>>(input, init)
37+
} catch (e) {
38+
// fallback to built-in levels while retaining the error
39+
res = await requestBuiltInLevels(init)
40+
;(res as any).__serverError = e
41+
}
3542

3643
res.data = withoutUnusedLevels(res.data)
3744

3845
return res
3946
},
4047
})
48+
49+
if ((response.data as any)?.__serverError) {
50+
return {
51+
...response,
52+
error: (response.data as any).__serverError,
53+
}
54+
}
55+
56+
return response
4157
}
4258

4359
const requestBuiltInLevels = async (

src/components/OperationCard.tsx

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@ import { RelativeTime } from 'components/RelativeTime'
1616
import { OperationRating } from 'components/viewer/OperationRating'
1717
import { OperationListItem } from 'models/operation'
1818

19+
import { useLevels } from '../apis/arknights'
20+
import { CopilotDocV1 } from '../models/copilot.schema'
21+
import { createCustomLevel, findLevelByStageName } from '../models/level'
1922
import { Paragraphs } from './Paragraphs'
2023
import { EDifficultyLevel } from './entity/ELevel'
2124
import { OperationViewer } from './viewer/OperationViewer'
2225

23-
const formatOperatorTag = (operator: string) => {
24-
const splitted = operator.split('::')
25-
return splitted.length > 1 ? `${splitted[0]} ${splitted[1]}` : operator
26-
}
27-
2826
export const OperationCard = ({
2927
operation,
28+
operationDoc,
3029
}: {
3130
operation: OperationListItem
31+
operationDoc: CopilotDocV1.Operation
3232
}) => {
33+
const levels = useLevels({ suspense: false })?.data?.data || []
3334
const [drawerOpen, setDrawerOpen] = useState(false)
3435
return (
3536
<>
@@ -100,7 +101,11 @@ export const OperationCard = ({
100101
</div>
101102
<H5 className="flex items-center text-slate-900 -mt-3">
102103
<EDifficultyLevel
103-
level={operation.level}
104+
level={
105+
operation.level ||
106+
findLevelByStageName(levels, operationDoc.stageName) ||
107+
createCustomLevel(operationDoc.stageName)
108+
}
104109
difficulty={operation.difficulty}
105110
/>
106111
</H5>
@@ -111,21 +116,43 @@ export const OperationCard = ({
111116
</div>
112117
<div className="w-1/2 ml-4">
113118
<div className="text-sm text-zinc-600 mb-2 font-bold">
114-
使用干员与技能
115-
</div>
116-
<div>
117-
{operation.operators.map((operator, index) => (
118-
<Tag key={index} className="mr-2 last:mr-0 mb-1 last:mb-0">
119-
{formatOperatorTag(operator)}
120-
</Tag>
121-
))}
122-
{operation.operators.length === 0 && (
123-
<span className="text-gray-500">无记录</span>
124-
)}
119+
干员/干员组
125120
</div>
121+
<OperatorTags operationDoc={operationDoc} />
126122
</div>
127123
</div>
128124
</Card>
129125
</>
130126
)
131127
}
128+
129+
const OperatorTags = ({
130+
operationDoc: { opers, groups },
131+
}: {
132+
operationDoc: CopilotDocV1.Operation
133+
}) => {
134+
return opers?.length && groups?.length ? (
135+
<div>
136+
{opers?.map(({ name, skill }, index) => (
137+
<Tag key={index} className="mr-2 last:mr-0 mb-1 last:mb-0">
138+
{`${name} ${skill ?? 1}`}
139+
</Tag>
140+
))}
141+
{groups?.map(({ name, opers }, index) => (
142+
<Tooltip2
143+
className="mr-2 last:mr-0 mb-1 last:mb-0"
144+
placement="top"
145+
content={
146+
opers
147+
?.map(({ name, skill }) => `${name} ${skill ?? 1}`)
148+
.join(', ') || '无干员'
149+
}
150+
>
151+
<Tag key={index}>[{name}]</Tag>
152+
</Tooltip2>
153+
))}
154+
</div>
155+
) : (
156+
<div className="text-gray-500">无记录</div>
157+
)
158+
}

src/components/OperationList.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Button, NonIdealState } from '@blueprintjs/core'
22

33
import { UseOperationsParams, useOperations } from 'apis/query'
4-
import { ComponentType } from 'react'
4+
import { ComponentType, useMemo } from 'react'
55

6+
import { toCopilotOperation } from '../models/converter'
7+
import { CopilotDocV1 } from '../models/copilot.schema'
8+
import { OperationListItem } from '../models/operation'
69
import { OperationCard } from './OperationCard'
710
import { withSuspensable } from './Suspensable'
811

@@ -11,10 +14,28 @@ export const OperationList: ComponentType<UseOperationsParams> =
1114
(props) => {
1215
const { operations, size, setSize, isValidating, isReachingEnd } =
1316
useOperations(props)
17+
18+
const docCache = useMemo(
19+
() => new WeakMap<OperationListItem, CopilotDocV1.Operation>(),
20+
[],
21+
)
22+
const operationsWithDoc = operations.map((operation) => {
23+
let doc = docCache.get(operation)
24+
if (!doc) {
25+
doc = toCopilotOperation(operation)
26+
docCache.set(operation, doc)
27+
}
28+
return { operation, doc }
29+
})
30+
1431
return (
1532
<>
16-
{operations?.map((operation) => (
17-
<OperationCard operation={operation} key={operation.id} />
33+
{operationsWithDoc.map(({ operation, doc }) => (
34+
<OperationCard
35+
operation={operation}
36+
operationDoc={doc}
37+
key={operation.id}
38+
/>
1839
))}
1940

2041
{isReachingEnd && operations.length === 0 && (

src/components/editor/OperationEditor.tsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type { CopilotDocV1 } from 'models/copilot.schema'
2929
import { Level, OpDifficulty, OpDifficultyBitFlag } from 'models/operation'
3030

3131
import {
32+
createCustomLevel,
3233
findLevelByStageName,
3334
getPrtsMapUrl,
3435
getStageIdWithDifficulty,
@@ -50,19 +51,6 @@ import {
5051
EditorPerformerProps,
5152
} from './operator/EditorPerformer'
5253

53-
function createArbitraryLevel(name: string): Level {
54-
return {
55-
name,
56-
stageId: name,
57-
levelId: '',
58-
catOne: '',
59-
catTwo: '',
60-
catThree: '自定义关卡',
61-
width: 0,
62-
height: 0,
63-
}
64-
}
65-
6654
export const StageNameInput: FC<{
6755
control: Control<CopilotDocV1.Operation, object>
6856
}> = ({ control }) => {
@@ -107,7 +95,7 @@ export const StageNameInput: FC<{
10795
const selectedLevel = useMemo(
10896
() =>
10997
value
110-
? findLevelByStageName(levels, value) || createArbitraryLevel(value)
98+
? findLevelByStageName(levels, value) || createCustomLevel(value)
11199
: // return null to ensure the component is controlled
112100
null,
113101
[levels, value],
@@ -144,9 +132,6 @@ export const StageNameInput: FC<{
144132
<p>键入以搜索</p>
145133
<p>对于主线、活动关卡:键入关卡代号、关卡中文名或活动名称</p>
146134
<p>对于悖论模拟关卡:键入关卡名或干员名</p>
147-
<p className="text-red-700">
148-
目前服务器尚未恢复,此处的关卡是内置在编辑器里的本地数据,可能不包含近期的新关卡
149-
</p>
150135
</>
151136
),
152137
}}
@@ -178,7 +163,7 @@ export const StageNameInput: FC<{
178163
className: 'max-h-64 overflow-auto',
179164
}}
180165
noResults={<MenuItem disabled text="没有匹配的关卡" />}
181-
createNewItemFromQuery={(query) => createArbitraryLevel(query)}
166+
createNewItemFromQuery={(query) => createCustomLevel(query)}
182167
createNewItemRenderer={(query, active, handleClick) => (
183168
<MenuItem
184169
key="create-new-item"

src/components/entity/ELevel.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,29 @@ import { FC } from 'react'
55
import { EDifficulty } from 'components/entity/EDifficulty'
66
import { Level, OpDifficulty } from 'models/operation'
77

8+
import { isCustomLevel } from '../../models/level'
9+
810
export const ELevel: FC<{
911
className?: string
1012
level: Level
1113
}> = ({ level }) => {
14+
let { catOne, catTwo, catThree } = level
15+
16+
if (isCustomLevel(level)) {
17+
catOne = '自定义关卡'
18+
catTwo = ''
19+
catThree = level.name
20+
}
21+
1222
return (
1323
<Tag className="transition border border-solid !text-xs tracking-tight !p-1 leading-none !min-h-0 bg-slate-200 border-slate-300 text-slate-700">
14-
<div className="flex mx-1">
24+
<div className="flex items-center mx-1">
1525
<div className="flex flex-col mr-2">
16-
<H4 className="inline-block font-bold my-auto">{level.catThree}</H4>
26+
<H4 className="inline-block font-bold my-auto">{catThree}</H4>
1727
</div>
1828
<div className="flex flex-col">
19-
<span className="text-xs font-light">{level.catOne}</span>
20-
<span className="text-xs">{level.catTwo}</span>
29+
<span className="text-xs font-light">{catOne}</span>
30+
<span className="text-xs">{catTwo}</span>
2131
</div>
2232
</div>
2333
</Tag>

src/components/viewer/OperationViewer.tsx

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ import { authAtom } from 'store/auth'
3737
import { NetworkError } from 'utils/fetcher'
3838
import { wrapErrorMessage } from 'utils/wrapErrorMessage'
3939

40+
import { useLevels } from '../../apis/arknights'
4041
import { toCopilotOperation } from '../../models/converter'
42+
import { CopilotDocV1 } from '../../models/copilot.schema'
43+
import { createCustomLevel, findLevelByStageName } from '../../models/level'
4144
import { toShortCode } from '../../models/shortCode'
4245

4346
const ManageMenu: FC<{
@@ -109,6 +112,8 @@ export const OperationViewer: ComponentType<{
109112
const { data, error, mutate } = useOperation(operationId)
110113
const operation = data?.data
111114

115+
const levels = useLevels({ suspense: false })?.data?.data || []
116+
112117
const [auth] = useAtom(authAtom)
113118
const authed = !!auth.token
114119

@@ -250,7 +255,11 @@ export const OperationViewer: ComponentType<{
250255
<div className="flex flex-col">
251256
<FactItem title="作战">
252257
<EDifficultyLevel
253-
level={operation.level}
258+
level={
259+
operation.level ||
260+
findLevelByStageName(levels, operationDoc.stageName) ||
261+
createCustomLevel(operationDoc.stageName)
262+
}
254263
difficulty={operation.difficulty}
255264
/>
256265
</FactItem>
@@ -316,35 +325,42 @@ export const OperationViewer: ComponentType<{
316325
<H4 className="mb-4">干员与干员组</H4>
317326
<H5 className="mb-4 text-slate-600">干员</H5>
318327
<div className="flex flex-col mb-4">
319-
{operation.operators.map((operator) => (
320-
<OperatorCard key={operator} operator={operator} />
328+
{operationDoc.opers?.map((operator) => (
329+
<OperatorCard key={operator.name} operator={operator} />
321330
))}
322-
{operation.operators.length === 0 && (
331+
{!operationDoc.opers?.length && (
323332
<EmptyOperator description="作业并未添加干员" />
324333
)}
325334
</div>
326335

327336
<H5 className="mb-4 text-slate-600">干员组</H5>
328337
<div className="flex flex-col">
329-
{operation.groups.map((el) => (
330-
<Card elevation={Elevation.ONE} className="mb-4">
338+
{operationDoc.groups?.map((group) => (
339+
<Card
340+
elevation={Elevation.ONE}
341+
className="mb-4"
342+
key={group.name}
343+
>
331344
<div className="flex flex-col">
332-
<H5 className="text-gray-800 font-bold">{el.name}</H5>
345+
<H5 className="text-gray-800 font-bold">{group.name}</H5>
333346

334347
<div className="flex flex-col">
335-
{el.operators.filter(Boolean).map((operator) => (
336-
<OperatorCard key={operator} operator={operator} />
348+
{group.opers?.filter(Boolean).map((operator) => (
349+
<OperatorCard
350+
key={operator.name}
351+
operator={operator}
352+
/>
337353
))}
338354

339-
{el.operators.filter(Boolean).length === 0 && (
355+
{group.opers?.filter(Boolean).length === 0 && (
340356
<EmptyOperator description="干员组中并未添加干员" />
341357
)}
342358
</div>
343359
</div>
344360
</Card>
345361
))}
346362

347-
{operation.groups.length === 0 && (
363+
{!operationDoc.groups?.length && (
348364
<EmptyOperator
349365
title="暂无干员组"
350366
description="作业并未添加干员组"
@@ -369,9 +385,9 @@ export const OperationViewer: ComponentType<{
369385
)
370386

371387
const OperatorCard: FC<{
372-
operator: string
388+
operator: CopilotDocV1.Operator
373389
}> = ({ operator }) => {
374-
const [name, skill] = operator.split('::')
390+
const { name, skill } = operator
375391
return (
376392
<Card elevation={Elevation.ONE} className="mb-2 last:mb-0 flex">
377393
<OperatorAvatar name={name} size="large" className="mr-3" />

0 commit comments

Comments
 (0)