moving model functionality into session.ts

This commit is contained in:
sawka
2022-06-16 16:34:46 -07:00
parent 71c67a4cc2
commit bb37a829de
4 changed files with 161 additions and 132 deletions
+27 -112
View File
@@ -6,75 +6,8 @@ import {boundMethod} from "autobind-decorator";
import * as dayjs from 'dayjs' 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 {TermWrap} from "./term";
import {TermWrap, getOrCreateTermWrap} from "./term"; import {getDefaultSession} from "./session";
type LineType = {
sessionid : string,
windowid : string,
lineid : number,
ts : number,
userid : string,
linetype : string,
text : string,
cmdid : string,
cmdtext : string,
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([
{sessionid: GSessionId, windowid: GWindowId, lineid: 1, userid: "sawka", ts: 1654631122000, linetype: "text", text: "hello"},
{sessionid: GSessionId, windowid: GWindowId, lineid: 2, userid: "sawka", ts: 1654631125000, linetype: "text", text: "again"},
]);
function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
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<any> {
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 @mobxReact.observer
class LineMeta extends React.Component<{line : LineType}, {}> { class LineMeta extends React.Component<{line : LineType}, {}> {
@@ -91,13 +24,13 @@ class LineMeta extends React.Component<{line : LineType}, {}> {
} }
@mobxReact.observer @mobxReact.observer
class LineText extends React.Component<{line : LineType}, {}> { class LineText extends React.Component<{line : LineType, session : Session}, {}> {
render() { render() {
let line = this.props.line; let line = this.props.line;
return ( return (
<div className="line line-text"> <div className="line line-text">
<div className="avatar"> <div className="avatar">
M S
</div> </div>
<div className="line-content"> <div className="line-content">
<div className="meta"> <div className="meta">
@@ -114,20 +47,16 @@ class LineText extends React.Component<{line : LineType}, {}> {
} }
@mobxReact.observer @mobxReact.observer
class LineCmd extends React.Component<{line : LineType}, {}> { class LineCmd extends React.Component<{line : LineType, session : Session}, {}> {
termWrap : TermWrap;
constructor(props) { constructor(props) {
super(props); super(props);
let {line} = this.props;
this.termWrap = new TermWrap(line.sessionid, line.cmdid, line.windowid, line.lineid);
} }
componentDidMount() { componentDidMount() {
let {line} = this.props; let {session, line} = this.props;
let termElem = document.getElementById(this.getId()); let termElem = document.getElementById(this.getId());
this.termWrap.connectToElem(termElem); let termWrap = session.getTermWrap(line);
this.termWrap.reloadTerminal(0); termWrap.connectToElem(termElem);
if (line.isnew) { if (line.isnew) {
setTimeout(() => { setTimeout(() => {
let lineElem = document.getElementById("line-" + this.getId()); let lineElem = document.getElementById("line-" + this.getId());
@@ -137,7 +66,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
})(); })();
}, 100); }, 100);
setTimeout(() => { setTimeout(() => {
this.termWrap.reloadTerminal(0); termWrap.reloadTerminal(0);
}, 1000); }, 1000);
} }
} }
@@ -149,7 +78,9 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
@boundMethod @boundMethod
doRefresh() { doRefresh() {
this.termWrap.reloadTerminal(500); let {session, line} = this.props;
let termWrap = session.getTermWrap(line);
termWrap.reloadTerminal(500);
} }
@boundMethod @boundMethod
@@ -169,14 +100,15 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
} }
render() { render() {
let {line} = this.props; let {session, line} = this.props;
let lineid = line.lineid.toString(); let lineid = line.lineid.toString();
let running = false; let running = false;
let rows = 0; let rows = 0;
let cols = 0; let cols = 0;
let renderVersion = this.termWrap.getRenderVersion(); let termWrap = session.getTermWrap(line);
this.termWrap.resizeToContent(); let renderVersion = termWrap.getRenderVersion();
let termSize = this.termWrap.getSize(); termWrap.resizeToContent();
let termSize = termWrap.getSize();
return ( return (
<div className="line line-cmd" id={"line-" + this.getId()}> <div className="line line-cmd" id={"line-" + this.getId()}>
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running})}> <div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running})}>
@@ -186,10 +118,10 @@ 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="metapart-mono">{line.cmdid} <If condition={termSize.rows > 0}>({termSize.rows}x{termSize.cols})</If> {this.termWrap.ptyPos} bytes, v{renderVersion}</div> <div className="metapart-mono">{line.cmdid} <If condition={termSize.rows > 0}>({termSize.rows}x{termSize.cols})</If> {termWrap.ptyPos} bytes, v{renderVersion}</div>
<div className="metapart-mono 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": termWrap.isFocused.get()})}>
<div className="terminal" id={this.getId()}></div> <div className="terminal" id={this.getId()}></div>
</div> </div>
</div> </div>
@@ -202,7 +134,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
} }
@mobxReact.observer @mobxReact.observer
class Line extends React.Component<{line : LineType}, {}> { class Line extends React.Component<{line : LineType, session : Session}, {}> {
render() { render() {
let line = this.props.line; let line = this.props.line;
if (line.linetype == "text") { if (line.linetype == "text") {
@@ -216,7 +148,7 @@ class Line extends React.Component<{line : LineType}, {}> {
} }
@mobxReact.observer @mobxReact.observer
class CmdInput extends React.Component<{sessionid : string, windowid : string}, {}> { class CmdInput extends React.Component<{session : Session, 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
@@ -241,22 +173,12 @@ class CmdInput extends React.Component<{sessionid : string, windowid : string},
@boundMethod @boundMethod
doSubmitCmd() { doSubmitCmd() {
let {sessionid, windowid} = this.props; let {session, 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"); session.submitCommand(windowid, 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) => {
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() { render() {
@@ -291,16 +213,16 @@ class CmdInput extends React.Component<{sessionid : string, windowid : string},
class SessionView extends React.Component<{session : SessionType}, {}> { class SessionView extends React.Component<{session : SessionType}, {}> {
render() { render() {
let session = this.props.session; let session = this.props.session;
let window = session.windows[0]; let window = session.getActiveWindow();
let lines = window.lines; 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}/> <Line key={line.lineid} line={line} session={session}/>
</For> </For>
</div> </div>
<CmdInput sessionid={session.sessionid} windowid={window.windowid}/> <CmdInput session={session} windowid={window.windowid}/>
</div> </div>
); );
} }
@@ -313,14 +235,7 @@ class Main extends React.Component<{}, {}> {
} }
render() { render() {
let windowLines = GlobalLines.get(); let session = getDefaultSession();
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">
+99
View File
@@ -0,0 +1,99 @@
import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator";
import {handleJsonFetchResponse} from "./util";
import {GlobalWS} from "./ws";
import {TermWrap, makeTermKey} from "./term";
var GlobalUser = "sawka";
var GSessionId = "47445c53-cfcf-4943-8339-2c04447f20a1";
var GWindowId = "1";
var GlobalLines = mobx.observable.box([
{sessionid: GSessionId, windowid: GWindowId, lineid: 1, userid: "sawka", ts: 1654631122000, linetype: "text", text: "hello"},
{sessionid: GSessionId, windowid: GWindowId, lineid: 2, userid: "sawka", ts: 1654631125000, linetype: "text", text: "again"},
]);
type LineType = {
sessionid : string,
windowid : string,
lineid : number,
ts : number,
userid : string,
linetype : string,
text : string,
cmdid : string,
cmdtext : string,
isnew : boolean,
};
type WindowType = {
sessionid : string;
windowid : string;
name : string;
lines : LineType[];
};
class Session {
sessionid : string;
name : string;
windows : WindowType[];
activeWindowId : string;
termMap : Record<string, TermWrap> = {};
submitCommand(windowid : string, commandStr : string) {
let url = sprintf("http://localhost:8080/api/run-command");
let data = {sessionid: this.sessionid, windowid: windowid, command: commandStr, userid: GlobalUser};
fetch(url, {method: "post", body: JSON.stringify(data)}).then((resp) => handleJsonFetchResponse(url, resp)).then((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)
});
}
getTermWrap(line : LineType) {
let termKey = makeTermKey(line.sessionid, line.cmdid, line.windowid, line.lineid);
let termWrap = this.termMap[termKey];
if (termWrap != null) {
return termWrap;
}
termWrap = new TermWrap(line.sessionid, line.cmdid, line.windowid, line.lineid);
this.termMap[termKey] = termWrap;
termWrap.initialized = true;
termWrap.reloadTerminal(0);
return termWrap;
}
getActiveWindow() : WindowType {
if (this.windows == null) {
return null;
}
for (let i=0; i<this.windows.length; i++) {
if (this.windows[i].windowid == this.activeWindowId) {
return this.windows[i];
}
}
return null;
}
}
function getDefaultSession() : Session {
let windowLines = GlobalLines.get();
let session = new Session();
session.sessionid = GSessionId;
session.name = "default";
session.activeWindowId = GWindowId;
session.windows = [
{sessionid: GSessionId, windowid: GWindowId, name: "default", lines: windowLines},
];
return session;
}
window.getDefaultSession = getDefaultSession;
export {Session, getDefaultSession};
export type {LineType, WindowType};
+1 -20
View File
@@ -4,19 +4,6 @@ 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>;
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 { function makeTermKey(sessionId : string, cmdId : string, windowId : string, lineid : number) : string {
return sprintf("%s/%s/%s/%s", sessionId, cmdId, windowId, lineid); return sprintf("%s/%s/%s/%s", sessionId, cmdId, windowId, lineid);
} }
@@ -57,7 +44,6 @@ class TermWrap {
this.windowId = windowId; this.windowId = windowId;
this.lineid = lineid; this.lineid = lineid;
this.terminal = new Terminal({rows: 2, cols: 80}); this.terminal = new Terminal({rows: 2, cols: 80});
TermMap[cmdId] = this;
} }
destroy() { destroy() {
@@ -150,9 +136,4 @@ class TermWrap {
} }
} }
if (window.TermMap == null) { export {TermWrap, makeTermKey};
TermMap = {};
window.TermMap = TermMap;
}
export {TermWrap, TermMap, makeTermKey, getOrCreateTermWrap};
+34
View File
@@ -0,0 +1,34 @@
function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
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<any> {
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;
}
export {handleJsonFetchResponse};