add contest_user

pull/472/head
panda 2 years ago
parent d67a0339fd
commit e6ffdaf11e

@ -7,7 +7,7 @@ import {
Counter, sortFiles, streamToBuffer, Time,
} from '@hydrooj/utils/lib/utils';
import {
BadRequestError, ContestNotEndedError, ContestNotFoundError, ContestNotLiveError,
BadRequestError, ContestNotAttendedError, ContestNotEndedError, ContestNotFoundError, ContestNotLiveError,
ContestScoreboardHiddenError, FileLimitExceededError, FileUploadError, ForbiddenError,
InvalidTokenError, NotAssignedError, PermissionError, ValidationError,
} from '../error';
@ -592,6 +592,40 @@ export class ContestFileDownloadHandler extends ContestDetailBaseHandler {
}
}
export class ContestUserHandler extends ContestDetailBaseHandler {
@param('tid', Types.ObjectID)
async get(domainId: string, tid: ObjectID) {
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray();
tsdocs.forEach((i) => {
i.endAt = this.tdoc.duration ? moment(this.tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate() : null;
});
const udict = await user.getListForRender(domainId, [this.tdoc.owner, ...tsdocs.map((i) => i.uid)]);
this.response.body = { tdoc: this.tdoc, tsdocs, udict };
this.response.pjax = 'partials/contest_user.html';
this.response.template = 'contest_user.html';
}
@param('tid', Types.ObjectID)
@param('uids', Types.NumericArray)
@param('unrank', Types.Boolean)
async postAddUser(domainId: string, tid: ObjectID, uids: number[], unrank = false) {
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
await Promise.all(uids.map((uid) => contest.attend(domainId, tid, uid, { unrank })));
this.back();
}
@param('tid', Types.ObjectID)
@param('uid', Types.PositiveInt)
async postRank(domainId: string, tid: ObjectID, uid: number) {
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
const tsdoc = await contest.getStatus(domainId, tid, uid);
if (!tsdoc) throw new ContestNotAttendedError(uid);
await contest.setStatus(domainId, tid, uid, { unrank: !tsdoc.unrank });
this.back();
}
}
export async function apply(ctx) {
ctx.Route('contest_create', '/contest/create', ContestEditHandler);
ctx.Route('contest_main', '/contest', ContestListHandler, PERM.PERM_VIEW_CONTEST);
@ -604,4 +638,5 @@ export async function apply(ctx) {
ctx.Route('contest_code', '/contest/:tid/code', ContestCodeHandler, PERM.PERM_VIEW_CONTEST);
ctx.Route('contest_files', '/contest/:tid/file', ContestFilesHandler, PERM.PERM_VIEW_CONTEST);
ctx.Route('contest_file_download', '/contest/:tid/file/:filename', ContestFileDownloadHandler, PERM.PERM_VIEW_CONTEST);
ctx.Route('contest_user', '/contest/:tid/user', ContestUserHandler, PERM.PERM_VIEW_CONTEST);
}

@ -9,12 +9,12 @@ const ranked: ranked = async (cursor, equ) => {
const results = [];
const docs = await cursor.toArray();
for (const doc of docs) {
count++;
if (!last || !equ(last, doc)) r = count;
if (doc.unrank) {
results.push([0, doc]);
continue;
}
count++;
if (!last || !equ(last, doc)) r = count;
last = doc;
results.push([r, doc]);
}

@ -59,6 +59,7 @@ Accepted Problems: 通过的题目
Accepted Ratio: 通过率
Accepted: 已通过
Access Control: 访问控制
According to the contest rules, you cannot view your submission details at current.: 根据比赛规则,你现在不能查看你的评测记录详情。
Account Settings: 账户设置
Action: 动作
Active Sessions: 活动会话
@ -92,6 +93,7 @@ At least 4 characters: 至少 4 个字符
Attend Contest: 参加比赛
Attend contests: 参加比赛
Attended: 已参加
Attender Manage: 参赛者管理
author: 作者
Auto hide problems during the contest: 在比赛过程中自动隐藏题目
Auto Read Tasks: 自动识别任务
@ -175,6 +177,7 @@ contest_detail_problem_submit: 递交比赛题目
contest_edit: 编辑比赛
contest_main: 比赛
contest_scoreboard: 比赛成绩表
contest_user: 参赛者管理
Contest: 比赛
Continue: 继续
Contributed Problems: 贡献的题目
@ -551,6 +554,7 @@ objective: 客观题
Oh, the user doesn't have any contributions!: 啊哦,这个用户还没贡献过题目~
Oh, the user hasn't created any discussions yet!: 这个用户还没有发布过讨论
Oh, the user hasn't submitted yet!: 这个用户还没有交过题 _(:зゝ∠)_
Oh, there is no submission!: 喔,还没有评测记录呢!
Oh, there is no task in the queue!: 喔,队列中目前没有任务。
Oh, there is no task that matches the filter!: 喔,目前没有符合过滤条件的任务。
Ok: 确定
@ -867,6 +871,7 @@ Unknown field {0}.: 未知字段 {0}。
Unknown: 未知
Unlimited: 无限制
Unordered List: 无序列表
Unrank: 打星
Unstar Topic: 取消收藏
Unstar: 取消收藏
Unsupported configure this type of problem. Please refer to the documentation.: 暂不支持快捷配置此类题目。请参照文档操作。

@ -0,0 +1,97 @@
import $ from 'jquery';
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
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';
const page = new NamedPage('contest_user', () => {
const addUserDialogContent = $(tpl`
<div>
<div class="row"><div class="columns">
<h1>${i18n('Add User')}</h1>
</div></div>
<div class="row"><div class="columns">
<label>
${i18n('Users')}
<input name="add_user_users" type="text" class="textbox" autocomplete="off">
</label>
</div></div>
<div class="row"><div class="columns">
<label>
${i18n('Rank')}
<br />
<label class="checkbox">
<input type="checkbox" name="unrank" class="checkbox">${i18n('UnRank')}
</label>
</label>
</div></div>
</div>
`);
addUserDialogContent.appendTo(document.body);
const userSelect = UserSelectAutoComplete.getOrConstruct<UserSelectAutoComplete<true>>(
addUserDialogContent.find('[name="add_user_users"]'),
{ multi: true, height: 'auto' },
);
const addUserDialog = new ActionDialog({
$body: addUserDialogContent,
onDispatch(action) {
if (action === 'ok' && !userSelect.value()) {
userSelect.focus();
return false;
}
return true;
},
});
addUserDialog.clear = function () {
userSelect.clear();
addUserDialog.$dom.find('[name="unrank"]').prop('checked', false);
return this;
};
async function handleClickAddUser() {
const action = await addUserDialog.clear().open();
if (action !== 'ok') return;
const unrank = addUserDialog.$dom.find('[name="unrank"]').prop('checked');
const uids = userSelect.value();
try {
const res = await request.post('', {
operation: 'add_user',
uids: uids.join(','),
unrank,
});
if (res.url && res.url !== window.location.href) window.location.href = res.url;
else {
Notification.success(i18n('User has been added.'));
pjax.request({ push: false });
}
} catch (error) {
Notification.error([error.message, ...error.params].join(' '));
}
}
async function handleEditRank(ev) {
const uid = $(ev.target).data('uid');
try {
const res = await request.post('', {
operation: 'rank',
uid,
});
if (res.url && res.url !== window.location.href) window.location.href = res.url;
else {
Notification.success(i18n('User Rank has been edited.'));
pjax.request({ push: false });
}
} catch (error) {
Notification.error([error.message, ...error.params].join(' '));
}
}
$('[name="add_user"]').on('click', () => handleClickAddUser());
$(document).on('click', '[name="edit_rank"]', handleEditRank);
});
export default page;

@ -35,11 +35,6 @@
<p>{{ _('The contest is a flexible time contest. You need to complete the contest within a specified time after you attended.') }}</p>
</blockquote>
{% endif %}
{% if tdoc.code %}
<blockquote class="note">
<p>{{ _('The contest is a invitational contest. However it allow users attend contest without the invitation code. Those users will not be ranked.') }}</p>
</blockquote>
{% endif %}
</div>
</div>
</div>

@ -0,0 +1,47 @@
{% extends "layout/basic.html" %}
{% import "components/contest.html" as contest with context %}
{% import "components/record.html" as record with context %}
{% import "components/problem.html" as problem with context %}
{% 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">{{ _('Attender Manage') }}</h1>
<div class="section__tools">
<button class="primary rounded button" name="add_user"><span class="icon icon-search"></span>{{ _('Add User') }}</button>
</div>
</div>
{{ noscript_note.render() }}
<div class="section__body no-padding">
<table class="data-table">
<colgroup>
<col class="col--uid">
<col class="col--user">
<col class="col--start-time">
<col class="col--end-time">
<col class="col--rank">
<col class="col--actions">
</colgroup>
<thead>
<tr>
<th class="col--uid">{{ _('User ID') }}</th>
<th class="col--user">{{ _('Username') }}</th>
<td class="col--start-time">{{ _('Begin Time') }}</th>
<th class="col--end-time">{{ _('End Time') }}</th>
<th class="col--rank">{{ _('Rank') }}</th>
<th class="col--actions">{{ _('Actions') }}</th>
</tr>
</thead>
{% include "partials/contest_user.html" %}
</table>
</div>
</div>
</div>
<div class="medium-3 columns">
{% set owner_udoc = udict[tdoc.owner] %}
{% include "partials/contest_sidebar.html" %}
</div>
</div>
{% endblock %}

@ -75,6 +75,9 @@
<li class="menu__item"><a class="menu__link" href="{{ url('contest_files', tid=tdoc.docId) }}">
<span class="icon icon-file"></span> {{ _('Files') }}
</a></li>
<li class="menu__item"><a class="menu__link" href="{{ url('contest_user', tid=tdoc.docId) }}">
<span class="icon icon-user"></span> {{ _('Attender Manage') }}
</a></li>
{% endif %}
{% if handler.user.own(tdoc) or handler.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE) or handler.user.hasPerm(perm.PERM_READ_RECORD_CODE) %}
{% if model.contest.canShowRecord.call(handler, tdoc, true) %}

@ -0,0 +1,16 @@
{% import "components/user.html" as user with context %}
{% import "components/contest.html" as contest with context %}
<tbody data-fragment-id="constest_user-tbody">
{%- for tsdoc in tsdocs -%}
<tr data-uid="{{ tsdoc.uid }}">
<td class="col--uid">{{ tsdoc.uid }}</td>
<td class="col--user">{{ user.render_inline(udict[tsdoc.uid], badge=false) }}</td>
<td class="col--start-time">{{ contest.render_time(tsdoc.startAt or tdoc.beginAt) }}</td>
<td class="col--end-time">{{ contest.render_time(tsdoc.endAt or tdoc.endAt) }}</td>
<td class="col--rank">{{ _('UnRank') if tsdoc.unrank else _('Rank') }}</td>
<td class="col--actions">
<a name="edit_rank" data-uid="{{ tsdoc.uid }}">{{ _('Rank') if tsdoc.unrank else _('UnRank') }}</a>
</td>
</tr>
{%- endfor -%}
</tbody>
Loading…
Cancel
Save