diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-05 04:08:22 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-05 04:08:22 +0900 |
| commit | fbd4f2129ce8fe106391302896dd86e05b2f331b (patch) | |
| tree | 3be97d86c89a4a8a497e1b8a2d7d032eb153438e /assets | |
| parent | ca3f1c4b0bd08ab6e836923601cf699d1fd894fb (diff) | |
| download | phperkaigi-2024-albatross-archive-fbd4f2129ce8fe106391302896dd86e05b2f331b.tar.gz phperkaigi-2024-albatross-archive-fbd4f2129ce8fe106391302896dd86e05b2f331b.tar.zst phperkaigi-2024-albatross-archive-fbd4f2129ce8fe106391302896dd86e05b2f331b.zip | |
add files
Diffstat (limited to 'assets')
| -rw-r--r-- | assets/chart.js | 146 | ||||
| -rw-r--r-- | assets/favicon.svg | 21 | ||||
| -rw-r--r-- | assets/index.js | 13 | ||||
| -rw-r--r-- | assets/loading.js | 60 |
4 files changed, 240 insertions, 0 deletions
diff --git a/assets/chart.js b/assets/chart.js new file mode 100644 index 0000000..9ff08e4 --- /dev/null +++ b/assets/chart.js @@ -0,0 +1,146 @@ +import { + Chart, + Colors, + LineController, + LineElement, + LinearScale, + PointElement, + TimeScale, + Tooltip, +} from 'chart.js' +import 'chartjs-adapter-date-fns'; + +Chart.register( + Colors, + LineController, + LineElement, + LinearScale, + PointElement, + TimeScale, + Tooltip, +); + +document.addEventListener('DOMContentLoaded', async () => { + const chartCanvas = document.getElementById('chart'); + const quizId = chartCanvas.dataset.quizId; + + const apiUrl = `${process.env.ALBATROSS_BASE_PATH}/api/quizzes/${quizId}/chart`; + const apiResult = await fetch(apiUrl).then(res => res.json()); + if (apiResult.error) { + return; + } + const stats = apiResult.stats; + + // Filter best scores. + for (const s of stats) { + const bestScores = []; + for (const score of s.scores) { + if (bestScores.length === 0 || bestScores[bestScores.length - 1].code_size > score.code_size) { + bestScores.push(score); + } + } + s.scores = bestScores; + } + + const scoresInChronologicalOrder = stats + .flatMap(s => s.scores.map(score => ({ ...score, user: s.user }))) + .toSorted((a, b) => a.submitted_at - b.submitted_at); + + const scoresAndRanksAtEachTime = (() => { + const result = []; + const currentScoresForUser = new Map(); + for (const { user, submitted_at, code_size } of scoresInChronologicalOrder) { + currentScoresForUser.set(user.name, { user, submitted_at, code_size }); + const ranking = currentScoresForUser + .values() + .toArray() + .toSorted( + (a, b) => a.code_size === b.code_size ? a.submitted_at - b.submitted_at : a.code_size - b.code_size, + ); + const scores = new Map(); + for (const [i, { user, code_size }] of ranking.entries()) { + scores.set(user.name, { + user, + code_size, + rank: i + 1, + }); + } + result.push({ submitted_at: submitted_at, scores }); + } + return result; + })(); + + const rankingHistory = (() => { + const result = new Map(); + for (const { submitted_at, scores } of scoresAndRanksAtEachTime) { + for (const [username, { user, code_size, rank }] of scores.entries()) { + if (!result.has(username)) { + result.set(username, []); + } + const scores = result.get(username); + scores.push({ user, code_size, rank, submitted_at }); + } + } + return result + .values() + .toArray() + .toSorted((a, b) => { + const finalRankA = a[a.length - 1].rank; + const finalRankB = b[b.length - 1].rank; + return finalRankA - finalRankB; + }); + })(); + + new Chart( + chartCanvas, + { + type: 'line', + data: { + datasets: rankingHistory.map(s => ({ + label: `${s[0].user.name}${s[0].user.is_admin ? ' (staff)' : ''}`, + data: s.map(row => ({ x: row.submitted_at * 1000, y: row.rank, code_size: row.code_size })), + })) + }, + options: { + scales: { + x: { + type: 'time', + time: { + parsing: false, + display: false, + unit: 'day', + tooltipFormat: 'yyyy-MM-dd HH:mm:ss', + displayFormats: { + day: 'yyyy-MM-dd', + }, + }, + title: { + display: true, + text: '日時', + }, + }, + y: { + title: { + display: true, + text: '順位', + }, + reverse: true, + min: 1, + offset: true, + }, + }, + plugins: { + tooltip: { + callbacks: { + label: (context) => { + const label = context.dataset.label; + const code_size = context.raw.code_size; + return `${label} (${code_size} byte)`; + }, + }, + }, + }, + }, + }, + ); +}); diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000..931fc7b --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,21 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="130" height="130" viewBox="0 0 130 130">
+ <defs>
+ <style>
+ .cls-1 {
+ fill: none;
+ }
+ .cls-2 {
+ fill: #e60082;
+ }
+ @media (prefers-color-scheme: dark) {
+ .cls-2 {
+ fill: #fff;
+ }
+ }
+ </style>
+ </defs>
+ <g transform="translate(627 -2695)">
+ <rect class="cls-1" width="130" height="130" rx="5" transform="translate(-627 2695)"/>
+ <path id="ttl_logo" class="cls-2" d="M109.44,46.755c-.044-.422-.184-1.635-.229-2.031a33.3,33.3,0,0,0-2.493-8.4C99.059,19.619,80.376,18.662,64.9,23.4A81.749,81.749,0,0,0,52.789,28.1a4.476,4.476,0,0,1-6.2-2.578C44.424,19.075,44.146,10.741,45.347,0,41.894,30.908,30.908,39.686,0,36.231c8.49.949,15.308,2.466,20.663,4.808a7.064,7.064,0,0,1,2.713,10.813q-1.432,1.849-2.762,3.781C7.53,74.662,3.576,99.531,9.39,121.736c.008-.4.02-1.02.047-1.771.194-5.34,1.6-39.572,25.766-63.228,1.965,6.352,2.186,14.475,1.029,24.842C39.686,50.67,50.669,41.894,81.578,45.347c-8.792-.983-15.791-2.576-21.228-5.062A70.955,70.955,0,0,1,72.492,36.52a37.193,37.193,0,0,1,8.647-.852,18.06,18.06,0,0,1,7.23,1.463c3.974,1.807,5.926,5.8,6.8,9.936.028.208.131.893.164,1.1.081.4.074,1.13.134,1.54.557,15.945-13.589,39.72-29.5,44.04A21.9,21.9,0,0,1,50.882,92.5a15.726,15.726,0,0,1-7.218-8.4,10.8,10.8,0,0,1-.124-7.89l-.612.876c-2.992,4.969-1.251,11.5,1.872,16.031,5.132,7.174,15.394,9.676,23.777,8.412,21.848-3.465,40.913-30.683,40.983-52.124-.029-.775-.039-1.894-.12-2.654" transform="translate(-617.883 2701.132)"/>
+ </g>
+</svg>
diff --git a/assets/index.js b/assets/index.js new file mode 100644 index 0000000..1125516 --- /dev/null +++ b/assets/index.js @@ -0,0 +1,13 @@ +import hljs from 'highlight.js/lib/core'; +import php from 'highlight.js/lib/languages/php'; +import plaintext from 'highlight.js/lib/languages/plaintext'; +import 'highlight.js/styles/github.css'; +import 'bootstrap/dist/css/bootstrap.css'; + +document.addEventListener('DOMContentLoaded', () => { + hljs.registerLanguage('php', php); + hljs.registerLanguage('plaintext', plaintext); + hljs.highlightAll(); +}); + +console.log(`#Hooray!Albatross!`); diff --git a/assets/loading.js b/assets/loading.js new file mode 100644 index 0000000..49d8b48 --- /dev/null +++ b/assets/loading.js @@ -0,0 +1,60 @@ +document.addEventListener('DOMContentLoaded', () => { + const phperTokenElem = document.getElementsByClassName('js-phper-token')[0]; + + const aggregatedStatusElem = document.getElementsByClassName('js-aggregated-execution-status')[0]; + const aggregatedStatusLoadingIndicatorElem = document.getElementsByClassName('js-aggregated-execution-status-loading-indicator')[0]; + const answerId = aggregatedStatusElem.dataset.answerId; + + const getElemsMap = cls => new Map( + Array.from(document.getElementsByClassName(cls) ?? []) + .map(e => [parseInt(e.dataset.testcaseExecutionId), e]) + ); + const statusElemsMap = getElemsMap('js-testcase-execution-status'); + const statusLoadingIndicatorElemsMap = getElemsMap('js-testcase-execution-status-loading-indicator'); + const stdoutElemsMap = getElemsMap('js-testcase-execution-stdout'); + const stderrElemsMap = getElemsMap('js-testcase-execution-stderr'); + + if (!aggregatedStatusLoadingIndicatorElem) { + return; + } + + const apiUrl = `${process.env.ALBATROSS_BASE_PATH}/api/answers/${answerId}/statuses`; + + let timerId; + timerId = setInterval(() => { + fetch(apiUrl) + .then(response => response.json()) + .then(({ aggregated_status, testcase_executions, phper_token }) => { + if (phper_token) { + phperTokenElem.innerHTML = `<div class="alert alert-success">バーディー! ${phper_token}</div>`; + } + + for (const ex of testcase_executions) { + const statusElem = statusElemsMap.get(ex.id); + const loadingIndicatorElem = statusLoadingIndicatorElemsMap.get(ex.id); + const stdoutElem = stdoutElemsMap.get(ex.id); + const stderrElem = stderrElemsMap.get(ex.id); + + const { status, stdout, stderr } = ex; + if (status.label === statusElem.textContent) { + continue; + } + statusElem.textContent = status.label; + stdoutElem.textContent = stdout; + stderrElem.textContent = stderr; + if (loadingIndicatorElem && !status.show_loading_indicator) { + loadingIndicatorElem.remove(); + } + } + + if (aggregated_status.label === aggregatedStatusElem.textContent) { + return; + } + aggregatedStatusElem.textContent = aggregated_status.label; + if (!aggregated_status.show_loading_indicator) { + aggregatedStatusLoadingIndicatorElem.remove(); + clearInterval(timerId); + } + }); + }, 5 * 1000); +}); |
