mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
core: fix contest paginator
This commit is contained in:
1 parent
0d7c53b927
commit
2a3933de1a
14 files changed
+46
-40
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.23.31",
|
||||
"version": "2.24.0",
|
||||
"bin": "bin/hydrooj.js",
|
||||
"main": "dist/loader.js",
|
||||
"typings": "dist/loader.d.ts",
|
||||
|
||||
@@ -136,9 +136,9 @@ class ContestBoardcastHandler extends Handler {
|
||||
|
||||
class ContestScoreboardHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('page', Types.PositiveInt)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
const [tdoc, rows, udict] = await contest.getScoreboard(domainId, tid, false, page);
|
||||
const [tdoc, rows, udict, , nPages] = await contest.getScoreboard.call(this, domainId, tid, false, page);
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['contest_main', 'contest_main'],
|
||||
@@ -147,7 +147,7 @@ class ContestScoreboardHandler extends Handler {
|
||||
];
|
||||
this.response.template = 'contest_scoreboard.html';
|
||||
this.response.body = {
|
||||
tdoc, rows, path, udict,
|
||||
tdoc, rows, path, udict, nPages, page,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -156,11 +156,12 @@ class ContestScoreboardDownloadHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('ext', Types.Range(['csv', 'html']))
|
||||
async get(domainId: string, tid: ObjectID, ext: string) {
|
||||
await this.limitRate('scoreboard_download', 120, 3);
|
||||
const getContent = {
|
||||
csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
|
||||
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
|
||||
};
|
||||
const [tdoc, rows] = await contest.getScoreboard(domainId, tid, true, 0);
|
||||
const [tdoc, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0);
|
||||
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,8 +356,9 @@ class HomeworkScoreboardHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
const [tdoc, rows, udict] = await contest.getScoreboard(
|
||||
domainId, tid, false, page, document.TYPE_HOMEWORK,
|
||||
const [tdoc, rows, udict, , nPages] = await contest.getScoreboard.call(
|
||||
this, domainId, tid, false,
|
||||
page, document.TYPE_HOMEWORK,
|
||||
);
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
@@ -367,7 +368,7 @@ class HomeworkScoreboardHandler extends Handler {
|
||||
];
|
||||
this.response.template = 'contest_scoreboard.html';
|
||||
this.response.body = {
|
||||
tdoc, rows, path, udict, page_name: 'homework_scoreboard',
|
||||
tdoc, rows, path, udict, page, nPages, page_name: 'homework_scoreboard',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -376,12 +377,13 @@ class HomeworkScoreboardDownloadHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('ext', Types.Name)
|
||||
async get(domainId: string, tid: ObjectID, ext: string) {
|
||||
await this.limitRate('scoreboard_download', 120, 3);
|
||||
const getContent = {
|
||||
csv: (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
|
||||
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
|
||||
};
|
||||
if (!getContent[ext]) throw new ValidationError('ext');
|
||||
const [tdoc, rows] = await contest.getScoreboard(domainId, tid, true, 0, document.TYPE_HOMEWORK);
|
||||
const [tdoc, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0, document.TYPE_HOMEWORK);
|
||||
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ export interface ContestRule {
|
||||
scoreboard: (
|
||||
isExport: boolean, _: (s: string) => string,
|
||||
tdoc: Tdoc<30 | 60>, pdict: Pdict, cursor: Cursor<any>, page: number,
|
||||
) => Promise<[board: ScoreboardRow[], udict: Udict]>;
|
||||
) => Promise<[board: ScoreboardRow[], udict: Udict, nPages: number]>;
|
||||
ranked: (tdoc: Tdoc<30 | 60>, cursor: Cursor<any>) => Promise<any[]>;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,24 +3,25 @@ import paginate from './paginate';
|
||||
|
||||
type G<T> = Promise<[[number, T], () => G<T>] | []>;
|
||||
interface ranked {
|
||||
<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean, page: number): Promise<[number, T][]>;
|
||||
<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean, page: number): Promise<[[number, T][], number]>;
|
||||
iterate<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean): G<T>;
|
||||
all<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean): Promise<[number, T][]>;
|
||||
all<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean): Promise<[[number, T][], 1]>;
|
||||
}
|
||||
|
||||
const ranked: ranked = async function rankedPage(cursor, equ = (a, b) => a === b, page) {
|
||||
if (!page) return await ranked.all(cursor, equ);
|
||||
let last = null;
|
||||
let r = (page - 1) * 100;
|
||||
let count = r;
|
||||
const results = [];
|
||||
const [docs] = await paginate(cursor, page, 100);
|
||||
const [docs, nPages] = await paginate(cursor, page, 100);
|
||||
for (const doc of docs) {
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
if (!last || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
results.push([r, doc]);
|
||||
}
|
||||
return results;
|
||||
return [results, nPages];
|
||||
};
|
||||
ranked.iterate = async function rankedIterate<T>(cursor: Cursor<T>, equ = (a, b) => a === b) {
|
||||
let last = null;
|
||||
@@ -30,7 +31,7 @@ ranked.iterate = async function rankedIterate<T>(cursor: Cursor<T>, equ = (a, b)
|
||||
if (!await cursor.hasNext()) return [];
|
||||
const doc = await cursor.next();
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
if (!last || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
return [[r, doc], getNext];
|
||||
}
|
||||
@@ -44,11 +45,11 @@ ranked.all = async function rankedAll(cursor, equ = (a, b) => a === b) {
|
||||
const docs = await cursor.toArray();
|
||||
for (const doc of docs) {
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
if (!last || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
results.push([r, doc]);
|
||||
}
|
||||
return results;
|
||||
return [results, 1];
|
||||
};
|
||||
|
||||
global.Hydro.lib.rank = ranked;
|
||||
|
||||
@@ -53,7 +53,7 @@ const acm: ContestRule = {
|
||||
return { accept, time, detail };
|
||||
},
|
||||
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
|
||||
const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score, page);
|
||||
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page);
|
||||
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
|
||||
const udict = await user.getList(tdoc.domainId, uids);
|
||||
const columns: ScoreboardRow = [
|
||||
@@ -139,7 +139,7 @@ const acm: ContestRule = {
|
||||
row.raw = tsdoc;
|
||||
rows.push(row);
|
||||
}
|
||||
return [rows, udict];
|
||||
return [rows, udict, nPages];
|
||||
},
|
||||
async ranked(tdoc, cursor) {
|
||||
return await ranked.all(cursor, (a, b) => a.score === b.score);
|
||||
@@ -162,7 +162,7 @@ const oi: ContestRule = {
|
||||
showScoreboard: (tdoc, now) => now > tdoc.endAt,
|
||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
|
||||
const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score, page);
|
||||
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page);
|
||||
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
|
||||
const udict = await user.getList(tdoc.domainId, uids);
|
||||
const columns: ScoreboardNode[] = [
|
||||
@@ -205,7 +205,7 @@ const oi: ContestRule = {
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return [rows, udict];
|
||||
return [rows, udict, nPages];
|
||||
},
|
||||
async ranked(tdoc, cursor) {
|
||||
return await ranked.all(cursor, (a, b) => a.score === b.score);
|
||||
@@ -268,7 +268,7 @@ const homework: ContestRule = {
|
||||
showScoreboard: () => true,
|
||||
showRecord: () => true,
|
||||
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
|
||||
const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score, page);
|
||||
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page);
|
||||
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
|
||||
const udict = await user.getList.call(this, tdoc.domainId, uids);
|
||||
const columns: ScoreboardNode[] = [
|
||||
@@ -358,7 +358,7 @@ const homework: ContestRule = {
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return [rows, udict];
|
||||
return [rows, udict, nPages];
|
||||
},
|
||||
async ranked(tdoc, cursor) {
|
||||
return await ranked.all(cursor, (a, b) => a.score === b.score);
|
||||
@@ -564,17 +564,17 @@ export function canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true)
|
||||
export async function getScoreboard(
|
||||
domainId: string, tid: ObjectID,
|
||||
isExport = false, page: number, docType: 30 | 60 = document.TYPE_CONTEST,
|
||||
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, Pdict]> {
|
||||
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, Pdict, number]> {
|
||||
const tdoc = await get(domainId, tid, docType);
|
||||
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
if (!this.canShowRecord(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
if (!canShowRecord.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort);
|
||||
const pdict = await problem.getList(domainId, tdoc.pids, true);
|
||||
const [rows, udict] = await RULES[tdoc.rule].scoreboard(
|
||||
const [rows, udict, nPages] = await RULES[tdoc.rule].scoreboard(
|
||||
isExport, this.translate.bind(this),
|
||||
tdoc, pdict, tsdocs, page,
|
||||
);
|
||||
return [tdoc, rows, udict, pdict];
|
||||
return [tdoc, rows, udict, pdict, nPages];
|
||||
}
|
||||
|
||||
export async function verifyProblems(domainId: string, pids: Array<number | string>) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as bus from '../service/bus';
|
||||
|
||||
const coll: Collection<Udoc> = db.collection('user');
|
||||
const logger = new Logger('model/user');
|
||||
const cache = new LRU<string, User>(500);
|
||||
const cache = new LRU<string, User>({ max: 500, maxAge: 300 * 1000 });
|
||||
|
||||
function deleteUserCache(udoc: User | Udoc) {
|
||||
if (udoc) {
|
||||
|
||||
@@ -468,7 +468,7 @@ export class Handler extends HandlerCommon {
|
||||
this.response.body = data;
|
||||
this.response.template = null;
|
||||
this.response.type = 'application/octet-stream';
|
||||
if (name) this.response.disposition = `attachment; filename="${name}"`;
|
||||
if (name) this.response.disposition = `attachment; filename="${encodeURIComponent(name)}"`;
|
||||
}
|
||||
|
||||
async getSession() {
|
||||
@@ -567,7 +567,7 @@ export class Handler extends HandlerCommon {
|
||||
}
|
||||
|
||||
async putResponse() {
|
||||
if (this.response.disposition) this.ctx.set('Content-Disposition', encodeURIComponent(this.response.disposition));
|
||||
if (this.response.disposition) this.ctx.set('Content-Disposition', this.response.disposition);
|
||||
if (!this.response.body) return;
|
||||
if (this.response.redirect && !this.request.json) {
|
||||
this.ctx.response.type = 'application/octet-stream';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hydrooj/ui-default",
|
||||
"version": "4.6.22",
|
||||
"version": "4.6.23",
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "hydro.js",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||
{% if psdict[pid] and psdict[pid].rid %}
|
||||
{% set rdoc = rdict[psdict[pid].rid] %}
|
||||
{% if handler.canShowRecord(tdoc) %}
|
||||
{% if model.contest.canShowRecord.call(handler, tdoc) %}
|
||||
{{ record.render_status_td(rdoc) }}
|
||||
{% else %}
|
||||
<td class="col--status record-status--border"><a href="{{ url('record_detail', rid=rdoc._id) }}">{{ _('Submitted') }}</a></td>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% import "components/paginator.html" as paginator with context %}
|
||||
{% extends "layout/basic.html" %}
|
||||
{% set type = 'homework' if tdoc.docType == 60 else 'contest' %}
|
||||
{% block content %}
|
||||
@@ -32,7 +33,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for row in rows -%}{% if loop.index0>0 %}
|
||||
{%- for row in rows -%}{% if loop.index0 > 0 %}
|
||||
<tr>
|
||||
{%- for column in row -%}
|
||||
<td class="col--{{ rows[0][loop.index0]['type'] }}">
|
||||
@@ -49,6 +50,7 @@
|
||||
{% endif %}{%- endfor -%}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ paginator.render(page, nPages) }}
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||
{% if psdict[pid] and psdict[pid]['rid'] %}
|
||||
{% set rdoc = rdict[psdict[pid]['rid']] %}
|
||||
{% if handler.canShowRecord(tdoc) %}
|
||||
{% if model.contest.canShowRecord.call(handler, tdoc) %}
|
||||
{{ record.render_status_td(rdoc) }}
|
||||
{% else %}
|
||||
<td class="col--status record-status--border"><a href="{{ url('record_detail', rid=rdoc._id) }}">{{ _('Submitted') }}</a></td>
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if handler.canShowScoreboard(tdoc, False) %}
|
||||
{% if model.contest.canShowScoreboard.call(handler, tdoc, False) %}
|
||||
<li class="menu__item"><a class="menu__link" href="{{ url('contest_scoreboard', tid=tdoc.docId) }}">
|
||||
<span class="icon icon-statistics"></span> {{ _('Scoreboard') }}
|
||||
</a></li>
|
||||
{% elif handler.canViewHiddenScoreboard(tdoc) %}
|
||||
{% elif model.contest.canViewHiddenScoreboard.call(handler, tdoc) %}
|
||||
<li class="menu__item"><a class="menu__link" href="{{ url('contest_scoreboard', tid=tdoc.docId) }}">
|
||||
<span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }}
|
||||
</a></li>
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if handler.canShowScoreboard(tdoc, false) %}
|
||||
{% if model.contest.canShowScoreboard.call(handler, tdoc, false) %}
|
||||
<li class="menu__item"><a class="menu__link" href="{{ url('homework_scoreboard', tid=tdoc.docId) }}">
|
||||
<span class="icon icon-statistics"></span> {{ _('Scoreboard') }}
|
||||
</a></li>
|
||||
{% elif handler.canViewHiddenScoreboard(tdoc) %}
|
||||
{% elif model.contest.canViewHiddenScoreboard(handler, tdoc) %}
|
||||
<li class="menu__item"><a class="menu__link" href="{{ url('homework_scoreboard', tid=tdoc.docId) }}">
|
||||
<span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }}
|
||||
</a></li>
|
||||
|
||||
Reference in new issue
Block a user