Skip to content

Commit 5e0bd70

Browse files
authored
Merge pull request #253 from MaaAssistantArknights/dev
Release
2 parents 9444ff3 + 2392521 commit 5e0bd70

24 files changed

+503
-246
lines changed

public/levels.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/App.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { Effects } from 'components/Effects'
66
import { GlobalErrorBoundary } from './components/GlobalErrorBoundary'
77
import { FCC } from './types'
88
import { request } from './utils/fetcher'
9-
import { localStorageProvider, swrCacheMiddleware } from './utils/swr-cache'
109

1110
export const App: FCC = ({ children }) => {
1211
return (
@@ -15,11 +14,9 @@ export const App: FCC = ({ children }) => {
1514
<SWRConfig
1615
value={{
1716
fetcher: request,
18-
provider: localStorageProvider,
1917
focusThrottleInterval: 1000 * 60,
2018
errorRetryInterval: 1000 * 3,
2119
errorRetryCount: 3,
22-
use: [swrCacheMiddleware],
2320
}}
2421
>
2522
<GlobalErrorBoundary>

src/apis/arknights.ts

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import type { Operator, Version } from 'models/arknights'
44
import type { Response } from 'models/network'
55
import type { Level } from 'models/operation'
66

7-
import { withoutUnusedLevels } from '../models/level'
87
import { request } from '../utils/fetcher'
9-
import { useSWRCache } from '../utils/swr-cache'
108

119
const ONE_DAY = 1000 * 60 * 60 * 24
1210

@@ -20,57 +18,40 @@ export const useLevels = ({ suspense }: { suspense?: boolean } = {}) => {
2018
const url = '/arknights/level'
2119
type LevelResponse = Response<Level[]>
2220

23-
useSWRCache(
24-
url,
25-
// discard the cache if the level data has no stageId
26-
({ data }) => {
27-
const firstLevel = (data as LevelResponse)?.data?.[0]
28-
return !!firstLevel && 'stageId' in firstLevel
29-
},
30-
)
31-
32-
const response = useSWR<LevelResponse>(url, {
21+
return useSWR<LevelResponse>(url, {
3322
focusThrottleInterval: ONE_DAY,
3423
dedupingInterval: ONE_DAY,
3524
suspense,
3625
fetcher: async (input: string, init?: RequestInit) => {
37-
let res: LevelResponse
26+
const res = await request<LevelResponse>(input, init)
3827

39-
try {
40-
res = await request<LevelResponse>(input, init)
41-
} catch (e) {
42-
// fallback to built-in levels while retaining the error
43-
res = await requestBuiltInLevels(init)
44-
;(res as any).__serverError = e
45-
}
28+
const stageIds = new Set<string>()
4629

47-
res.data = withoutUnusedLevels(res.data)
30+
res.data = res.data.filter((level) => {
31+
if (
32+
// 引航者试炼
33+
level.levelId.includes('bossrush') ||
34+
// 肉鸽
35+
level.levelId.includes('roguelike') ||
36+
// 保全派驻
37+
level.levelId.includes('legion')
38+
) {
39+
return false
40+
}
4841

49-
return res
50-
},
51-
})
42+
if (stageIds.has(level.stageId)) {
43+
console.warn('Duplicate level removed:', level.stageId, level.name)
44+
return false
45+
}
5246

53-
if ((response.data as any)?.__serverError) {
54-
return {
55-
...response,
56-
error: (response.data as any).__serverError,
57-
}
58-
}
47+
stageIds.add(level.stageId)
5948

60-
return response
61-
}
49+
return true
50+
})
6251

63-
const requestBuiltInLevels = async (
64-
init?: RequestInit,
65-
): Promise<Response<Level[]>> => {
66-
const res = await fetch('/levels.json', init)
67-
const data = await res.json()
68-
return {
69-
data,
70-
statusCode: 200,
71-
message: 'OK',
72-
traceId: '',
73-
}
52+
return res
53+
},
54+
})
7455
}
7556

7657
export const useOperators = () => {

src/apis/comment.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,13 @@ export const requestRateComment = (
117117
},
118118
})
119119
}
120+
121+
export const requestTopComment = (commentId: string, topping: boolean) => {
122+
return jsonRequest<Response<string>>('/comments/topping', {
123+
method: 'POST',
124+
json: {
125+
comment_id: commentId,
126+
topping,
127+
},
128+
})
129+
}

src/components/OperationCard.tsx

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
H4,
88
H5,
99
Icon,
10-
Tag,
10+
Tag
1111
} from '@blueprintjs/core'
1212
import { Tooltip2 } from '@blueprintjs/popover2'
1313

