Skip to content

Commit a034fbf

Browse files
authored
Merge pull request #247 from Handiwork/dev
refactor: index page layout and OperationCard layout
2 parents 9a35dd3 + 15f530e commit a034fbf

File tree

5 files changed

+202
-11
lines changed

5 files changed

+202
-11
lines changed

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
)

src/components/entity/ELevel.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ import { Level, OpDifficulty } from 'models/operation'
77

88
import { isCustomLevel } from '../../models/level'
99

10+
export const NeoELevel: FC<{
11+
className?: string
12+
level: Level
13+
}> = ({ level }) => {
14+
let { catOne, catTwo, catThree } = level
15+
16+
if (isCustomLevel(level)) {
17+
catOne = '自定义关卡'
18+
catTwo = ''
19+
catThree = level.name
20+
}
21+
22+
return (
23+
<Tag className="transition border border-solid !text-xs tracking-tight !px-2 !py-1 !my-1 leading-none !min-h-0 bg-slate-200 border-slate-300 text-slate-700">
24+
<div className="flex items-center">
25+
<div className="flex whitespace-pre">
26+
<span className="inline-block font-bold my-auto">{catThree}</span>
27+
{" | "}
28+
<span className="text-xs">{catTwo}</span>
29+
{" | "}
30+
<span className="text-xs">{catOne}</span>
31+
</div>
32+
</div>
33+
</Tag>
34+
)
35+
}
36+
1037
export const ELevel: FC<{
1138
className?: string
1239
level: Level
@@ -34,6 +61,7 @@ export const ELevel: FC<{
3461
)
3562
}
3663

64+
3765
export const EDifficultyLevel: FC<{
3866
level: Level
3967
difficulty?: OpDifficulty

src/store/pref.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { atomWithStorage } from "jotai/utils"
2+
3+
export const neoLayoutAtom = atomWithStorage("neo-layout", true)

0 commit comments

Comments
 (0)