Commit ccd68731 by zhanghaozhe

ml小程序

parent 0c2a2645
...@@ -15823,6 +15823,11 @@ ...@@ -15823,6 +15823,11 @@
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.5.0.tgz", "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.5.0.tgz",
"integrity": "sha512-4t12tsvtYnv13FBJwewddxdI/j4kSonmbQQv50j34R/rPIFbUNGtptbprmuUlTDAKvHLMDZ/Np2XcpNimga/HQ==" "integrity": "sha512-4t12tsvtYnv13FBJwewddxdI/j4kSonmbQQv50j34R/rPIFbUNGtptbprmuUlTDAKvHLMDZ/Np2XcpNimga/HQ=="
}, },
"xterm-addon-fit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz",
"integrity": "sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w=="
},
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
......
...@@ -92,7 +92,8 @@ ...@@ -92,7 +92,8 @@
"webpack-dev-server": "3.1.14", "webpack-dev-server": "3.1.14",
"webpack-manifest-plugin": "2.0.4", "webpack-manifest-plugin": "2.0.4",
"workbox-webpack-plugin": "3.6.3", "workbox-webpack-plugin": "3.6.3",
"xterm": "^4.5.0" "xterm": "^4.5.0",
"xterm-addon-fit": "^0.4.0"
}, },
"scripts": { "scripts": {
"start": "node scripts/start.js", "start": "node scripts/start.js",
......
var API = { var API = {
'www': 'http://www-test.julyedu.com', 'www': 'http://www-test.julyedu.com',
'home': 'http://fast-test.julyedu.com', 'home': 'http://fast-test.julyedu.com',
'search-api': 'https://search.julyedu.com', 'search-api': 'https://search.julyedu.com',
'passport-api': 'http://passport-test.julyedu.com', 'passport-api': 'http://passport-test.julyedu.com',
'base-api': 'http://api-test.julyedu.com', 'base-api': 'http://api-test.julyedu.com',
'record': 'record.julyedu.com:8001', 'record': 'record.julyedu.com:8001',
'process-api': 'ws:process-test.julyedu.com:9502', 'process-api': 'ws:process-test.julyedu.com:9502',
'm': 'http://m-test.julyedu.com', 'm': 'http://m-test.julyedu.com',
//terminal
'ws': 'ws://47.93.119.175:8888/ws',
} }
.catalog { .catalog {
display: flex; display: flex;
padding-left: 12px; padding-left: 12px;
margin-bottom: 38px; margin-bottom: 24px;
font-size: 20px; font-size: 20px;
color: #FFD667; color: #FFD667;
align-items: center; align-items: center;
......
...@@ -7,9 +7,8 @@ export const questionType = { ...@@ -7,9 +7,8 @@ export const questionType = {
} }
export const status = { export const status = {
practice: '练习', practicingSingle: '练习中...',
practicing: '练习中...', practicingProgram: '练习中...(无法运行时退出重进即可)',
startPractice: '开始练习', resumePractice: '继续',
resumePractice: '继续练习',
nextSection: '下一关', nextSection: '下一关',
} }
\ No newline at end of file
.container { .container {
width: 100%;
display: flex; display: flex;
padding-right: 10px; padding-right: 10px;
margin-bottom: 21.5px; margin-bottom: 21.5px;
...@@ -23,12 +24,10 @@ ...@@ -23,12 +24,10 @@
} }
.content { .content {
width: 100%;
padding-left: 10.5px; padding-left: 10.5px;
color: rgba(207, 219, 229, .8); color: rgba(207, 219, 229, .8);
font-size: 15px; font-size: 15px;
/*img{
width: 100%;
}*/
} }
.subtitle{ .subtitle{
......
...@@ -9,35 +9,40 @@ import { Toast } from 'antd-mobile' ...@@ -9,35 +9,40 @@ import { Toast } from 'antd-mobile'
import { questionType, status } from './consts' import { questionType, status } from './consts'
import Catalog from './catalog' import Catalog from './catalog'
class InteractiveStudy extends Component { class InteractiveStudy extends Component {
singleIcon = 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png' container = null
programIcon = 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/program-icon.png'
state = { state = {
options: [
'A. 程序向屏幕输出信息的过程,是人与计算机的单向沟通。',
'B. 程序向内部输入信息的过程。',
'C. 程序向内部输入信息的过程。',
],
img: 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/weekend/bigdata/project/07.png',
page: 1, page: 1,
progress: 0,
schedule: {}, schedule: {},
pageData: {}, pageData: {},
cachedList: [],
processContent: [], processContent: [],
processStatus: status.startPractice, processStatus: status.resumePractice,
isComplete: false, isComplete: false,
avatar: '',
isFirst: true,
isProgramShowed: 0,
videoId: '',
} }
componentDidMount() { componentDidMount() {
this.getSchedule().then(res => { this.getSchedule().then(res => {
const {code, msg, data} = res.data const {code, msg, data} = res.data
if (code === 200) { if (code === 200) {
const pageInfo = data.page_info
const page = pageInfo.current_page
this.setState({ this.setState({
schedule: data, schedule: data,
avatar: data.teacher_avatar,
page,
videoId: data.current_video_id,
})
this.getPageContent(data.current_video_id, () => {
this.updatePosition()
this.getPageContent(data.current_video_id)
}) })
this.getPageContent(data.current_video_id)
} else { } else {
this.showToast(msg) this.showToast(msg)
} }
...@@ -50,20 +55,45 @@ class InteractiveStudy extends Component { ...@@ -50,20 +55,45 @@ class InteractiveStudy extends Component {
}) })
} }
updatePosition = () => {
setTimeout(() => {
window.scrollTo(0, this.container.scrollHeight)
})
}
resetProcessStatus = () => {
this.setState({
processStatus: status.resumePractice,
})
}
showToast = (msg, type = 'info') => { showToast = (msg, type = 'info') => {
Toast[type](msg, 2, null, false) Toast[type](msg, 2, null, false)
} }
getPageContent = (videoId) => { getPageContent = (videoId, cb) => {
http.post(`${API.home}/m/it/study/syllabus?page=${this.state.page}`, { http.post(`${API.home}/m/it/study/syllabus?page=${this.state.isFirst ? 1 : this.state.page}`, {
course_id: getParam('id'), course_id: getParam('id'),
video_id: videoId, video_id: videoId,
type: 1, type: this.state.isFirst ? 1 : 2,
}).then(res => { }).then(res => {
const {code, msg, data} = res.data const {code, msg, data} = res.data
if (code === 200) { if (code === 200) {
this.setState({ this.setState(state => {
pageData: data, const totalPage = state.schedule.page_info.total_pages
const page = state.page < totalPage ? state.isFirst ? state.page : state.page + 1 : totalPage
const progress = state.processContent.length
? data.syllabus_list.findIndex(item => item.syllabus_id === state.processContent[state.processContent.length - 1].syllabus_id) + 1
: 0
return {
processContent: state.isFirst ? (state.processContent.concat(data.syllabus_list)) : state.processContent,
isFirst: false,
page,
pageData: data,
progress: state.isFirst ? state.progress : progress,
}
}, () => {
cb && cb()
}) })
} else { } else {
this.showToast(msg) this.showToast(msg)
...@@ -71,26 +101,92 @@ class InteractiveStudy extends Component { ...@@ -71,26 +101,92 @@ class InteractiveStudy extends Component {
}) })
} }
render() { saveSchedule = (syllabusId, unitInfoId) => {
http.post(`/m/it/user/saveSchedule`, {
syllabus_id: syllabusId,
unit_info_id: unitInfoId,
}).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
} else {
this.showToast(msg, 'fail')
}
})
}
process = () => {
const {processStatus} = this.state const {processStatus} = this.state
if (processStatus === status.practicingProgram || processStatus === status.practicingSingle) {
this.showToast('有其他正在进行的练习')
return
}
this.setState(state => {
let nextQuestion, progress, cachedList = state.cachedList
if (state.cachedList.length) {
nextQuestion = [cachedList.pop()]
progress = 0
} else {
nextQuestion = state.pageData.syllabus_list.slice(state.progress, state.progress + 1)
progress = state.progress + 1
}
if (!cachedList.length && state.progress === state.pageData.syllabus_list.length - 6) {
cachedList = state.pageData.syllabus_list.slice(state.progress + 1).reverse()
console.log(state.page)
this.getPageContent(state.videoId)
}
let processStatus = nextQuestion[0].type === questionType.singleAnswer
? status.practicingSingle
: nextQuestion[0].type === questionType.program
? status.practicingProgram
: status.resumePractice
return {
processContent: state.processContent.concat(nextQuestion),
progress,
processStatus,
isProgramShowed: processStatus === status.practicingProgram && (state.isProgramShowed + 1),
cachedList,
}
}, () => {
// console.log(this.state.processContent)
// console.log(this.state.cachedList)
this.updatePosition()
})
}
render() {
const {processStatus, processContent, avatar, isProgramShowed} = this.state
return ( return (
<div id={'interactive-study'}> <div id={'interactive-study'} ref={el => this.container = el}>
<Catalog title={'Print()函数'}/> {
<Container> !!processContent.length && processContent.map(item => {
<span className={'subtitle'}>副标题</span> if (item.catalogue === 1) {
</Container> switch (item.type) {
<Container> case questionType.text:
<img return <Container user={avatar} content={item.content} key={item.syllabus_id}/>
src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590155695725&di=0a1cad792ef55dc43305ab904aae04fb&imgtype=0&src=http%3A%2F%2Fsearchfoto.ru%2Fimg%2FxyygpKbDS1y8pTjXUy83VS8rMS9fLSy3RL8mwz0yx9fcM0EtJ0S2PyCnOy8sOSTMOqQq38I23NHf0SHTNV0vMLbAutzUyNgCzMmwNzSGsomJbQzCjIDnHNgUMwNx8W1OIMNBoQz1DAA.jpg" case questionType.image:
alt=""/> return <Container user={avatar} key={item.syllabus_id}>
</Container> <img src={item.content} alt=""/>
<Container user={'https://julyedu-img-public.oss-cn-beijing.aliyuncs.com/Public/Image/home/avatar_20191104.png'} </Container>
content={'Hi!初次见面,我叫小柒,是你在七月在线的课程导师。从现在开始,你就要跟着我一起学Python啦,请敲回车键继续课程。'}/> case questionType.singleAnswer:
<SingleAnswerQuestion user={this.singleIcon} topic={'请问,你会推荐哪个物品给用户C?'} options={this.state.options} return <SingleAnswerQuestion user={avatar} topic={item.content} question={item}
img={this.state.img}/> key={item.syllabus_id} updatePosition={this.updatePosition}
<Program user={this.programIcon} code={"week = ['星期一', '星期二', '星期三']\nprint(123)"}/> resetStatus={this.resetProcessStatus}/>
case questionType.codeBlock:
case questionType.program:
return <Program question={item} key={item.syllabus_id} isProgramShowed={isProgramShowed}/>
}
} else if (item.catalogue === 2) {
return <Catalog title={item.content} key={item.syllabus_id}/>
} else {
return <Container key={item.syllabus_id}>
<span className={'subtitle'}>{item.content}</span>
</Container>
}
})
}
{/*<Project user={this.singleIcon}/>*/} {/*<Project user={this.singleIcon}/>*/}
<div className="status-bar"> <div className="status-bar" onClick={this.process}>
{/*<span className={'complete'}>已学完全部课时</span>*/} {/*<span className={'complete'}>已学完全部课时</span>*/}
<span className={'status'}>{processStatus}</span> <span className={'status'}>{processStatus}</span>
{/*<div className="free-trial-end">*/} {/*<div className="free-trial-end">*/}
......
body{
background-color: #252529;
}
#interactive-study { #interactive-study {
position: relative;
min-height: 100%; min-height: 100%;
position: relative;
background-color: #252529; background-color: #252529;
padding-bottom: 49px; padding-bottom: 49px;
padding-top: 20px; padding-top: 20px;
...@@ -30,9 +34,9 @@ ...@@ -30,9 +34,9 @@
font-size: 18px; font-size: 18px;
} }
.status{ .status {
color: #CFDBE5; color: #CFDBE5;
font-size: 12px; font-size: 16px;
} }
.free-trial-end { .free-trial-end {
...@@ -40,7 +44,8 @@ ...@@ -40,7 +44,8 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 15px; padding: 0 15px;
button{
button {
width: 80px; width: 80px;
height: 30px; height: 30px;
-webkit-appearance: none; -webkit-appearance: none;
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import './index.scss' import './index.scss'
import Container from '../container' import Container from '../container'
import Terminal from '../terminal'
import AceEditor from 'react-ace' import AceEditor from 'react-ace'
import 'ace-builds/src-min-noconflict/theme-dracula' import 'ace-builds/src-min-noconflict/theme-dracula'
import 'ace-builds/src-min-noconflict/mode-python' import 'ace-builds/src-min-noconflict/mode-python'
import StatusBar from './status-bar' import StatusBar from './status-bar'
import { http } from "@/utils"
import { Toast } from "antd-mobile";
import { questionType } from '../consts'
const {First, Normal, Pass, Error, InputTip} = StatusBar
const {First, Normal, Pass, Error, InputTip, Skipped, Finished} = StatusBar
class Program extends Component { class Program extends Component {
editor = null userEditor = null
icon = 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/program-icon.png'
ws = null
state = { state = {
editorWidth: '', editorWidth: '',
showTerminal: false,
code: '',
answer: '',
showAnswer: false,
result: '',
filename: '',
executing: false,
} }
componentDidMount() { componentDidMount() {
const content = document.querySelector('.container .content') const contentElem = document.querySelector('.container .content')
const contentStyles = window.getComputedStyle(content) const contentStyles = window.getComputedStyle(contentElem)
const contentWidth = content.clientWidth const contentWidth = contentElem.clientWidth
const contentPaddingLeft = parseFloat(contentStyles.getPropertyValue('padding-left')) const contentPaddingLeft = parseFloat(contentStyles.getPropertyValue('padding-left'))
const {type, code_ques, content, user_answer} = this.props.question
let code = content
let answer = '', result = '', showTerminal = false
if (type === questionType.program) {
code = code_ques.code
answer = code_ques.answer
if (user_answer && user_answer.is_finish && user_answer.result) {
result = user_answer.result
showTerminal = true
}
}
this.setState({ this.setState({
editorWidth: `${contentWidth - contentPaddingLeft}px`, editorWidth: `${contentWidth - contentPaddingLeft}px`,
code,
answer,
result,
showTerminal,
}) })
} }
onLoad = editor => { onLoad = editor => {
this.editor = editor this.userEditor = editor
} }
execute = () => {
// this.getAuthenticationData()
this.uploadCode().then(filename => {
this.setState({
filename,
executing: true,
showTerminal: true,
})
})
}
uploadCode = () => {
return http.post(`${API.home}/web/python/practice/file`, {
code: this.userEditor.getValue(),
}).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
return data.filename
} else {
Toast.fail(msg, 2)
}
})
}
render() { render() {
const {editorWidth} = this.state const {editorWidth, showTerminal, code, answer, showAnswer, result, executing, filename} = this.state
const {user, code} = this.props const {user, question: {type, user_answer, content}, isProgramShowed} = this.props
return ( return (
<Container <>
user={user} {
content={ content && type !== questionType.codeBlock && <Container content={content}></Container>
<div className="program">
<AceEditor
name={'ace-editor'}
mode={'python'}
theme={'dracula'}
readOnly={true}
value={code}
width={editorWidth}
height={'141px'}
onLoad={this.onLoad}
/>
<InputTip/>
</div>
} }
/> <Container
user={this.icon}
content={
<div className="program">
<AceEditor
name={'ace-editor'}
mode={'python'}
theme={'dracula'}
readOnly={true}
value={code}
width={editorWidth}
height={'141px'}
onLoad={this.onLoad}
/>
{
showAnswer && <AceEditor
name={'ace-editor'}
mode={'python'}
theme={'dracula'}
readOnly={true}
value={answer}
width={editorWidth}
height={'141px'}
/>
}
{
type !== questionType.codeBlock &&
<ToolBar isProgramShowed={isProgramShowed} userAnswer={user_answer} execute={this.execute}/>
}
{
showTerminal && <Terminal result={result} executing={executing} filename={filename}/>
}
</div>
}
/>
</>
); );
} }
} }
function ToolBar({isProgramShowed, userAnswer, isSuccessful, execute}) {
if (userAnswer && userAnswer.is_finish) {
if (userAnswer.is_jump) {
return <Skipped/>
}
return <Finished/>
}
return isProgramShowed ? <Normal/> : <First execute={execute}/>
}
export default Program; export default Program;
\ No newline at end of file
.program { .program {
width: 100%;
background-color: #1f1f24; background-color: #1f1f24;
padding-top: 12px; padding-top: 12px;
......
...@@ -52,10 +52,28 @@ const InputTip = () => { ...@@ -52,10 +52,28 @@ const InputTip = () => {
</Base> </Base>
} }
const Skipped = () => {
return <Base className={'skipped complete'}>
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/exclamation-mark.png" alt=""/>
<div className="tip">已跳过课堂练习!</div>
<a href="javascript:void(0);">重新运行</a>
</Base>
}
const Finished = () => {
return <Base className={'finished complete'}>
<i className={'iconfont icondanseshixintubiao-5'}></i>
<div className="tip">已跳过课堂练习!</div>
<a href="javascript:void(0);">重新运行</a>
</Base>
}
export default { export default {
First, First,
Normal, Normal,
Pass, Pass,
Error, Error,
InputTip InputTip,
Skipped,
Finished
}; };
\ No newline at end of file
...@@ -83,14 +83,50 @@ $primaryColor: rgba(0, 153, 255, .8); ...@@ -83,14 +83,50 @@ $primaryColor: rgba(0, 153, 255, .8);
} }
} }
.input-tip{ .input-tip {
.tip{ .tip {
font-size: 12px; font-size: 12px;
color: #CFDBE5; color: #CFDBE5;
} }
button{
button {
border: 1px solid #09f; border: 1px solid #09f;
background: transparent; background: transparent;
color: #09f; color: #09f;
} }
}
.complete {
display: flex;
align-items: center;
justify-content: flex-start;
.iconfont {
font-size: 18px;
}
a {
font-size: 12px;
color: #CFDBE5;
text-decoration: underline;
}
img {
width: 18px;
height: 18px;
}
.tip {
margin-left: 11px;
font-size: 15px;
}
&.finish {
color: #74C17C;
}
&.skipped {
color: #CFDBE6;
}
} }
\ No newline at end of file
html,body{
min-height: 100%;
}
#project { #project {
table { table {
width: 315px; width: 315px;
......
...@@ -2,43 +2,85 @@ import React, { Component } from 'react'; ...@@ -2,43 +2,85 @@ import React, { Component } from 'react';
import './index.scss' import './index.scss'
import Container from '../container' import Container from '../container'
import classnames from 'classnames' import classnames from 'classnames'
import { html } from "@/utils"
class SingleAnswerQuestion extends Component { class SingleAnswerQuestion extends Component {
icon = 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png'
state = { state = {
correct: '', correct: '',
wrong: '' wrong: '',
replies: [],
selectable: false,
} }
handleSelect = index => { handleSelect = (info, index) => {
if (!this.state.selectable) {
return
}
const {question: {unit_questions}, updatePosition, resetStatus} = this.props
const userSelect = unit_questions[index]
this.setState({ this.setState({
wrong: index wrong: userSelect.is_ans ? '' : index,
correct: userSelect.is_ans ? index : '',
replies: info,
selectable: false,
}, () => {
updatePosition()
resetStatus()
}) })
} }
componentDidMount() {
const {user_answer: userAnswer, unit_questions: options} = this.props.question
if (userAnswer && userAnswer.answer) {
const userSelectId = userAnswer.answer
const userSelectIndex = options.findIndex(item => item.unit_id == userSelectId)
const isUserCorrect = options[userSelectIndex].is_ans
this.setState({
correct: isUserCorrect ? userSelectIndex : '',
wrong: isUserCorrect ? '' : userSelectIndex,
replies: options[userSelectIndex].unit_info,
selectable: false,
})
} else {
this.setState({
selectable: true,
})
}
}
render() { render() {
const {correct, wrong} = this.state const {correct, wrong, replies} = this.state
const {user, topic, img, options} = this.props const {user, topic, img, question: {unit_questions: options}} = this.props
return ( return (
<Container <>
user={user} <Container
content={ user={this.icon}
<div className={'single-answer-question'}> content={
<div className="topic">{topic}</div> <div className={'single-answer-question'}>
{img && <img src={img} alt=""/>} <div className="topic" dangerouslySetInnerHTML={html(topic)}></div>
<ul> {img && <img src={img} alt=""/>}
{ <ul>
options && !!options.length && options.map((item, index) => { {
return <li options && !!options.length && options.map((item, index) => {
className={classnames(['option', {'correct': index === correct, 'wrong': index === wrong}])} return <li
key={index} className={classnames(['option', {'correct': index === correct, 'wrong': index === wrong}])}
onClick={this.handleSelect.bind(this, index)}>{item}</li> key={index}
}) onClick={this.handleSelect.bind(this, item.unit_info, index)}>{item.des}</li>
} })
</ul> }
</div> </ul>
</div>
}
/>
{
!!replies.length && replies.map(item => <Container key={item.info_id} content={item.content}/>)
} }
/> </>
); );
} }
} }
......
import React, { Component } from 'react';
import './index.scss'
import { Terminal } from "xterm"
import 'xterm/css/xterm.css'
import { FitAddon } from "xterm-addon-fit";
import { http } from "@/utils"
import { Toast } from "antd-mobile";
class TerminalInterface extends Component {
terminal = null
termElem = null
quit = '\u0003'
socket = null
state = {
terminalWidth: '',
}
componentDidMount() {
const content = document.querySelector('.container .content')
const contentStyles = window.getComputedStyle(content)
const contentWidth = content.clientWidth
const contentPaddingLeft = parseFloat(contentStyles.getPropertyValue('padding-left'))
this.setState({
terminalWidth: `${contentWidth - contentPaddingLeft}px`,
})
this.terminal = new Terminal()
const fitAddon = new FitAddon()
this.terminal.loadAddon(fitAddon)
this.terminal.open(this.termElem)
const {result, executing} = this.props
setTimeout(() => {
fitAddon.fit()
if (result) {
this.terminal && this.terminal.write(this.props.result)
} else if (executing) {
this.getAuthenticationData()
}
})
}
getAuthenticationData = () => {
http.get(`${API.home}/sys/ws/auth`).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.getLinkId(data.token)
} else {
Toast.fail(msg, 2)
}
})
}
getLinkId = token => {
http.get(`${API.home}/sys/ws/connection/${token}`).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.connectServer(data.id)
} else {
Toast.fail(msg, 2, null, false)
}
})
}
connectServer = (id) => {
this.socket = new WebSocket(`${API.ws}?id=${id}`)
this.socket.addEventListener('open', () => {
console.log('opened')
this.socket.send(JSON.stringify({
data: this.props.filename,
}))
})
this.socket.onmessage = event => {
console.log(event.message)
this.terminal.write(event.data)
}
this.socket.onerror = () => {
console.log('error')
}
this.socket.onclose = () => {
console.log('closed')
}
}
render() {
const {terminalWidth} = this.state
return (
<div className={'terminal-container'}>
<div className={'terminal'} ref={el => this.termElem = el} style={{width: `${terminalWidth}`}}></div>
</div>
);
}
}
export default TerminalInterface;
\ No newline at end of file
.terminal {
height: 141px;
padding: 5px;
background: #000;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment