UNPKG

tasmota-esp-web-tools

Version:
164 lines (147 loc) 4.32 kB
import { ColoredConsole, coloredConsoleStyles } from "../util/console-color"; import { sleep } from "../util/sleep"; import { LineBreakTransformer } from "../util/line-break-transformer"; import { Logger } from "../const"; export class EwtConsole extends HTMLElement { public port!: SerialPort; public logger!: Logger; public allowInput = true; private _console?: ColoredConsole; private _cancelConnection?: () => Promise<void>; public logs(): string { return this._console?.logs() || ""; } public connectedCallback() { if (this._console) { return; } const shadowRoot = this.attachShadow({ mode: "open" }); shadowRoot.innerHTML = ` <style> :host, input { background-color: #1c1c1c; color: #ddd; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; line-height: 1.45; display: flex; flex-direction: column; } form { display: flex; align-items: center; padding: 0 8px 0 16px; } input { flex: 1; padding: 4px; margin: 0 8px; border: 0; outline: none; } ${coloredConsoleStyles} </style> <div class="log"></div> ${ this.allowInput ? `<form> > <input autofocus> </form> ` : "" } `; this._console = new ColoredConsole(this.shadowRoot!.querySelector("div")!); if (this.allowInput) { const input = this.shadowRoot!.querySelector("input")!; this.addEventListener("click", () => { // Only focus input if user didn't select some text if (getSelection()?.toString() === "") { input.focus(); } }); input.addEventListener("keydown", (ev) => { if (ev.key === "Enter") { ev.preventDefault(); ev.stopPropagation(); this._sendCommand(); } }); } const abortController = new AbortController(); const connection = this._connect(abortController.signal); this._cancelConnection = () => { abortController.abort(); return connection; }; } private async _connect(abortSignal: AbortSignal) { this.logger.debug("Starting console read loop"); try { await this.port .readable!.pipeThrough(new TextDecoderStream(), { signal: abortSignal, }) .pipeThrough(new TransformStream(new LineBreakTransformer())) .pipeTo( new WritableStream({ write: (chunk) => { this._console!.addLine(chunk.replace("\r", "")); }, }), ); if (!abortSignal.aborted) { this._console!.addLine(""); this._console!.addLine(""); this._console!.addLine("Terminal disconnected"); } } catch (e) { this._console!.addLine(""); this._console!.addLine(""); this._console!.addLine(`Terminal disconnected: ${e}`); } finally { await sleep(100); this.logger.debug("Finished console read loop"); } } private async _sendCommand() { const input = this.shadowRoot!.querySelector("input")!; const command = input.value; const encoder = new TextEncoder(); const writer = this.port.writable!.getWriter(); await writer.write(encoder.encode(command + "\r\n")); this._console!.addLine(`> ${command}\r\n`); input.value = ""; input.focus(); try { writer.releaseLock(); } catch (err) { console.error("Ignoring release lock error", err); } } public async disconnect() { if (this._cancelConnection) { await this._cancelConnection(); this._cancelConnection = undefined; } } public async reset() { this.logger.debug("Triggering reset."); await this.port.setSignals({ dataTerminalReady: false, requestToSend: true, }); await this.port.setSignals({ dataTerminalReady: false, requestToSend: false, }); await new Promise((resolve) => setTimeout(resolve, 1000)); } } customElements.define("ewt-console", EwtConsole); declare global { interface HTMLElementTagNameMap { "ewt-console": EwtConsole; } }