core&ui: add contest balloon list (#578)
Co-authored-by: undefined <i@undefined.moe>pull/642/head^2
parent
8e8c263477
commit
867cd069c5
@ -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 |
@ -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> ({{ 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>
|
Loading…
Reference in New Issue