122 lines
3.0 KiB
TypeScript
122 lines
3.0 KiB
TypeScript
import React, { useEffect, useRef } from 'react'
|
|
import ReactDOM from 'react-dom'
|
|
import styled from '@emotion/styled'
|
|
import { Terminal } from 'xterm'
|
|
import { FitAddon } from 'xterm-addon-fit'
|
|
import { Shell } from 'blessing-skin-shell'
|
|
import 'xterm/css/xterm.css'
|
|
import Draggable from 'react-draggable'
|
|
import * as event from './event'
|
|
import AptCommand from './cli/AptCommand'
|
|
import ClosetCommand from './cli/ClosetCommand'
|
|
import DnfCommand from './cli/DnfCommand'
|
|
import PacmanCommand from './cli/PacmanCommand'
|
|
import RmCommand from './cli/RmCommand'
|
|
import * as breakpoints from '@/styles/breakpoints'
|
|
|
|
let launched = false
|
|
|
|
const TerminalContainer = styled.div`
|
|
z-index: 1040;
|
|
position: fixed;
|
|
bottom: 7vh;
|
|
user-select: none;
|
|
|
|
.card-body {
|
|
background-color: #000;
|
|
}
|
|
|
|
${breakpoints.greaterThan(breakpoints.Breakpoint.xl)} {
|
|
left: 25vw;
|
|
width: 50vw;
|
|
height: 50vh;
|
|
}
|
|
|
|
${breakpoints.between(breakpoints.Breakpoint.md, breakpoints.Breakpoint.xl)} {
|
|
left: 5vw;
|
|
width: 90vw;
|
|
height: 40vh;
|
|
}
|
|
|
|
${breakpoints.lessThan(breakpoints.Breakpoint.md)} {
|
|
left: 1vw;
|
|
width: 98vw;
|
|
height: 35vh;
|
|
}
|
|
`
|
|
|
|
const TerminalWindow: React.FC<{ onClose(): void }> = (props) => {
|
|
const mount = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
const el = mount.current
|
|
if (!el) {
|
|
return
|
|
}
|
|
|
|
const terminal = new Terminal()
|
|
const fitAddon = new FitAddon()
|
|
terminal.loadAddon(fitAddon)
|
|
terminal.setOption(
|
|
'fontFamily',
|
|
'Monaco, Consolas, "Roboto Mono", "Noto Sans", "Droid Sans Mono"',
|
|
)
|
|
terminal.open(el)
|
|
fitAddon.fit()
|
|
|
|
const shell = new Shell(terminal)
|
|
shell.addExternal(AptCommand.name, AptCommand)
|
|
shell.addExternal('closet', ClosetCommand)
|
|
shell.addExternal(DnfCommand.name, DnfCommand)
|
|
shell.addExternal(PacmanCommand.name, PacmanCommand)
|
|
shell.addExternal('rm', RmCommand)
|
|
|
|
const unbindData = terminal.onData((e) => shell.input(e))
|
|
const unbindKey = terminal.onKey((e) =>
|
|
event.emit('terminalKeyPress', e.key),
|
|
)
|
|
launched = true
|
|
|
|
return () => {
|
|
unbindData.dispose()
|
|
unbindKey.dispose()
|
|
shell.free()
|
|
fitAddon.dispose()
|
|
terminal.dispose()
|
|
launched = false
|
|
}
|
|
}, [])
|
|
|
|
return (
|
|
<Draggable handle=".card-header">
|
|
<TerminalContainer className="card">
|
|
<div className="card-header">
|
|
<div className="d-flex justify-content-between">
|
|
<h4 className="card-title mt-1">Blessing Skin Shell</h4>
|
|
<button className="btn btn-default" onClick={props.onClose}>
|
|
×
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="card-body p-2" ref={mount}></div>
|
|
</TerminalContainer>
|
|
</Draggable>
|
|
)
|
|
}
|
|
|
|
export function launch() {
|
|
if (launched) {
|
|
return
|
|
}
|
|
|
|
const container = document.createElement('div')
|
|
document.body.appendChild(container)
|
|
|
|
const handleClose = () => {
|
|
ReactDOM.unmountComponentAtNode(container)
|
|
container.remove()
|
|
}
|
|
|
|
ReactDOM.render(<TerminalWindow onClose={handleClose} />, container)
|
|
}
|