import { ITerminalAddon, Terminal } from "xterm"; import { Browser, Detection, Offer, Sentry, Session } from 'zmodem.js/src/zmodem_browser'; import { MyModal, Button } from "./MyModal"; import { Component, ComponentChildren, createRef, render } from "preact"; export class ZModemAddon implements ITerminalAddon { term: Terminal; elem: HTMLDivElement; sentry: Sentry; toTerminal: (data: Uint8Array) => void; toServer: (data: Uint8Array) => void; constructor(props: { toTerminal: (data: Uint8Array) => void, toServer: (data: Uint8Array) => void }) { this.createElement(); this.toTerminal = props.toTerminal; this.toServer = props.toServer; this.init(); } private createElement() { this.elem = document.createElement("div"); document.body.prepend(this.elem); } consume(data: Uint8Array) { this.sentry.consume(data) } activate(terminal: Terminal): void { this.term = terminal } dispose() { } private init() { render(<>>, this.elem); this.sentry = new Sentry({ 'to_terminal': (d: Uint8Array) => this.toTerminal(d), 'on_detect': (detection: Detection) => this.onDetect(detection), 'sender': (x: Uint8Array) => { this.toServer(x) }, 'on_retract': () => this.reset(), }); } private reset() { this.init(); this.term.options.disableStdin = false; this.term.focus(); } private onDetect(detection: Detection) { var zsession = detection.confirm(); this.term.options.disableStdin = true; zsession.on('session_end', () => { this.reset() }); if (zsession.type === "send") { this.send(zsession); } else { zsession.on("offer", (xfer: any) => this.onOffer(xfer)); zsession.start(); } } private send(zsession: Session) { render(, this.elem) } private onOffer(xfer: Offer) { render( this.reset()} />, this.elem) } } // Renders a bootstrap progress bar function Progress(props: { min: number, max: number, now: number, children?: ComponentChildren }) { let { min, max, now } = props; let percentage = "0"; if ((typeof min === "number") && (typeof max === "number") && (typeof now === "number") && (min != max)) { percentage = (100 * (now - min) / (max - min)).toFixed(0); } return {props.children} } interface ReceiveFileModalProps { xfer: Offer; onFinish?: () => void; } interface ReceiveFileModalState { state: "notstarted" | "started" | "skipped" | "done" } export class ReceiveFileModal extends Component { constructor(props) { super(props) this.setState({ state: "notstarted" }) } accept() { this.setState({ state: "started" }); let timerID = setInterval( () => this.forceUpdate(), 100 ); this.props.xfer.accept().then((payloads: any) => { // All done, so stop updating the progress bar // and perform a final render. clearInterval(timerID); this.forceUpdate(); if (this.state.state != "skipped") { Browser.save_to_disk( payloads, this.props.xfer.get_details().name ); } this.setState({ state: "done" }) }) } finish() { console.log('finished'); if (this.props.onFinish) this.props.onFinish(); } progress() { if (this.state.state !== "notstarted") { return } } skip() { this.props.xfer.skip() this.setState({ state: "skipped" }) } buttons() { switch (this.state.state) { case "notstarted": return <> { this.accept(); }}>Accept { this.skip(); }}>Decline > case "started": return <> { this.skip(); }}>Cancel > case "skipped": return <> Skipping... > } } render() { if (this.state.state != "done") return Accept {this.props.xfer.get_details().name} ({this.props.xfer.get_details().size.toLocaleString(undefined, { maximumFractionDigits: 0 })} bytes)? {this.progress()} } } export class SendFileModal extends Component { filePickerRef = createRef(); constructor(props: SendFileModalProps) { super(props) this.setState({ state: "notstarted" }) } buttons() { switch (this.state.state) { case "started": return <> { this.send(); }} disabled={true}> Sending... > case "notstarted": return <> { this.send(); }}>Send > default: return } } send() { Browser.send_files(this.props.session, this.filePickerRef.current!.files, { on_offer_response: (f, xfer) => { this.setState({ state: "started" }) }, }).then(() => { this.setState({ state: "done" }) this.props.session.close() if (this.props.onFinish !== undefined) { this.props.onFinish(); } }) .catch(e => console.log(e)); } render() { if (this.state.state != "done") return Remote requested file transfer } } interface SendFileModalProps { onFinish?: () => void; session: Session; } interface SendFileModalState { state: "notstarted" | "started" | "done" currentFile: any }
{this.props.xfer.get_details().name}