-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathLanguageIcon.tsx
143 lines (124 loc) · 3.43 KB
/
LanguageIcon.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
import { FC, memo, useEffect, useMemo, useState } from 'react'
import styled from 'styled-components/macro'
import { debounce } from '../../../utils'
import {
selectUserRanking,
selectContestInfo,
useGetFileIconsQuery,
} from './rankSlice'
import { Portal } from '@/components/Portal'
import { useAppSelector } from '@/hooks'
import { useUser } from './utils'
type ItmeType = {
parent: HTMLElement
lang?: string
beta?: boolean
}
const DefaultIcon = styled.span`
&::before {
content: '\f1c9';
}
`
const StyleSvg = styled.svg<{ size?: number }>`
height: 1em;
width: 1em;
transform: translateY(0.125em) translateX(-5px) scale(1.4);
& > image {
height: 1em;
width: 1em;
}
${({ size }) => (size ? `font-size: ${size}px;` : '')}
`
function setDisplay(el: Element | undefined, display: string) {
if (el instanceof HTMLElement || el instanceof SVGElement) {
el.style.display = display
}
}
function isShow(parent: HTMLElement) {
return parent.textContent && !!parent.textContent.trim()
}
const LanguageIcon: FC<ItmeType> = ({ parent, lang, beta }) => {
const [show, setShow] = useState(isShow(parent))
const { data: iconFiles } = useGetFileIconsQuery()
useEffect(() => {
const p = beta ? parent.children[0].children[0] : parent.children[0]
const handleChange = debounce(() => {
const show = isShow(parent)
setShow(show)
if (show) {
if (p?.nodeName !== 'DIV') {
setDisplay(p?.children[0], 'none')
}
}
}, 10)
handleChange()
const observer = new MutationObserver(handleChange)
observer.observe(parent, { childList: true })
return () => {
handleChange.cancel()
observer.disconnect()
setDisplay(p?.children[0], '')
}
}, [beta])
if (!show || !lang || !iconFiles) return null
if (parent.childNodes[0]?.nodeName === '#text') {
// 当前处于比赛中,则需要手动创建一个元素用于图标的渲染
const span = document.createElement('span')
parent.insertBefore(span, parent.childNodes[0])
span.style.display = 'inline-block'
}
const iconFile = iconFiles[lang]
return (
<Portal
container={
beta
? (parent.children[0].children[0] as HTMLElement)
: (parent.children[0] as HTMLElement)
}
>
{!iconFile ? (
<DefaultIcon className="fa fa-file-code-o" />
) : (
<StyleSvg>
<image href={iconFile} />
</StyleSvg>
)}
</Portal>
)
}
export const LanguageIconRow: FC<{
contestSlug: string
row: HTMLElement
i: number
hasMyRank: boolean
beta?: boolean
}> = memo(function LanguageIconRow({ contestSlug, row, i, hasMyRank, beta }) {
const { username, region } = useUser(hasMyRank, i, row, beta)
const user = useAppSelector(state =>
selectUserRanking(state, contestSlug, region, username)
)
const contestInfo = useAppSelector(state =>
selectContestInfo(state, contestSlug)
)
const tds = useMemo(() => {
if (beta) {
return Array.prototype.slice.call(row.children, 3, 7)
}
return Array.prototype.slice.call(row.children, 4, 8)
}, [beta, row])
if (!user || !contestInfo) return null
const { questions } = contestInfo
return (
<>
{tds.map((td, j) => (
<LanguageIcon
key={j}
parent={td}
lang={user.submission[questions[j].question_id]?.lang}
beta={beta}
/>
))}
</>
)
})
export default LanguageIcon