diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 130cbc9a8..de4462891 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.26.14", + "version": "2.26.15", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index fc963df5f..8a07cfc88 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -269,9 +269,10 @@ export interface RecordDoc { } export interface ScoreboardNode { - type: string, - value: string, - raw?: any, + type: string; + value: string; + raw?: any; + score?: number; } export type ScoreboardRow = ScoreboardNode[] & { raw?: any }; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index aab0c2c49..7ddf045da 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -110,21 +110,10 @@ const acm: ContestRule = { } for (const pid of tdoc.pids) { const doc = tsddict[pid] || {}; - let rid; - let colAccepted; - let colTime; - let colTimeStr; - if (doc.accept) { - rid = doc.rid; - colAccepted = `${_('Accepted')} ${doc.naccept ? ` (-${doc.naccept})` : ''}`; - colTime = doc.time; - colTimeStr = misc.formatSeconds(colTime); - } else { - rid = null; - colAccepted = doc.naccept ? `(-${doc.naccept})` : '-'; - colTime = '-'; - colTimeStr = ''; - } + const rid = doc.accept ? doc.rid : null; + const colAccepted = `${doc.accept ? `${_('Accepted')} ` : ''}${doc.naccept ? ` (-${doc.naccept})` : ''}`; + const colTime = doc.accept ? doc.time : '-'; + const colTimeStr = doc.accept ? misc.formatSeconds(colTime) : '-'; if (isExport) { row.push({ type: 'string', value: colAccepted }); row.push({ type: 'string', value: colTime }); @@ -132,7 +121,8 @@ const acm: ContestRule = { } else { row.push({ type: 'record', - value: '{0}
{1}'.format(colAccepted, colTimeStr), + score: doc.accept ? 100 : 0, + value: '{0}\n{1}'.format(colAccepted, colTimeStr), raw: rid, }); } @@ -185,6 +175,19 @@ const oi: ContestRule = { }); } } + const psdict = {}; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + if (isDone(tdoc)) { + const psdocs = await problem.getMultiStatus(tdoc.domainId, { + $or: rankedTsdocs.map(([, i]) => ({ + uid: i.uid, + docId: { $in: tdoc.pids }, + })), + }).toArray(); + for (const psdoc of psdocs) { + psdict[`${psdoc.uid}/${psdoc.docId}`] = psdoc; + } + } const rows = [columns]; for (const [rank, tsdoc] of rankedTsdocs) { const tsddict = {}; @@ -198,11 +201,29 @@ const oi: ContestRule = { { type: 'string', value: tsdoc.score || 0 }, ); for (const pid of tdoc.pids) { - row.push({ - type: 'record', - value: tsddict[pid]?.score ?? '-', - raw: tsddict[pid]?.rid || null, - }); + // eslint-disable-next-line @typescript-eslint/no-use-before-define + if (isDone(tdoc)) { + row.push({ + type: 'records', + value: '', + raw: [ + { + value: tsddict[pid]?.score ?? '-', + raw: tsddict[pid]?.rid || null, + }, + { + value: psdict[`${tsdoc.uid}/${pid}`]?.score ?? '-', + raw: psdict[`${tsdoc.uid}/${pid}`]?.rid ?? null, + }, + ], + }); + } else { + row.push({ + type: 'record', + value: tsddict[pid]?.score ?? '-', + raw: tsddict[pid]?.rid || null, + }); + } } rows.push(row); } @@ -313,13 +334,13 @@ const homework: ContestRule = { }); } } - const rows = [columns]; + const rows: ScoreboardRow[] = [columns]; for (const [rank, tsdoc] of rankedTsdocs) { const tsddict = {}; for (const item of tsdoc.detail || []) { tsddict[item.pid] = item; } - const row = [ + const row: ScoreboardRow = [ { type: 'string', value: rank }, { type: 'user', diff --git a/packages/ui-default/backendlib/template.js b/packages/ui-default/backendlib/template.js index 3ebb103f5..59fc9e057 100644 --- a/packages/ui-default/backendlib/template.js +++ b/packages/ui-default/backendlib/template.js @@ -66,6 +66,7 @@ class Nunjucks extends nunjucks.Environment { this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64')); this.addFilter('base64_decode', (s) => Buffer.from(s, 'base64').toString()); this.addFilter('bitand', (self, val) => self & val); + this.addFilter('replaceBr', (self) => self.toString().replace(/\n/g, '
')); this.addFilter('toString', (self) => (typeof self === 'string' ? self : JSON.stringify(self, replacer))); this.addFilter('content', (content, language, html) => { let s = ''; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 961f6cf1a..beb056d8c 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.8.29", + "version": "4.8.30", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/templates/contest_scoreboard.html b/packages/ui-default/templates/contest_scoreboard.html index df1415808..97ffab617 100644 --- a/packages/ui-default/templates/contest_scoreboard.html +++ b/packages/ui-default/templates/contest_scoreboard.html @@ -41,10 +41,21 @@ {{ user.render_inline(udict[column.raw], badge=false) }} {% elif column.type == 'record' and column.raw %} - {{ column.value|safe }} + {{ column.value|replaceBr|safe }} + {% elif column.type == 'records' %} + {%- for record in column.raw -%} + {% if loop.index0 %}/{% endif %} + {% if record.raw %} + + {{ record.value|replaceBr|safe }} + + {% else %} + {{ record.value|replaceBr|safe }} + {% endif %} + {%- endfor -%} {% else %} - {{ column.value|safe }} + {{ column.value|replaceBr|safe }} {% endif %} {%- endfor -%} diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 5bf1cf821..4d4b1be75 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -292,7 +292,7 @@ function _digit2(number: number) { } export function formatSeconds(_seconds = '0') { - const seconds = parseInt(_seconds, 10); + const seconds = +_seconds; return '{0}:{1}:{2}'.format( _digit2(Math.floor(seconds / 3600)), _digit2(Math.floor((seconds % 3600) / 60)),