import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import Snackbar from 'material-ui/Snackbar';
import Fade from 'material-ui/transitions/Fade';
import update from 'immutability-helper'
import Dialog, {
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from 'material-ui/Dialog';
import Button from 'material-ui/Button';
import EvaluateSuccessEffectDisplay from './EvaluateSuccessEffectDisplay'
import _ from 'lodash'
/*
若干js库
http://inorganik.github.io/countUp.js/
*/
/*
切下一关需要更新:
LeftViewContainer state.gameAnswer
*/
import TPIContext from './TPIContext'
import { EDU_ADMIN, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL} from 'educoder'
import { MuiThemeProvider, createMuiTheme, withStyles } from 'material-ui/styles';
import MUIDialogStyleUtil from '../modules/page/component/MUIDialogStyleUtil'
const styles = MUIDialogStyleUtil.getTwoButtonStyle()
// 主题自定义
const theme = createMuiTheme({
palette: {
primary: {
main: '#4CACFF',
contrastText: 'rgba(255, 255, 255, 0.87)'
},
secondary: { main: '#4CACFF' }, // This is just green.A700 as hex.
},
});
const testSetsExpandedArrayInitVal = [false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false,
false, false, false, false, false]
window.__fetchAllFlag = false; // 是否调用过fetchAll TODO 如何多次使用provider?
class TPIContextProvider extends Component {
constructor(props) {
super(props)
this.onRunCodeTestFinish = this.onRunCodeTestFinish.bind(this)
this.onRunChooseTestFinish = this.onRunChooseTestFinish.bind(this)
this.testSetUnlock = this.testSetUnlock.bind(this)
this.onTestSetHeaderClick = this.onTestSetHeaderClick.bind(this)
this.onShowPrevStage = this.onShowPrevStage.bind(this)
this.onShowNextStage = this.onShowNextStage.bind(this)
this.readGameAnswer = this.readGameAnswer.bind(this)
this.praisePlus = this.praisePlus.bind(this)
this.onGamePassed = this.onGamePassed.bind(this)
this.onPathChange = this.onPathChange.bind(this)
this.showSnackbar = this.showSnackbar.bind(this)
this.showDialog = this.showDialog.bind(this)
this.onShowUpdateDialog = this.onShowUpdateDialog.bind(this)
this.updateDialogClose = this.updateDialogClose.bind(this)
// this.showEffectDisplay();
this.state = {
loading: true, // 正在加载数据
gDialogOpen: false,
currentGamePassed: false, // 当前game评测通过
currentPassedGameGainGold: 0, // 当前通过的game获得的金币数
currentPassedGameGainExperience: 0, // 当前通过的game获得的经验数
user: {},
challenge: {},
shixun_name: '',
hide_code: false,
showUpdateDialog: false,
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0),
}
}
showEffectDisplay = (data) => {
const dom = document.getElementById('picture_display');
window.$(dom).show();
ReactDOM.render(, dom);
}
onShowUpdateDialog() {
this.setState({showUpdateDialog: true})
}
// updateNowSuccess true 立即更新成功
// TODO updateDialogClose方法名不对, 改为updateDialogCallback
updateDialogClose(nextUpdateSuccess, updateNowSuccess) {
const { myshixun } = this.state;
if (nextUpdateSuccess) {
myshixun.system_tip = true;
}
let { tpm_cases_modified, tpm_modified, tpm_script_modified } = this.state;
if (updateNowSuccess) {
tpm_cases_modified = false;
tpm_modified = false;
tpm_script_modified = false;
}
this.setState({
myshixun,
tpm_cases_modified,
tpm_modified,
tpm_script_modified,
showUpdateDialog: false
})
}
componentWillUnmount() {
this.costTimeInterval && window.clearInterval(this.costTimeInterval)
}
componentDidMount() {
// TODO 登录状态的判断?
// request
// var shixunId = this.props.match.params.shixunId;
var stageId = this.props.match.params.stageId;
window.__fetchAllFlag = false;
this.fetchAll(stageId);
this.costTimeInterval = window.setInterval(()=> {
const { game } = this.state;
if (!game || game.status === 2) { // 已完成的任务不需要计时
return;
}
if (game.cost_time || game.cost_time === 0) {
// game.cost_time += 1;
this.setState({
game: update(game, {cost_time: { $set: (game.cost_time+1) }})
})
}
}, 1000)
// 页面离开时存下用户的任务耗时
window.$(window).unload( ()=>{
this._updateCostTime();
});
}
// force 评测通过后,异步执行该方法,强制同步costTime到服务端
_updateCostTime(async = false, force) {
const { game, loading } = this.state;
// TODO 还有一种情况,通关后cost_time计时停止,没法通过这个判断
if (!force && (loading || !game || game.status === 2)) {
return; // 已完成的任务不需要处理
}
let testPath = ''
if (window.location.port == 3007) {
testPath = 'https://testeduplus2.educoder.net'
}
// var url = `${testPath}/api/v1/games/${ game.identifier }/cost_time`
var url = `${testPath}/api/tasks/${ game.identifier }/cost_time`
window.$.ajax({
type: 'get',
url: url,
async: async, //IMPORTANT, the call will be synchronous
data: {
time: game.cost_time
}
}).done((data) => {
console.log('complete');
});
}
onGamePassed(passed) {
const { game } = this.state
// 随便给个分,以免重新评测时又出现评星组件(注意:目前game.star没有显示在界面上,如果有则不能这么做)
// game.star = 6;
this.setState({
game: update(game, {star: { $set: 6 }}),
currentGamePassed: !!passed
})
}
onTestSetHeaderClick(index) {
// let { testSetsExpandedArray } = this.state;
let testSetsExpandedArray;
// 一次只打开一个
if (this.state.testSetsExpandedArray[index] === false) {
testSetsExpandedArray = testSetsExpandedArrayInitVal.slice(0);
} else {
testSetsExpandedArray = this.state.testSetsExpandedArray.slice(0);
}
testSetsExpandedArray[index] = !testSetsExpandedArray[index];
this.setState({
testSetsExpandedArray,
})
}
onShowPrevStage() {
}
onShowNextStage() {
window.__fetchAllFlag = false;
console.log('onShowNextStage.........')
// this.fetchAll('vznhx7mctwfq')
}
componentWillReceiveProps(newProps, oldProps) {
var newStageId = newProps.match.params.stageId;
if (!this.props || newStageId !== this.props.match.params.stageId) {
window.__fetchAllFlag = false;
this.fetchAll(newStageId)
}
}
// praise_tread/praise_plus?obj_id=569&obj_type=Challenge&horizontal=true&game_praise=true
/*
TODO 旧的接口在未登录时的返回值
//获取登录页面地址
var signinPath = '/';
var htmlvalue = '
';
pop_box_new(htmlvalue, 480, 182);
*/
praisePlus() {
const { challenge, game } = this.state;
let praise = true;
const url = `/tasks/${game.identifier}/plus_or_cancel_praise.json`
// const url = `/praise_tread/praise_plus?obj_id=${challenge.id}&obj_type=Challenge&horizontal=${praise}&game_praise=true`
axios.post(url)
.then((response) => {
if (response.data) {
const { praise_count, praise } = response.data;
// challenge.praise_count = praise_tread_count;
// challenge.user_praise = praise;
this.setState({ challenge: update(challenge,
{
praise_count: { $set: praise_count },
user_praise: { $set: praise },
})
})
}
})
.catch(function (error) {
console.log(error);
});
}
onPathChange(index) {
let { challenge } = this.state;
// challenge = Object.assign({}, challenge)
// challenge.pathIndex = index;
this.setState({
challenge: update(challenge, {pathIndex: { $set: index }}),
})
// TODO load new path content
}
updateChallengePath = (path) => {
const challenge = this.state.challenge;
if (challenge.path === path) {
return;
}
const { myshixun } = this.state;
// myshixun.system_tip = false;
challenge.path = path;
const newChallenge = this.handleChallengePath(challenge);
this.setState({ challenge: newChallenge,
myshixun: update(myshixun, {system_tip: { $set: false }}),
})
}
handleChallengePath(challenge) {
if (challenge.path && typeof challenge.path === "string") { // 多path的处理
let path = challenge.path.split(';');
_.remove(path, (item)=> !item)
if (path.length > 1) {
challenge.path = path;
challenge.multiPath = true;
} else {
challenge.path = challenge.path.replace(';', '').trim() // 多path 改为单path 出现了 aaa.java;的情况
challenge.multiPath = false;
}
}
challenge.pathIndex = 0;
return challenge;
}
newResData2OldResData(newResData) {
newResData.latest_output = newResData.last_compile_output
// newResData.power
newResData.record = newResData.record_onsume_time
// 老版用的hide_code
newResData.hide_code = newResData.shixun.hide_code;
newResData.image_url = newResData.user.image_url
newResData.grade = newResData.user.grade
newResData.user_url = newResData.user.user_url
newResData.username = newResData.user.name
newResData.output_sets = {}
// newResData.output_sets.had_test_count = newResData.test_sets_count
newResData.output_sets.test_sets = newResData.test_sets // JSON.stringify()
newResData.output_sets.test_sets_count = newResData.test_sets_count
// newResData.output_sets.had_passed_testsests_error_count = newResData.sets_error_count
newResData.output_sets.had_passed_testsests_error_count = newResData.test_sets_count
- newResData.sets_error_count
// allowed_hidden_testset
// sets_error_count
// test_sets_count
// test_sets
// had_passed_testsests_error_count
// test_sets
// test_sets
return newResData
}
// 将若干数据重新组织一下
_handleResponseData(resData_arg) {
const resData = this.newResData2OldResData(Object.assign({}, resData_arg))
let challenge = resData.challenge;
challenge.isHtml = false;
challenge.isWeb = false;
challenge.isAndroid = false;
challenge.showLanguagePictrue = false;
challenge.hasAnswer = resData.has_answer;
let output_sets = resData.output_sets;
if (resData.st === 0) { // 代码题
challenge = this.handleChallengePath(challenge)
const mirror_name = (resData.mirror_name && resData.mirror_name.join)
? resData.mirror_name.join(';') : (resData.mirror_name || '');
if (mirror_name.indexOf('Html') !== -1) {
challenge.isHtml = true;
challenge.showLanguagePictrue = true;
} else if (mirror_name.indexOf('Web') !== -1 || mirror_name.indexOf('JFinal') !== -1) {
challenge.isWeb = true;
} else if (mirror_name.indexOf('Android') !== -1) {
challenge.isAndroid = true;
}
if (output_sets && output_sets.test_sets && typeof output_sets.test_sets == 'string') {
const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
output_sets.test_sets_array = test_sets_array;
} else {
output_sets.test_sets_array = output_sets.test_sets
}
} else { // 选择题
// 选择题题干markdown初始化
const $ = window.$
window.setTimeout(()=>{
var lens = $("#choiceRepositoryView textarea").length;
for(var i = 1; i <= lens; i++){
window.editormd.markdownToHTML("choose_subject_" + i, {
htmlDecode: "style,script,iframe", // you can filter tags decode
taskList: true,
tex: true, // 数学公式
// flowChart: true, // 默认不解析
// sequenceDiagram: true // 默认不解析
});
}
}, 400)
}
challenge.user_praise = resData.user_praise;
challenge.praise_count = resData.praise_count;
challenge.showWebDisplayButton = false;
resData.challenge = challenge;
// 将一些属性写到game上
let game = resData.game;
game.prev_game = resData.prev_game;
game.next_game = resData.next_game;
resData.game = game;
const { tpm_cases_modified, tpm_modified, tpm_script_modified, myshixun } = resData;
if (myshixun.system_tip) {
// system_tip为true的时候 不弹框提示用户更新
resData.showUpdateDialog = false
} else {
let needUpdateScript = (tpm_modified || tpm_script_modified) && challenge.st === 0;
resData.showUpdateDialog = needUpdateScript || tpm_cases_modified
}
/**
email: "721773699@qq.com"
grade: 213996
identity: 1
image_url: "avatars/User/1"
login: "innov"
name: "Coder"
user_url: "/users/innov"
*/
let user = resData.user;
user.username = resData.user.name;
// user.user_url = resData.user_url;
// user.image_url = resData.image_url;
user.is_teacher = resData.is_teacher;
resData.user = user;
this._handleUserAuthor(resData)
// TODO 测试
// resData.power = 0;
this.setState({
...resData,
currentGamePassed: false,
loading: false,
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0),
})
window.document.title = resData.shixun.name
window.__myshixun = resData.myshixun; // tpi_html_show需要用到
}
_handleUserAuthor(resData) {
// tpi tpm权限控制
// const EDU_ADMIN = 1 // 超级管理员
// const EDU_SHIXUN_MANAGER = 2 // 实训管理员
// const EDU_SHIXUN_MEMBER = 3 // 实训成员
// const EDU_CERTIFICATION_TEACHER = 4 // 平台认证的老师
// const EDU_GAME_MANAGER = 5 // TPI的创建者
// const EDU_TEACHER = 6 // 平台老师,但是未认证
// const EDU_NORMAL = 7 // 普通用户
// myshixun_manager power is_teacher
resData.power = 0
resData.myshixun_manager = false
resData.is_teacher = false
if (resData.user.identity === EDU_ADMIN) {
resData.power = 1
resData.myshixun_manager = true
} else if (resData.user.identity === EDU_SHIXUN_MANAGER) {
resData.power = 1
resData.myshixun_manager = true
} else if (resData.user.identity === EDU_SHIXUN_MEMBER) {
resData.power = 1
resData.myshixun_manager = true
} else if (resData.user.identity === EDU_CERTIFICATION_TEACHER) {
resData.power = 1
resData.is_teacher = true
} else if (resData.user.identity === EDU_TEACHER) {
resData.is_teacher = true
} else if (resData.user.identity === EDU_NORMAL) {
}
return resData
}
fetchAll(stageId) {
if (window.__fetchAllFlag == true ) {
console.log('TPIContextProvider call fetchAll repeatly!')
return;
}
// 切换关卡的时候,同步costTime
this._updateCostTime(true);
if (!stageId) {
// stageId = 'zl6kx8f7vfpo';
// http://localhost:3000/myshixuns/so5w6iap97/stages/zl6kx8f7vfpo
}
// var url = `/api/v1/games/${stageId}`
var url = `/tasks/${stageId}.json`
// {"status":1,"message":"undefined method `authenticate!' for #"}
window.__fetchAllFlag = true;
this.setState({
loading: true,
currentGamePassed: false, // 切换game时重置passed字段
})
axios.get(url, {
// https://stackoverflow.com/questions/48861290/the-value-of-the-access-control-allow-origin-header-in-the-response-must-not-b
// withCredentials: true,
})
.then((response) => {
// {"status":1,"message":"Unauthorized. \u7528\u6237\u8ba4\u8bc1\u5931\u8d25."}
if (response.data.status == 403) {
window.location.href = "/403";
return;
}
if (response.data.status == 404) {
window.location.href = '/myshixuns/not_found'
return;
}
this._handleResponseData(response.data)
})
.catch(function (error) {
console.log(error);
});
}
readGameAnswer(resData) {
// game.final_score = resData.final_score;
if (resData.final_score) {
var game = this.state.game;
this.setState({
game: update(game, {final_score: { $set: resData.final_score }}),
grade: resData.grade
})
} else {
this.setState({
grade: resData.grade
})
}
}
closeTaskResultLayer() {
this.setState({
currentGamePassed: false
})
}
onRunChooseTestFinish(response) {
const { test_sets, challenge_chooses_count, choose_correct_num, grade, experience, gold, had_submmit, next_game } = response;
response.had_submmit = true; // 是否已提交
const { game } = this.state;
let currentGamePassed = false
if (challenge_chooses_count === choose_correct_num) {
game.status = 2;
game.next_game = next_game;
currentGamePassed = true;
this._updateCostTime(true, true);
}
this.setState({
choose_test_cases: response,
grade: grade,
game,
next_game,
currentGamePassed: currentGamePassed,
currentPassedGameGainGold: gold,
currentPassedGameGainExperience: experience,
})
}
language_display(data) {
const { game, tomcat_url } = this.state;
const $ = window.$;
const challenge = Object.assign({}, this.state.challenge)
if(challenge.isWeb && data.port != -1) {
// var $result = $("#php_display");
challenge.showWebDisplayButton = true; // ActionView处是否出现查看效果按钮
const path = challenge.web_route || challenge.path
const webDisplayUrl = `${tomcat_url}:${data.port}/${path}`
challenge.webDisplayUrl = webDisplayUrl
challenge.showLanguagePictrue = true; // 评测通过弹出层是否出现查看效果按钮
}
// else if(challenge.isAndroid && data.picture != 0){
// // https://www.educoder.net/shixuns/qrcode?game_id=218589&_=1525571882782
// $.ajax({
// url: `/shixuns/qrcode?game_id=${game.id}`,
// dataType: 'script'
// });
// challenge.showLanguagePictrue = true;
// }
else if(data.picture != 0){
// 对应服务端erb文件为 _picture_display.html.erb
// $.ajax({
// url: "/users/picture_show?game_id="+data.picture,
// cache: false,
// dataType: 'script'
// });
/**
{
"type": "image",
"orignal_picture": [],
"user_picture": [],
"answer_picture": []
}
*/
const url = `/tasks/${game.identifier}/picture_display.json`
axios.get(url)
.then((response) => {
// response.data.type qrcode_str
this.showEffectDisplay(response.data)
})
challenge.showLanguagePictrue = true;
}
this.setState({
challenge
})
}
onRunCodeTestFinish(response) {
console.log('onRunCodeTestFinish', response)
const { test_sets, test_sets_count, test_sets_hidden_count, test_sets_public_count
, had_test_count, had_passed_testsests_error_count, had_passed_testsests_hidden_count
, had_passed_testsests_public_count, final_score, gold, experience, latest_output, status
, had_done, score, tag_count, power, record, next_game, grade, picture,
sets_error_count, last_compile_output, record_consume_time} = response;
const { game } = this.state;
const currentGamePassed = this.props.game !== 2 && status === 2
currentGamePassed && this.language_display(response);
// 评测通过了,立即同步costTime
currentGamePassed && this._updateCostTime(true, true);
if (currentGamePassed) {
game.status = 2;
game.next_game = next_game;
} else {
this.showDialog({
contentText: ,
isSingleButton: true
})
}
const output_sets = {
"test_sets": test_sets,
"test_sets_array": test_sets,
"had_test_count": had_test_count || test_sets_count,
"test_sets_count": test_sets_count,
// "had_passed_testsests_error_count": had_passed_testsests_error_count,
"had_passed_testsests_error_count": test_sets_count - sets_error_count,
"test_sets_hidden_count": test_sets_hidden_count,
"test_sets_public_count": test_sets_public_count,
"had_passed_testsests_hidden_count": had_passed_testsests_hidden_count,
"had_passed_testsests_public_count": had_passed_testsests_public_count
};
// if (output_sets && output_sets.test_sets) {
// const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
// output_sets.test_sets_array = test_sets_array;
// }
this.setState({
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
currentGamePassed,
currentPassedGameGainGold: gold,
currentPassedGameGainExperience: experience,
output_sets,
game,
next_game,
latest_output: last_compile_output,
record: record_consume_time,
grade,
had_done,
})
}
resetTestSetsExpandedArray = () => {
this.setState({
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
})
}
testSetUnlock() {
const { game } = this.state;
const url = `/v1/games/${game.identifier}/check_test_sets.json`
axios.get(url, {
withCredentials: true,
})
.then((response) => {
// TODO status -2 重复操作,直接解锁
if (response.data.test_sets == -1) {
console.error('testSetUnlock失败!')
this.showSnackbar(response.data.message)
return;
} else {
// 被扣除的金币,是负数
const deltaScore = response.data.score;
// output_sets
let { output_sets } = this.state;
output_sets = Object.assign({}, output_sets);
const test_sets_array = JSON.parse("[" + response.data.test_sets + "]");
output_sets.test_sets_array = test_sets_array;
this.setState({
output_sets: output_sets,
grade: this.state.grade + deltaScore,
game : update(game, {test_sets_view: { $set: true }}),
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0)
})
this.handleGdialogClose();
}
})
.catch(function (error) {
console.log(error);
});
}
handleSnackbarClose() {
this.setState({
snackbarOpen: false,
snackbarVertical: '',
snackbarHorizontal: '',
})
}
// 全局的snackbar this.props.showSnackbar调用即可
showSnackbar(text, vertical, horizontal) {
this.setState({
snackbarOpen: true,
snackbarText: text,
snackbarVertical: vertical,
snackbarHorizontal: horizontal,
})
}
/*
TODO 写成HOC组件,更好复用
全局的Dialog this.props.showDialog调用即可
@param contentText dialog显示的提示文本
@param callback 确定按钮回调方法
@param moreButtonsRender 除了“确定”、“取消”按钮外的其他按钮
@param okButtonText “确定”按钮显示文本,如 继续查看
*/
showDialog(params) {
const { contentText, callback, moreButtonsRender, okButtonText, isSingleButton } = params;
this.dialogOkCallback = callback;
this.moreButtonsRender = moreButtonsRender
this.okButtonText = okButtonText;
this.isSingleButton = isSingleButton;
this.setState({
gDialogOpen: true,
gDialogContentText: contentText
})
}
onGdialogOkBtnClick() {
this.dialogOkCallback && this.dialogOkCallback();
// this.setState({
// gDialogOpen: true
// })
}
handleGdialogClose = () => {
this.setState({
gDialogOpen: false
})
}
render() {
const { classes } = this.props;
return (
this.closeTaskResultLayer(),
onPathChange: this.onPathChange,
updateChallengePath: this.updateChallengePath,
showSnackbar: this.showSnackbar,
showDialog: this.showDialog,
handleGdialogClose: () => this.handleGdialogClose(),
onShowUpdateDialog: this.onShowUpdateDialog,
updateDialogClose: this.updateDialogClose,
match: this.props.match
}}
>
this.handleSnackbarClose()}
transition={Fade}
SnackbarContentProps={{
'aria-describedby': 'message-id',
}}
resumeHideDuration={2000}
message={{this.state.snackbarText}}
/>
{this.props.children}
)
}
}
export default withStyles(styles) (TPIContextProvider);