import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; import {Terminal} from 'xterm'; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import * as dayjs from 'dayjs' import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames" type LineType = { lineid : number, ts : number, userid : string, linetype : string, text : string, cmdid : string, cmdtext : string, isnew : boolean, }; var GlobalLines = mobx.observable.box([ {lineid: 1, userid: "sawka", ts: 1654631122000, linetype: "text", text: "hello"}, {lineid: 2, userid: "sawka", ts: 1654631125000, linetype: "text", text: "again"}, ]); var TermMap = {}; window.TermMap = TermMap; function fetchJsonData(resp : any, ctErr : boolean) : Promise { let contentType = resp.headers.get("Content-Type"); if (contentType != null && contentType.startsWith("application/json")) { return resp.text().then((textData) => { try { return JSON.parse(textData); } catch (err) { let errMsg = sprintf("Unparseable JSON: " + err.message); let rtnErr = new Error(errMsg); throw rtnErr; } }); } if (ctErr) { throw new Error("non-json content-type"); } } function handleJsonFetchResponse(url : URL, resp : any) : Promise { if (!resp.ok) { let errData = fetchJsonData(resp, false); if (errData && errData["error"]) { throw new Error(errData["error"]) } let errMsg = sprintf("Bad status code response from fetch '%s': %d %s", url.toString(), resp.status, resp.statusText); let rtnErr = new Error(errMsg); throw rtnErr; } let rtnData = fetchJsonData(resp, true); return rtnData; } @mobxReact.observer class LineMeta extends React.Component<{line : LineType}, {}> { render() { let line = this.props.line; return (
{line.lineid}
{line.userid}
{dayjs(line.ts).format("hh:mm:ss a")}
); } } @mobxReact.observer class LineText extends React.Component<{line : LineType}, {}> { render() { let line = this.props.line; return (
M
{line.userid}
{dayjs(line.ts).format("hh:mm:ss a")}
{line.text}
); } } function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, callback?: () => void) { console.log("loadptyout", cmdId); term.clear() let url = sprintf("http://localhost:8080/api/ptyout?sessionid=%s&cmdid=%s", sessionId, cmdId); fetch(url).then((resp) => { if (!resp.ok) { throw new Error(sprintf("Bad fetch response for /api/ptyout: %d %s", resp.status, resp.statusText)); } return resp.text() }).then((resptext) => { setTimeout(() => term.write(resptext, callback), 0); }); } @mobxReact.observer class LineCmd extends React.Component<{line : LineType}, {}> { terminal : mobx.IObservableValue = mobx.observable.box(null, {name: "terminal"}); focus : mobx.IObservableValue = mobx.observable.box(false, {name: "focus"}); version : mobx.IObservableValue = mobx.observable.box(0, {name: "lineversion"}); componentDidMount() { let {line, sessionid} = this.props; let terminal = new Terminal(); TermMap[line.cmdid] = terminal; let termElem = document.getElementById(this.getId()); terminal.open(termElem); loadPtyOut(terminal, sessionid, line.cmdid, this.incVersion); terminal.textarea.addEventListener("focus", () => { mobx.action(() => { this.focus.set(true); })(); }); terminal.textarea.addEventListener("blur", () => { mobx.action(() => { this.focus.set(false); })(); }); if (line.isnew) { setTimeout(() => { let lineElem = document.getElementById("line-" + this.getId()); lineElem.scrollIntoView({block: "end"}); mobx.action(() => { line.isnew = false; })(); }, 100); setTimeout(() => { loadPtyOut(terminal, sessionid, line.cmdid); }, 1000); } mobx.action(() => this.terminal.set(terminal))(); } @boundMethod incVersion() : void { console.log("inc version!", this.version.get()+1); mobx.action(() => this.version.set(this.version.get() + 1))(); } getId() : string { let {line} = this.props; return "cmd-" + line.lineid + "-" + line.cmdid; } @boundMethod doRefresh() { let {line, sessionid} = this.props; loadPtyOut(this.terminal.get(), sessionid, line.cmdid, this.incVersion); } render() { let {line} = this.props; let lineid = line.lineid.toString(); let running = false; let term = this.terminal.get(); let termNumLines = 0; let termYPos = 0; let version = this.version.get(); console.log("render version!", this.version.get(), line.cmdid); if (term != null) { termNumLines = term._core.buffer.lines.length; termYPos = term._core.buffer.y; console.log(term._core.buffer.y, termYPos, term._core.buffer); } return (
= 5}, {"running": running})}> {lineid}
{line.userid}
{dayjs(line.ts).format("hh:mm:ss a")}
{line.cmdid} ({termNumLines}) ({termYPos}) v{version}
> {line.cmdtext}
Refresh
); } } @mobxReact.observer class Line extends React.Component<{line : LineType}, {}> { render() { let line = this.props.line; if (line.linetype == "text") { return ; } if (line.linetype == "cmd") { return ; } return
[invalid line type '{line.linetype}']
; } } @mobxReact.observer class CmdInput extends React.Component<{line : LineType, sessionid : string}, {}> { curLine : mobx.IObservableValue = mobx.observable("", {name: "command-line"}); @mobx.action @boundMethod onKeyDown(e : any) { mobx.action(() => { let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift"); if (e.code == "Enter" && !ctrlMod) { e.preventDefault(); setTimeout(() => this.doSubmitCmd(), 0); return; } // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e); })(); } @boundMethod onChange(e : any) { mobx.action(() => { this.curLine.set(e.target.value); })(); } @boundMethod doSubmitCmd() { let commandStr = this.curLine.get(); mobx.action(() => { this.curLine.set(""); })(); let url = sprintf("http://localhost:8080/api/run-command"); let data = {sessionid: this.props.sessionid, command: commandStr}; fetch(url, {method: "post", body: JSON.stringify(data)}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { console.log("got success data", data); mobx.action(() => { let lines = GlobalLines.get(); data.data.line.isnew = true; lines.push(data.data.line); })(); }).catch((err) => { console.log("error calling run-command", err) }); } render() { return (
[ mike@imac27 master ~/work/gopath/src/github.com/sawka/darktile-termutil ]
mike@local
); } } @mobxReact.observer class Main extends React.Component<{sessionid : string}, {}> { render() { let lines = GlobalLines.get(); console.log("main-lines", mobx.toJS(lines)); return (

ScriptHaus

); } } export {Main};