checkpoint, session, window, termmap

This commit is contained in:
sawka 2022-06-16 15:51:17 -07:00
parent a9f96dd983
commit 71c67a4cc2
4 changed files with 91 additions and 41 deletions

View File

@ -7,9 +7,11 @@ import * as dayjs from 'dayjs'
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
import cn from "classnames" import cn from "classnames"
import {GlobalWS} from "./ws"; import {GlobalWS} from "./ws";
import {TermWrap} from "./term"; import {TermWrap, getOrCreateTermWrap} from "./term";
type LineType = { type LineType = {
sessionid : string,
windowid : string,
lineid : number, lineid : number,
ts : number, ts : number,
userid : string, userid : string,
@ -20,9 +22,25 @@ type LineType = {
isnew : boolean, isnew : boolean,
}; };
type SessionType = {
sessionid : string;
name : string;
windows : WindowType[];
};
type WindowType = {
windowid : string;
name : string;
lines : LineType[];
};
var GlobalUser = "sawka";
var GSessionId = "47445c53-cfcf-4943-8339-2c04447f20a1";
var GWindowId = "1";
var GlobalLines = mobx.observable.box([ var GlobalLines = mobx.observable.box([
{lineid: 1, userid: "sawka", ts: 1654631122000, linetype: "text", text: "hello"}, {sessionid: GSessionId, windowid: GWindowId, lineid: 1, userid: "sawka", ts: 1654631122000, linetype: "text", text: "hello"},
{lineid: 2, userid: "sawka", ts: 1654631125000, linetype: "text", text: "again"}, {sessionid: GSessionId, windowid: GWindowId, lineid: 2, userid: "sawka", ts: 1654631125000, linetype: "text", text: "again"},
]); ]);
function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> { function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
@ -101,12 +119,12 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
constructor(props) { constructor(props) {
super(props); super(props);
let {line, sessionid} = this.props; let {line} = this.props;
this.termWrap = new TermWrap(sessionid, line.cmdid); this.termWrap = new TermWrap(line.sessionid, line.cmdid, line.windowid, line.lineid);
} }
componentDidMount() { componentDidMount() {
let {line, sessionid} = this.props; let {line} = this.props;
let termElem = document.getElementById(this.getId()); let termElem = document.getElementById(this.getId());
this.termWrap.connectToElem(termElem); this.termWrap.connectToElem(termElem);
this.termWrap.reloadTerminal(0); this.termWrap.reloadTerminal(0);
@ -168,8 +186,8 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
<div className="meta"> <div className="meta">
<div className="user">{line.userid}</div> <div className="user">{line.userid}</div>
<div className="ts">{dayjs(line.ts).format("hh:mm:ss a")}</div> <div className="ts">{dayjs(line.ts).format("hh:mm:ss a")}</div>
<div className="cmdid">{line.cmdid} <If condition={termSize.rows > 0}>({termSize.rows}x{termSize.cols})</If> v{renderVersion}</div> <div className="metapart-mono">{line.cmdid} <If condition={termSize.rows > 0}>({termSize.rows}x{termSize.cols})</If> {this.termWrap.ptyPos} bytes, v{renderVersion}</div>
<div className="cmdtext">&gt; {this.singleLineCmdText(line.cmdtext)}</div> <div className="metapart-mono cmdtext">&gt; {this.singleLineCmdText(line.cmdtext)}</div>
</div> </div>
<div className={cn("terminal-wrapper", {"focus": this.termWrap.isFocused.get()})}> <div className={cn("terminal-wrapper", {"focus": this.termWrap.isFocused.get()})}>
<div className="terminal" id={this.getId()}></div> <div className="terminal" id={this.getId()}></div>
@ -198,7 +216,7 @@ class Line extends React.Component<{line : LineType}, {}> {
} }
@mobxReact.observer @mobxReact.observer
class CmdInput extends React.Component<{line : LineType, sessionid : string}, {}> { class CmdInput extends React.Component<{sessionid : string, windowid : string}, {}> {
curLine : mobx.IObservableValue<string> = mobx.observable("", {name: "command-line"}); curLine : mobx.IObservableValue<string> = mobx.observable("", {name: "command-line"});
@mobx.action @boundMethod @mobx.action @boundMethod
@ -223,12 +241,13 @@ class CmdInput extends React.Component<{line : LineType, sessionid : string}, {}
@boundMethod @boundMethod
doSubmitCmd() { doSubmitCmd() {
let {sessionid, windowid} = this.props;
let commandStr = this.curLine.get(); let commandStr = this.curLine.get();
mobx.action(() => { mobx.action(() => {
this.curLine.set(""); this.curLine.set("");
})(); })();
let url = sprintf("http://localhost:8080/api/run-command"); let url = sprintf("http://localhost:8080/api/run-command");
let data = {sessionid: this.props.sessionid, command: commandStr}; let data = {sessionid: sessionid, windowid: windowid, command: commandStr, userid: GlobalUser};
fetch(url, {method: "post", body: JSON.stringify(data)}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { fetch(url, {method: "post", body: JSON.stringify(data)}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
mobx.action(() => { mobx.action(() => {
let lines = GlobalLines.get(); let lines = GlobalLines.get();
@ -269,36 +288,46 @@ class CmdInput extends React.Component<{line : LineType, sessionid : string}, {}
} }
@mobxReact.observer @mobxReact.observer
class SessionView extends React.Component<{sessionid : string}, {}> { class SessionView extends React.Component<{session : SessionType}, {}> {
render() { render() {
let lines = GlobalLines.get(); let session = this.props.session;
let window = session.windows[0];
let lines = window.lines;
return ( return (
<div className="session-view"> <div className="session-view">
<div className="lines"> <div className="lines">
<For each="line" of={lines}> <For each="line" of={lines}>
<Line key={line.lineid} line={line} sessionid={this.props.sessionid}/> <Line key={line.lineid} line={line}/>
</For> </For>
</div> </div>
<CmdInput sessionid={this.props.sessionid}/> <CmdInput sessionid={session.sessionid} windowid={window.windowid}/>
</div> </div>
); );
} }
} }
@mobxReact.observer @mobxReact.observer
class Main extends React.Component<{sessionid : string}, {}> { class Main extends React.Component<{}, {}> {
constructor(props : any) { constructor(props : any) {
super(props); super(props);
} }
render() { render() {
let windowLines = GlobalLines.get();
let session : SessionType = {
sessionid: GSessionId,
name: "default",
windows: [
{windowid: GWindowId, name: "default", lines: windowLines},
],
};
return ( return (
<div className="main"> <div className="main">
<h1 className="title scripthaus-logo-small"> <h1 className="title scripthaus-logo-small">
<div className="title-cursor">&#9608;</div> <div className="title-cursor">&#9608;</div>
ScriptHaus ScriptHaus
</h1> </h1>
<SessionView sessionid={this.props.sessionid}/> <SessionView session={session}/>
</div> </div>
); );
} }

View File

@ -79,7 +79,7 @@
color: #777; color: #777;
} }
.cmdid { .metapart-mono {
color: #777; color: #777;
margin-left: 8px; margin-left: 8px;
font-size: 0.75rem; font-size: 0.75rem;
@ -89,11 +89,7 @@
} }
.cmdtext { .cmdtext {
font-family: 'JetBrains Mono', monospace; color: black;
font-weight: 400;
font-size: 0.75rem;
margin-top: 5px;
margin-left: 8px;
overflow: hidden; overflow: hidden;
} }
} }

View File

@ -6,12 +6,10 @@ import {Main} from "./main";
import {GlobalWS} from "./ws"; import {GlobalWS} from "./ws";
let VERSION = __SHVERSION__; let VERSION = __SHVERSION__;
let terminal = null;
let sessionId = "47445c53-cfcf-4943-8339-2c04447f20a1";
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
GlobalWS.reconnect(); GlobalWS.reconnect();
let reactElem = React.createElement(Main, {sessionid: sessionId}, null); let reactElem = React.createElement(Main, null, null);
let elem = document.getElementById("main"); let elem = document.getElementById("main");
let root = createRoot(elem); let root = createRoot(elem);
root.render(reactElem); root.render(reactElem);

View File

@ -4,8 +4,23 @@ import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator"; import {boundMethod} from "autobind-decorator";
import {GlobalWS} from "./ws"; import {GlobalWS} from "./ws";
//
var TermMap : Record<string, TermWrap>; var TermMap : Record<string, TermWrap>;
function getOrCreateTermWrap(sessionId : string, cmdId : string, windowId : string, lineid : number) : TermWrap {
let termKey = makeTermKey(sessionId, cmdId, windowId, lineid);
let termWrap = TermMap[termKey];
if (termWrap != null) {
return termWrap;
}
termWrap = new TermWrap(sessionId, cmdId, windowId, lineid);
return termWrap;
}
function makeTermKey(sessionId : string, cmdId : string, windowId : string, lineid : number) : string {
return sprintf("%s/%s/%s/%s", sessionId, cmdId, windowId, lineid);
}
function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, delayMs : number, callback?: (number) => void) { function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, delayMs : number, callback?: (number) => void) {
term.clear() term.clear()
let url = sprintf("http://localhost:8080/api/ptyout?sessionid=%s&cmdid=%s", sessionId, cmdId); let url = sprintf("http://localhost:8080/api/ptyout?sessionid=%s&cmdid=%s", sessionId, cmdId);
@ -23,26 +38,24 @@ class TermWrap {
terminal : Terminal; terminal : Terminal;
sessionId : string; sessionId : string;
cmdId : string; cmdId : string;
ptyPos : number; windowId : string;
runPos : number; lineid : number;
runData : string; ptyPos : number = 0;
runPos : number = 0;
runData : string = "";
renderVersion : mobx.IObservableValue<number> = mobx.observable.box(1, {name: "renderVersion"}); renderVersion : mobx.IObservableValue<number> = mobx.observable.box(1, {name: "renderVersion"});
isFocused : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "focus"}); isFocused : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "focus"});
flexRows : boolean; flexRows : boolean = true;
maxRows : number; maxRows : number = 25;
cols : number; cols : number = 80;
atRowMax : boolean; atRowMax : boolean = false;
initialized : boolean = false;
constructor(sessionId : string, cmdId : string) { constructor(sessionId : string, cmdId : string, windowId : string, lineid : number) {
this.sessionId = sessionId; this.sessionId = sessionId;
this.cmdId = cmdId; this.cmdId = cmdId;
this.ptyPos = 0; this.windowId = windowId;
this.runPos = 0; this.lineid = lineid;
this.runData = "";
this.maxRows = 25;
this.cols = 80;
this.flexRows = true;
this.atRowMax = false;
this.terminal = new Terminal({rows: 2, cols: 80}); this.terminal = new Terminal({rows: 2, cols: 80});
TermMap[cmdId] = this; TermMap[cmdId] = this;
} }
@ -51,6 +64,20 @@ class TermWrap {
} }
// datalen is passed because data could be utf-8 and data.length is not the actual *byte* length
updatePtyData(pos : number, data : string, datalen : number) {
if (pos != this.ptyPos) {
throw new Error(sprintf("invalid pty-update, data-pos[%d] does not match term-pos[%d]", pos, this.ptyPos));
}
this.ptyPos += datalen;
term.write(data, () => {
mobx.action(() => {
this.resizeToContent();
this.incRenderVersion();
})();
});
}
resizeToContent() { resizeToContent() {
if (this.atRowMax) { if (this.atRowMax) {
return; return;
@ -128,4 +155,4 @@ if (window.TermMap == null) {
window.TermMap = TermMap; window.TermMap = TermMap;
} }
export {TermWrap, TermMap}; export {TermWrap, TermMap, makeTermKey, getOrCreateTermWrap};