|
|
import React, { Component } from 'react';
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
|
// import getMuiTheme from 'material-ui/styles/getMuiTheme'
|
|
|
import Dialog, {
|
|
|
DialogActions,
|
|
|
DialogContent,
|
|
|
DialogContentText,
|
|
|
DialogTitle,
|
|
|
} from 'material-ui/Dialog';
|
|
|
|
|
|
import Button from 'material-ui/Button';
|
|
|
|
|
|
import MainContent from './MainContent'
|
|
|
import UpdateDrawer from './component/UpdateDrawer'
|
|
|
|
|
|
import axios from 'axios'
|
|
|
|
|
|
import _ from 'lodash'
|
|
|
|
|
|
const CancelToken = axios.CancelToken;
|
|
|
|
|
|
let locationPath = window.location.pathname;
|
|
|
|
|
|
let readingRepoTimes = 0;
|
|
|
|
|
|
function updateCodeMirror () {
|
|
|
// (TODO 这里的处理方式比较蹩脚,css没搞定)总高度减去页签头高度
|
|
|
window.$('#games_repository_contents .CodeMirror').height(window.$('#games_repository_contents').height() - 40)
|
|
|
// 为了解决这个问题:一开始进入TPI,代码框的内容显示不全,需要点击一下代码框才会显示全
|
|
|
$('#games_repository_valuation .CodeMirror .CodeMirror-scroll').scrollTop('2')
|
|
|
// 选择题切换到编程题会出现codemirror没初始化成功的问题,加下refresh
|
|
|
window.editor_CodeMirror && window.editor_CodeMirror.refresh()
|
|
|
}
|
|
|
|
|
|
// ============================ ============================ ============================ ============================
|
|
|
// 老版本的评测等待通知代码
|
|
|
// ============================ ============================ ============================ ============================
|
|
|
const $ = window.$;
|
|
|
let next_game = null;
|
|
|
function getRandom(min, max){
|
|
|
var r = Math.random() * (max - min);
|
|
|
var re = Math.round(r + min);
|
|
|
re = Math.max(Math.min(re, max), min);
|
|
|
|
|
|
return re;
|
|
|
}
|
|
|
|
|
|
function tipContent(flag, num, mintes, second, first){
|
|
|
console.log("第几次查询get_waiting_time:" + first);
|
|
|
var random = getRandom(15, 120);
|
|
|
var mintes = parseInt((second - random) / 60);
|
|
|
var html=tip_half(flag, num, mintes, second);
|
|
|
$("#update_game_tip").html(html);
|
|
|
if(first == 1){
|
|
|
show_tip_content();
|
|
|
}
|
|
|
}
|
|
|
// 显示右侧评测等待通知
|
|
|
function show_tip_content(){
|
|
|
var $tip = $(".tip-panel-animate");
|
|
|
$(".update_back_main").show();
|
|
|
$tip.addClass("animate-tip").show();
|
|
|
setTimeout(function(){$tip.removeClass("animate-tip")}, 700);
|
|
|
}
|
|
|
//隐藏右侧评测等待通知
|
|
|
function hide_tip_content(){
|
|
|
var $tip = $(".tip-panel-animate");
|
|
|
$tip.addClass("animate-tip-hide");
|
|
|
$(".-task-title").hide();
|
|
|
setTimeout(function(){
|
|
|
$tip.hide().removeClass("animate-tip-hide animate-tip");
|
|
|
check_hide();
|
|
|
}, 700);
|
|
|
}
|
|
|
window.hide_tip_content = hide_tip_content;
|
|
|
|
|
|
//判断是否所有的提示信息都已经隐藏
|
|
|
function check_hide(){
|
|
|
var dis = true;
|
|
|
var $tipLeft = $(".tip-panel-animate-left");
|
|
|
for(var i = 0; i < $tipLeft.length; i++){
|
|
|
if($tipLeft.eq(i).css("display") == "block"){
|
|
|
dis = false;
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
if($(".tip-panel-animate").css("display") == "block"){
|
|
|
dis = false;
|
|
|
}
|
|
|
if(dis){
|
|
|
$(".update_back_main").hide();
|
|
|
$('#info_tip_tab').removeClass('leftnav-active');//去除系统通知的选中状态
|
|
|
$('#current_task_tab').addClass('leftnav-active');//选中当前任务
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/*评测等待通知*/
|
|
|
function tip_half(flag, num, mintes, second){
|
|
|
var htmlvalue="";
|
|
|
htmlvalue+="<p class=\"ml15 mr15 mt3 color-grey3 font-16\">评测等待通知</p>";
|
|
|
if(flag == 0){
|
|
|
htmlvalue+="<p class=\"color-dark-grey\" style=\"line-height: 20px;margin:10px 15px\">目前有<span class=\"color-orange03\">" + num + "</span>人正在等待评测,还需等待约<span class=\"color-orange03\">" + (mintes > 0 ? mintes + "分钟" : second + "秒") + "</span>。您可以先完成“下一关”任务。</p>";
|
|
|
}else{
|
|
|
htmlvalue+="<p class=\"color-dark-grey\" style=\"line-height: 20px;margin:0px 15px\">目前有超过<span class=\"color-orange03\">" + num*10 + "</span>人正在等待评测,请您稍后再试。您可以先完成其他关卡</p>";
|
|
|
}
|
|
|
|
|
|
htmlvalue+="<div class=\"tip-operator-btn\">";
|
|
|
if(flag == 0){
|
|
|
htmlvalue+="<a href=\"javascript:void(0)\" class=\"fl color-orange03\" data-tip-top=\"留在本页等待评测结果\" onclick=\"hide_tip_content()\">继续等待</a>";
|
|
|
if (next_game) {
|
|
|
htmlvalue+="<a href=\"/tasks/" + next_game + "\" class=\"fl color-grey\" data-tip-top=\"稍后查看评测结果\" onclick=\"hide_tip_content()\">下一关</a>";
|
|
|
} else {
|
|
|
htmlvalue+="<a href=\"javascript:void(0)\" class=\"fl color-grey\" >已经是最后一关啦</a>"
|
|
|
}
|
|
|
}else{
|
|
|
htmlvalue+="<a href=\"javascript:void(0)\" class=\"fl color-orange03\" data-tip-top=\"留在本页重新评测\" onclick=\"hide_tip_content()\">知道啦</a>";
|
|
|
if (next_game) {
|
|
|
htmlvalue+="<a href=\"/tasks/" + next_game + "\" class=\"fl color-grey\" data-tip-top=\"下一关\" onclick=\"hide_tip_content()\">下一关</a>";
|
|
|
} else {
|
|
|
htmlvalue+="<a href=\"javascript:void(0)\" class=\"fl color-grey\" >已经是最后一关啦</a>"
|
|
|
}
|
|
|
}
|
|
|
htmlvalue+="</div>";
|
|
|
return htmlvalue;
|
|
|
}
|
|
|
|
|
|
/*更新通知*/
|
|
|
function tip_half_update(){
|
|
|
var htmlvalue="";
|
|
|
|
|
|
htmlvalue+="<p class=\"mb5 ml15 mt8 mr15 color-grey3 font-14 mt8\" style=\"line-height: 20px\">本次更新设计以下几个步骤,预计需要花费几分钟时间,请耐心等待~</p>";
|
|
|
htmlvalue+="<ol class=\"ml15\">";
|
|
|
htmlvalue+="<li style=\"list-style-type: disc;margin-left: 20px;line-height: 20px\">更新版本库</li>";
|
|
|
htmlvalue+="<li style=\"list-style-type: disc;margin-left: 20px;line-height: 20px\">同步评测机制</li></ol>";
|
|
|
htmlvalue+="<div class=\"tip-operator-btn\">";
|
|
|
htmlvalue+="<a href=\"javascript:void(0)\" class=\"fl color-orange03\" style=\"width:100%;text-align: center;border:none\" onclick=\"hide_tip_content()\">知道啦</a>";
|
|
|
htmlvalue+="</div>";
|
|
|
return htmlvalue;
|
|
|
}
|
|
|
// ============================ ============================ ============================ ============================
|
|
|
// ============================ ============================ ============================ ============================
|
|
|
|
|
|
const UPDATED = 0;
|
|
|
const SAVING = 1;
|
|
|
const SAVED = 2;
|
|
|
const SAVE_FAILED = 3;
|
|
|
|
|
|
class MainContentContainer extends Component {
|
|
|
|
|
|
constructor(props) {
|
|
|
super(props)
|
|
|
|
|
|
this.onRepositoryCodeUpdate = this.onRepositoryCodeUpdate.bind(this)
|
|
|
this.onRunCodeTest = this.onRunCodeTest.bind(this)
|
|
|
this.codemirrorDidMount = this.codemirrorDidMount.bind(this)
|
|
|
this.fetchRepositoryCode = this.fetchRepositoryCode.bind(this)
|
|
|
|
|
|
this.showResetCodeDialog = this.showResetCodeDialog.bind(this)
|
|
|
this.showResetPassedCodeDialog = this.showResetPassedCodeDialog.bind(this)
|
|
|
|
|
|
|
|
|
this.oldRepositoryCode = '';
|
|
|
|
|
|
this.state = {
|
|
|
repositoryCode: '',
|
|
|
|
|
|
open: false, // 繁忙等级等提示用Dialog,考虑重构封装到根组件
|
|
|
gameBuilding: false, // 评测中标志
|
|
|
codeStatus: SAVED, // 0 已修改 1 保存中 2 已保存 3 保存失败
|
|
|
|
|
|
codeLoading: true, // code加载中
|
|
|
readRepoTimeout: false, // 加载代码轮训超时
|
|
|
resetCodeDialogOpen: false, // 考虑重构封装到根组件
|
|
|
resetPassedCodeDialogOpen: false, // 考虑重构封装到根组件
|
|
|
|
|
|
isEditablePath: true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ------------------------------------------------
|
|
|
// static childContextTypes = {
|
|
|
// muiTheme: PropTypes.object
|
|
|
// }
|
|
|
|
|
|
// getChildContext() {
|
|
|
// return {
|
|
|
// muiTheme: getMuiTheme()
|
|
|
// }
|
|
|
// }
|
|
|
// ------------------------------------------------
|
|
|
handleClose = () => {
|
|
|
this.setState({open: false});
|
|
|
};
|
|
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
|
// _.isEqual(this.props, nextProps)
|
|
|
if (JSON.stringify(nextProps) !== JSON.stringify(this.props) || !_.isEqual(this.state, nextState)) {
|
|
|
return true
|
|
|
} else {
|
|
|
return false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
componentWillReceiveProps(newProps, nContext) {
|
|
|
// 编程题才需要拿代码
|
|
|
if (newProps.game && newProps.st === 0) {
|
|
|
if (!this.props || !this.props.game
|
|
|
|| ( newProps.game.identifier !== this.props.game.identifier ) ) {
|
|
|
setTimeout(this.fetchRepositoryCode( newProps), 1500);
|
|
|
} else if ( this.props.challenge.pathIndex != newProps.challenge.pathIndex
|
|
|
&& newProps.challenge.pathIndex !== -1) { // 切换到只读文件
|
|
|
// pathIndex切换
|
|
|
setTimeout(this.fetchRepositoryCode( newProps), 1500);
|
|
|
}
|
|
|
}
|
|
|
if (newProps.myshixun) {
|
|
|
var stageId = newProps.match.params.stageId;
|
|
|
var shixunId = newProps.myshixun.identifier;
|
|
|
// locationPath = `/myshixuns/${shixunId}/stages/${stageId}`
|
|
|
locationPath = `/api/v1/games/${newProps.game.identifier}`;
|
|
|
next_game = newProps.next_game;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
|
|
const { game } = this.props
|
|
|
|
|
|
if (game && prevProps.game
|
|
|
&& prevProps.game.identifier !== game.identifier) {
|
|
|
// 切换关卡时,停止轮训
|
|
|
this.oldGameIdentifier = prevProps.game.identifier;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// arg_path 点击文件目录树时,传入的点击节点对应的path
|
|
|
fetchRepositoryCode( props, arg_path, type, isRetry) {
|
|
|
const { challenge, showSnackbar, game, shixun, myshixun, hide_code } = props ? props : this.props;
|
|
|
if (shixun.vnc == true) {
|
|
|
// vnc模式下不需要加载代码
|
|
|
return true;
|
|
|
}
|
|
|
if (
|
|
|
// true ||
|
|
|
hide_code) { // 隐藏code的实训
|
|
|
this.setState({ codeLoading: false });
|
|
|
return;
|
|
|
}
|
|
|
const stageId = game.identifier
|
|
|
let path;
|
|
|
let isEditablePath = false;
|
|
|
if (arg_path) {
|
|
|
path = arg_path;
|
|
|
if (challenge.multiPath) {
|
|
|
challenge.path.forEach(item => {
|
|
|
if (path == item) {
|
|
|
isEditablePath = true;
|
|
|
}
|
|
|
})
|
|
|
} else if (path == challenge.path) {
|
|
|
isEditablePath = true;
|
|
|
}
|
|
|
} else {
|
|
|
isEditablePath = true;
|
|
|
path = challenge.multiPath ? challenge.path[challenge.pathIndex] : challenge.path;
|
|
|
}
|
|
|
if (readingRepoTimes === 0) {
|
|
|
this.setState({
|
|
|
readRepoTimeout: false
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// http://localhost:3000/api/v1/games/zl6kx8f7vfpo/rep_content
|
|
|
let status = type ? type : 0; // type 1 目录树点击 默认 0: 正常代码加载
|
|
|
// const fetchRepoCodeUrl = `/api/v1/games/${stageId}/rep_content?path=${path}&shixun_gpid=${shixun.gpid}&status=${status}&retry=${isRetry ? 1 : 0}`
|
|
|
const fetchRepoCodeUrl = `/tasks/${game.identifier}/rep_content.json?path=${path}&status=${status}&retry=${isRetry ? 1 : 0}`
|
|
|
// ?path=${path}&shixun_gpid=${shixun.gpid}&status=${status}&retry=${isRetry ? 1 : 0}
|
|
|
|
|
|
if (this.state.codeLoading === true && this._cancel) { // 多次请求,则cancel掉前面的请求
|
|
|
// bug cancel掉第一次请求后,第二次请求也没法发出,先注释掉了
|
|
|
// this._cancel();
|
|
|
// this._cancel = null;
|
|
|
}
|
|
|
const that = this;
|
|
|
this.setState({ gameBuilding: false, codeLoading: true });
|
|
|
axios.get(fetchRepoCodeUrl, {
|
|
|
// path: path
|
|
|
|
|
|
// withCredentials: true,
|
|
|
// cancelToken: new CancelToken(function executor(c) {
|
|
|
// // An executor function receives a cancel function as a parameter
|
|
|
// that._cancel = c;
|
|
|
// })
|
|
|
}).then((fetchRepositoryCodeResponse) => {
|
|
|
// 空字符串还是正常切换
|
|
|
if (fetchRepositoryCodeResponse.data.status == 0) {
|
|
|
readingRepoTimes = readingRepoTimes + 1;
|
|
|
if(readingRepoTimes > 9) {
|
|
|
this.setState({
|
|
|
readRepoTimeout: true
|
|
|
})
|
|
|
readingRepoTimes = 0;
|
|
|
showSnackbar(`网络异常,请稍后重试。`);
|
|
|
return
|
|
|
}else{
|
|
|
if (status === 0) {
|
|
|
// 轮训后点击切换关卡的话,停止上一关的轮训
|
|
|
if (game.identifier == this.oldGameIdentifier) {
|
|
|
this.oldGameIdentifier = null;
|
|
|
return;
|
|
|
}
|
|
|
setTimeout(() => {
|
|
|
this.fetchRepositoryCode(props,arg_path,type)
|
|
|
}, 1500);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}else if (fetchRepositoryCodeResponse.data && fetchRepositoryCodeResponse.data.status === -1) {
|
|
|
this.setState({ codeLoading: false })
|
|
|
console.error('`获取代码失败!')
|
|
|
showSnackbar(`获取代码失败:${fetchRepositoryCodeResponse.data.message}`)
|
|
|
|
|
|
}else if (fetchRepositoryCodeResponse.data.status === -3) {
|
|
|
readingRepoTimes = readingRepoTimes + 1;
|
|
|
if(readingRepoTimes > 9) {
|
|
|
this.setState({
|
|
|
readRepoTimeout: true
|
|
|
})
|
|
|
readingRepoTimes = 0;
|
|
|
showSnackbar(`网络异常,请稍后重试。`);
|
|
|
return
|
|
|
}else{
|
|
|
if (status === 0) {
|
|
|
// 轮训后点击切换关卡的话,停止上一关的轮训
|
|
|
if (game.identifier == this.oldGameIdentifier) {
|
|
|
this.oldGameIdentifier = null;
|
|
|
return;
|
|
|
}
|
|
|
// 重试调用的这个 this.props.fetchRepositoryCode(this.props, null, null, true)
|
|
|
setTimeout(() => {
|
|
|
this.fetchRepositoryCode(props, arg_path, type, 1)
|
|
|
}, 1500);
|
|
|
}
|
|
|
}
|
|
|
} else if (fetchRepositoryCodeResponse.data.status === -4) {
|
|
|
// 异常 直接重试
|
|
|
this.fetchRepositoryCode(props, null, null, true)
|
|
|
} else{
|
|
|
this.setState({ isEditablePath, currentPath: path });
|
|
|
this.oldRepositoryCode = fetchRepositoryCodeResponse.data.content || '';
|
|
|
this.updateRepositoryCode(this.oldRepositoryCode, updateCodeMirror)
|
|
|
}
|
|
|
|
|
|
}).catch(error =>{
|
|
|
console.log(error)
|
|
|
this.setState({ codeLoading: false });
|
|
|
showSnackbar(`服务端异常,请联系管理员!`);
|
|
|
})
|
|
|
}
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
|
|
|
|
window.$(window.documents).bind("submitChoose",function(){
|
|
|
|
|
|
alert("hello world!");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
doFileUpdateRequestOnCodeMirrorBlur = () => {
|
|
|
var fileUpdatePromise = this.doFileUpdateRequest(true)
|
|
|
if (fileUpdatePromise) {
|
|
|
fileUpdatePromise.then((resData) => {
|
|
|
if (resData.status === -1) { // 保存文件出现异常
|
|
|
this.setState({
|
|
|
codeStatus: SAVE_FAILED
|
|
|
})
|
|
|
return;
|
|
|
}
|
|
|
this.setState({
|
|
|
codeStatus: SAVED
|
|
|
})
|
|
|
}).catch((e) => {
|
|
|
console.log(e)
|
|
|
this.setState({
|
|
|
codeStatus: SAVE_FAILED
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
componentWillUnmount() {
|
|
|
this.autoSaveInterval && clearInterval(this.autoSaveInterval);
|
|
|
}
|
|
|
|
|
|
onEditorBlur = () => {
|
|
|
if (this.autoSaveInterval) {
|
|
|
clearInterval(this.autoSaveInterval);
|
|
|
this.autoSaveInterval = null;
|
|
|
}
|
|
|
this.doFileUpdateRequestOnCodeMirrorBlur()
|
|
|
}
|
|
|
onEditorFocus = () => {
|
|
|
if (this.autoSaveInterval) {
|
|
|
return;
|
|
|
}
|
|
|
this.autoSaveInterval = setInterval( () => {
|
|
|
// code变化时,,每30秒保存到数据库,没变化则不处理
|
|
|
var autoSave = true
|
|
|
var fileUpdatePromise = this.doFileUpdateRequest(true)
|
|
|
if (fileUpdatePromise) {
|
|
|
fileUpdatePromise.then((resData) => {
|
|
|
// TODO 保存失败的处理
|
|
|
this.setState({
|
|
|
codeStatus: SAVED
|
|
|
})
|
|
|
}).catch((e) => {
|
|
|
console.log(e)
|
|
|
})
|
|
|
}
|
|
|
}, 30000);
|
|
|
}
|
|
|
|
|
|
codemirrorDidMount() {
|
|
|
var editor_CodeMirror = window.editor_CodeMirror;
|
|
|
var editor_monaco = window.editor_monaco;
|
|
|
// 失去焦点终止保存事件
|
|
|
// editor_CodeMirror && editor_CodeMirror.on('blur', this.onEditorBlur);
|
|
|
// editor_monaco && editor_monaco.onDidBlurEditorText(this.onEditorBlur)
|
|
|
|
|
|
// 每60秒钟保存一次
|
|
|
editor_CodeMirror && editor_CodeMirror.on('focus', this.onEditorFocus);
|
|
|
editor_monaco && editor_monaco.onDidFocusEditorText(this.onEditorFocus)
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
_refreshHtmlIframe(newCode) {
|
|
|
// TODO 是否是html的code
|
|
|
const isHtmlCode = this.props.challenge.isHtml;
|
|
|
if (isHtmlCode) {
|
|
|
// TODO 参考 _code_actions.html.erb 做link script标签替换
|
|
|
// var $iframe = window.$('#htmlIframe');
|
|
|
// $iframe.ready( () => {
|
|
|
// $iframe.contents().find("body").html(newCode || this.state.repositoryCode);
|
|
|
// });
|
|
|
window.tpi_html_show(newCode)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
updateRepositoryCode(newCode, callback) {
|
|
|
this.setState({
|
|
|
codeLoading: false,
|
|
|
repositoryCode: newCode,
|
|
|
}, () => { callback && callback() });
|
|
|
this._refreshHtmlIframe(newCode)
|
|
|
}
|
|
|
|
|
|
onRepositoryCodeUpdate(newCode) {
|
|
|
if (this.refreshHtmlTimeout) {
|
|
|
clearTimeout(this.refreshHtmlTimeout)
|
|
|
}
|
|
|
// 每2s刷新一次
|
|
|
this.setState({
|
|
|
codeStatus: UPDATED
|
|
|
})
|
|
|
this.refreshHtmlTimeout = setTimeout(()=>{
|
|
|
// this.setState({
|
|
|
// // codeStatus: UPDATED,
|
|
|
// repositoryCode: newCode
|
|
|
// });
|
|
|
|
|
|
this._refreshHtmlIframe(newCode)
|
|
|
|
|
|
this.refreshHtmlTimeout = null;
|
|
|
}, 1500)
|
|
|
|
|
|
}
|
|
|
|
|
|
// forTest true 是评测时触发的file_update
|
|
|
doFileUpdateRequest(checkIfCodeChanged, forTest) {
|
|
|
const { codeStatus } = this.state;
|
|
|
if (!forTest && codeStatus !== UPDATED) { // 已修改状态才能保存
|
|
|
return;
|
|
|
}
|
|
|
// TODO 连着2次请求服务端会报500
|
|
|
// if (forTest == true) {
|
|
|
// if (this.state.codeStatus != UPDATED) {
|
|
|
|
|
|
// return;
|
|
|
// }
|
|
|
// }
|
|
|
let codeContent = this.state.repositoryCode;
|
|
|
if (window['editor_CodeMirror']) {
|
|
|
codeContent = window.editor_CodeMirror.getValue();
|
|
|
} else if (window.editor_monaco) {
|
|
|
codeContent = window.editor_monaco.getValue()
|
|
|
}
|
|
|
if (checkIfCodeChanged === true && this.oldRepositoryCode == codeContent) {
|
|
|
// console.log('code not changed');
|
|
|
this.setState({
|
|
|
codeStatus: SAVED
|
|
|
})
|
|
|
return null;
|
|
|
}
|
|
|
const { challenge, output_sets, onRunCodeTestFinish, myshixun } = this.props
|
|
|
// var url = `${locationPath}/file_update?path=${encodeURIComponent(challenge.path)}`
|
|
|
var url = `/myshixuns/${myshixun.identifier}/update_file.json`
|
|
|
|
|
|
this.setState({
|
|
|
codeStatus: SAVING
|
|
|
})
|
|
|
this.oldRepositoryCode = codeContent;
|
|
|
|
|
|
let argPath;
|
|
|
if (challenge.pathIndex === -1) { // 当前是只读文件
|
|
|
argPath = challenge.multiPath === true ? challenge.path[0] : challenge.path
|
|
|
} else {
|
|
|
argPath = challenge.multiPath === true ? challenge.path[challenge.pathIndex] : challenge.path
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 跨域请求时会多一个options请求
|
|
|
// https://github.com/axios/axios/issues/475
|
|
|
return axios.post(url, {
|
|
|
content: codeContent,
|
|
|
// 评测的时候传1,其它情况不用传,主要是为了区分是用户自己提交还是自动提交
|
|
|
// type: forTest === true ? 1 : 0,
|
|
|
evaluate: forTest === true ? 1 : 0,
|
|
|
game_id : this.props.game.id,
|
|
|
path: argPath
|
|
|
},
|
|
|
{
|
|
|
// withCredentials: true
|
|
|
}
|
|
|
|
|
|
)
|
|
|
}
|
|
|
|
|
|
onRunChooseTest() {
|
|
|
const { st, game, onRunChooseTestFinish, showSnackbar } = this.props;
|
|
|
// 获取form表单值
|
|
|
var value = this.refs.mainContent.refs.chooseQ.getForm().getFieldsValue();
|
|
|
|
|
|
var valueArray = []; // map转array
|
|
|
var unSelectOptionIndexArray = [] // 自己做未选提示
|
|
|
for(var key in value) {
|
|
|
if (!value[key] || (_.isArray(value[key]) && !value[key].join('')[0])) {
|
|
|
unSelectOptionIndexArray.push(key)
|
|
|
break; // 为空处理
|
|
|
}
|
|
|
// TODO array判断
|
|
|
valueArray[parseInt(key)] = _.isArray(value[key]) ? value[key].join('') : value[key] ;
|
|
|
}
|
|
|
if (unSelectOptionIndexArray.length) {
|
|
|
var unSelectOptionIndex = unSelectOptionIndexArray[0]
|
|
|
var _unSelectOptionDom = window.$('#games_repository_contents #choice' + unSelectOptionIndex )[0]
|
|
|
_unSelectOptionDom.scrollIntoView()
|
|
|
showSnackbar(`请先给第${parseInt(unSelectOptionIndex)+1}题选择一个答案。`) // , 'top', 'center'
|
|
|
// 有未选择的题
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// TODO 未选提示、props.onRunChooseTest
|
|
|
console.log(unSelectOptionIndexArray)
|
|
|
|
|
|
console.log('valueArray', valueArray)
|
|
|
|
|
|
var url = `/api/v1/games/${game.identifier}/choose_build`
|
|
|
this.setState({
|
|
|
gameBuilding: true,
|
|
|
})
|
|
|
axios.post(url, {
|
|
|
answer: valueArray
|
|
|
},
|
|
|
{
|
|
|
withCredentials: true,
|
|
|
// headers: {
|
|
|
// 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
|
// }
|
|
|
}
|
|
|
).then((response) => {
|
|
|
if (response.data.test_sets) { // 评测完成
|
|
|
|
|
|
onRunChooseTestFinish(response.data);
|
|
|
this.setState({
|
|
|
gameBuilding: false,
|
|
|
})
|
|
|
}
|
|
|
console.log(response)
|
|
|
}).catch((error) => {
|
|
|
console.log(error)
|
|
|
this.setState({
|
|
|
gameBuilding: false,
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
setGameBuildFalse() {
|
|
|
this.setState({
|
|
|
gameBuilding: false
|
|
|
})
|
|
|
}
|
|
|
onRunCodeTest() {
|
|
|
// tipContent(0, 100, 30, 360, 1)
|
|
|
// return; // for test
|
|
|
|
|
|
const { st, challenge, output_sets, onRunCodeTestFinish, loading, showDialog, handleGdialogClose, onPathChange } = this.props
|
|
|
const { isEditablePath } = this.state;
|
|
|
if (isEditablePath === false) {
|
|
|
showDialog({
|
|
|
contentText: '需要先切回可编辑的文件才可评测,确认要现在切换吗?',
|
|
|
callback: () => {
|
|
|
onPathChange(0)
|
|
|
handleGdialogClose();
|
|
|
}
|
|
|
})
|
|
|
return;
|
|
|
}
|
|
|
if (loading === true) {
|
|
|
return; // 还在加载中
|
|
|
}
|
|
|
if (st === 1) { // 选择题
|
|
|
this.onRunChooseTest();
|
|
|
return;
|
|
|
}
|
|
|
// ---------------------- 以下是编程题处理 ----------------------
|
|
|
|
|
|
console.log('onRunCodeTest onRunCodeTest')
|
|
|
|
|
|
// http://localhost:3000/myshixuns/so5w6iap97/stages/zl6kx8f7vfpo/file_update?path=src%2Fstep6%2FLinkedList.java
|
|
|
|
|
|
|
|
|
// var url = `${locationPath}/file_update?path=${encodeURIComponent(challenge.path)}`
|
|
|
|
|
|
// var timeOut = parseInt(<%= @myshixun.main_mirror.try(:time_limit) %>); // 超时参数
|
|
|
|
|
|
this.setEvaluateMiddleStatusText('')
|
|
|
|
|
|
this.setState({
|
|
|
gameBuilding: true
|
|
|
})
|
|
|
this.doFileUpdateRequest(null, true)
|
|
|
.then((fileUpdateResponse) => {
|
|
|
console.log(fileUpdateResponse);
|
|
|
if (fileUpdateResponse.data.status === -1) { // 保存文件出现异常
|
|
|
this.setGameBuildFalse()
|
|
|
this.setState({
|
|
|
codeStatus: SAVE_FAILED
|
|
|
})
|
|
|
return;
|
|
|
}
|
|
|
this.setState({
|
|
|
codeStatus: SAVED
|
|
|
})
|
|
|
this.gameBuild(fileUpdateResponse, 1)
|
|
|
})
|
|
|
.catch( (fileUpdateError) => {
|
|
|
console.log(fileUpdateError);
|
|
|
this.setGameBuildFalse()
|
|
|
});
|
|
|
}
|
|
|
setEvaluateMiddleStatusText = (msg) => {
|
|
|
window.$('#evaluateMiddleStatusText').html(msg)
|
|
|
}
|
|
|
// 之前的task_commit方法
|
|
|
gameBuild(fileUpdateResponse, first) {
|
|
|
const { st, challenge, output_sets, onRunCodeTestFinish, resetTestSetsExpandedArray, showSnackbar, time_limit, game } = this.props
|
|
|
const { resubmit, content_modified, sec_key } = fileUpdateResponse.data;
|
|
|
const timeOut = time_limit;
|
|
|
// http://localhost:3000/myshixuns/so5w6iap97/stages/zl6kx8f7vfpo/game_build?first=1&resubmit=GDBEX741_1993
|
|
|
// const game_build_url = `${locationPath}/game_build?first=${first}&resubmit=${resubmit}&content_modified=${content_modified}`
|
|
|
const game_build_url = `/tasks/${game.identifier}/game_build.json?first=${first}&resubmit=${resubmit}&content_modified=${!!content_modified ? 1 : 0}&sec_key=${sec_key}`
|
|
|
|
|
|
// var timeOut = parseInt(<%= @myshixun.main_mirror.try(:time_limit) %>); // 超时参数
|
|
|
|
|
|
resetTestSetsExpandedArray();
|
|
|
axios.get(game_build_url, {
|
|
|
// withCredentials: true,
|
|
|
}).then((gameBuildResponse) => {
|
|
|
console.log(gameBuildResponse);
|
|
|
// D:\Code\trustieplus\app\views\games\_code_actions.html.erb
|
|
|
|
|
|
let { port, ableToCreate, waitNum, waitingTime, had_done } = gameBuildResponse.data;
|
|
|
// http://localhost:3000/myshixuns/so5w6iap97/stages/zl6kx8f7vfpo/game_status?resubmit=GDBEX741_1993&port=50241&time_out=false
|
|
|
|
|
|
// TODO dead code 往下走
|
|
|
|
|
|
// if (ableToCreate == 1) {
|
|
|
|
|
|
if (gameBuildResponse.data.status == -1) {
|
|
|
return
|
|
|
}
|
|
|
let timeOutFlag = false;
|
|
|
const intervalDuring = 1000;
|
|
|
let requestTimes = 0;
|
|
|
|
|
|
var gameStatusIntervalId = setInterval(()=>{
|
|
|
// let game_status_url = `${locationPath}/game_status?port=${port}&resubmit=${resubmit||""}&time_out=${timeOutFlag}`
|
|
|
let game_status_url = `/tasks/${game.identifier}/game_status.json?port=${port}&resubmit=${resubmit||""}&time_out=${timeOutFlag}&sec_key=${sec_key}`
|
|
|
|
|
|
axios.get(game_status_url, {
|
|
|
// withCredentials: true,
|
|
|
}).then((gameStatusResponse) => {
|
|
|
requestTimes++;
|
|
|
const { status, running_code_message } = gameStatusResponse.data;
|
|
|
|
|
|
if(!gameStatusResponse.data || !status && status !== 0) {
|
|
|
if(requestTimes >= timeOut - 1){ // 最后一次参数改为true
|
|
|
timeOutFlag = true;
|
|
|
}
|
|
|
if (running_code_message) {
|
|
|
this.setEvaluateMiddleStatusText(running_code_message)
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (timeOutFlag === false && (status === 2 || status === 0)) {
|
|
|
// 网络太慢或服务处理较慢的情况下,可能这里会执行第二次
|
|
|
console.log('clearIntervalclearIntervalclearIntervalclearInterval status2 0')
|
|
|
clearInterval(gameStatusIntervalId);
|
|
|
onRunCodeTestFinish(gameStatusResponse.data)
|
|
|
this.setGameBuildFalse()
|
|
|
timeOutFlag = true; // 用这个处理重复的获取status为2或0的状态
|
|
|
}
|
|
|
// 超时处理
|
|
|
if(timeOutFlag === true) {
|
|
|
// clearInterval(gameStatusIntervalId);
|
|
|
console.log('超时处理返回值')
|
|
|
this._refreshHtmlIframe();
|
|
|
this.setGameBuildFalse()
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(requestTimes >= timeOut - 1){ // 最后一次参数改为true
|
|
|
timeOutFlag = true;
|
|
|
}
|
|
|
|
|
|
|
|
|
}).catch((gameStatusError) => {
|
|
|
console.log(gameStatusError);
|
|
|
})
|
|
|
|
|
|
if (timeOutFlag === true) {
|
|
|
clearInterval(gameStatusIntervalId);
|
|
|
this.setState({
|
|
|
open: true,
|
|
|
gameBuilding: false
|
|
|
})
|
|
|
}
|
|
|
}, intervalDuring)
|
|
|
|
|
|
window.gameStatusIntervalId = gameStatusIntervalId;
|
|
|
// } else if(ableToCreate === 0) { // 排队等待,重新执行gameBuild
|
|
|
// const mintes = parseInt((waitNum * 120) / 60); // 等待时间 = 等待人数 * (15(秒)---3分钟的随机数);
|
|
|
// tipContent(ableToCreate, waitNum, mintes, waitNum * 180, first);
|
|
|
// setTimeout(()=>{
|
|
|
// this.gameBuild(fileUpdateResponse, first + 1)
|
|
|
// }, 10000)
|
|
|
// this.setGameBuildFalse()
|
|
|
// } else if(ableToCreate == -1) { // 排队人数过多,建议先玩下一关
|
|
|
// tipContent(ableToCreate, waitNum, 0, 0, first);
|
|
|
// this.setGameBuildFalse()
|
|
|
// } else {
|
|
|
// showSnackbar("实训云平台繁忙(繁忙等级:94),请稍后刷新并重试")
|
|
|
// this.setGameBuildFalse()
|
|
|
// }
|
|
|
|
|
|
|
|
|
}).catch((gameBuildError) => {
|
|
|
console.log(gameBuildError);
|
|
|
this.setGameBuildFalse()
|
|
|
})
|
|
|
}
|
|
|
handleResetCodeDialogClose() {
|
|
|
this.setState({ resetCodeDialogOpen: false })
|
|
|
}
|
|
|
showResetCodeDialog() {
|
|
|
this.setState({ resetCodeDialogOpen: true })
|
|
|
}
|
|
|
doResetCode() {
|
|
|
const { game, challenge, showSnackbar } = this.props;
|
|
|
this.handleResetCodeDialogClose();
|
|
|
|
|
|
let path = challenge.path
|
|
|
if (challenge.multiPath) {
|
|
|
path = path[challenge.pathIndex]
|
|
|
}
|
|
|
// const url = `/api/v1/games/${game.identifier}/reset_original_code?path=${path}`;
|
|
|
const url = `/tasks/${game.identifier}/reset_original_code.json?path=${path}`;
|
|
|
this.setState({
|
|
|
codeLoading: true
|
|
|
})
|
|
|
axios.get(url, {
|
|
|
// withCredentials: true,
|
|
|
}).then((res) => {
|
|
|
this.handleResetCodeDialogClose();
|
|
|
if (res.data.status === -1) {
|
|
|
console.error('代码重置失败!');
|
|
|
this.setState({
|
|
|
codeLoading: false
|
|
|
})
|
|
|
showSnackbar(res.data.message || '加载初始的代码失败')
|
|
|
return;
|
|
|
}
|
|
|
// this.setState({
|
|
|
// codeLoading: false
|
|
|
// })
|
|
|
this.updateRepositoryCode(res.data.content, updateCodeMirror)
|
|
|
}).catch(error =>{
|
|
|
this.setState({
|
|
|
codeLoading: false
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
handleResetPassedCodeDialogClose() {
|
|
|
this.setState({ resetPassedCodeDialogOpen: false })
|
|
|
}
|
|
|
showResetPassedCodeDialog() {
|
|
|
this.setState({ resetPassedCodeDialogOpen: true })
|
|
|
}
|
|
|
doResetPassedCode() {
|
|
|
this.handleResetPassedCodeDialogClose();
|
|
|
const { game, challenge, showSnackbar } = this.props;
|
|
|
let path = challenge.path
|
|
|
if (challenge.multiPath) {
|
|
|
path = path[challenge.pathIndex]
|
|
|
}
|
|
|
// const url = `/api/v1/games/${game.identifier}/reset_passed_code?path=${path}`;
|
|
|
const url = `/tasks/${game.identifier}/reset_passed_code.json?path=${path}`;
|
|
|
this.setState({
|
|
|
codeLoading: true
|
|
|
})
|
|
|
axios.get(url
|
|
|
// , {
|
|
|
// withCredentials: true,
|
|
|
// }
|
|
|
).then((res) => {
|
|
|
this.handleResetPassedCodeDialogClose();
|
|
|
if (res.data.status === -1) {
|
|
|
console.error('passed代码重置失败!')
|
|
|
this.setState({
|
|
|
codeLoading: false
|
|
|
})
|
|
|
showSnackbar(res.data.message || '加载上次通过的代码失败')
|
|
|
return;
|
|
|
}
|
|
|
// 注意,最好是合并到一个setState, 这里分两次setState的话,触发了2次重新渲染
|
|
|
// this.setState({
|
|
|
// codeLoading: false
|
|
|
// })
|
|
|
this.updateRepositoryCode(res.data.content, updateCodeMirror)
|
|
|
}).catch(error =>{
|
|
|
this.setState({
|
|
|
codeLoading: false
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
render() {
|
|
|
const { challenge, output_sets, time_limit } = this.props;
|
|
|
// TODO 这里直接写死了,game_status在超时时也会返回这句话
|
|
|
let msg = (
|
|
|
<div>
|
|
|
{`代码评测超时!本关限定时间为:${time_limit}s,`}<br></br>
|
|
|
请检查代码是否存在死循环或其他耗时操作
|
|
|
</div>)
|
|
|
|
|
|
return (
|
|
|
<React.Fragment>
|
|
|
<Dialog
|
|
|
open={this.state.open}
|
|
|
disableEscapeKeyDown={true}
|
|
|
onClose={this.handleClose}
|
|
|
>
|
|
|
<DialogTitle id="alert-dialog-title">{"提示"}</DialogTitle>
|
|
|
<DialogContent>
|
|
|
<DialogContentText id="alert-dialog-description" style={{textAlign: 'center'}}>
|
|
|
{ msg }
|
|
|
</DialogContentText>
|
|
|
</DialogContent>
|
|
|
<DialogActions>
|
|
|
<Button onClick={this.handleClose} color="primary">
|
|
|
关闭
|
|
|
</Button>
|
|
|
</DialogActions>
|
|
|
</Dialog>
|
|
|
|
|
|
<Dialog
|
|
|
open={this.state.resetCodeDialogOpen}
|
|
|
disableEscapeKeyDown={true}
|
|
|
onClose={() => this.handleResetCodeDialogClose()}
|
|
|
>
|
|
|
<DialogTitle id="alert-dialog-title">{"提示"}</DialogTitle>
|
|
|
<DialogContent>
|
|
|
<div style={{textAlign: 'center'}}>
|
|
|
你在本文件中修改的内容将丢失<br></br>是否确定重新加载初始代码?
|
|
|
</div>
|
|
|
</DialogContent>
|
|
|
<DialogActions>
|
|
|
<Button onClick={() => this.handleResetCodeDialogClose()} color="primary">
|
|
|
关闭
|
|
|
</Button>
|
|
|
<Button variant="raised" onClick={() => this.doResetCode()} color="primary">
|
|
|
确定
|
|
|
</Button>
|
|
|
</DialogActions>
|
|
|
</Dialog>
|
|
|
|
|
|
<Dialog
|
|
|
disableEscapeKeyDown={true}
|
|
|
open={this.state.resetPassedCodeDialogOpen}
|
|
|
onClose={() => this.handleResetPassedCodeDialogClose()}
|
|
|
>
|
|
|
<DialogTitle id="alert-dialog-title">{"提示"}</DialogTitle>
|
|
|
<DialogContent>
|
|
|
<div style={{textAlign: 'center'}}>
|
|
|
你在本关中修改的内容将丢失<br></br>是否确定重新加载上次通过的代码?
|
|
|
</div>
|
|
|
</DialogContent>
|
|
|
<DialogActions>
|
|
|
<Button onClick={() => this.handleResetPassedCodeDialogClose()} color="primary">
|
|
|
关闭
|
|
|
</Button>
|
|
|
<Button variant="raised" onClick={() => this.doResetPassedCode()} color="primary">
|
|
|
确定
|
|
|
</Button>
|
|
|
</DialogActions>
|
|
|
</Dialog>
|
|
|
|
|
|
<UpdateDrawer {...this.props} fetchRepositoryCode={this.fetchRepositoryCode}></UpdateDrawer>
|
|
|
<MainContent ref="mainContent" {...this.props} output_sets={output_sets} {...this.state}
|
|
|
onRepositoryCodeUpdate={this.onRepositoryCodeUpdate} onRunCodeTest={this.onRunCodeTest}
|
|
|
codemirrorDidMount={this.codemirrorDidMount} fetchRepositoryCode={this.fetchRepositoryCode}
|
|
|
showResetCodeDialog={this.showResetCodeDialog} showResetPassedCodeDialog={this.showResetPassedCodeDialog}
|
|
|
doFileUpdateRequestOnCodeMirrorBlur={this.doFileUpdateRequestOnCodeMirrorBlur} ></MainContent>
|
|
|
</React.Fragment>
|
|
|
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export default MainContentContainer;
|