core&ui: add contest balloon list (#578)

Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
pandaandundefined authored and GitHub committed 2023-11-05 02:51:25 +08:00
1 parent 8e8c263477
commit 867cd069c5
15 files changed
+341 -8

No files matched your search

+1 -1
View File
@@ -201,7 +201,7 @@ export default function (env: { watch?: boolean, production?: boolean, measure?:
name(module) {
const packageName = module.context.replace(/\\/g, '/').split('node_modules/').pop().split('/')[0];
if (packageName === 'monaco-editor-nls') {
return `i.monaco.${module.userRequest.split('/').pop().split('.')[0]}`;
return `i.monaco.${module.userRequest.replace(/\\/g, '/').split('/').pop().split('.')[0]}`;
}
return `n.${packageName.replace('@', '')}`;
},
+8
View File
@@ -109,6 +109,8 @@ Auto configure: 自动配置
Auto detect: 自动检测
Auto hide problems during the contest: 在比赛过程中自动隐藏题目
Auto Read Tasks: 自动识别任务
Awards: 奖项
Balloon Status: 气球状态
Basic Info: 基础信息
Basic: 基础
Be Copied: 被复制
@@ -159,6 +161,7 @@ Code language: 代码语言
Code: 代码
codeFontFamily: 代码字体
collapse: 收缩
Color: 颜色
Comment: 评论
Comments: 评论
CommonMark Syntax: CommonMark 语法
@@ -374,6 +377,8 @@ File: 文件
Filename: 文件名
Files: 文件
Filter: 过滤
First of Contest: 全场一血
First of Problem: 题目一血
fontFamily: 字体
Footer: 页脚
Footnote: 脚注
@@ -636,6 +641,7 @@ Please follow the instructions on your device to complete the verification.: 请
Please select at least one file to perform this operation.: 请选择至少一个文件来执行此操作。
Please select at least one role to perform this operation.: 请选择至少一个角色来进行操作。
Please select at least one user to perform this operation.: 请选择至少一个用户来进行操作。
Please set the balloon color for each problem first.: 请先为每道题设置气球颜色。
Please wait until contest host unfreeze the scoreboard.: 请等待比赛主办方解除封榜。
Preference Settings: 偏好设置
preferredPrefix_hint: 此选项用于重排题号。例如,若题目包中所给的题号分别是 P1001, P1002, P1003,而此选项填写了 T,则导入后三道题的题号分别为 T1001, T1002 和 T1003。
@@ -776,6 +782,7 @@ Selected files have been deleted.: 所选文件已被删除。
Selected problems have been deleted.: 所选题目已被删除。
Selected roles have been deleted.: 所选角色已删除。
Selected users have been removed from the domain.: 所选用户已从此域中移除。
Send By: 递送者
Send Code after acceptance: 通过题目后发送源代码
Send Message: 发送站内信息
Send Password Reset Email: 发送密码重置邮件
@@ -783,6 +790,7 @@ Send Verification Email: 发送验证邮件
Send: 发送
Seperated with ',': 用 ',' 分隔
Service Status: 服务状态
Set Color: 设置颜色
Set Privilege: 设置权限
Set Role: 设置角色
Set Roles for Selected User: 设置所选用户的角色
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><path d="M763.672 600.137c63.965-90.528 90.996-198.496 76.172-304.063C813.402 108.27 639.719-22.539 451.914 3.906c-187.5 26.348-318.633 200.352-292.285 387.852 14.785 105.254 70.547 201.484 156.973 270.976 48.007 38.438 102.168 65.43 153.554 77.5 8.067 1.938 13.215 9.848 11.719 18.008l-9.512 51.524a31.817 31.817 0 0 0 2.89 20.644 31.247 31.247 0 0 0 27.735 16.817 29.546 29.546 0 0 0 4.356-.313l34.785-4.883a3.905 3.905 0 0 1 4.082 2.227C578.867 915.176 635 968.965 706.66 997.305a31.251 31.251 0 0 0 41.016-18.848c5.526-15.977-3.204-33.34-18.907-39.61A231.571 231.571 0 0 1 612.95 837.52a3.907 3.907 0 0 1 2.831-5.86l14.453-1.953a32.32 32.32 0 0 0 19.688-10.215 31.25 31.25 0 0 0 4.668-34.765l-23.555-47.325c-3.719-7.468-.914-16.539 6.367-20.605 46.075-25.723 90.723-66.406 126.27-116.66Zm-302.54 32.773c-3.933 0-7.827-.742-11.484-2.187-80.586-31.875-149.023-102.93-178.613-185.43a31.242 31.242 0 0 1 5.367-31.004 31.25 31.25 0 0 1 53.461 9.91c28.32 78.985 92.325 128.457 142.774 148.438 13.957 5.515 22.023 20.176 19.21 34.918-2.812 14.742-15.706 25.402-30.714 25.394Zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -84,6 +84,7 @@
"qrcode": "^1.5.3",
"queue-microtask": "^1.2.3",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
@@ -195,3 +195,16 @@ $highlight-button-color = #F6DF45
.page--contest_user
.col--uid
width: rem(100px)
.page--contest_balloon
.col--bid
width: rem(100px)
.col--status
width: rem(100px)
.col--problem
width: rem(150px)
.col--awards
width: rem(130px)
@@ -0,0 +1,123 @@
import $ from 'jquery';
import yaml from 'js-yaml';
import React from 'react';
import { HexColorInput, HexColorPicker } from 'react-colorful';
import { createRoot } from 'react-dom/client';
import { ActionDialog } from 'vj/components/dialog';
import Notification from 'vj/components/notification';
import { NamedPage } from 'vj/misc/Page';
import {
i18n, pjax, request, tpl,
} from 'vj/utils';
function Balloon({ tdoc, val }) {
const [color, setColor] = React.useState('');
const [now, setNow] = React.useState('');
return (
<div className="row">
<div className="medium-12 columns">
<table className="data-table">
<thead>
<tr>
<th>{i18n('Problem')}</th>
<th>{i18n('Color')}</th>
<th>{i18n('Name')}</th>
<th><span className="icon icon-wrench"></span></th>
</tr>
</thead>
<tbody>
{tdoc.pids.map((pid) => {
const { color: c, name } = val[+pid];
return (
<tr key={pid}>
<td>
{now === pid
? (<b>{String.fromCharCode(65 + tdoc.pids.indexOf(+pid))}</b>)
: (<span>{String.fromCharCode(65 + tdoc.pids.indexOf(+pid))}</span>)}
</td>
<td>
<HexColorInput
className='textbox'
color={c}
onFocus={() => { setNow(pid); setColor(c); }}
onChange={(e) => { val[+pid].color = e; setColor(e); }}
/>
</td>
<td>
<input
type="text"
className="textbox"
defaultValue={name}
onFocus={() => { setNow(pid); setColor(c); }}
onChange={(e) => { val[+pid].name = e.target.value; }}
/>
</td>
{tdoc.pids.indexOf(+pid) === 0 && <td rowSpan={0}>
{now && <HexColorPicker color={color} onChange={(e) => { val[+now].color = e; setColor(e); }} style={{ padding: '1rem' }} />}
</td>}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
async function handleSetColor(tdoc) {
let val = tdoc.balloon;
if (!val) {
val = {};
for (const pid of tdoc.pids) val[+pid] = { color: '#ffffff', name: '' };
}
Notification.info(i18n('Loading...'));
const promise = new ActionDialog({
$body: tpl`
<div className="row"><div className="columns">
<h1>${i18n('Set Color')}</h1>
</div></div>
<div id="balloon"></div>`,
}).open();
createRoot($('#balloon').get(0)).render(
<Balloon tdoc={tdoc} val={val} />,
);
const action = await promise;
if (action !== 'ok') return;
Notification.info(i18n('Updating...'));
try {
await request.post('', { operation: 'set_color', color: yaml.dump(val) });
} catch (e) {
Notification.error(`${e.message} ${e.params?.[0]}`);
}
Notification.info(i18n('Successfully updated.'));
pjax.request({ url: '', push: false });
}
const page = new NamedPage('contest_balloon', () => {
const { tdoc } = UiContext;
const beginAt = new Date(tdoc.beginAt).getTime();
const endAt = new Date(tdoc.endAt).getTime();
function update() {
const now = Date.now();
if (beginAt <= now && now <= endAt) pjax.request({ url: '', push: false });
}
$('[name="set_color"]').on('click', () => handleSetColor(tdoc));
setInterval(update, 60000);
$(document).on('click', '[value="done"]', async (ev) => {
ev.preventDefault();
const balloon = $(ev.currentTarget).data('balloon');
try {
await request.post('', { balloon, operation: 'done' });
} catch (e) {
Notification.error(`${e.message} ${e.params?.[0]}`);
}
Notification.info(i18n('Successfully updated.'));
pjax.request({ url: '', push: false });
});
});
export default page;
@@ -0,0 +1,48 @@
{% import "components/nothing.html" as nothing with context %}
{% extends "layout/basic.html" %}
{% block content %}
{{ set(UiContext, 'tdoc', tdoc) }}
<div class="row">
<div class="medium-9 columns">
<div class="section">
<div class="section__header">
<h1 class="section__title">{{ _('Balloon Status') }}</h1>
<div class="section__tools">
<button class="primary rounded button" name="set_color">{{ _('Set Color') }}</button>
</div>
</div>
{{ noscript_note.render() }}
<div class="section__body no-padding">
{% if not tdoc.balloon|length %}
{{ nothing.render('Please set the balloon color for each problem first.') }}
{% else %}
<table class="data-table record_main__table">
<colgroup>
<col class="col--status">
<col class="col--bid">
<col class="col--problem">
<col class="col--submit-by">
<col class="col--send-by">
<col class="col--awards">
</colgroup>
<thead>
<tr>
<th class="col--status record-status--border">{{ _('Status') }}</th>
<th class="col--bid">{{ _('#') }}</th>
<th class="col--problem">{{ _('Problem') }}</th>
<th class="col--submit-by">{{ _('Submit By') }}</th>
<th class="col--send-by">{{ _('Send By') }}</th>
<th class="col--awards">{{ _('Awards') }}</th>
</tr>
</thead>
{% include "partials/contest_balloon.html" %}
</table>
{% endif %}
</div>
</div>
</div>
<div class="medium-3 columns">
{% include 'partials/contest_sidebar_management.html' %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,37 @@
{% import "components/user.html" as user with context %}
{% import "components/contest.html" as contest with context %}
<tbody data-fragment-id="constest_balloon-tbody">
{%- for bdoc in bdocs -%}
<tr data-bid="{{ bdoc.bid }}">
{% if bdoc.sent %}
<td class="col--status record-status--border pass">
<span class="icon record-status--icon pass"></span>
<span class="record-status--text pass">{{ _('Sent') }}</span>
</td>
{% else %}
<td class="col--status record-status--border pending">
<span class="icon record-status--icon pending"></span>
<span class="record-status--text pending">{{ _('Waiting') }}</span>
</td>
{% endif %}
<td class="col--bid">{{ bdoc._id.toHexString()|truncate(8,true,'') }}</td>
<td class="col--problem col--problem-name" style="color:{{ tdoc.balloon[bdoc.pid].color }}">
{% if not bdoc.sent %}
<form class="form--inline" method="post">
<input type="hidden" name="balloon" value="{{ bdoc._id.toHexString() }}">
<button type="submit" name="operation" value="done" class="link text-maroon lighter" data-balloon="{{ bdoc._id.toHexString() }}">
<span class="icon icon-send"></span>
{{ _('Send') }}
</button>
</form>
{% endif %}
<b>{{ String.fromCharCode(65+tdoc.pids.indexOf(bdoc.pid)) }}</b>&nbsp;&nbsp;({{ tdoc.balloon[bdoc.pid].name }})
</td>
<td class="col--submit-by" data-tooltip="{{ bdoc._id.getTimestamp().format() }}">{{ user.render_inline(udict[bdoc.uid], badge=false) }}</td>
<td class="col--send-by"{% if bdoc.sent %} data-tooltip="{{ bdoc.sentAt.format() }}"{% endif %}>
{% if bdoc.sent %}{{ user.render_inline(udict[bdoc.sent], avatar=false, badge=false) }}{% else %}-{% endif %}
</td>
<td class="col--awards">{% if bdoc.first %}{{ _('First of Problem') }}{% endif %}</td>
</tr>
{%- endfor -%}
</tbody>
@@ -26,6 +26,9 @@
<li class="menu__item"><a class="menu__link" href="{{ url('record_main', query={tid:tdoc.docId}) }}">
<span class="icon icon-flag"></span> {{ _('All Submissions') }}
</a></li>
<li class="menu__item"><a class="menu__link" href="{{ url('contest_balloon', tid=tdoc.docId) }}">
<span class="icon icon-balloon"></span> {{ _('Balloon Status') }}
</a></li>
<li class="menu__seperator"></li>
</ol>
</div>