Browse Source

Merge branch 'master' of zhaojunzhe/fair-web into master

pull/31/head
richie 5 years ago committed by Gogs
parent
commit
4114cc5870
  1. 2
      libs/submissions_process.js
  2. 10
      models-built/judge_state.js
  3. 2
      models-built/judge_state.js.map
  4. 7
      models/judge_state.ts
  5. 17
      modules/practice.js
  6. 4
      modules/problem.js
  7. 2
      modules/submission.js
  8. 58
      views/submission.ejs
  9. 439
      views/submission_practice.ejs
  10. 7
      views/submissions_item.ejs

2
libs/submissions_process.js

@ -11,6 +11,8 @@ const getSubmissionInfo = (s, displayConfig) => ({
language: displayConfig.showCode ? ((s.language != null && s.language !== '') ? syzoj.languages[s.language].show : null) : null,
codeSize: displayConfig.showCode ? s.code_length : null,
submitTime: syzoj.utils.formatDate(s.submit_time),
isPractice: s.is_practice,
c_id: s.c_id
});
const getRoughResult = (x, displayConfig, roughOnly) => {

10
models-built/judge_state.js

@ -354,6 +354,16 @@ var JudgeState = /** @class */ (function (_super) {
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], JudgeState.prototype, "submit_time");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], JudgeState.prototype, "c_id");
__decorate([
TypeORM.Index(),
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)
], JudgeState.prototype, "is_practice");
__decorate([
TypeORM.Column({ nullable: true, type: "integer" }),
__metadata("design:type", Number)

2
models-built/judge_state.js.map

File diff suppressed because one or more lines are too long

7
models/judge_state.ts

@ -87,6 +87,13 @@ export default class JudgeState extends Model {
@TypeORM.Column({ nullable: true, type: "integer" })
submit_time: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
c_id: number;
@TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
is_practice: number;
/*
* "type" indicate it's contest's submission(type = 1) or normal submission(type = 0)
* if it's contest's submission (type = 1), the type_info is contest_id

17
modules/practice.js

@ -27,6 +27,7 @@ app.get('/practice', async (req, res) => {
if (!currentPId) {
// 用户第一次进入练习板块
await userQuery.update(User).set({current_p_id: 1}).where("id = :id", { id:userId }).execute();
currentPId = 1;
} else {
await result.forEachAsync(async resultItem => {
const c_id = resultItem.id;
@ -56,12 +57,6 @@ app.get('/practice', async (req, res) => {
}
item.isNow = item.order === currentPId;
});
let userACArray = await utopQuery.where('u_id=:u_id',{u_id:userId}).andWhere("is_finished=1").getMany();
if (syzoj.config.practice_rating) {
const originRating = syzoj.config.default.user.rating;
const newRating = originRating + userACArray.length * syzoj.config.practice_rating;
await userQuery.update(User).set({rating: newRating}).where('id=:u_id',{u_id:userId}).execute();
}
res.render('practice', {
user:res.locals.user,
result
@ -196,13 +191,21 @@ app.get('/api/pass/:cid/:pid',async (req, res) => {
});
await utop.save();
}
let newutopQuery = UToP.createQueryBuilder();
let userACArray = await newutopQuery.where('u_id=:u_id',{u_id}).andWhere("is_finished=1").getMany();
console.log(userACArray.length);
if (syzoj.config.practice_rating) {
const originRating = syzoj.config.default.user.rating;
const newRating = originRating + userACArray.length * syzoj.config.practice_rating;
await userQuery.update(User).set({rating: newRating}).where('id=:u_id',{u_id}).execute();
}
res.send();
} catch(e) {
res.send({ error_code: e.errno, error_msg: '练习失败,请稍后重试' });
}
});
app.get('/api/practice/nopass/:cid/:pid',async (req, res) => {
app.get('/api/nopass/:cid/:pid',async (req, res) => {
try {
const c_id = parseInt(req.params.cid);
const p_id = parseInt(req.params.pid);

4
modules/problem.js

@ -627,7 +627,9 @@ app.post('/problem/practice/:pid/:cid/submit', app.multer.fields([{ name: 'answe
language: req.body.language,
user_id: curUser.id,
problem_id: pid,
is_public: problem.is_public
is_public: problem.is_public,
c_id:cid,
is_practice:1
});
if (!await problem.isAllowedUseBy(curUser)) throw new ErrorMessage('您没有权限进行此操作。');

2
modules/submission.js

@ -194,7 +194,7 @@ app.get('/submission/practice/:id/:pid/:cid', async (req, res) => {
} else {
judge.code = "作者没有开放此题代码,请联系作者分享。";
}
res.render('submission', {
res.render('submission_practice', {
info: getSubmissionInfo(judge, currentConfig),
roughResult: getRoughResult(judge, currentConfig, false),
code: judge.code,

58
views/submission.ejs

@ -28,9 +28,6 @@
.accordion > .content > .accordion > .content > :last-child {
margin-bottom: -10px !important;
}
.disabled{
display: none;
}
</style>
<span id="submission_content">
<div class="padding" id="vueAppFuckSafari">
@ -55,8 +52,6 @@
<tr is="submission-item" v-bind:data="roughData" :config="displayConfig" :show-rejudge="showRejudge" :show-share="showShare" :progress="getProgress()" :compiling="detailResult && !detailResult.compile"></tr>
</tbody>
</table>
<div id="cid" class="disabled"><%= cid %></div>
<div id="pid" class="disabled"><%= pid %></div>
<code-box no-escape v-bind:content="code">
<% if (formattedCode !== null) { %>
<a onclick="toggleFormattedCode()" class="ui button" style="position: absolute; top: 0px; right: -4px; border-top-left-radius: 0; border-bottom-right-radius: 0; ">
@ -317,8 +312,6 @@ const vueApp = new Vue({
});
if (token != null) {
const loadSocketIO = function () {
const cid = parseInt(document.getElementById('cid').innerHTML);
const pid = parseInt(document.getElementById('pid').innerHTML);
let currentVersion = 0;
const socket = io(socketUrl);
socket.on('connect', function () {
@ -343,68 +336,17 @@ if (token != null) {
console.log("Judge finished");
vueApp.roughData.running = false;
vueApp.roughData.result = p.roughResult;
if (p.roughResult.result === 'Accepted') {
$.ajax({
url: `/api/pass/${cid}/${pid}`,
type: 'GET',
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('练习功能发生故障');
}
});
} else {
$.ajax({
url: `/api/nopass/${cid}/${pid}`,
type: 'GET',
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('练习功能发生故障');
}
});
}
vueApp.detailResult = p.result;
socket.close();
});
socket.emit('join', token, function (data) {
console.log("join! ", data);
// data.ok=true;
// data.finished=true;
// data.roughResult = {
// memory: 7128,
// result: "Accepted",
// score: 100,
// time: 272,
// }
// data.running=false;
if (data && data.ok) {
if (data.finished) {
vueApp.roughData.result = data.roughResult;
// if (!data.result) location.reload(true);
vueApp.detailResult = data.result;
vueApp.roughData.running = false;
// if (data.roughResult.result === 'Accepted') {
// $.ajax({
// url: `/api/pass/${cid}/${pid}`,
// type: 'GET',
// success: function (data) {
// },
// error: function (XMLHttpRequest, textStatus, errorThrown) {
// alert('练习功能发生故障');
// }
// });
// } else {
// $.ajax({
// url: `/api/nopass/${cid}/${pid}`,
// type: 'GET',
// success: function (data) {
// },
// error: function (XMLHttpRequest, textStatus, errorThrown) {
// alert('练习功能发生故障');
// }
// });
// }
socket.close();
} else {
if (data.running) {

439
views/submission_practice.ejs

@ -0,0 +1,439 @@
<% this.title = '提交记录 #' + info.submissionId %>
<% include util %>
<% include header %>
<script src="<%- lib('textfit/2.3.1/textFit.min.js') %>"></script>
<style>
.single-subtask {
box-shadow: none !important;
}
.single-subtask > .title {
display: none;
}
.single-subtask > .content {
padding: 0 !important;
}
.accordion > .content > .accordion {
margin-top: 0;
margin-bottom: 0;
}
.accordion > .content > .accordion > .content {
margin-top: 0;
margin-bottom: 14px;
}
.accordion > .content > .accordion > .content > :last-child {
margin-bottom: -10px !important;
}
.disabled{
display: none;
}
</style>
<span id="submission_content">
<div class="padding" id="vueAppFuckSafari">
<table class="ui very basic center aligned table" id="status_table">
<thead>
<tr>
<th>编号</th>
<th>题目</th>
<th>状态</th>
<th v-if="displayConfig.showScore">分数</th>
<th v-if="displayConfig.showUsage">总时间</th>
<th v-if="displayConfig.showUsage">单项时间</th>
<th v-if="displayConfig.showUsage">内存</th>
<th v-if="displayConfig.showCode">代码 / 答案文件</th>
<th>提交者</th>
<th>提交时间</th>
<th v-if="showRejudge">重新评测</th>
<th v-if="showShare">分享</th>
</tr>
</thead>
<tbody>
<tr is="submission-item" v-bind:data="roughData" :config="displayConfig" :show-rejudge="showRejudge" :show-share="showShare" :progress="getProgress()" :compiling="detailResult && !detailResult.compile"></tr>
</tbody>
</table>
<div id="cid" class="disabled"><%= cid %></div>
<div id="pid" class="disabled"><%= pid %></div>
<code-box no-escape v-bind:content="code">
<% if (formattedCode !== null) { %>
<a onclick="toggleFormattedCode()" class="ui button" style="position: absolute; top: 0px; right: -4px; border-top-left-radius: 0; border-bottom-right-radius: 0; ">
<template v-if="currentFormatted">
显示原始代码
</template>
<template v-if="!currentFormatted">
格式化代码
</template>
</a>
<% } %>
</code-box>
<code-box v-if="detailResult && detailResult.compile" no-escape title="编译信息" v-bind:content="detailResult.compile.message"></code-box>
<code-box v-if="detailResult" title="系统信息" v-bind:content="detailResult.systemMessage"></code-box>
<div class="ui styled fluid accordion" :class="singleSubtask ? 'single-subtask' : '' " v-if="detailResult && detailResult.judge && detailResult.judge.subtasks">
<template v-for="subtask, $index in detailResult.judge.subtasks">
<div class="title" :class="singleSubtask ? 'active' : ''">
<div class="ui grid">
<div class="three wide column">
<i class="dropdown icon"></i>
子任务 #{{ $index + 1 }}
</div>
<div class="four wide column">
<status-label :status="getSubtaskResult(subtask)" :indetail="true" :progress="getProgress($index)"></status-label>
</div>
<div class="three wide column" v-if="subtask.score != null">
得分:<span style="font-weight: normal; ">{{ Math.trunc(subtask.score) }}</span>
</div>
</div>
</div>
<div class="content" :class="singleSubtask ? 'active' : ''">
<div class="accordion">
<template v-for="curCase, $caseIndex in subtask.cases">
<div class="title" :class="checkTestcaseOK(curCase) || curCase.errorMessage ? '' : 'unexpandable'">
<div class="ui grid">
<div class="three wide column">
<i class="dropdown icon"></i>
测试点 #{{ $caseIndex + 1 }}
</div>
<div class="four wide column">
<status-label :status="getTestcaseStatus(curCase)" :indetail="true"></status-label>
</div>
<template v-if="checkTestcaseOK(curCase)">
<div class="three wide column">
得分:<span style="font-weight: normal; ">{{ Math.trunc(curCase.result.scoringRate * 100) }}</span>
</div>
<div class="three wide column" v-if="curCase.result.time != null && curCase.result.time !== NaN">
用时:<span style="font-weight: normal; ">{{ curCase.result.time }} ms</span>
</div>
<div class="three wide column" v-if="curCase.result.memory != null && curCase.result.memory !== NaN">
内存:<span style="font-weight: normal; ">{{ curCase.result.memory }} KiB</span>
</div>
</template>
</div>
</div>
<div class="content">
<template v-if="checkTestcaseOK(curCase)">
<code-box v-if="curCase.result.input" :title="'输入文件(<span style=\'font-family: monospace; \'>'+ curCase.result.input.name +'</span>)'" :content="curCase.result.input.content" :download="<%= serializejs(syzoj.utils.makeUrl(['problem', info.problemId, 'testdata', 'download'])) %> + '/' + curCase.result.input.name"></code-box>
<code-box v-if="curCase.result.output" :title="'答案文件(<span style=\'font-family: monospace; \'>'+ curCase.result.output.name +'</span>)'" :content="curCase.result.output.content" :download="<%= serializejs(syzoj.utils.makeUrl(['problem', info.problemId, 'testdata', 'download'])) %> + '/' + curCase.result.output.name"></code-box>
<code-box title="用户输出" :content="curCase.result.userOutput"></code-box>
<code-box title="标准错误流" :content="curCase.result.userError"></code-box>
<code-box title="Special Judge 信息" :content="curCase.result.spjMessage"></code-box>
<code-box title="系统信息" :content="curCase.result.systemMessage"></code-box>
</template>
<code-box title="错误信息" :content="curCase.errorMessage"></code-box>
</div>
</template>
</div>
</div>
</template>
</div>
</div>
<script src="<%- lib('vue/2.5.21/vue.min.js') %>"></script>
<script src="<%- lib('socket.io/2.2.0/socket.io.js') %>"></script>
<script src="<%- lib('jsondiffpatch/0.2.5/jsondiffpatch.min.js') %>"></script>
<% include submissions_item %>
<script type="text/x-template" id="codeBoxTemplate">
<div style="margin-top: 0px; margin-bottom: 14px; " v-if="content != null && content !== ''">
<p v-if="title" class="transition visible">
<strong v-html="title"></strong>
<a v-if="download" style="color: #000; " :href="download"><i class="download icon"></i></a>
</p>
<div class="ui existing segment">
<slot></slot>
<pre v-if="!noEscape" style="margin-top: 0; margin-bottom: 0; "><code>{{ content }}</code></pre>
<pre v-if="noEscape" style="margin-top: 0; margin-bottom: 0; "><code v-html="content"></code></pre>
</div>
</div>
</script>
<script>
Vue.component("code-box", {
template: "#codeBoxTemplate",
props: {
title: String,
content: String,
noEscape: {
type: Boolean,
default: false
},
download: String
}
});
const socketUrl = "/detail";
const displayConfig = <%- serializejs(displayConfig) %>;
const token = <%- serializejs(socketToken) %>;
const TestcaseResultType = {};
(function (TestcaseResultType) {
TestcaseResultType[TestcaseResultType["Accepted"] = 1] = "Accepted";
TestcaseResultType[TestcaseResultType["WrongAnswer"] = 2] = "WrongAnswer";
TestcaseResultType[TestcaseResultType["PartiallyCorrect"] = 3] = "PartiallyCorrect";
TestcaseResultType[TestcaseResultType["MemoryLimitExceeded"] = 4] = "MemoryLimitExceeded";
TestcaseResultType[TestcaseResultType["TimeLimitExceeded"] = 5] = "TimeLimitExceeded";
TestcaseResultType[TestcaseResultType["OutputLimitExceeded"] = 6] = "OutputLimitExceeded";
TestcaseResultType[TestcaseResultType["FileError"] = 7] = "FileError";
TestcaseResultType[TestcaseResultType["RuntimeError"] = 8] = "RuntimeError";
TestcaseResultType[TestcaseResultType["JudgementFailed"] = 9] = "JudgementFailed";
TestcaseResultType[TestcaseResultType["InvalidInteraction"] = 10] = "InvalidInteraction";
})(TestcaseResultType);
const statusToString = {};
statusToString[TestcaseResultType.Accepted] = "Accepted";
statusToString[TestcaseResultType.WrongAnswer] = "Wrong Answer";
statusToString[TestcaseResultType.PartiallyCorrect] = "Partially Correct";
statusToString[TestcaseResultType.MemoryLimitExceeded] = "Memory Limit Exceeded";
statusToString[TestcaseResultType.TimeLimitExceeded] = "Time Limit Exceeded";
statusToString[TestcaseResultType.OutputLimitExceeded] = "Output Limit Exceeded";
statusToString[TestcaseResultType.RuntimeError] = "Runtime Error";
statusToString[TestcaseResultType.FileError] = "File Error";
statusToString[TestcaseResultType.JudgementFailed] = "Judgement Failed";
statusToString[TestcaseResultType.InvalidInteraction] = "Invalid Interaction";
const TaskStatus = {};
(function (TaskStatus) {
TaskStatus[TaskStatus["Waiting"] = 0] = "Waiting";
TaskStatus[TaskStatus["Running"] = 1] = "Running";
TaskStatus[TaskStatus["Done"] = 2] = "Done";
TaskStatus[TaskStatus["Failed"] = 3] = "Failed";
TaskStatus[TaskStatus["Skipped"] = 4] = "Skipped";
})(TaskStatus);
const unformattedCode = <%- serializejs(code) %>;
const formattedCode = <%- serializejs(formattedCode) %>;
function toggleFormattedCode() {
if (vueApp.currentFormatted) {
vueApp.currentFormatted = false;
vueApp.code = unformattedCode;
} else {
vueApp.currentFormatted = true;
vueApp.code = formattedCode;
}
}
const vueApp = new Vue({
el: '#vueAppFuckSafari',
data: {
roughData: {
info: <%- serializejs(info) %>,
result: <%- serializejs(roughResult) %>,
running: false,
displayConfig: displayConfig
},
code: <%- serializejs(preferFormattedCode && formattedCode !== null) -%> ? formattedCode : unformattedCode,
currentFormatted: <%- serializejs(preferFormattedCode && formattedCode !== null) -%>,
detailResult: <%- serializejs(detailResult) %>,
displayConfig: displayConfig,
},
computed: {
singleSubtask() {
return this.detailResult.judge.subtasks.length === 1;
},
showRejudge() {
return this.displayConfig.showRejudge && (!this.roughData.running);
},
showShare() {
return this.displayConfig.showShare;
}
},
methods: {
getStatusString(statusCode) {
return statusToString[statusCode];
},
firstNonAC(t) {
if (t.every(function(v){ return v === TestcaseResultType.Accepted;})) {
return TestcaseResultType.Accepted;
} else {
return t.find(function(r) { return r !== TestcaseResultType.Accepted;});
}
},
getSubtaskResult(t) {
if (t.cases.some(function(c){ return c.status === TaskStatus.Running;})) {
return "Running";
} else if (t.cases.some(function(c) { return c.status === TaskStatus.Waiting;})) {
return "Waiting";
} else if (t.cases.every(function(c){ return c.status === TaskStatus.Done || c.status === TaskStatus.Skipped;})) {
return this.getStatusString(this.firstNonAC(t.cases.filter(function(c) { return c.result; })
.map(function(c) { return c.result.type;})));
} else {
return "System Error";
}
},
getTestcaseStatus(c) {
if (c.status === TaskStatus.Done) {
return this.getStatusString(c.result.type);
} else if (c.status === TaskStatus.Waiting) {
return "Waiting";
} else if (c.status === TaskStatus.Running) {
return "Running";
} else if (c.status === TaskStatus.Skipped) {
return "Skipped";
} else {
return "System Error";
}
},
checkTestcaseOK(c) {
return c.status === TaskStatus.Done;
},
getProgress(index) {
if (!this.detailResult || !this.detailResult.judge || !this.detailResult.judge.subtasks) return {
finished: 0,
total: 0
};
var isPending = status => [TaskStatus.Waiting, TaskStatus.Running].includes(status);
var subtaskProgress = [], allFinished = 0, allTotal = 0;
for (var i in this.detailResult.judge.subtasks) {
var subtaskFinished = 0, subtaskTotal = 0;
for (var j in this.detailResult.judge.subtasks[i].cases) {
subtaskTotal++, allTotal++;
if (!isPending(this.detailResult.judge.subtasks[i].cases[j].status)) subtaskFinished++, allFinished++;
}
subtaskProgress.push({
finished: subtaskFinished,
total: subtaskTotal
});
}
var allProgress = {
finished: allFinished,
total: allTotal
};
return typeof index === 'undefined' ? allProgress : subtaskProgress[index];
}
},
mounted() {
$(document).ready(function(){ $('.ui.accordion').accordion({ selector: { trigger: '.title:not(.unexpandable)' } })});
},
updated() {
$('.ui.accordion').off().accordion({ selector: { trigger: '.title:not(.unexpandable)' } });
}
});
if (token != null) {
const loadSocketIO = function () {
const cid = parseInt(document.getElementById('cid').innerHTML);
const pid = parseInt(document.getElementById('pid').innerHTML);
let currentVersion = 0;
const socket = io(socketUrl);
socket.on('connect', function () {
socket.on('start', function () {
vueApp.roughData.running = true;
console.log("Judge start!");
vueApp.detailResult = {};
});
socket.on('update', function (p) {
console.log("Delta: ", p);
if (p.from === currentVersion) {
currentVersion = p.to;
jsondiffpatch.patch(vueApp.detailResult, p.delta);
vueApp.detailResult = JSON.parse(JSON.stringify(vueApp.detailResult));// WTF?
vueApp.roughData.result = p.roughResult;
} else { // Some packets are dropped. Let's reset.
socket.close();
setTimeout(loadSocketIO, 0);
}
});
socket.on('finish', function (p) {
console.log("Judge finished");
vueApp.roughData.running = false;
vueApp.roughData.result = p.roughResult;
if (p.roughResult.result === 'Accepted') {
$.ajax({
url: `/api/pass/${cid}/${pid}`,
type: 'GET',
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('练习功能发生故障');
}
});
} else {
$.ajax({
url: `/api/nopass/${cid}/${pid}`,
type: 'GET',
success: function (data) {
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert('练习功能发生故障');
}
});
}
vueApp.detailResult = p.result;
socket.close();
});
socket.emit('join', token, function (data) {
console.log("join! ", data);
// data.ok=true;
// data.finished=true;
// data.roughResult = {
// memory: 7128,
// result: "Accepted",
// score: 100,
// time: 272,
// }
// data.running=false;
if (data && data.ok) {
if (data.finished) {
vueApp.roughData.result = data.roughResult;
// if (!data.result) location.reload(true);
vueApp.detailResult = data.result;
vueApp.roughData.running = false;
// if (data.roughResult.result === 'Accepted') {
// $.ajax({
// url: `/api/pass/${cid}/${pid}`,
// type: 'GET',
// success: function (data) {
// },
// error: function (XMLHttpRequest, textStatus, errorThrown) {
// alert('练习功能发生故障');
// }
// });
// } else {
// $.ajax({
// url: `/api/nopass/${cid}/${pid}`,
// type: 'GET',
// success: function (data) {
// },
// error: function (XMLHttpRequest, textStatus, errorThrown) {
// alert('练习功能发生故障');
// }
// });
// }
socket.close();
} else {
if (data.running) {
// vueApp.roughData.running = true;
vueApp.detailResult = data.current.content;
// vueApp.roughData.result = data.roughResult;
currentVersion = data.current.version;
}
}
} else {
alert("ERROR: " + JSON.stringify(data));
}
});
});
};
loadSocketIO();
}
</script>
<script>
document.addEventListener('keydown', function (event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
var sel = window.getSelection();
var rg = document.createRange()
rg.selectNodeContents(document.querySelector('code'));
sel.removeAllRanges();
sel.addRange(rg);
event.preventDefault();
}
});
</script>
</span>
<% include footer %>

7
views/submissions_item.ejs

@ -34,9 +34,16 @@
} else return 'Waiting';
},
submissionLink() {
if (this.data.info.c_id && this.data.info.isPractice) {
const practiceUrl = `practice/${this.data.info.submissionId}/${this.data.info.problemId}/${this.data.info.c_id}`
return submissionUrl.replace('20000528', practiceUrl);
}
return submissionUrl.replace('20000528', this.data.info.submissionId);
},
problemLink() {
if (this.data.info.c_id && this.data.info.isPractice) {
return `practice/classify/${this.data.info.c_id}/problem/${this.data.info.problemId}`
}
return problemUrl.replace('20000528', this.data.info.problemId);
},
userLink() {

Loading…
Cancel
Save