|
| 1 | +import { ContestInfo, ContestRanking, LeetCode, UserContestInfo } from "leetcode-query"; |
| 2 | +import { Generator } from "../card"; |
| 3 | +import { Gradient } from "../elements"; |
| 4 | +import { Item } from "../item"; |
| 5 | +import { Extension } from "../types"; |
| 6 | + |
| 7 | +export function ContestExtension(generator: Generator): Extension { |
| 8 | + const pre_result = new Promise<null | { ranking: ContestRanking; history: ContestInfo[] }>( |
| 9 | + (resolve) => { |
| 10 | + const lc = new LeetCode(); |
| 11 | + lc.once("receive-graphql", async (res) => { |
| 12 | + try { |
| 13 | + const { data } = (await res.json()) as { data: UserContestInfo }; |
| 14 | + const history = data.userContestRankingHistory.filter((x) => x.attended); |
| 15 | + |
| 16 | + if (history.length === 0) { |
| 17 | + resolve(null); |
| 18 | + return; |
| 19 | + } |
| 20 | + |
| 21 | + resolve({ ranking: data.userContestRanking, history }); |
| 22 | + } catch (e) { |
| 23 | + resolve(null); |
| 24 | + } |
| 25 | + }); |
| 26 | + lc.user_contest_info(generator.config.username).catch(() => resolve(null)); |
| 27 | + }, |
| 28 | + ); |
| 29 | + |
| 30 | + return async function Contest(generator, data, body, styles) { |
| 31 | + const result = await pre_result; |
| 32 | + |
| 33 | + if (result) { |
| 34 | + if (generator.config.height < 400) { |
| 35 | + generator.config.height = 400; |
| 36 | + } |
| 37 | + |
| 38 | + const start_time = result.history[0].contest.startTime; |
| 39 | + const end_time = result.history[result.history.length - 1].contest.startTime; |
| 40 | + const [min_rating, max_rating] = result.history.reduce( |
| 41 | + ([min, max], { rating }) => [Math.min(min, rating), Math.max(max, rating)], |
| 42 | + [Infinity, -Infinity], |
| 43 | + ); |
| 44 | + |
| 45 | + const width = generator.config.width - 90; |
| 46 | + const height = 100; |
| 47 | + const x_scale = width / (end_time - start_time); |
| 48 | + const y_scale = height / (max_rating - min_rating); |
| 49 | + |
| 50 | + const points = result.history.map((d) => { |
| 51 | + const { rating } = d; |
| 52 | + const time = d.contest.startTime; |
| 53 | + const x = (time - start_time) * x_scale; |
| 54 | + const y = (max_rating - rating) * y_scale; |
| 55 | + return [x, y]; |
| 56 | + }); |
| 57 | + |
| 58 | + const extension = new Item("g", { |
| 59 | + id: "ext-contest", |
| 60 | + style: { transform: `translate(0px, 200px)` }, |
| 61 | + children: [ |
| 62 | + new Item("line", { |
| 63 | + attr: { x1: 10, y1: 0, x2: generator.config.width - 10, y2: 0 }, |
| 64 | + style: { stroke: "var(--bg-1)", "stroke-width": 1 }, |
| 65 | + }), |
| 66 | + new Item("text", { |
| 67 | + content: "Contest Rating", |
| 68 | + id: "ext-contest-rating-title", |
| 69 | + style: { |
| 70 | + transform: `translate(20px, 20px)`, |
| 71 | + fill: "var(--text-1)", |
| 72 | + "font-size": "0.8rem", |
| 73 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 74 | + animation: |
| 75 | + generator.config.animation !== false |
| 76 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 77 | + : "", |
| 78 | + }, |
| 79 | + }), |
| 80 | + new Item("text", { |
| 81 | + content: result.ranking.rating.toFixed(0), |
| 82 | + id: "ext-contest-rating", |
| 83 | + style: { |
| 84 | + transform: `translate(20px, 50px)`, |
| 85 | + fill: "var(--text-0)", |
| 86 | + "font-size": "2rem", |
| 87 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 88 | + animation: |
| 89 | + generator.config.animation !== false |
| 90 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 91 | + : "", |
| 92 | + }, |
| 93 | + }), |
| 94 | + new Item("text", { |
| 95 | + content: "Highest Rating", |
| 96 | + id: "ext-contest-highest-rating-title", |
| 97 | + style: { |
| 98 | + transform: `translate(160px, 20px)`, |
| 99 | + fill: "var(--text-1)", |
| 100 | + "font-size": "0.8rem", |
| 101 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 102 | + animation: |
| 103 | + generator.config.animation !== false |
| 104 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 105 | + : "", |
| 106 | + }, |
| 107 | + }), |
| 108 | + new Item("text", { |
| 109 | + content: max_rating.toFixed(0), |
| 110 | + id: "ext-contest-highest-rating", |
| 111 | + style: { |
| 112 | + transform: `translate(160px, 50px)`, |
| 113 | + fill: "var(--text-0)", |
| 114 | + "font-size": "2rem", |
| 115 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 116 | + animation: |
| 117 | + generator.config.animation !== false |
| 118 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 119 | + : "", |
| 120 | + }, |
| 121 | + }), |
| 122 | + new Item("text", { |
| 123 | + content: |
| 124 | + result.ranking.globalRanking + " / " + result.ranking.totalParticipants, |
| 125 | + id: "ext-contest-ranking", |
| 126 | + style: { |
| 127 | + transform: `translate(${generator.config.width - 20}px, 20px)`, |
| 128 | + "text-anchor": "end", |
| 129 | + fill: "var(--text-1)", |
| 130 | + "font-size": "0.8rem", |
| 131 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 132 | + animation: |
| 133 | + generator.config.animation !== false |
| 134 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 135 | + : "", |
| 136 | + }, |
| 137 | + }), |
| 138 | + new Item("text", { |
| 139 | + content: result.ranking.topPercentage.toFixed(2) + "%", |
| 140 | + id: "ext-contest-percentage", |
| 141 | + style: { |
| 142 | + transform: `translate(${generator.config.width - 20}px, 50px)`, |
| 143 | + "text-anchor": "end", |
| 144 | + fill: "var(--text-0)", |
| 145 | + "font-size": "2rem", |
| 146 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 147 | + animation: |
| 148 | + generator.config.animation !== false |
| 149 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 150 | + : "", |
| 151 | + }, |
| 152 | + }), |
| 153 | + ], |
| 154 | + }); |
| 155 | + |
| 156 | + for (let i = Math.ceil(min_rating / 100) * 100; i < max_rating; i += 100) { |
| 157 | + const y = (max_rating - i) * y_scale; |
| 158 | + const text = new Item("text", { |
| 159 | + content: i.toFixed(0), |
| 160 | + id: "ext-contest-rating-label-" + i, |
| 161 | + style: { |
| 162 | + transform: `translate(45px, ${y + 73.5}px)`, |
| 163 | + "text-anchor": "end", |
| 164 | + fill: "var(--text-2)", |
| 165 | + "font-size": "0.7rem", |
| 166 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 167 | + animation: |
| 168 | + generator.config.animation !== false |
| 169 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 170 | + : "", |
| 171 | + }, |
| 172 | + }); |
| 173 | + const line = new Item("line", { |
| 174 | + attr: { x1: 0, y1: y, x2: width + 20, y2: y }, |
| 175 | + style: { |
| 176 | + stroke: "var(--bg-1)", |
| 177 | + "stroke-width": 1, |
| 178 | + transform: `translate(50px, 70px)`, |
| 179 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 180 | + animation: |
| 181 | + generator.config.animation !== false |
| 182 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 183 | + : "", |
| 184 | + }, |
| 185 | + }); |
| 186 | + extension.children?.push(text, line); |
| 187 | + } |
| 188 | + |
| 189 | + extension.children?.push( |
| 190 | + new Item("polyline", { |
| 191 | + id: "ext-contest-polyline", |
| 192 | + attr: { |
| 193 | + points: points.map(([x, y]) => `${x},${y}`).join(" "), |
| 194 | + }, |
| 195 | + style: { |
| 196 | + transform: `translate(60px, 70px)`, |
| 197 | + stroke: "var(--color-0)", |
| 198 | + "stroke-width": 2, |
| 199 | + "stroke-linecap": "round", |
| 200 | + "stroke-linejoin": "round", |
| 201 | + fill: "none", |
| 202 | + opacity: generator.config.animation !== false ? 0 : 1, |
| 203 | + animation: |
| 204 | + generator.config.animation !== false |
| 205 | + ? "fade_in 1 0.3s 1.7s forwards" |
| 206 | + : "", |
| 207 | + }, |
| 208 | + }), |
| 209 | + ); |
| 210 | + |
| 211 | + body["ext-contest"] = () => extension; |
| 212 | + } |
| 213 | + }; |
| 214 | +} |
0 commit comments