mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
feat: add xcpcio
This commit is contained in:
8 files changed
+241
-26
No files matched your search
@@ -0,0 +1,138 @@
|
||||
import path from 'path';
|
||||
import {
|
||||
ContestModel, Context, fs, Handler, ObjectId, Schema, STATUS, Types, UserModel,
|
||||
} from 'hydrooj';
|
||||
|
||||
const file = fs.readFileSync(path.join(__dirname, 'public/assets/board.html'), 'utf8');
|
||||
const indexJs = file.match(/index-([A-Za-z0-9_-]+)\.js"/)?.[1];
|
||||
const indexCss = file.match(/index-([A-Za-z0-9_-]+)\.css"/)?.[1];
|
||||
|
||||
class XcpcioHandler extends Handler {
|
||||
async get() {
|
||||
this.response.body = {
|
||||
js: indexJs,
|
||||
css: indexCss,
|
||||
};
|
||||
this.response.template = 'xcpcio_board.html';
|
||||
}
|
||||
}
|
||||
|
||||
const status = {
|
||||
[STATUS.STATUS_WRONG_ANSWER]: 'WRONG_ANSWER',
|
||||
[STATUS.STATUS_ACCEPTED]: 'CORRECT',
|
||||
[STATUS.STATUS_COMPILING]: 'PENDING',
|
||||
[STATUS.STATUS_WAITING]: 'PENDING',
|
||||
[STATUS.STATUS_JUDGING]: 'PENDING',
|
||||
[STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'TIME_LIMIT_EXCEEDED',
|
||||
[STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'MEMORY_LIMIT_EXCEEDED',
|
||||
[STATUS.STATUS_RUNTIME_ERROR]: 'RUNTIME_ERROR',
|
||||
[STATUS.STATUS_SYSTEM_ERROR]: 'SYSTEM_ERROR',
|
||||
[STATUS.STATUS_COMPILE_ERROR]: 'COMPILATION_ERROR',
|
||||
[STATUS.STATUS_FETCHED]: 'PENDING',
|
||||
[STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'OUTPUT_LIMIT_EXCEEDED',
|
||||
[STATUS.STATUS_ETC]: 'SYSTEM_ERROR',
|
||||
[STATUS.STATUS_CANCELED]: 'CANCELED',
|
||||
};
|
||||
|
||||
export async function apply(ctx: Context) {
|
||||
ctx.Route('board_xcpcio', '/board', XcpcioHandler);
|
||||
ctx.inject(['scoreboard'], ({ scoreboard }) => {
|
||||
scoreboard.addView('xcpcio', 'XCPCIO', {
|
||||
tdoc: 'tdoc',
|
||||
groups: 'groups',
|
||||
json: Types.Boolean,
|
||||
gold: Schema.transform(Schema.union([Schema.string(), Schema.number().step(1).min(0)]), (v) => +v).default(0),
|
||||
silver: Schema.transform(Schema.union([Schema.string(), Schema.number().step(1).min(0)]), (v) => +v).default(0),
|
||||
bronze: Schema.transform(Schema.union([Schema.string(), Schema.number().step(1).min(0)]), (v) => +v).default(0),
|
||||
}, {
|
||||
async display({
|
||||
tdoc, groups, json, gold, silver, bronze,
|
||||
}) {
|
||||
if (json || this.request.json) {
|
||||
const tsdocs = await ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc.docId }).toArray();
|
||||
const udict = await UserModel.getList(tdoc.domainId, tsdocs.map((i) => i.uid));
|
||||
const teams = tsdocs.map((i) => {
|
||||
const udoc = udict[i.uid];
|
||||
return {
|
||||
team_id: `${udoc._id}`,
|
||||
name: udoc.uname,
|
||||
organization: udoc.school,
|
||||
members: [],
|
||||
coach: '',
|
||||
group: [
|
||||
...(udoc.group || []),
|
||||
i.unrank ? 'unofficial' : 'official',
|
||||
],
|
||||
};
|
||||
});
|
||||
const relatedGroups = teams.flatMap((i) => i.group);
|
||||
this.response.body = {
|
||||
contest: {
|
||||
contest_name: tdoc.title,
|
||||
start_time: Math.floor(tdoc.beginAt.getTime() / 1000),
|
||||
end_time: Math.floor(tdoc.endAt.getTime() / 1000),
|
||||
frozen_time: tdoc.lockAt ? Math.floor((tdoc.endAt.getTime() - tdoc.lockAt.getTime()) / 1000) : 0,
|
||||
penalty: 1200,
|
||||
problem_quantity: tdoc.pids.length,
|
||||
problem_id: tdoc.pids.map((i, idx) => String.fromCharCode(65 + idx)),
|
||||
group: {
|
||||
official: '正式队伍',
|
||||
unofficial: '打星队伍',
|
||||
...Object.fromEntries(groups.filter((i) => relatedGroups.includes(i.name)).map((i) => [i.name, i.name])),
|
||||
},
|
||||
organization: 'School',
|
||||
status_time_display: {
|
||||
'correct': true,
|
||||
'incorrect': true,
|
||||
'pending': true,
|
||||
},
|
||||
medal: {
|
||||
official: {
|
||||
gold,
|
||||
silver,
|
||||
bronze,
|
||||
},
|
||||
},
|
||||
balloon_color: tdoc.balloon
|
||||
? tdoc.pids.map((i) => ({
|
||||
color: '#000',
|
||||
background_color: typeof tdoc.balloon[i] === 'string' ? tdoc.balloon[i] : tdoc.balloon[i].color,
|
||||
}))
|
||||
: [],
|
||||
logo: {
|
||||
preset: 'ICPC',
|
||||
},
|
||||
// banner: {
|
||||
// url: 'assets/banner.png',
|
||||
// },
|
||||
options: {
|
||||
submission_timestamp_unit: 'millisecond',
|
||||
},
|
||||
},
|
||||
submissions: tsdocs.flatMap((i) => (i.journal || []).map((j) => ({
|
||||
problem_id: tdoc.pids.indexOf(j.pid),
|
||||
team_id: `${i.uid}`,
|
||||
timestamp: Math.floor(new ObjectId(j.rid).getTimestamp().getTime() - tdoc.beginAt.getTime()),
|
||||
status: status[j.status] || 'SYSTEM_ERROR',
|
||||
language: j.lang || '',
|
||||
submission_id: j.rid,
|
||||
}))),
|
||||
teams,
|
||||
};
|
||||
} else {
|
||||
this.response.template = 'xcpcio_board.html';
|
||||
let query = '';
|
||||
if (gold || silver || bronze) {
|
||||
query = `&gold=${gold}&silver=${silver}&bronze=${bronze}`;
|
||||
}
|
||||
this.response.body = {
|
||||
dataSource: `/d/${tdoc.domainId}/contest/${tdoc.docId}/scoreboard/xcpcio?json=true${query}#allInOne=true`,
|
||||
js: indexJs,
|
||||
css: indexCss,
|
||||
};
|
||||
}
|
||||
},
|
||||
supportedRules: ['acm'],
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const dist = path.join(path.dirname(require.resolve('@xcpcio/board-app/package.json')), 'dist');
|
||||
const target = path.join(__dirname, 'public');
|
||||
if (fs.existsSync(target)) {
|
||||
fs.rmSync(target, { recursive: true });
|
||||
}
|
||||
fs.mkdirSync(target, { recursive: true });
|
||||
fs.cpSync(path.join(dist, 'assets'), path.join(target, 'assets'), { recursive: true });
|
||||
fs.cpSync(path.join(dist, 'index.html'), path.join(target, 'assets/board.html'));
|
||||
console.log('Copied board app to', target);
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@hydrooj/scoreboard-xcpcio",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "0.0.1",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"install": "node -r @hydrooj/register install.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xcpcio/board-app": "^0.53.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{% set page_name = 'contest_scoreboard_xcpcio' %}
|
||||
{% extends "layout/basic.html" %}
|
||||
{% block content %}
|
||||
<script>
|
||||
function normalizePath(_) {
|
||||
for (; _.endsWith("/");)_ = _.slice(0, -1);
|
||||
return `${_}/`;
|
||||
}
|
||||
window.CDN_HOST = "/";
|
||||
window.__toAssetUrl = url => window.CDN_HOST + url;
|
||||
window.DATA_HOST = "/";
|
||||
window.DATA_REGION = "CN";
|
||||
window.DEFAULT_LANG = "{{ 'zh-CN' if _('__id').startsWith('zh') else 'en' }}";
|
||||
window.DATA_SOURCE = "{{ dataSource }}";
|
||||
if (document.documentElement.classList.contains("theme--dark")) document.documentElement.classList.toggle("dark", !0);
|
||||
setInterval(() => {
|
||||
const location = window.location.href;
|
||||
if (location.includes("%2F")) {
|
||||
window.history.replaceState(null, "", location.replace(/%2F/g, "/"));
|
||||
}
|
||||
}, 500);
|
||||
</script>
|
||||
<style>
|
||||
#app .nav, .omnibar-toggle { display: none; }
|
||||
table { border-spacing: 2px; }
|
||||
th { text-align: center; }
|
||||
td.unattempted, td.empty { background-color: transparent !important; }
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-{{ js }}.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-{{ css }}.css">
|
||||
<div id="app"></div>
|
||||
<noscript>This website requires JavaScript to function properly. Please enable JavaScript tocontinue.</noscript>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user