import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import Drawer from 'material-ui/Drawer'; import { CircularProgress } from 'material-ui/Progress'; import Tooltip from 'material-ui/Tooltip'; import WebSSHTimer from '../component/WebSSHTimer' import Webssh from './Webssh' import Tree, { TreeNode } from 'rc-tree'; import 'rc-tree/assets/index.css'; import classNames from 'classnames' // import CodeMirror from 'react-codeMirror' import axios from 'axios' import _ from 'lodash' import notEditablePathImg from '../../../images/tpi/notEditablePath.png' import TPICodeMirror from '../component/TPICodeMirror' import TPIMonaco from '../component/monaco/TPIMonaco' import { loadSshScript, openTerminal } from './Webssh' const $ = window.$; const STABLE_SSH_TAB_ID = 81 const addtionalSSHStartId = 82; const totalAddtionalTabCount = 2; let addtionalSSHIdMap = { 82: false, 83: false, // 84: false, // 85: false, // 86: false, }; class CodeRepositoryView extends Component { constructor(props) { super(props); this.treeExpanded = false; this.state = { autoExpandParent: false, expandedKeys: [], addtionalSSHArray: [], sshIsClosed: false } } componentDidUpdate(prevProps, prevState, snapshot) { const { game, challenge, hide_code, tabIndex, } = this.props if ( // 初始化 或者 game切换 !this.treeExpanded && challenge.path && challenge.path.length && this.state.expandedKeys.length === 0 || game && (!prevProps.game || prevProps.game.identifier !== this.props.game.identifier) ) { if (!this.treeExpanded) { this.treeExpanded = true const _path = challenge.multiPath ? challenge.path[0] : challenge.path; let _ar = []; const expandedKeys = []; if (_path) { _ar = _path.split('/') _ar.length = _ar.length - 1 _ar.forEach( (item, index) => { expandedKeys.push( index === 0 ? item : expandedKeys[index - 1] + '/' + item) }) } expandedKeys.length = 1 // 没办法做到多级初始化,而且会引起点击其他子目录,加载当前文件目录的问题 // 初始化时无法展开到根节点 https://github.com/fis-components/rc-tree/issues/3 expandedKeys.length && this.setState({ expandedKeys, }) } if (game && (!prevProps.game || prevProps.game.identifier !== this.props.game.identifier)) { // 切换关卡时切换到第一个tab // 如果隐藏code tab,就只有一个ssh tab了 if (hide_code == true) { this.props.tabIndexChange(81); } else if (hide_code == undefined || hide_code == false || tabIndex !== 0) { // 不加if会有死循环 this.props.tabIndexChange(0); } } } } componentDidMount() { // 隐藏code tab,则显示ssh tab(81) if (this.props.hide_code === true) { this.tabIndexChange(81) } //显示所有代码文件 $(".code-file-tab").hover(function(){ // var ulwidth=$("#blacktab_nav").width(); // $(".code-flie-list").width(ulwidth); $(".code-flie-list").show(); $(this).find("i") .addClass("codeRepoShow") // .removeClass("fa-caret-right").addClass("fa-caret-down"); },function(){ $(".code-flie-list").hide(); $(".code-file-tab").find("i") .removeClass("codeRepoShow") // .removeClass("fa-caret-down").addClass("fa-caret-right"); }) // $('#codetab_con_1').append(``) // $('#codetab_con_1 .codemirrorBackground').hide() } onTreeSelect = (selectedKeys, info) => { if (!info.node.isLeaf()) { const expandedKeys = this.state.expandedKeys.slice(0) const _index = expandedKeys.indexOf(selectedKeys[0]); if (_index == -1) { expandedKeys.push(selectedKeys[0]) } else { expandedKeys.splice( _index, 1) } this.setState({ expandedKeys }) } this.props.onTreeSelect(selectedKeys, info) } buildTree() { // TODO http://localhost:3007/tasks/xgffnuomytpj 这个实训没有文件树 const { fileTreeData, onLoadData, fileTreeSelectedKeys } = this.props; if (!fileTreeData || fileTreeData.length === 0) { return "" } const loop = (data) => { return data.map((item) => { if (item.children) { return {loop(item.children)}; } return ( ); }); }; const treeNodes = loop(fileTreeData); // selectable={false} return ( {treeNodes} ) } onExpand = (expandedKeys) => { // console.log('onExpand', arguments); // if not set autoExpandParent to false, if children expanded, parent can not collapse. // or, you can remove all expanded children keys. this.setState({ expandedKeys, autoExpandParent: false, }); } onPathChange(index, isDropDown) { const { challenge, onPathChange, doFileUpdateRequestOnCodeMirrorBlur } = this.props; if (challenge.pathIndex !== index) { // 切换时保存文件 // doFileUpdateRequestOnCodeMirrorBlur(true) onPathChange(index, isDropDown) } } renderChallengePath() { const { challenge } = this.props; let { pathIndex, path } = challenge; var domArray = []; const pathArray = path.forEach ? path : [path] pathArray.forEach( (item, index) => { domArray.push(

