From 84ac40a6146fadcd164dc12172eb05ebffdb0975 Mon Sep 17 00:00:00 2001 From: Shuanglei Tao Date: Thu, 4 Jul 2019 00:16:21 +0800 Subject: [PATCH] html: move zmodem code as Component/ITerminalAddon --- html/src/components/terminal/index.tsx | 141 ++------------------- html/src/components/zmodem/index.tsx | 162 +++++++++++++++++++++++++ src/index.html | 2 +- 3 files changed, 172 insertions(+), 133 deletions(-) create mode 100644 html/src/components/zmodem/index.tsx diff --git a/html/src/components/terminal/index.tsx b/html/src/components/terminal/index.tsx index 639a86b..3a7a594 100644 --- a/html/src/components/terminal/index.tsx +++ b/html/src/components/terminal/index.tsx @@ -3,10 +3,9 @@ import { Component, h } from 'preact'; import { ITerminalOptions, Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { WebLinksAddon } from 'xterm-addon-web-links'; -import * as Zmodem from 'zmodem.js/src/zmodem_browser'; import { OverlayAddon } from './overlay'; -import { Modal } from '../modal'; +import { ZmodemAddon } from '../zmodem'; import 'xterm/dist/xterm.css'; @@ -34,24 +33,18 @@ interface Props { options: ITerminalOptions; } -interface State { - modal: boolean; -} - -export class Xterm extends Component { +export class Xterm extends Component { private textEncoder: TextEncoder; private textDecoder: TextDecoder; private container: HTMLElement; private terminal: Terminal; private fitAddon: FitAddon; private overlayAddon: OverlayAddon; + private zmodemAddon: ZmodemAddon; private socket: WebSocket; private title: string; private reconnect: number; private resizeTimeout: number; - private sentry: Zmodem.Sentry; - private session: Zmodem.Session; - private detection: Zmodem.Detection; constructor(props) { super(props); @@ -60,12 +53,6 @@ export class Xterm extends Component { this.textDecoder = new TextDecoder(); this.fitAddon = new FitAddon(); this.overlayAddon = new OverlayAddon(); - this.sentry = new Zmodem.Sentry({ - to_terminal: (octets: ArrayBuffer) => this.zmodemWrite(octets), - sender: (octets: ArrayLike) => this.zmodemSend(octets), - on_retract: () => {}, - on_detect: (detection: any) => this.zmodemDetect(detection), - }); } componentDidMount() { @@ -80,34 +67,16 @@ export class Xterm extends Component { window.removeEventListener('beforeunload', this.onWindowUnload); } - render({ id }: Props, { modal }: State) { + render({ id }: Props) { return (
(this.container = c)}> - - - + (this.zmodemAddon = c)} sender={this.sendData} />
); } @bind - private zmodemWrite(data: ArrayBuffer): void { - const { terminal } = this; - terminal.writeUtf8(new Uint8Array(data)); - } - - @bind - private zmodemSend(data: ArrayLike): void { + private sendData(data: ArrayLike) { const { socket } = this; const payload = new Uint8Array(data.length + 1); payload[0] = Command.INPUT.charCodeAt(0); @@ -115,78 +84,6 @@ export class Xterm extends Component { socket.send(payload); } - @bind - private zmodemDetect(detection: Zmodem.Detection): void { - const { terminal, receiveFile } = this; - terminal.setOption('disableStdin', true); - this.detection = detection; - this.session = detection.confirm(); - - if (this.session.type === 'send') { - this.setState({ modal: true }); - } else { - receiveFile(); - } - } - - @bind - private sendFile(event: Event) { - this.setState({ modal: false }); - - const { terminal, session, writeProgress } = this; - const files: FileList = (event.target as HTMLInputElement).files; - - Zmodem.Browser.send_files(session, files, { - on_progress: (_, xfer: any) => writeProgress(xfer), - }) - .then(() => { - session.close(); - this.detection = null; - terminal.setOption('disableStdin', false); - }) - .catch(e => { - console.log(`[ttyd] zmodem send: `, e); - }); - } - - @bind - private receiveFile() { - const { terminal, session, writeProgress } = this; - - session.on('offer', (xfer: any) => { - const fileBuffer = []; - xfer.on('input', payload => { - writeProgress(xfer); - fileBuffer.push(new Uint8Array(payload)); - }); - xfer.accept().then(() => { - Zmodem.Browser.save_to_disk(fileBuffer, xfer.get_details().name); - }); - }); - - session.on('session_end', () => { - this.detection = null; - terminal.setOption('disableStdin', false); - }); - - session.start(); - } - - @bind - private writeProgress(xfer: any) { - const { terminal, bytesHuman } = this; - - const file = xfer.get_details(); - const name = file.name; - const size = file.size; - const offset = xfer.get_offset(); - const percent = ((100 * offset) / size).toFixed(2); - - terminal.write( - `${name} ${percent}% ${bytesHuman(offset, 2)}/${bytesHuman(size, 2)}\r` - ); - } - @bind private onWindowResize() { const { fitAddon } = this; @@ -219,6 +116,7 @@ export class Xterm extends Component { terminal.loadAddon(fitAddon); terminal.loadAddon(overlayAddon); terminal.loadAddon(new WebLinksAddon()); + terminal.loadAddon(this.zmodemAddon); terminal.onTitleChange(data => { if (data && data !== '') { @@ -273,23 +171,14 @@ export class Xterm extends Component { @bind private onSocketData(event: MessageEvent) { - const { terminal, textDecoder } = this; + const { terminal, textDecoder, zmodemAddon } = this; const rawData = event.data as ArrayBuffer; const cmd = String.fromCharCode(new Uint8Array(rawData)[0]); const data = rawData.slice(1); switch (cmd) { case Command.OUTPUT: - try { - this.sentry.consume(data); - } catch (e) { - console.log(`[ttyd] zmodem consume: `, e); - terminal.setOption('disableStdin', false); - if (this.detection) { - this.detection.deny(); - this.detection = null; - } - } + zmodemAddon.consume(data); break; case Command.SET_WINDOW_TITLE: this.title = textDecoder.decode(data); @@ -331,16 +220,4 @@ export class Xterm extends Component { socket.send(textEncoder.encode(Command.INPUT + data)); } } - - private bytesHuman(bytes: any, precision: number): string { - if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) { - return '-'; - } - if (bytes === 0) return '0'; - if (typeof precision === 'undefined') precision = 1; - const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; - const num = Math.floor(Math.log(bytes) / Math.log(1024)); - const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision); - return `${value} ${units[num]}`; - } } diff --git a/html/src/components/zmodem/index.tsx b/html/src/components/zmodem/index.tsx new file mode 100644 index 0000000..5938d2f --- /dev/null +++ b/html/src/components/zmodem/index.tsx @@ -0,0 +1,162 @@ +import { bind } from 'decko'; +import { Component, h } from 'preact'; +import { ITerminalAddon, Terminal } from 'xterm'; +import * as Zmodem from 'zmodem.js/src/zmodem_browser'; + +import { Modal } from '../modal'; + +interface Props { + sender: (data: ArrayLike) => void; +} + +interface State { + modal: boolean; +} + +export class ZmodemAddon extends Component + implements ITerminalAddon { + private terminal: Terminal | undefined; + private sentry: Zmodem.Sentry; + private session: Zmodem.Session; + + constructor(props) { + super(props); + + this.sentry = new Zmodem.Sentry({ + to_terminal: (octets: ArrayBuffer) => this.zmodemWrite(octets), + sender: (octets: ArrayLike) => this.zmodemSend(octets), + on_retract: () => this.zmodemRetract(), + on_detect: (detection: any) => this.zmodemDetect(detection), + }); + } + + render(_, { modal }: State) { + return ( + + + + ); + } + + activate(terminal: Terminal): void { + this.terminal = terminal; + } + + dispose(): void {} + + consume(data: ArrayBuffer) { + const { sentry, terminal } = this; + try { + sentry.consume(data); + } catch (e) { + console.log(`[ttyd] zmodem consume: `, e); + terminal.setOption('disableStdin', false); + } + } + + @bind + private zmodemWrite(data: ArrayBuffer): void { + this.terminal.writeUtf8(new Uint8Array(data)); + } + + @bind + private zmodemSend(data: ArrayLike): void { + this.props.sender(data); + } + + @bind + private zmodemRetract(): void { + this.terminal.setOption('disableStdin', false); + } + + @bind + private zmodemDetect(detection: Zmodem.Detection): void { + const { terminal, receiveFile } = this; + terminal.setOption('disableStdin', true); + this.session = detection.confirm(); + + if (this.session.type === 'send') { + this.setState({ modal: true }); + } else { + receiveFile(); + } + } + + @bind + private sendFile(event: Event) { + this.setState({ modal: false }); + + const { terminal, session, writeProgress } = this; + const files: FileList = (event.target as HTMLInputElement).files; + + Zmodem.Browser.send_files(session, files, { + on_progress: (_, xfer: any) => writeProgress(xfer), + }) + .then(() => { + session.close(); + terminal.setOption('disableStdin', false); + }) + .catch(e => { + console.log(`[ttyd] zmodem send: `, e); + }); + } + + @bind + private receiveFile() { + const { terminal, session, writeProgress } = this; + + session.on('offer', (xfer: any) => { + const fileBuffer = []; + xfer.on('input', payload => { + writeProgress(xfer); + fileBuffer.push(new Uint8Array(payload)); + }); + xfer.accept().then(() => { + Zmodem.Browser.save_to_disk(fileBuffer, xfer.get_details().name); + }); + }); + + session.on('session_end', () => { + terminal.setOption('disableStdin', false); + }); + + session.start(); + } + + @bind + private writeProgress(xfer: any) { + const { terminal, bytesHuman } = this; + + const file = xfer.get_details(); + const name = file.name; + const size = file.size; + const offset = xfer.get_offset(); + const percent = ((100 * offset) / size).toFixed(2); + + terminal.write( + `${name} ${percent}% ${bytesHuman(offset, 2)}/${bytesHuman(size, 2)}\r` + ); + } + + private bytesHuman(bytes: any, precision: number): string { + if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) { + return '-'; + } + if (bytes === 0) return '0'; + if (typeof precision === 'undefined') precision = 1; + const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; + const num = Math.floor(Math.log(bytes) / Math.log(1024)); + const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision); + return `${value} ${units[num]}`; + } +} diff --git a/src/index.html b/src/index.html index 4291a0a..de0ee2d 100644 --- a/src/index.html +++ b/src/index.html @@ -1 +1 @@ -ttyd - Terminal \ No newline at end of file +ttyd - Terminal \ No newline at end of file -- 2.43.4