@@ -3,10 +3,9 @@ import { Component, h } from 'preact';
33import { ITerminalOptions , Terminal } from 'xterm' ;
44import { FitAddon } from 'xterm-addon-fit' ;
55import { WebLinksAddon } from 'xterm-addon-web-links' ;
6- import * as Zmodem from 'zmodem.js/src/zmodem_browser' ;
76
87import { OverlayAddon } from './overlay' ;
9- import { Modal } from '../modal ' ;
8+ import { ZmodemAddon } from '../zmodem ' ;
109
1110import 'xterm/dist/xterm.css' ;
1211
@@ -34,24 +33,18 @@ interface Props {
3433 options : ITerminalOptions ;
3534}
3635
37- interface State {
38- modal : boolean ;
39- }
40-
41- export class Xterm extends Component < Props , State > {
36+ export class Xterm extends Component < Props > {
4237 private textEncoder : TextEncoder ;
4338 private textDecoder : TextDecoder ;
4439 private container : HTMLElement ;
4540 private terminal : Terminal ;
4641 private fitAddon : FitAddon ;
4742 private overlayAddon : OverlayAddon ;
43+ private zmodemAddon : ZmodemAddon ;
4844 private socket : WebSocket ;
4945 private title : string ;
5046 private reconnect : number ;
5147 private resizeTimeout : number ;
52- private sentry : Zmodem . Sentry ;
53- private session : Zmodem . Session ;
54- private detection : Zmodem . Detection ;
5548
5649 constructor ( props ) {
5750 super ( props ) ;
@@ -60,12 +53,6 @@ export class Xterm extends Component<Props, State> {
6053 this . textDecoder = new TextDecoder ( ) ;
6154 this . fitAddon = new FitAddon ( ) ;
6255 this . overlayAddon = new OverlayAddon ( ) ;
63- this . sentry = new Zmodem . Sentry ( {
64- to_terminal : ( octets : ArrayBuffer ) => this . zmodemWrite ( octets ) ,
65- sender : ( octets : ArrayLike < number > ) => this . zmodemSend ( octets ) ,
66- on_retract : ( ) => { } ,
67- on_detect : ( detection : any ) => this . zmodemDetect ( detection ) ,
68- } ) ;
6956 }
7057
7158 componentDidMount ( ) {
@@ -80,113 +67,23 @@ export class Xterm extends Component<Props, State> {
8067 window . removeEventListener ( 'beforeunload' , this . onWindowUnload ) ;
8168 }
8269
83- render ( { id } : Props , { modal } : State ) {
70+ render ( { id } : Props ) {
8471 return (
8572 < div id = { id } ref = { c => ( this . container = c ) } >
86- < Modal show = { modal } >
87- < label class = "file-label" >
88- < input
89- onChange = { this . sendFile }
90- class = "file-input"
91- type = "file"
92- multiple
93- />
94- < span class = "file-cta" >
95- < strong > Choose files…</ strong >
96- </ span >
97- </ label >
98- </ Modal >
73+ < ZmodemAddon ref = { c => ( this . zmodemAddon = c ) } sender = { this . sendData } />
9974 </ div >
10075 ) ;
10176 }
10277
10378 @bind
104- private zmodemWrite ( data : ArrayBuffer ) : void {
105- const { terminal } = this ;
106- terminal . writeUtf8 ( new Uint8Array ( data ) ) ;
107- }
108-
109- @bind
110- private zmodemSend ( data : ArrayLike < number > ) : void {
79+ private sendData ( data : ArrayLike < number > ) {
11180 const { socket } = this ;
11281 const payload = new Uint8Array ( data . length + 1 ) ;
11382 payload [ 0 ] = Command . INPUT . charCodeAt ( 0 ) ;
11483 payload . set ( data , 1 ) ;
11584 socket . send ( payload ) ;
11685 }
11786
118- @bind
119- private zmodemDetect ( detection : Zmodem . Detection ) : void {
120- const { terminal, receiveFile } = this ;
121- terminal . setOption ( 'disableStdin' , true ) ;
122- this . detection = detection ;
123- this . session = detection . confirm ( ) ;
124-
125- if ( this . session . type === 'send' ) {
126- this . setState ( { modal : true } ) ;
127- } else {
128- receiveFile ( ) ;
129- }
130- }
131-
132- @bind
133- private sendFile ( event : Event ) {
134- this . setState ( { modal : false } ) ;
135-
136- const { terminal, session, writeProgress } = this ;
137- const files : FileList = ( event . target as HTMLInputElement ) . files ;
138-
139- Zmodem . Browser . send_files ( session , files , {
140- on_progress : ( _ , xfer : any ) => writeProgress ( xfer ) ,
141- } )
142- . then ( ( ) => {
143- session . close ( ) ;
144- this . detection = null ;
145- terminal . setOption ( 'disableStdin' , false ) ;
146- } )
147- . catch ( e => {
148- console . log ( `[ttyd] zmodem send: ` , e ) ;
149- } ) ;
150- }
151-
152- @bind
153- private receiveFile ( ) {
154- const { terminal, session, writeProgress } = this ;
155-
156- session . on ( 'offer' , ( xfer : any ) => {
157- const fileBuffer = [ ] ;
158- xfer . on ( 'input' , payload => {
159- writeProgress ( xfer ) ;
160- fileBuffer . push ( new Uint8Array ( payload ) ) ;
161- } ) ;
162- xfer . accept ( ) . then ( ( ) => {
163- Zmodem . Browser . save_to_disk ( fileBuffer , xfer . get_details ( ) . name ) ;
164- } ) ;
165- } ) ;
166-
167- session . on ( 'session_end' , ( ) => {
168- this . detection = null ;
169- terminal . setOption ( 'disableStdin' , false ) ;
170- } ) ;
171-
172- session . start ( ) ;
173- }
174-
175- @bind
176- private writeProgress ( xfer : any ) {
177- const { terminal, bytesHuman } = this ;
178-
179- const file = xfer . get_details ( ) ;
180- const name = file . name ;
181- const size = file . size ;
182- const offset = xfer . get_offset ( ) ;
183- const percent = ( ( 100 * offset ) / size ) . toFixed ( 2 ) ;
184-
185- terminal . write (
186- `${ name } ${ percent } % ${ bytesHuman ( offset , 2 ) } /${ bytesHuman ( size , 2 ) } \r`
187- ) ;
188- }
189-
19087 @bind
19188 private onWindowResize ( ) {
19289 const { fitAddon } = this ;
@@ -219,6 +116,7 @@ export class Xterm extends Component<Props, State> {
219116 terminal . loadAddon ( fitAddon ) ;
220117 terminal . loadAddon ( overlayAddon ) ;
221118 terminal . loadAddon ( new WebLinksAddon ( ) ) ;
119+ terminal . loadAddon ( this . zmodemAddon ) ;
222120
223121 terminal . onTitleChange ( data => {
224122 if ( data && data !== '' ) {
@@ -273,23 +171,14 @@ export class Xterm extends Component<Props, State> {
273171
274172 @bind
275173 private onSocketData ( event : MessageEvent ) {
276- const { terminal, textDecoder } = this ;
174+ const { terminal, textDecoder, zmodemAddon } = this ;
277175 const rawData = event . data as ArrayBuffer ;
278176 const cmd = String . fromCharCode ( new Uint8Array ( rawData ) [ 0 ] ) ;
279177 const data = rawData . slice ( 1 ) ;
280178
281179 switch ( cmd ) {
282180 case Command . OUTPUT :
283- try {
284- this . sentry . consume ( data ) ;
285- } catch ( e ) {
286- console . log ( `[ttyd] zmodem consume: ` , e ) ;
287- terminal . setOption ( 'disableStdin' , false ) ;
288- if ( this . detection ) {
289- this . detection . deny ( ) ;
290- this . detection = null ;
291- }
292- }
181+ zmodemAddon . consume ( data ) ;
293182 break ;
294183 case Command . SET_WINDOW_TITLE :
295184 this . title = textDecoder . decode ( data ) ;
@@ -331,16 +220,4 @@ export class Xterm extends Component<Props, State> {
331220 socket . send ( textEncoder . encode ( Command . INPUT + data ) ) ;
332221 }
333222 }
334-
335- private bytesHuman ( bytes : any , precision : number ) : string {
336- if ( ! / ^ ( [ - + ] ) ? | ( \. \d + ) ( \d + ( \. \d + ) ? | ( \d + \. ) | I n f i n i t y ) $ / . test ( bytes ) ) {
337- return '-' ;
338- }
339- if ( bytes === 0 ) return '0' ;
340- if ( typeof precision === 'undefined' ) precision = 1 ;
341- const units = [ 'bytes' , 'KB' , 'MB' , 'GB' , 'TB' , 'PB' ] ;
342- const num = Math . floor ( Math . log ( bytes ) / Math . log ( 1024 ) ) ;
343- const value = ( bytes / Math . pow ( 1024 , Math . floor ( num ) ) ) . toFixed ( precision ) ;
344- return `${ value } ${ units [ num ] } ` ;
345- }
346223}
0 commit comments