Skip to content

Commit d0d179e

Browse files
committed
feat: 适配新版排行版
1 parent 3fdad37 commit d0d179e

File tree

8 files changed

+407
-45
lines changed

8 files changed

+407
-45
lines changed

src/content/pages/ranking/App.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useAppDispatch, useAppSelector, useEffectMount } from '@/hooks'
55

66
import { selectOptions } from '../global/optionsSlice'
77
import { Portal } from '@/components/Portal'
8-
import { findElement, findAllElement } from '@/utils'
8+
import { findElement, findAllElement, isBetaUI } from '@/utils'
99
import Predict from './Predict'
1010
import { useUrlChange } from './Item'
1111
import Title from './Title'
@@ -22,8 +22,23 @@ import { RealTimePredict } from './RealTimePredict'
2222
import { User } from './utils'
2323
import { format } from 'date-fns'
2424
import { css } from 'styled-components/macro'
25+
import { BetaApp } from './BetaApp'
2526

26-
const App: FC = () => {
27+
const App = () => {
28+
const [beta, setBeta] = useState<boolean>()
29+
30+
useEffectMount(async state => {
31+
const beta = await isBetaUI()
32+
if (!state.isMount) return
33+
setBeta(beta)
34+
}, [])
35+
if (beta === undefined) return null
36+
if (beta) {
37+
return <BetaApp />
38+
}
39+
return <LegacyApp />
40+
}
41+
const LegacyApp: FC = () => {
2742
const options = useAppSelector(selectOptions)
2843
const [titleRoot, setTitleRoot] = useState<HTMLElement>()
2944
const [rows, setRows] = useState<HTMLElement[]>()

src/content/pages/ranking/BetaApp.tsx

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import { FC, useEffect, useState } from 'react'
2+
3+
import { useAppDispatch, useAppSelector, useEffectMount } from '@/hooks'
4+
5+
import { selectOptions } from '../global/optionsSlice'
6+
import { Portal } from '@/components/Portal'
7+
import { findElement, findAllElement, findElementByXPath } from '@/utils'
8+
import Predict from './Predict'
9+
import { useUrlChange } from './Item'
10+
import Title from './Title'
11+
import { LanguageIconRow } from './LanguageIcon'
12+
import { debounce } from 'src/utils'
13+
import {
14+
fetchContestRanking,
15+
fetchContestInfo,
16+
selectContestInfo,
17+
fetchMyRank,
18+
selectPreviousRatingUpdateTime,
19+
} from './rankSlice'
20+
import { RealTimePredict } from './RealTimePredict'
21+
import { User } from './utils'
22+
import { format } from 'date-fns'
23+
import { css } from 'styled-components/macro'
24+
25+
export const BetaApp: FC = () => {
26+
const options = useAppSelector(selectOptions)
27+
const [titleRoot, setTitleRoot] = useState<HTMLElement>()
28+
const [rows, setRows] = useState<HTMLElement[]>()
29+
const [param] = useUrlChange()
30+
const dispatch = useAppDispatch()
31+
const hasMyRank = rows?.[0]?.className === 'success' ? true : false
32+
const [userInfos, setUserInfos] = useState<User[]>([])
33+
34+
useEffect(() => {
35+
void (async () => {
36+
if (!rows?.length) return
37+
const res = await dispatch(
38+
fetchContestRanking({
39+
contestSlug: param.contestId,
40+
page: param.page,
41+
region: param.region,
42+
})
43+
).unwrap()
44+
const userInfos = res.total_rank.map(a => ({
45+
region: a.data_region,
46+
username: a.username,
47+
}))
48+
if (hasMyRank) {
49+
userInfos.unshift({
50+
region: 'CN',
51+
username: (window as any).LeetCodeData.userStatus.username,
52+
})
53+
}
54+
setUserInfos(userInfos)
55+
})()
56+
}, [dispatch, param, hasMyRank, rows])
57+
58+
useEffect(() => {
59+
dispatch(fetchContestInfo(param.contestId))
60+
}, [dispatch, param.contestId])
61+
62+
useEffectMount(async state => {
63+
const handleChange = debounce(async () => {
64+
// const parent = await findElement('.table-responsive>table>thead>tr')
65+
// console.log('handleChange')
66+
const el = await findElementByXPath(
67+
'//*[@id="__next"]//div[text()="用户名"]',
68+
el => {
69+
let p = el
70+
while (p && p !== document.body) {
71+
if (p.nextElementSibling?.textContent === '得分') {
72+
return true
73+
}
74+
p = p.parentElement
75+
}
76+
return false
77+
}
78+
)
79+
let p: HTMLElement
80+
if (el) {
81+
p = el
82+
while (p && p !== document.body) {
83+
if (p.nextElementSibling?.textContent === '得分') {
84+
break
85+
}
86+
p = p.parentElement!
87+
}
88+
const trs = p.parentElement!.parentElement!
89+
.children as unknown as HTMLElement[]
90+
if (state.isMount) {
91+
setTitleRoot(trs[0])
92+
// console.log([...trs].slice(1).map(a=>a.children[0]))
93+
setRows([...trs].slice(1).map(a => a.children[0]) as HTMLElement[])
94+
}
95+
}
96+
}, 100)
97+
handleChange()
98+
99+
window.addEventListener('urlchange', handleChange)
100+
}, [])
101+
102+
useEffect(() => {
103+
if (hasMyRank) {
104+
dispatch(fetchMyRank(param.contestId))
105+
}
106+
}, [dispatch, hasMyRank, param])
107+
108+
const contestInfo = useAppSelector(state =>
109+
selectContestInfo(state, param.contestId)
110+
)
111+
const updateTime = useAppSelector(state =>
112+
selectPreviousRatingUpdateTime(state, param.contestId)
113+
)
114+
115+
const showPredictordelta = !!options?.contestRankingPage.ratingPredictor
116+
const showLanguageIcon = !!options?.contestRankingPage.languageIcon
117+
const showNewRating = !!options?.contestRankingPage.showNewRating
118+
const showOldRating = !!options?.contestRankingPage.showOldRating
119+
const showPredict = !!options?.contestRankingPage.showPredict
120+
const realTimePredict = !!options?.contestRankingPage.realTimePredict
121+
const showExpectingRanking = !!options?.contestRankingPage.expectingRanking
122+
const widescreen =
123+
(showPredict || realTimePredict) &&
124+
(showPredictordelta || showNewRating || showOldRating)
125+
126+
useEffectMount(
127+
async state => {
128+
if (!widescreen) return
129+
let p = titleRoot
130+
while (p && p !== document.body) {
131+
if (getComputedStyle(p).maxWidth !== 'none') {
132+
p.style.maxWidth = 'unset'
133+
p.style.alignItems = 'center'
134+
break
135+
}
136+
p = p.parentElement!
137+
}
138+
},
139+
[widescreen, titleRoot]
140+
)
141+
if (!contestInfo || !rows) return null
142+
143+
return (
144+
<>
145+
{(((showPredictordelta || showNewRating || showOldRating) &&
146+
(showPredict || realTimePredict)) ||
147+
showExpectingRanking) &&
148+
titleRoot && (
149+
<Portal container={titleRoot}>
150+
<>
151+
{showPredict && (
152+
<div
153+
style={{
154+
height: '100%',
155+
display: 'flex',
156+
alignItems: 'center',
157+
width: 200,
158+
}}
159+
>
160+
<Title
161+
showOldRating={showOldRating}
162+
showPredictordelta={showPredictordelta}
163+
showNewRating={showNewRating}
164+
showExpectingRanking={showExpectingRanking}
165+
realTime={false}
166+
help={
167+
<>
168+
预测数据来自
169+
<a
170+
href="https://lccn.lbao.site/"
171+
target="_blank"
172+
rel="noreferrer"
173+
style={{ paddingLeft: 2 }}
174+
>
175+
lccn.lbao.site
176+
</a>
177+
</>
178+
}
179+
/>
180+
</div>
181+
)}
182+
{realTimePredict && (
183+
<div
184+
css={css`
185+
&&&& {
186+
border: 2px dashed #888;
187+
border-bottom-style: solid;
188+
}
189+
`}
190+
style={{
191+
height: '100%',
192+
display: 'flex',
193+
alignItems: 'center',
194+
width: 300,
195+
padding: 8,
196+
}}
197+
>
198+
<Title
199+
showOldRating={showOldRating}
200+
showPredictordelta={showPredictordelta}
201+
showNewRating={showNewRating}
202+
showExpectingRanking={showExpectingRanking}
203+
realTime={true}
204+
help={
205+
<div>
206+
实时预测,仅供参考,详细说明查看帖子
207+
<a
208+
href="https://leetcode.cn/circle/discuss/0OHPDu/"
209+
target="_blank"
210+
rel="noreferrer"
211+
>
212+
实时预测功能
213+
</a>
214+
<br />
215+
{updateTime
216+
? `当前数据更新时间为:「${format(
217+
new Date(updateTime),
218+
'yyyy-MM-dd HH:mm'
219+
)}」`
220+
: ''}
221+
</div>
222+
}
223+
/>
224+
</div>
225+
)}
226+
</>
227+
</Portal>
228+
)}
229+
{(((showPredictordelta || showNewRating || showOldRating) &&
230+
(showPredict || realTimePredict)) ||
231+
showExpectingRanking) &&
232+
rows && (
233+
<>
234+
<Predict
235+
userInfos={userInfos}
236+
rows={rows}
237+
hasMyRank={hasMyRank}
238+
showOldRating={showOldRating}
239+
showPredictordelta={showPredictordelta}
240+
showNewRating={showNewRating}
241+
showExpectingRanking={showExpectingRanking}
242+
beta={true}
243+
/>
244+
{realTimePredict && (
245+
<RealTimePredict
246+
rows={rows}
247+
hasMyRank={hasMyRank}
248+
showOldRating={showOldRating}
249+
showPredictordelta={showPredictordelta}
250+
showNewRating={showNewRating}
251+
showExpectingRanking={showExpectingRanking}
252+
beta={true}
253+
/>
254+
)}
255+
</>
256+
)}
257+
{showLanguageIcon &&
258+
rows?.map((row, i) => (
259+
<LanguageIconRow
260+
contestSlug={param.contestId}
261+
key={i}
262+
row={row}
263+
i={i}
264+
hasMyRank={hasMyRank}
265+
beta={true}
266+
/>
267+
))}
268+
</>
269+
)
270+
}

src/content/pages/ranking/Item.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type ItmeType = {
1515
showNewRating: boolean
1616
showExpectingRanking: boolean
1717
realTime: boolean
18+
beta?: boolean
1819
}
1920

2021
export type PageParamType = {
@@ -76,6 +77,7 @@ export const Item: FC<ItmeType> = memo(function Item({
7677
showNewRating,
7778
showExpectingRanking,
7879
realTime,
80+
beta,
7981
}) {
8082
let { delta, oldRating, erank, rank, isStable } =
8183
useAppSelector(state =>
@@ -98,6 +100,7 @@ export const Item: FC<ItmeType> = memo(function Item({
98100
? `rgb(0 136 0 / ${Math.min(delta / 100, 1) * 70 + 30}%)`
99101
: `rgb(64 64 64 / ${Math.min(-delta / 100, 1) * 70 + 30}%)`};
100102
width: 60px;
103+
${beta && delta < 0 ? `filter: invert(100%);;` : ''}
101104
`}
102105
>
103106
{deltaNum > 0 ? `+${deltaNum}` : deltaNum}
@@ -115,6 +118,7 @@ export const Item: FC<ItmeType> = memo(function Item({
115118
color: ${delta >= 0
116119
? `rgb(0 136 0 / ${Math.min(delta / 100, 1) * 70 + 30}%)`
117120
: `rgb(64 64 64 / ${Math.min(-delta / 100, 1) * 70 + 30}%)`};
121+
${beta && delta < 0 ? `filter: invert(100%);;` : ''}
118122
`
119123
: // 如果没有显示分数变化,则需要将分数变化反应到颜色的深浅中
120124
css`
@@ -123,6 +127,7 @@ export const Item: FC<ItmeType> = memo(function Item({
123127
color: ${delta >= 0
124128
? `rgb(0 136 0 / ${Math.min(delta / 100, 1) * 70 + 30}%)`
125129
: `rgb(64 64 64 / ${Math.min(-delta / 100, 1) * 70 + 30}%)`};
130+
${beta && delta < 0 ? `filter: invert(100%);;` : ''}
126131
`
127132
}
128133
>
@@ -135,18 +140,22 @@ export const Item: FC<ItmeType> = memo(function Item({
135140
const { start_time, duration } = info.contest
136141
const inContest = new Date().valueOf() <= (start_time + duration) * 1000
137142

143+
const color = beta ? 'rgba(255, 255, 255, 0.6)' : '#000'
144+
138145
return (
139146
<div
140147
css={css`
141148
display: flex;
149+
height: 100%;
150+
align-items: center;
142151
`}
143152
>
144153
{showOldRating && <div style={{ width: 60 }}>{oldRating}</div>}
145154
{showPredictordelta && deltaEl}
146155
{showNewRating && newRatingEl}
147156
{showExpectingRanking && realTime && erank && (
148157
<div style={{ display: 'flex' }}>
149-
<span style={{ color: isStable || !inContest ? '#000' : '#bbb' }}>
158+
<span style={{ color: isStable || !inContest ? color : '#bbb' }}>
150159
{rank}
151160
</span>
152161
<span style={{ margin: '0 10px' }}>/</span>

0 commit comments

Comments
 (0)