-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathItem.tsx
193 lines (181 loc) · 5.52 KB
/
Item.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import { useAppSelector, useEffectMount } from '@/hooks'
import { FC, memo, useState } from 'react'
import { css } from 'styled-components/macro'
import { debounce } from 'src/utils'
import { selectUserPredict, selectContestInfo } from './rankSlice'
import { findElementByXPath } from '@/utils'
type ItmeType = {
contestSlug: string
region: string
username: string
showOldRating: boolean
showPredictordelta: boolean
showNewRating: boolean
showExpectingRanking: boolean
realTime: boolean
beta?: boolean
}
export type PageParamType = {
contestId: string
page: number
username?: string
region: 'local' | 'global'
}
function getParam(beta?: boolean): PageParamType {
const [, contestId, , pageStr = '1'] = location.pathname
.split('/')
.filter(Boolean)
const page = Number(pageStr)
let region: 'local' | 'global' = 'local'
if (beta) {
const btn = document.evaluate(
'//*[@id="__next"]//button[text()="全国"]',
document,
null,
XPathResult['FIRST_ORDERED_NODE_TYPE'],
null
).singleNodeValue as HTMLButtonElement
if (btn && btn.dataset.state !== 'active') {
region = 'global'
}
} else {
const checkbox = document.querySelector(
'.checkbox>label>input'
) as HTMLInputElement
region = checkbox?.checked ? 'global' : 'local'
}
return { contestId, page, region }
}
export function useUrlChange(beta?: boolean): [PageParamType] {
const [param, setParam] = useState(getParam(beta))
useEffectMount(state => {
const handle = debounce(() => {
if (state.isMount) setParam(getParam(beta))
}, 100)
window.addEventListener('urlchange', handle)
state.unmount.push(() => {
handle.cancel()
window.removeEventListener('urlchange', handle)
})
}, [])
// 是否选中「显示全球」
useEffectMount(async state => {
const handle = debounce((_e: Event) => {
if (state.isMount) setParam(getParam(beta))
}, 100)
if (beta) {
const el = await findElementByXPath(
'//*[@id="__next"]//button[text()="全国"]'
)
el.parentElement!.addEventListener('click', handle)
state.unmount.push(() => {
handle.cancel()
el.parentElement!.removeEventListener('change', handle)
})
} else {
const checkbox = document.querySelector(
'.checkbox>label>input'
) as HTMLInputElement
if (!checkbox) return
checkbox.addEventListener('change', handle)
state.unmount.push(() => {
handle.cancel()
checkbox.removeEventListener('change', handle)
})
}
})
return [param]
}
export const Item: FC<ItmeType> = memo(function Item({
contestSlug,
region,
username,
showOldRating,
showPredictordelta,
showNewRating,
showExpectingRanking,
realTime,
beta,
}) {
let { delta, oldRating, erank, rank, isStable } =
useAppSelector(state =>
selectUserPredict(state, contestSlug, region, username, !!realTime)
) ?? {}
const info = useAppSelector(state => selectContestInfo(state, contestSlug))
if (!oldRating || !info) return <></>
let deltaEl, newRatingEl
if (typeof delta !== 'number') {
deltaEl = <></>
newRatingEl = <></>
} else {
const deltaNum = Number(delta.toFixed(1))
deltaEl = (
<div
css={css`
font-weight: bold;
color: ${delta >= 0
? `rgb(0 136 0 / ${Math.min(delta / 100, 1) * 70 + 30}%)`
: `rgb(64 64 64 / ${Math.min(-delta / 100, 1) * 70 + 30}%)`};
width: 60px;
${beta && delta < 0 ? `filter: invert(100%);;` : ''}
`}
>
{deltaNum > 0 ? `+${deltaNum}` : deltaNum}
</div>
)
const newRating = Number(((delta ?? 0) + oldRating).toFixed(1))
newRatingEl = (
<div
css={
showPredictordelta
? // 如果有显示分数变化,则新分数只需要区分颜色
css`
width: 70px;
font-weight: bold;
color: ${delta >= 0
? `rgb(0 136 0 / ${Math.min(delta / 100, 1) * 70 + 30}%)`
: `rgb(64 64 64 / ${Math.min(-delta / 100, 1) * 70 + 30}%)`};
${beta && delta < 0 ? `filter: invert(100%);;` : ''}
`
: // 如果没有显示分数变化,则需要将分数变化反应到颜色的深浅中
css`
width: 70px;
font-weight: bold;
color: ${delta >= 0
? `rgb(0 136 0 / ${Math.min(delta / 100, 1) * 70 + 30}%)`
: `rgb(64 64 64 / ${Math.min(-delta / 100, 1) * 70 + 30}%)`};
${beta && delta < 0 ? `filter: invert(100%);;` : ''}
`
}
>
{newRating}
</div>
)
}
oldRating = Number(oldRating.toFixed(1))
const { start_time, duration } = info.contest
const inContest = new Date().valueOf() <= (start_time + duration) * 1000
const color = beta ? 'rgba(255, 255, 255, 0.6)' : '#000'
return (
<div
css={css`
display: flex;
height: 100%;
align-items: center;
`}
>
{showOldRating && <div style={{ width: 60 }}>{oldRating}</div>}
{showPredictordelta && deltaEl}
{showNewRating && newRatingEl}
{showExpectingRanking && realTime && erank && (
<div style={{ display: 'flex' }}>
<span style={{ color: isStable || !inContest ? color : '#bbb' }}>
{rank}
</span>
<span style={{ margin: '0 10px' }}>/</span>
<span>{Math.round(erank)}</span>
</div>
)}
</div>
)
})