@@ -16,15 +16,152 @@ import { handleCopyShortCode, handleDownloadJSON } from 'services/operation'
1616

1717
import { RelativeTime } from 'components/RelativeTime'
1818
import { OperationRating } from 'components/viewer/OperationRating'
19-
import { OperationListItem } from 'models/operation'
19+
import { OpDifficulty, OperationListItem } from 'models/operation'
2020

2121
import { useLevels } from '../apis/arknights'
2222
import { CopilotDocV1 } from '../models/copilot.schema'
2323
import { createCustomLevel, findLevelByStageName } from '../models/level'
2424
import { Paragraphs } from './Paragraphs'
25-
import { EDifficultyLevel } from './entity/ELevel'
25+
import { EDifficulty } from './entity/EDifficulty'
26+
import { EDifficultyLevel, NeoELevel } from './entity/ELevel'
2627
import { OperationViewer } from './viewer/OperationViewer'
2728

29+
export const NeoOperationCard = ({
30+
operation,
31+
operationDoc,
32+
}: {
33+
operation: OperationListItem
34+
operationDoc: CopilotDocV1.Operation
35+
}) => {
36+
const levels = useLevels()?.data?.data || []
37+
const [drawerOpen, setDrawerOpen] = useState(false)
38+
39+
return (
40+
<>
41+
<Drawer
42+
size={DrawerSize.LARGE}
43+
isOpen={drawerOpen}
44+
onClose={() => setDrawerOpen(false)}
45+
>
46+
<OperationViewer
47+
operationId={operation.id}
48+
onCloseDrawer={() => setDrawerOpen(false)}
49+
/>
50+
</Drawer>
51+
52+
<Card
53+
interactive={true}
54+
elevation={Elevation.TWO}
55+
className="flex flex-col gap-2"
56+
onClick={() => setDrawerOpen(true)}
57+
>
58+
59+
{/* title */}
60+
<div className="flex gap-1">
61+
<Tooltip2 content={operationDoc.doc.title} className='flex-1 whitespace-nowrap overflow-hidden text-ellipsis'>
62+
<H4 className="p-0 m-0 whitespace-nowrap overflow-hidden text-ellipsis">
63+
{operationDoc.doc.title}
64+
</H4>
65+
</Tooltip2>
66+
<Tooltip2
67+
placement="bottom"
68+
content={
69+
<div className="max-w-sm dark:text-slate-900">
70+
下载原 JSON
71+
</div>
72+
}
73+
>
74+
<Button
75+
small
76+
icon="download"
77+
onClick={(e) => {
78+
e.stopPropagation()
79+
handleDownloadJSON(operationDoc)
80+
}}
81+
/>
82+
</Tooltip2>
83+
<Tooltip2
84+
placement="bottom"
85+
content={
86+
<div className="max-w-sm dark:text-slate-900">
87+
复制神秘代码
88+
</div>
89+
}
90+
>
91+
<Button
92+
small
93+
icon="clipboard"
94+
onClick={(e) => {
95+
e.stopPropagation()
96+
handleCopyShortCode(operation)
97+
}}
98+
/>
99+
</Tooltip2>
100+
</div>
101+
<div className="flex items-center text-slate-900">
102+
<div className="flex flex-wrap">
103+
<NeoELevel level={findLevelByStageName(levels, operationDoc.stageName) ||
104+
createCustomLevel(operationDoc.stageName)} />
105+
<EDifficulty difficulty={operationDoc.difficulty ?? OpDifficulty.UNKNOWN} />
106+
</div>
107+
</div>
108+
<div className='flex-1 flex flex-col gap-2 justify-center'>
109+
<div className="text-gray-700 leading-normal">
110+
<Paragraphs
111+
content={operationDoc.doc.details}
112+
limitHeight={21 * 13.5} // 13 lines, 21px per line; the extra 0.5 line is intentional so the `mask` effect is obvious
113+
/>
114+
</div>
115+
<div>
116+
<div className="text-sm text-zinc-600 mb-2 font-bold">
117+
干员/干员组
118+
</div>
119+
<OperatorTags operationDoc={operationDoc} />
120+
</div>
121+
</div>
122+
123+
<div className='flex'>
124+
<div className="flex items-center gap-1.5">
125+
<Icon icon="star" />
126+
<OperationRating
127+
className="text-sm"
128+
operation={operation}
129+
layout="horizontal"
130+
/>
131+
</div>
132+
<div className='flex-1' />
133+
134+
<Tooltip2 placement="top" content={`访问量:${operation.views}`}>
135+
<div>
136+
<Icon icon="eye-open" className="mr-1.5" />
137+
<span>{operation.views}</span>
138+
</div>
139+
</Tooltip2>
140+
</div>
141+
142+
<div className='flex'>
143+
<div>
144+
<Icon icon="time" className="mr-1.5" />
145+
<RelativeTime
146+
Tooltip2Props={{ placement: 'top' }}
147+
moment={operation.uploadTime}
148+
/>
149+
</div>
150+
<div className='flex-1' />
151+
<div className="text-zinc-500">
152+
<Tooltip2 placement="top" content={`作者:${operation.uploader}`}>
153+
<div>
154+
<Icon icon="user" className="mr-1.5" />
155+
<span>{operation.uploader}</span>
156+
</div>
157+
</Tooltip2>
158+
</div>
159+
</div>
160+
</Card>
161+
</>
162+
)
163+
}
164+
28165
export const OperationCard = ({
29166
operation,
30167
operationDoc,

src/components/OperationList.tsx

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

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

66
import { toCopilotOperation } from '../models/converter'
77
import { CopilotDocV1 } from '../models/copilot.schema'
88
import { OperationListItem } from '../models/operation'
9-
import { OperationCard } from './OperationCard'
9+
import { NeoOperationCard, OperationCard } from './OperationCard'
1010
import { withSuspensable } from './Suspensable'
1111

12-
export const OperationList: ComponentType<UseOperationsParams> =
12+
export const OperationList: ComponentType<UseOperationsParams & { neoLayout?: boolean }> =
1313
withSuspensable(
1414
(props) => {
1515
const { operations, size, setSize, isValidating, isReachingEnd } =
@@ -28,15 +28,29 @@ export const OperationList: ComponentType<UseOperationsParams> =
2828
return { operation, doc }
2929
})
3030

31-
return (
32-
<>
31+
const { neoLayout = true } = props
32+
33+
const items: ReactNode = neoLayout ?
34+
<div className='grid gap-4' style={{ gridTemplateColumns: "repeat(auto-fill, minmax(20rem, 1fr)" }}>
3335
{operationsWithDoc.map(({ operation, doc }) => (
34-
<OperationCard
36+
<NeoOperationCard
3537
operation={operation}
3638
operationDoc={doc}
3739
key={operation.id}
3840
/>
3941
))}
42+
</div> :
43+
operationsWithDoc.map(({ operation, doc }) => (
44+
<OperationCard
45+
operation={operation}
46+
operationDoc={doc}
47+
key={operation.id}
48+
/>
49+
))
50+
51+
return (
52+
<>
53+
{items}
4054

4155
{isReachingEnd && operations.length === 0 && (
4256
<NonIdealState

src/components/Operations.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Card,
55
FormGroup,
66
InputGroup,
7+
Switch
78
} from '@blueprintjs/core'
89

910
import { UseOperationsParams } from 'apis/query'
@@ -14,6 +15,7 @@ import { ComponentType, useMemo, useState } from 'react'
1415
import { CardTitle } from 'components/CardTitle'
1516
import { OperationList } from 'components/OperationList'
1617

18+
import { neoLayoutAtom } from 'store/pref'
1719
import { authAtom } from '../store/auth'
1820
import { OperatorSelect } from './OperatorSelect'
1921
import { withSuspensable } from './Suspensable'
@@ -27,6 +29,7 @@ export const Operations: ComponentType = withSuspensable(() => {
2729
[],
2830
)
2931
const [authState] = useAtom(authAtom)
32+
const [neoLayout, setNeoLayout] = useAtom(neoLayoutAtom)
3033

3134
return (
3235
<>
@@ -86,7 +89,7 @@ export const Operations: ComponentType = withSuspensable(() => {
8689
}
8790
/>
8891
</FormGroup>
89-
<FormGroup label="排序" contentClassName="flex flex-wrap gap-y-2">
92+
<FormGroup label="排序" contentClassName="flex flex-wrap gap-y-2 gap-x-2">
9093
<ButtonGroup>
9194
<Button
9295
icon="flame"
@@ -130,11 +133,17 @@ export const Operations: ComponentType = withSuspensable(() => {
130133
看看我的
131134
</Button>
132135
)}
136+
137+
<Switch checked={neoLayout} onChange={(e) => {
138+
setNeoLayout((e.target as HTMLInputElement).checked)
139+
}}>
140+
启用新版布局
141+
</Switch>
133142
</FormGroup>
134143
</Card>
135144

136145
<div className="tabular-nums">
137-
<OperationList {...queryParams} />
146+
<OperationList {...queryParams} neoLayout={neoLayout} />
138147
</div>
139148
</>
140149
)

0 commit comments

Comments
 (0)