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.handleGdialogClose()} > {"提示"} {this.state.gDialogContentText} { this.isSingleButton ? : } {this.moreButtonsRender && this.moreButtonsRender()} this.handleSnackbarClose()} transition={Fade} SnackbarContentProps={{ 'aria-describedby': 'message-id', }} resumeHideDuration={2000} message={{this.state.snackbarText}} /> {this.props.children} ) } } export default withStyles(styles) (TPIContextProvider);