this.onPathChange(index, true) } > {item}

) }) return (
{domArray}
) } initSsh = ($, tabIndex, isReInit) => { const { myshixun } = this.props; const url = `/myshixuns/${myshixun.identifier}/open_webssh.json` tabIndex && $(`#codetab_con_${tabIndex}`).html('正在连接命令行服务...') axios.get(url, { // withCredentials: true } ).then((response) => { tabIndex && $(`#codetab_con_${tabIndex}`).html('') // 清空 ‘正在连接命令行服务...’ if (response.data.game_id) { // 重置ssh状态 this.setSSHClosed(false); const { game_id, host, password, port, username, webssh_url} = response.data // js_min_all.js有同样的计算逻辑,用来拖拽时计算ssh高宽 // TODO 结合new Terminal 时的fontSize参数来定高度 var h = $("#games_repository_contents").height() - 50; var w = $("#games_repository_contents").width(); var line_h = (navigator.userAgent.indexOf('Chrome') >= 0 ? 18 : 19); var rows = Math.round(h / line_h); var cols = parseInt(w / 9.9); response.data.width = w; response.data.height = h; response.data.line_h = line_h; response.data.rows = rows; response.data.cols = cols; // https://stackoverflow.com/questions/5645485/detect-mousemove-when-over-an-iframe // this.loadSshInIframe( response.data, tabIndex ) this.loadSshNormal(response.data, tabIndex, isReInit) } console.log(response) }).catch((error) => { console.log(error) }) } loadSshNormal(data, tabIndex, isReInit) { if (isReInit) { const addtionalSSHArray = this.state.addtionalSSHArray.slice(0) addtionalSSHArray.unshift(STABLE_SSH_TAB_ID) addtionalSSHArray.forEach(item => { this.loadSshNormal(data, item) }) return; } // 不用iframe直接渲染ssh // $(".game_webssh").css({"min-height":h, "max-height":h}); const container = $(`#codetab_con_${tabIndex}`); // ReactDOM.unmountComponentAtNode(container) // ReactDOM.render(, container); // this.setState({ // sshData: data // }) loadSshScript(() => { container.html('
') openTerminal(data, `#codetab_con_${tabIndex}`) }) } loadSshInIframe(data, tabIndex) { const { game_id, host, password, port, username, webssh_url , height, width, line_h, rows, cols, } = data let _domain = webssh_url || 'https://webssh.educoder.net'; // window.location.host === "www.educoder.net" ? // 'https://webssh.educoder.net' // : 'https://testwebssh.educoder.net' const html = `` $(`#codetab_con_${tabIndex}`).html(html) } tabIndexChange(index) { // webssh TODO 需要接口,是否显示ssh const $ = window.$ this.props.tabIndexChange(index); if ($(`#codetab_con_${index} .terminal.xterm`).length === 1) { // 已经初始化过了 return; } this.initSsh($, index); } /** multi ssh */ setSSHClosed = (isClose) => { this.setState({ sshIsClosed: isClose }) } getAddtionalSSHNewID() { // addtionalSSHIdMap for(let i = addtionalSSHStartId; i < addtionalSSHStartId + totalAddtionalTabCount; i++) { if (!addtionalSSHIdMap[i]) { return i } } return null; } addSSHTabs = () => { let addtionalSSHArray = this.state.addtionalSSHArray.slice(0) let newId = this.getAddtionalSSHNewID(); if (newId) { addtionalSSHArray.push(newId) addtionalSSHIdMap[newId] = true; this.tabIndexChange(newId) this.setState({ addtionalSSHArray }) } } close_ssh_cocket_iframe = (id) => { /* 关闭连接,清空tab content */ let _w = $(`.game_webssh_${id}`)[0].contentWindow _w && _w.postMessage({tp: 'close_ssh_cocket'}, "*"); $(`#codetab_con_${id}`).html('') } close_ssh_cocket_normal = (id) => { /* 关闭连接,清空tab content */ // let _w = $(`.game_webssh_${id}`)[0].contentWindow // _w && _w.postMessage({tp: 'close_ssh_cocket'}, "*"); // $(`#codetab_con_${id}`).html('') } onSSHTabClose = (id, e) => { let addtionalSSHArray = this.state.addtionalSSHArray.slice(0) _.remove(addtionalSSHArray, (item)=> item === id) addtionalSSHIdMap[id] = false; this.tabIndexChange(STABLE_SSH_TAB_ID) this.setState({ addtionalSSHArray }) // game_webssh_${tabIndex} TODO this.close_ssh_cocket_normal(id) e.stopPropagation(); } isSSHTabIndex = () => { const index = this.props.tabIndex if (index >= 81 && index < 90) { return true; } return false; } /** multi ssh end */ render() { const { repositoryCode, onRepositoryCodeUpdate, showFilesDrawer, drawerOpen, loadingFirstRepoFiles , challenge, evaluateViewExpanded, onRepositoryViewExpand, codeStatus , showResetCodeDialog, showResetPassedCodeDialog, tabIndex, tabIndexChange, game, shixun, isEditablePath, currentPath , showSettingDrawer, hide_code } = this.props; // onRequestChange={(drawerOpen) => showFilesDrawer(drawerOpen)} /*

请点击文件名称切换代码显示,代表当前显示的代码文件

*/ const { tpm_cases_modified, tpm_modified, tpm_script_modified, myshixun, onShowUpdateDialog } = this.props; let needUpdateScript = (tpm_modified || tpm_script_modified) && challenge.st === 0; const showUpdateButton = (tpm_cases_modified || needUpdateScript) && myshixun.system_tip === true; const { addtionalSSHArray, sshIsClosed } = this.state; // shixun.multi_webssh = true; return ( showFilesDrawer( false )} > { loadingFirstRepoFiles ? (
) : this.buildTree()}
{/* 没必要显示这个,注释掉了 */} {/* { !isEditablePath &&
{currentPath}
} */} {this.props.readRepoTimeout === true ?
代码加载失败, this.props.fetchRepositoryCode(this.props, null, null, true, true)}>重试
:
{/**/} {/* cm monaco 切换 */} {/* */}
}
{/* { tabIndex === STABLE_SSH_TAB_ID && this.state.sshData && } */}
); } } /* <%= Redmine::CodesetUtil.replace_invalid_utf8(@content) %> , overflow: 'scroll' ref="editor" */ export default CodeRepositoryView;