Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mr-julyedu
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
baiguangyao
mr-julyedu
Commits
1dd3b96a
Commit
1dd3b96a
authored
Sep 17, 2020
by
zhanghaozhe
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ml
parent
9031f1bf
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
495 additions
and
288 deletions
+495
-288
package-lock.json
+10
-0
package.json
+4
-1
src/App.js
+1
-1
src/components/interactive-tutorial/catalog/index.js
+0
-19
src/components/interactive-tutorial/catalog/index.tsx
+20
-0
src/components/interactive-tutorial/container/index.tsx
+13
-11
src/components/interactive-tutorial/index.js
+0
-0
src/components/interactive-tutorial/index.scss
+12
-8
src/components/interactive-tutorial/program/index.js
+113
-66
src/components/interactive-tutorial/program/index.scss
+12
-8
src/components/interactive-tutorial/single-answer-question/index.js
+95
-57
src/components/interactive-tutorial/terminal/index.js
+0
-114
src/components/interactive-tutorial/terminal/index.scss
+38
-2
src/components/interactive-tutorial/terminal/index.tsx
+175
-0
src/router/router-config.js
+1
-1
src/utils/index.js
+1
-0
No files found.
package-lock.json
View file @
1dd3b96a
...
...
@@ -24800,6 +24800,16 @@
"resolved"
:
"https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
,
"integrity"
:
"sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
},
"xterm"
:
{
"version"
:
"4.9.0"
,
"resolved"
:
"https://registry.npmjs.org/xterm/-/xterm-4.9.0.tgz"
,
"integrity"
:
"sha512-wGfqufmioctKr8VkbRuZbVDfjlXWGZZ1PWHy1yqqpGT3Nm6yaJx8lxDbSEBANtgaiVPTcKSp97sxOy5IlpqYfw=="
},
"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"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz"
,
...
...
package.json
View file @
1dd3b96a
...
...
@@ -133,7 +133,10 @@
}
},
"lint-staged"
:
{
"*.(js|css|ts[x])"
:
[
"prettier --write"
,
"git add ."
]
"*.(js|css|ts[x])"
:
[
"prettier --write"
,
"git add ."
]
},
"eslintConfig"
:
{
"extends"
:
"react-app"
,
...
...
src/App.js
View file @
1dd3b96a
...
...
@@ -17,7 +17,7 @@ import {
endFetchNoTrace
,
}
from
"src/store/no-trace-validation/reducer"
import
{
initialState
}
from
"src/store/userReducer"
import
{
withRouter
}
from
"react-router-dom"
import
{
withRouter
,
Link
}
from
"react-router-dom"
import
{
compose
}
from
"redux"
import
{
getParam
,
http
,
browser
,
loadScript
,
getTimestamp
}
from
"src/utils"
import
{
Toast
}
from
"antd-mobile"
...
...
src/components/interactive-tutorial/catalog/index.js
deleted
100644 → 0
View file @
9031f1bf
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
class
Catalog
extends
Component
{
render
()
{
return
(
<
div
className
=
{
'catalog'
}
>
<
div
className
=
"icon"
>
<
img
src
=
"https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/level1-title-icon.png"
alt
=
""
/>
<
/div
>
<
div
className
=
"title"
>
{
this
.
props
.
title
}
<
/div
>
<
/div
>
);
}
}
export
default
Catalog
;
\ No newline at end of file
src/components/interactive-tutorial/catalog/index.tsx
0 → 100644
View file @
1dd3b96a
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
class
Catalog
extends
Component
<
{
title
:
string
}
>
{
render
()
{
return
(
<
div
className=
{
"catalog"
}
>
<
div
className=
"icon"
>
<
img
src=
"https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/level1-title-icon.png"
alt=
""
/>
</
div
>
<
div
className=
"title"
>
{
this
.
props
.
title
}
</
div
>
</
div
>
)
}
}
export
default
Catalog
src/components/interactive-tutorial/container/index.
js
→
src/components/interactive-tutorial/container/index.
tsx
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
React
,
{
Component
,
ReactNode
}
from
"react"
import
"./index.scss"
class
Container
extends
Component
{
interface
Props
{
user
?:
string
content
?:
ReactNode
}
class
Container
extends
Component
<
Props
>
{
render
()
{
const
{
user
,
content
,
children
}
=
this
.
props
const
{
user
,
content
,
children
}
=
this
.
props
return
(
<
div
className
=
{
'container'
}
>
<
div
className=
{
"container"
}
>
<
div
className=
"user"
>
{
user
&&
<
img
className
=
{
'avatar'
}
src
=
{
user
}
alt
=
""
/>
}
{
user
&&
<
img
className=
{
"avatar"
}
src=
{
user
}
alt=
""
/>
}
</
div
>
<
div
className=
"divide"
></
div
>
<
div
className=
"content"
>
{
content
?
content
:
children
}
</
div
>
</
div
>
)
;
)
}
}
export
default
Container
;
\ No newline at end of file
export
default
Container
src/components/interactive-tutorial/index.js
View file @
1dd3b96a
This diff is collapsed.
Click to expand it.
src/components/interactive-tutorial/index.scss
View file @
1dd3b96a
body
{
body
{
background-color
:
#252529
;
}
...
...
@@ -9,6 +9,10 @@ body{
padding-bottom
:
49px
;
padding-top
:
20px
;
&
+
div
{
display
:
none
;
}
&
+
.year19-index
{
display
:
none
;
}
...
...
@@ -24,18 +28,19 @@ body{
left
:
0
;
width
:
100%
;
height
:
$height
;
background
:
#3
E3F
47
;
background
:
#3
e3f
47
;
line-height
:
$height
;
text-align
:
center
;
color
:
#CFDBE5
;
color
:
#cfdbe5
;
z-index
:
100
;
.complete
{
color
:
rgba
(
207
,
219
,
229
,
.2
);
color
:
rgba
(
207
,
219
,
229
,
0
.2
);
font-size
:
18px
;
}
.status
{
color
:
#
CFDBE
5
;
color
:
#
cfdbe
5
;
font-size
:
16px
;
}
...
...
@@ -51,10 +56,10 @@ body{
-webkit-appearance
:
none
;
border
:
none
;
outline
:
0
;
background
:
#
FA5C
19
;
background
:
#
fa5c
19
;
font-size
:
14px
;
color
:
#fff
;
}
}
}
}
\ No newline at end of file
}
src/components/interactive-tutorial/program/index.js
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
Container
from
'../container'
import
Terminal
from
'../terminal'
import
AceEditor
from
'react-ace'
import
'ace-builds/src-min-noconflict/theme-dracula'
import
'ace-builds/src-min-noconflict/mode-python'
import
StatusBar
from
'./status-bar'
import
{
http
}
from
"
@
/utils"
import
{
Toast
}
from
"antd-mobile"
;
import
{
questionType
}
from
'../consts'
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
import
Container
from
"../container"
import
Terminal
from
"../terminal"
import
AceEditor
from
"react-ace"
import
"ace-builds/src-min-noconflict/theme-dracula"
import
"ace-builds/src-min-noconflict/mode-python"
import
StatusBar
from
"./status-bar"
import
{
http
}
from
"
src
/utils"
import
{
Toast
}
from
"antd-mobile"
import
{
questionType
}
from
"../consts"
const
{
First
,
Normal
,
Pass
,
Error
,
InputTip
,
Skipped
,
Finished
}
=
StatusBar
/* eslint-disable-next-line no-unused-vars */
const
{
First
,
Normal
,
Pass
,
Error
,
InputTip
,
Skipped
,
Finished
}
=
StatusBar
class
Program
extends
Component
{
userEditor
=
null
icon
=
'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/program-icon.png'
icon
=
"https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/program-icon.png"
ws
=
null
state
=
{
editorWidth
:
''
,
editorWidth
:
""
,
showTerminal
:
false
,
code
:
''
,
answer
:
''
,
code
:
""
,
answer
:
""
,
showAnswer
:
false
,
result
:
''
,
filename
:
''
,
result
:
""
,
filename
:
""
,
executing
:
false
,
}
componentDidMount
()
{
const
contentElem
=
document
.
querySelector
(
'.container .content'
)
console
.
log
(
this
.
userEditor
.
session
.
getLength
())
const
contentElem
=
document
.
querySelector
(
".container .content"
)
const
contentStyles
=
window
.
getComputedStyle
(
contentElem
)
const
contentWidth
=
contentElem
.
clientWidth
const
contentPaddingLeft
=
parseFloat
(
contentStyles
.
getPropertyValue
(
'padding-left'
))
const
{
type
,
code_ques
,
content
,
user_answer
}
=
this
.
props
.
question
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
let
answer
=
""
,
result
=
""
,
showTerminal
=
false
if
(
type
===
questionType
.
program
)
{
code
=
code_ques
.
code
answer
=
code_ques
.
answer
...
...
@@ -54,13 +61,12 @@ class Program extends Component {
})
}
onLoad
=
editor
=>
{
onLoad
=
(
editor
)
=>
{
this
.
userEditor
=
editor
}
execute
=
()
=>
{
// this.getAuthenticationData()
this
.
uploadCode
().
then
(
filename
=>
{
this
.
uploadCode
().
then
((
filename
)
=>
{
this
.
setState
({
filename
,
executing
:
true
,
...
...
@@ -70,74 +76,116 @@ class Program extends Component {
}
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
)
}
})
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
)
}
})
}
uploadPracticeResult
=
(
result
)
=>
{
http
.
post
(
`
${
API
}
/m/submit/python/practice`
,
{
syllabus_id
:
this
.
props
.
question
.
syllabus_id
,
type
:
2
,
answer
:
this
.
userEditor
.
getValue
(),
result
,
lines
:
this
.
userEditor
.
session
.
getLength
(),
})
.
then
((
res
)
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
200
)
{
this
.
handleUploadedResult
(
data
)
}
else
{
Toast
.
fail
(
msg
,
2
,
null
,
false
)
}
})
}
handleUploadedResult
=
(
data
)
=>
{}
render
()
{
const
{
editorWidth
,
showTerminal
,
code
,
answer
,
showAnswer
,
result
,
executing
,
filename
}
=
this
.
state
const
{
user
,
question
:
{
type
,
user_answer
,
content
},
isProgramShowed
}
=
this
.
props
const
{
editorWidth
,
showTerminal
,
code
,
answer
,
showAnswer
,
result
,
executing
,
filename
,
}
=
this
.
state
const
{
question
:
{
type
,
user_answer
,
content
},
isProgramShowed
,
}
=
this
.
props
return
(
<>
{
content
&&
type
!==
questionType
.
codeBlock
&&
<
Container
content
=
{
content
}
><
/Container
>
}
{
content
&&
type
!==
questionType
.
codeBlock
&&
(
<
Container
content
=
{
content
}
><
/Container
>
)
}
<
Container
user
=
{
this
.
icon
}
content
=
{
<
div
className
=
"program"
>
<
AceEditor
name
=
{
'ace-editor'
}
mode
=
{
'python'
}
theme
=
{
'dracula'
}
name
=
{
"ace-editor"
}
mode
=
{
"python"
}
theme
=
{
"dracula"
}
readOnly
=
{
true
}
value
=
{
code
}
width
=
{
editorWidth
}
height
=
{
'141px'
}
height
=
{
"141px"
}
onLoad
=
{
this
.
onLoad
}
/
>
{
showAnswer
&&
<
AceEditor
name
=
{
'ace-editor'
}
mode
=
{
'python'
}
theme
=
{
'dracula'
}
{
showAnswer
&&
(
<
AceEditor
name
=
{
"ace-editor"
}
mode
=
{
"python"
}
theme
=
{
"dracula"
}
readOnly
=
{
true
}
value
=
{
answer
}
width
=
{
editorWidth
}
height
=
{
'141px'
}
height
=
{
"141px"
}
/
>
)}
{
type
!==
questionType
.
codeBlock
&&
(
<
ToolBar
isProgramShowed
=
{
isProgramShowed
}
userAnswer
=
{
user_answer
}
execute
=
{
this
.
execute
}
/
>
)}
{
showTerminal
&&
(
<
Terminal
result
=
{
result
}
executing
=
{
executing
}
filename
=
{
filename
}
/
>
}
{
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
})
{
function
ToolBar
({
isProgramShowed
,
userAnswer
,
isSuccessful
,
execute
})
{
if
(
userAnswer
&&
userAnswer
.
is_finish
)
{
if
(
userAnswer
.
is_jump
)
{
return
<
Skipped
/>
return
<
Skipped
/>
}
return
<
Finished
/>
return
<
Finished
/>
}
return
isProgramShowed
>
1
?
<
Normal
/>
:
<
First
execute
=
{
execute
}
/
>
return
isProgramShowed
>
1
?
<
Normal
/>
:
<
First
execute
=
{
execute
}
/
>
}
export
default
Program
;
\ No newline at end of file
export
default
Program
src/components/interactive-tutorial/program/index.scss
View file @
1dd3b96a
...
...
@@ -25,6 +25,14 @@
.ace_marker-layer
.ace_active-line
{
background
:
rgba
(
207
,
219
,
229
,
.1
);
}
.ace_gutter
{
background
:
transparent
;
}
.ace_gutter-active-line
{
background
:
rgba
(
207
,
219
,
229
,
.1
);
}
}
.ace_scrollbar
{
...
...
@@ -71,13 +79,8 @@
color
:
#09f
;
}
.ace-dracula
{
.ace_gutter
{
background
:
transparent
;
}
.ace_gutter-active-line
{
background
:
rgba
(
207
,
219
,
229
,
.1
);
}
.ace_dark
>
.ace_mobile-menu
{
display
:
none
;
}
}
\ No newline at end of file
src/components/interactive-tutorial/single-answer-question/index.js
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
Container
from
'../container'
import
classnames
from
'classnames'
import
{
html
,
isValidHttpUrl
}
from
"
@
/utils"
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
import
Container
from
"../container"
import
classnames
from
"classnames"
import
{
html
,
isValidHttpUrl
}
from
"
src
/utils"
class
SingleAnswerQuestion
extends
Component
{
icon
=
'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png'
icon
=
"https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png"
state
=
{
correct
:
''
,
wrong
:
''
,
replies
:
''
,
correct
:
""
,
wrong
:
""
,
replies
:
""
,
selectable
:
false
,
}
...
...
@@ -20,37 +20,53 @@ class SingleAnswerQuestion extends Component {
return
}
const
{
question
:
{
unit_questions
,
type
,
syllabus_id
},
updatePosition
,
resetStatus
,
saveSchedule
,
addCache
,
savePractice
}
=
this
.
props
const
{
question
:
{
unit_questions
,
syllabus_id
},
updatePosition
,
resetStatus
,
saveSchedule
,
addCache
,
savePractice
,
}
=
this
.
props
const
userSelect
=
unit_questions
[
index
]
const
isCorrect
=
userSelect
.
is_ans
this
.
setState
({
wrong
:
isCorrect
?
''
:
index
,
correct
:
isCorrect
?
index
:
''
,
replies
:
info
.
slice
(
0
,
1
),
selectable
:
false
,
},
()
=>
{
updatePosition
()
resetStatus
()
addCache
(
info
.
slice
(
1
))
savePractice
({
type
:
1
,
syllabus_id
:
syllabus_id
,
answer
:
unit_questions
[
index
].
unit_id
,
result
:
isCorrect
,
})
saveSchedule
(
syllabus_id
,
info
[
0
].
info_id
)
})
this
.
setState
(
{
wrong
:
isCorrect
?
""
:
index
,
correct
:
isCorrect
?
index
:
""
,
replies
:
info
.
slice
(
0
,
1
),
selectable
:
false
,
},
()
=>
{
updatePosition
()
resetStatus
()
addCache
(
info
.
slice
(
1
))
savePractice
({
type
:
1
,
syllabus_id
:
syllabus_id
,
answer
:
unit_questions
[
index
].
unit_id
,
result
:
isCorrect
,
})
saveSchedule
(
syllabus_id
,
info
[
0
].
info_id
)
}
)
}
componentDidMount
()
{
const
{
user_answer
:
userAnswer
,
unit_questions
:
options
}
=
this
.
props
.
question
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
)
// eslint-disable-next-line eqeqeq
const
userSelectIndex
=
options
.
findIndex
(
(
item
)
=>
item
.
unit_id
==
userSelectId
)
const
isUserCorrect
=
options
[
userSelectIndex
].
is_ans
this
.
setState
({
correct
:
isUserCorrect
?
userSelectIndex
:
''
,
wrong
:
isUserCorrect
?
''
:
userSelectIndex
,
correct
:
isUserCorrect
?
userSelectIndex
:
""
,
wrong
:
isUserCorrect
?
""
:
userSelectIndex
,
replies
:
options
[
userSelectIndex
].
unit_info
,
selectable
:
false
,
})
...
...
@@ -61,44 +77,67 @@ class SingleAnswerQuestion extends Component {
}
}
render
()
{
const
{
correct
,
wrong
,
replies
}
=
this
.
state
const
{
user
,
topic
,
img
,
question
:
{
unit_questions
:
options
},
updatePosition
}
=
this
.
props
const
{
correct
,
wrong
,
replies
}
=
this
.
state
const
{
topic
,
img
,
question
:
{
unit_questions
:
options
},
updatePosition
,
}
=
this
.
props
return
(
<>
<
Container
user
=
{
this
.
icon
}
content
=
{
<
div
className
=
{
'single-answer-question'
}
>
<
div
className
=
"topic"
dangerouslySetInnerHTML
=
{
html
(
topic
)}
><
/div
>
{
img
&&
<
img
src
=
{
img
}
alt
=
""
/>
}
<
div
className
=
{
"single-answer-question"
}
>
<
div
className
=
"topic"
dangerouslySetInnerHTML
=
{
html
(
topic
)}
><
/div
>
{
img
&&
<
img
src
=
{
img
}
alt
=
""
/>
}
<
ul
>
{
options
&&
!!
options
.
length
&&
options
.
map
((
item
,
index
)
=>
{
return
<
li
className
=
{
classnames
([
'option'
,
{
'correct'
:
index
===
correct
,
'wrong'
:
index
===
wrong
}])}
key
=
{
index
}
onClick
=
{
this
.
handleSelect
.
bind
(
this
,
item
.
unit_info
,
index
)}
>
{
item
.
des
}
<
/li
>
})
}
{
options
&&
!!
options
.
length
&&
options
.
map
((
item
,
index
)
=>
{
return
(
<
li
className
=
{
classnames
([
"option"
,
{
correct
:
index
===
correct
,
wrong
:
index
===
wrong
,
},
])}
key
=
{
index
}
onClick
=
{
this
.
handleSelect
.
bind
(
this
,
item
.
unit_info
,
index
)}
>
{
item
.
des
}
<
/li
>
)
})}
<
/ul
>
<
/div
>
}
/
>
{
!!
replies
.
length
&&
replies
.
map
(
item
=>
{
{
!!
replies
.
length
&&
replies
.
map
((
item
)
=>
{
if
(
isValidHttpUrl
(
item
.
content
))
{
return
<
Container
key
=
{
item
.
info_id
}
>
<
img
src
=
{
item
.
content
}
alt
=
""
onLoad
=
{
updatePosition
}
/
>
<
/Container
>
return
(
<
Container
key
=
{
item
.
info_id
}
>
<
img
src
=
{
item
.
content
}
alt
=
""
onLoad
=
{
updatePosition
}
/
>
<
/Container
>
)
}
return
<
Container
content
=
{
item
.
content
}
key
=
{
item
.
info_id
}
/
>
})
}
return
<
Container
content
=
{
item
.
content
}
key
=
{
item
.
info_id
}
/
>
})}
<
/
>
)
;
)
}
}
export
default
SingleAnswerQuestion
;
\ No newline at end of file
export
default
SingleAnswerQuestion
src/components/interactive-tutorial/terminal/index.js
deleted
100644 → 0
View file @
9031f1bf
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"
;
import
axios
from
'axios'
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
=
()
=>
{
// axios.post(`https://fwjizuub9f1tqlcb.julyedu.com`, {
axios
.
post
(
`http://47.93.119.175:8888/auth`
,
{
username
:
'3guh394h3fhj0f4'
,
password
:
'okqdw029j038hrv3890cv'
,
},
{
withCredentials
:
false
,
}).
then
(
res
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
0
)
{
this
.
getLinkId
(
data
)
}
else
{
Toast
.
fail
(
msg
,
2
)
}
})
}
getLinkId
=
token
=>
{
axios
.
post
(
`http://47.93.119.175:8888`
,
{},
{
headers
:
{
Token
:
token
,
},
withCredentials
:
false
,
}).
then
(
res
=>
{
const
{
id
}
=
res
.
data
this
.
connectServer
(
id
)
})
}
connectServer
=
(
id
)
=>
{
this
.
socket
=
new
WebSocket
(
`
${
API
.
ws
}
?id=
${
id
}
`
)
this
.
socket
.
addEventListener
(
'open'
,
()
=>
{
this
.
socket
.
send
(
JSON
.
stringify
({
data
:
this
.
props
.
filename
,
}))
})
this
.
socket
.
onmessage
=
event
=>
{
const
reader
=
new
FileReader
()
reader
.
onload
=
()
=>
{
this
.
terminal
.
write
(
this
.
ab2str
(
reader
.
result
))
}
reader
.
readAsArrayBuffer
(
event
.
data
)
}
this
.
socket
.
onerror
=
(
event
)
=>
{
console
.
log
(
event
)
// this.connectServer(id)
}
this
.
socket
.
onclose
=
(
event
)
=>
{
console
.
log
(
'closed'
)
}
}
ab2str
=
buf
=>
{
return
String
.
fromCharCode
.
apply
(
null
,
new
Uint8Array
(
buf
));
}
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
src/components/interactive-tutorial/terminal/index.scss
View file @
1dd3b96a
.terminal-container
{
.input
{
position
:
relative
;
height
:
44px
;
input
{
width
:
100%
;
height
:
100%
;
-webkit-appearance
:
none
;
background
:
transparent
;
border
:
0
;
font-size
:
14px
;
line-height
:
44px
;
color
:
#cfdbe5
;
&
:
:
placeholder
{
font-size
:
12px
;
}
}
label
{
position
:
absolute
;
right
:
10px
;
top
:
7px
;
width
:
53px
;
height
:
30px
;
}
button
{
width
:
53px
;
height
:
30px
;
border
:
1px
solid
rgba
(
0
,
153
,
255
,
.8
);
border-radius
:
4px
;
color
:
#09f
;
background
:
transparent
;
outline
:
0
;
-webkit-appearance
:
none
;
}
}
}
.terminal
{
height
:
141px
;
padding
:
5px
;
overflow
:
hidden
;
background
:
#000
;
}
\ No newline at end of file
}
src/components/interactive-tutorial/terminal/index.tsx
0 → 100644
View file @
1dd3b96a
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
import
{
Terminal
}
from
"xterm"
import
"xterm/css/xterm.css"
import
{
FitAddon
}
from
"xterm-addon-fit"
import
{
Toast
}
from
"antd-mobile"
import
axios
from
"axios"
interface
Props
{
result
:
string
executing
:
boolean
filename
:
string
}
class
TerminalInterface
extends
Component
<
Props
>
{
terminal
:
Terminal
|
undefined
termElem
:
HTMLElement
|
null
=
null
quit
=
"
\
u0003"
socket
:
WebSocket
|
null
=
null
endFlag
:
string
=
"wrvgh38v3hc923hcv39sa"
state
=
{
terminalWidth
:
""
,
userInput
:
""
,
isEnding
:
false
,
}
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
.
setState
({
isEnding
:
true
,
})
this
.
terminal
&&
this
.
terminal
.
write
(
this
.
props
.
result
)
}
else
if
(
executing
)
{
this
.
getAuthenticationData
()
}
})
}
getAuthenticationData
=
()
=>
{
axios
.
post
(
// `http://47.93.119.175:8888/auth`,
`http://webssh-test.julyedu.com:8888/auth`
,
{
username
:
"3guh394h3fhj0f4"
,
password
:
"okqdw029j038hrv3890cv"
,
},
{
withCredentials
:
false
,
}
)
.
then
((
res
)
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
0
)
{
this
.
getLinkId
(
data
)
}
else
{
Toast
.
fail
(
msg
,
2
)
}
})
}
getLinkId
=
(
token
:
string
)
=>
{
axios
.
post
(
`http://webssh-test.julyedu.com:8888`
,
{},
{
headers
:
{
Token
:
token
,
},
withCredentials
:
false
,
}
)
.
then
((
res
)
=>
{
const
{
id
}
=
res
.
data
this
.
connectServer
(
id
)
})
}
connectServer
=
(
id
:
string
)
=>
{
this
.
socket
=
new
WebSocket
(
`
${
API
.
ws
}
?id=
${
id
}
`
)
this
.
socket
.
onopen
=
()
=>
{
this
.
sendMessage
(
this
.
props
.
filename
)
this
.
sendMessage
(
"
\
r"
)
}
this
.
socket
.
onmessage
=
(
event
)
=>
{
const
reader
=
new
FileReader
()
reader
.
onload
=
()
=>
{
if
(
this
.
ab2str
(
reader
.
result
).
trim
()
===
this
.
endFlag
)
{
return
this
.
socket
?.
close
()
}
this
.
terminal
!
.
write
(
this
.
ab2str
(
reader
.
result
))
}
reader
.
readAsArrayBuffer
(
event
.
data
)
}
this
.
socket
.
onerror
=
(
event
)
=>
{
console
.
log
(
event
)
// this.connectServer(id)
}
this
.
socket
.
onclose
=
(
event
)
=>
{
console
.
log
(
"closed"
)
}
}
ab2str
=
(
buf
:
any
)
=>
{
return
String
.
fromCharCode
.
apply
(
null
,
new
Uint8Array
(
buf
)
as
any
)
}
handleChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
this
.
setState
({
userInput
:
e
.
target
.
value
,
})
}
sendMessage
=
(
message
:
string
)
=>
{
this
.
socket
?.
send
(
JSON
.
stringify
({
data
:
message
,
})
)
}
render
()
{
const
{
terminalWidth
,
isEnding
}
=
this
.
state
return
(
<
div
className=
{
"terminal-container"
}
>
<
div
className=
{
"terminal"
}
ref=
{
(
el
)
=>
(
this
.
termElem
=
el
)
}
style=
{
{
width
:
`${terminalWidth}`
}
}
></
div
>
{
!
isEnding
&&
(
<
div
className=
"input"
>
<
input
type=
"text"
id=
{
"terminal-input"
}
placeholder=
{
"输入完毕后请点击确认"
}
onChange=
{
this
.
handleChange
}
/>
<
label
htmlFor=
"terminal-input"
>
<
button
onClick=
{
()
=>
{
this
.
sendMessage
(
this
.
state
.
userInput
)
}
}
>
确认
</
button
>
</
label
>
</
div
>
)
}
</
div
>
)
}
}
export
default
TerminalInterface
src/router/router-config.js
View file @
1dd3b96a
...
...
@@ -536,7 +536,7 @@ export default [
path
:
"/interactive-tutorial"
,
component
:
loadable
(()
=>
import
(
/* webpackChunkName: 'interactive-tutorial' */
"
@
/components/interactive-tutorial"
/* webpackChunkName: 'interactive-tutorial' */
"
src
/components/interactive-tutorial"
)
),
},
...
...
src/utils/index.js
View file @
1dd3b96a
...
...
@@ -115,5 +115,6 @@ export {
isValidUrl
,
loadScript
,
getTimestamp
,
isValidHttpUrl
,
}
export
{
default
as
SendMessageToApp
}
from
"./app"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment