got terminal rendering working

This commit is contained in:
sawka 2023-03-29 01:05:36 -07:00
parent e83abeaa5b
commit 99cf937ffd
8 changed files with 382 additions and 49 deletions

View File

@ -55,6 +55,12 @@ node_modules/.bin/webpack-dev-server --config webpack.share.dev.js --host 127.0.
node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/sh2.ts
```
```bash
# @scripthaus command typecheck-webshare
# @scripthaus cd :playbook
node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/webshare.ts
```
```bash
# @scripthaus command build-package
# @scripthaus cd :playbook

View File

@ -6,7 +6,7 @@ import {boundMethod} from "autobind-decorator";
import dayjs from "dayjs";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, getRendererContext} from "./model";
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen} from "./model";
import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure";
import type {LineType, CmdDataType, FeStateType, RemoteType, RemotePtrType, RenderModeType, RendererContext, RendererOpts, SimpleBlobRendererComponent, RendererPluginType} from "./types";
import cn from "classnames";
@ -348,7 +348,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
// else: 53+(lines*lineheight)
let height = (isCollapsed ? 30 : 42); // height of zero height terminal
if (!isCollapsed) {
let usedRows = screen.getUsedRows(getRendererContext(line), line, cmd, width);
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
if (usedRows > 0) {
height = 53 + termHeightFromRows(usedRows, GlobalModel.termFontSize.get());
}
@ -386,7 +386,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
}
else {
let {screen, line, width} = this.props;
let usedRows = screen.getUsedRows(getRendererContext(line), line, cmd, width);
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
height = 36 + usedRows;
}
}
@ -769,7 +769,7 @@ class TerminalRenderer extends React.Component<{screen : LineContainerModel, lin
return isPhysicalFocused && (screenFocusType == "cmd" || screenFocusType == "cmd-fg")
}, {name: "computed-isFocused"}).get();
let cmd = screen.getCmd(line); // will not be null
let usedRows = screen.getUsedRows(getRendererContext(line), line, cmd, width);
let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width);
let termHeight = termHeightFromRows(usedRows, GlobalModel.termFontSize.get());
let termLoaded = this.termLoaded.get();
return (

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {isBlank, getDateStr} from "./util";
import {LineType, WebLine} from "./types";
import {LineType, WebLine, RendererContext} from "./types";
dayjs.extend(localizedFormat)
@ -73,4 +73,26 @@ function getSingleLineCmdText(cmdText : string) {
return cmdText;
}
export {getRendererType, getLineDateStr, getLineDateTimeStr, isMultiLineCmdText, getFullCmdText, getSingleLineCmdText};
function getRendererContext(line : LineType) : RendererContext {
return {
screenId: line.screenid,
cmdId: line.cmdid,
lineId: line.lineid,
lineNum: line.linenum,
};
}
function getWebRendererContext(line : WebLine) : RendererContext {
return {
screenId: line.screenid,
cmdId: line.lineid,
lineId: line.lineid,
lineNum: line.linenum,
};
}
function cmdStatusIsRunning(status : string) : boolean {
return status == "running" || status == "detached";
}
export {getRendererType, getLineDateStr, getLineDateTimeStr, isMultiLineCmdText, getFullCmdText, getSingleLineCmdText, getRendererContext, getWebRendererContext, cmdStatusIsRunning};

View File

@ -11,6 +11,7 @@ import {measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, ter
import dayjs from "dayjs";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import {getRendererContext, cmdStatusIsRunning} from "./lineutil";
dayjs.extend(customParseFormat)
dayjs.extend(localizedFormat)
@ -58,19 +59,6 @@ type SWLinePtr = {
screen : Screen,
};
function getRendererContext(line : LineType) : RendererContext {
return {
screenId: line.screenid,
cmdId: line.cmdid,
lineId: line.lineid,
lineNum: line.linenum,
};
}
function cmdStatusIsRunning(status : string) : boolean {
return status == "running" || status == "detached";
}
function keyHasNoMods(e : any) {
return !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey;
}
@ -3434,7 +3422,7 @@ if ((window as any).GlobalModel == null) {
GlobalModel = (window as any).GlobalModel;
GlobalCommandRunner = (window as any).GlobalCommandRunner;
export {Model, Session, ScreenLines, GlobalModel, GlobalCommandRunner, Cmd, Screen, riToRPtr, TabColors, RemoteColors, getRendererContext, getTermPtyData};
export {Model, Session, ScreenLines, GlobalModel, GlobalCommandRunner, Cmd, Screen, riToRPtr, TabColors, RemoteColors, getTermPtyData};
export type {LineContainerModel};

View File

@ -3182,6 +3182,7 @@ body.prompt-webshare #main {
padding: 10px;
border-bottom: 1px solid #777;
align-items: center;
flex-shrink: 0;
.logo-text {
.mono-font(32px);
@ -3203,7 +3204,22 @@ body.prompt-webshare #main {
.prompt-content {
flex-grow: 1;
padding: 10px;
padding: 10px;
display: flex;
flex-direction: row;
overflow: hidden;
.web-screen-view {
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
.web-lines {
height: 100%;
flex-grow: 1;
}
}
}
.prompt-footer {
@ -3215,6 +3231,11 @@ body.prompt-webshare #main {
height: 50px;
padding-left: 20px;
padding-right: 20px;
flex-shrink: 0;
.footer-copy {
font-size: 12px;
}
a {
display: block;

View File

@ -534,4 +534,4 @@ type WebFullScreen = {
vts : number,
}
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion};
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion, WebRemote};

View File

@ -11,6 +11,11 @@ import {isBlank} from "./util";
import {PluginModel} from "./plugins";
import * as lineutil from "./lineutil";
import * as util from "./util";
import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure";
type OV<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
type OMap<K,V> = mobx.ObservableMap<K,V>;
// TODO selection
// TODO remotevars
@ -38,7 +43,8 @@ function replaceHomePath(path : string, homeDir : string) : string {
return path;
}
function getCwdStr(state : FeStateType) : string {
function getCwdStr(state : T.FeStateType) : string {
let remote = null;
if ((state == null || state.cwd == null) && remote != null) {
return "~";
}
@ -46,13 +52,14 @@ function getCwdStr(state : FeStateType) : string {
if (state && state.cwd) {
cwd = state.cwd;
}
// if (remote && remote.remotevars.home) {
// cwd = replaceHomePath(cwd, remote.remotevars.cwd)
// }
// TODO fix
if (remote && remote.remotevars.home) {
cwd = replaceHomePath(cwd, remote.remotevars.cwd)
}
return cwd;
}
function getRemoteStr(remote : WebRemote) : string {
function getRemoteStr(remote : T.WebRemote) : string {
if (remote == null) {
return "(invalid remote)";
}
@ -74,9 +81,9 @@ class Prompt extends React.Component<{remote : T.WebRemote, festate : T.FeStateT
// }
// }
let remoteColorClass = (isRoot ? "color-red" : "color-green");
if (remote && remote.remoteopts && remote.remoteopts.color) {
remoteColorClass = "color-" + remote.remoteopts.color;
}
// if (remote && remote.remoteopts && remote.remoteopts.color) {
// remoteColorClass = "color-" + remote.remoteopts.color;
// }
let remoteTitle : string = null;
if (remote && remote.canonicalname) {
remoteTitle = remote.canonicalname;
@ -113,17 +120,28 @@ class LineAvatar extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}>
}
@mobxReact.observer
class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, topBorder : boolean}, {}> {
isCmdExpanded : OV<boolean> = mobx.observable.box(false, {name: "cmd-expanded"});
isOverflow : OV<boolean> = mobx.observable.box(false, {name: "line-overflow"});
cmdTextRef : React.RefObject<any> = React.createRef();
renderSimple() {
<div className={cn("web-line line", (line.linetype == "cmd" ? "line-cmd" : "line-text"))}>
<LineAvatar line={line}/>
</div>
let {line} = this.props;
return (
<div className={cn("web-line line", (line.linetype == "cmd" ? "line-cmd" : "line-text"))}>
<LineAvatar line={line} cmd={null}/>
</div>
);
}
renderCmdText(cmd : Cmd, remote : WebRemote) : any {
@boundMethod
handleExpandCmd() : void {
mobx.action(() => {
this.isCmdExpanded.set(true);
})();
}
renderCmdText(cmd : T.WebCmd, remote : T.WebRemote) : any {
if (cmd == null) {
return (
<div className="metapart-mono cmdtext">
@ -140,7 +158,7 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd},
</div>
</div>
<div key="meta3" className="meta meta-line3 cmdtext-expanded-wrapper">
<div className="cmdtext-expanded">{getFullCmdText(cmd.cmdstr)}</div>
<div className="cmdtext-expanded">{lineutil.getFullCmdText(cmd.cmdstr)}</div>
</div>
</React.Fragment>
);
@ -160,6 +178,10 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd},
);
}
@boundMethod
handleHeightChange() : void {
}
renderMetaWrap() {
let {line, cmd} = this.props;
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
@ -184,37 +206,42 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd},
}
render() {
let {line, cmd} = this.props;
let {line, cmd, topBorder} = this.props;
let model = WebShareModel;
let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
let rendererPlugin : RendererPluginType = null;
let rendererPlugin : T.RendererPluginType = null;
let isNoneRenderer = (line.renderer == "none");
if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) {
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
}
let rendererType = lineutil.getRendererType(line);
let mainCn = cn("web-line line line-cmd", {"top-border": topBorder});
let visObs = mobx.observable.box(true, {name: "visObs"});
return (
<div className={cn("web-line line line-cmd")}>
<div className={mainCn}>
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
<div className="line-header">
<LineAvatar line={line} cmd={cmd}/>
{this.renderMetaWrap()}
</div>
<TerminalRenderer line={line} cmd={cmd} width={1024} staticRender={false} visible={visObs} onHeightChange={this.handleHeightChange}/>
</div>
);
}
}
@mobxReact.observer
class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, topBorder : boolean}, {}> {
render() {
let {line} = this.props;
let {line, topBorder} = this.props;
let model = WebShareModel;
let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
let mainCn = cn("web-line line line-text", {"top-border": topBorder});
return (
<div className={cn("web-line line line-text")}>
<div className={mainCn}>
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
<div className="line-header">
<LineAvatar line={line}/>
<LineAvatar line={line} cmd={null}/>
</div>
<div>
<div>{line.text}</div>
@ -225,7 +252,121 @@ class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}
}
@mobxReact.observer
class WebLineView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : () => void}, {}> {
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "termrenderer-termLoaded"});
elemRef : React.RefObject<any> = React.createRef();
termRef : React.RefObject<any> = React.createRef();
constructor(props) {
super(props);
}
componentDidMount() {
this.componentDidUpdate(null, null, null);
}
componentWillUnmount() {
if (this.termLoaded.get()) {
this.unloadTerminal(true);
}
}
getSnapshotBeforeUpdate(prevProps, prevState) : {height : number} {
let elem = this.elemRef.current;
if (elem == null) {
return {height: 0};
}
return {height: elem.offsetHeight};
}
componentDidUpdate(prevProps, prevState, snapshot : {height : number}) : void {
if (this.props.onHeightChange == null) {
return;
}
let {line} = this.props;
let curHeight = 0;
let elem = this.elemRef.current;
if (elem != null) {
curHeight = elem.offsetHeight;
}
if (snapshot == null) {
snapshot = {height: 0};
}
if (snapshot.height != curHeight) {
this.props.onHeightChange();
// console.log("term-render height change: ", line.linenum, snapshot.height, "=>", curHeight);
}
this.checkLoad();
}
checkLoad() : void {
let {line, staticRender, visible} = this.props;
if (staticRender) {
return;
}
let vis = visible && visible.get();
let curVis = this.termLoaded.get();
if (vis && !curVis) {
this.loadTerminal();
}
else if (!vis && curVis) {
this.unloadTerminal(false);
}
}
loadTerminal() : void {
let {line, cmd} = this.props;
if (cmd == null) {
return;
}
let termElem = this.termRef.current;
if (termElem == null) {
console.log("cannot load terminal, no term elem found", line);
return;
}
WebShareModel.loadTerminalRenderer(termElem, line, cmd, this.props.width);
mobx.action(() => this.termLoaded.set(true))();
}
unloadTerminal(unmount : boolean) : void {
let {line} = this.props;
WebShareModel.unloadRenderer(line.lineid);
if (!unmount) {
mobx.action(() => this.termLoaded.set(false))();
let termElem = this.termRef.current;
if (termElem != null) {
termElem.replaceChildren();
}
}
}
@boundMethod
clickTermBlock(e : any) {
let {line} = this.props;
let termWrap = WebShareModel.getTermWrap(line.lineid);
if (termWrap != null) {
termWrap.giveFocus();
}
}
render() {
let {cmd, line, width, staticRender, visible} = this.props;
let isVisible = visible.get(); // for reaction
let usedRows = WebShareModel.getUsedRows(lineutil.getWebRendererContext(line), line, cmd, width);
let termHeight = termHeightFromRows(usedRows, WebShareModel.getTermFontSize());
let termLoaded = this.termLoaded.get();
return (
<div ref={this.elemRef} key="term-wrap" className={cn("terminal-wrapper", {"cmd-done": !lineutil.cmdStatusIsRunning(cmd.status)}, {"zero-height": (termHeight == 0)})}>
<div key="term-connectelem" className="terminal-connectelem" ref={this.termRef} data-cmdid={line.lineid} style={{height: termHeight}}></div>
<If condition={!termLoaded}><div key="term-loading" className="terminal-loading-message">...</div></If>
</div>
);
}
}
@mobxReact.observer
class WebLineView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, topBorder : boolean}, {}> {
render() {
let {line} = this.props;
if (line.linetype == "text") {
@ -246,7 +387,7 @@ class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> {
let {screen} = this.props;
let lines = screen.lines ?? [];
let cmds = screen.cmds ?? [];
let cmdMap : Record<string, WebCmd> = {};
let cmdMap : Record<string, T.WebCmd> = {};
for (let i=0; i<cmds.length; i++) {
let cmd = cmds[i];
cmdMap[cmd.lineid] = cmd;
@ -286,7 +427,7 @@ class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> {
@mobxReact.observer
class WebShareMain extends React.Component<{}, {}> {
renderCopy() {
return (<div>&copy; 2023 Dashborg Inc</div>);
return (<div className="footer-copy">&copy; 2023 Dashborg Inc</div>);
}
render() {

View File

@ -1,7 +1,11 @@
import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator";
import {handleJsonFetchResponse} from "./util";
import {handleJsonFetchResponse, isModKeyPress} from "./util";
import * as T from "./types";
import {TermWrap} from "./term";
import * as lineutil from "./lineutil";
import {windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows} from "./textmeasure";
type OV<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
@ -21,6 +25,10 @@ class WebShareModelClass {
screenId : string;
errMessage : OV<string> = mobx.observable.box(null, {name: "errMessage"});
screen : OV<T.WebFullScreen> = mobx.observable.box(null, {name: "webScreen"});
terminals : Record<string, TermWrap> = {}; // lineid => TermWrap
renderers : Record<string, T.RendererModel> = {}; // lineid => RendererModel
contentHeightCache : Record<string, number> = {}; // lineid => height
selectedLine : OV<number> = mobx.observable.box(0, {name: "selectedLine"});
constructor() {
let urlParams = new URLSearchParams(window.location.search);
@ -37,7 +45,126 @@ class WebShareModelClass {
}
getSelectedLine() : number {
return 10;
return this.selectedLine.get();
}
getTermFontSize() : number {
return 12;
}
loadTerminalRenderer(elem : Element, line : T.WebLine, cmd : T.WebCmd, width : number) : void {
let lineId = cmd.lineid;
let termWrap = this.getTermWrap(lineId);
if (termWrap != null) {
console.log("term-wrap already exists for", lineId);
return;
}
let cols = windowWidthToCols(width, this.getTermFontSize());
let usedRows = this.getContentHeight(lineutil.getWebRendererContext(line));
if (line.contentheight != null && line.contentheight != -1) {
usedRows = line.contentheight;
}
let termContext = lineutil.getWebRendererContext(line);
termWrap = new TermWrap(elem, {
termContext: termContext,
usedRows: usedRows,
termOpts: cmd.termopts,
winSize: {height: 0, width: width},
dataHandler: null,
focusHandler: (focus : boolean) => this.setTermFocus(line.linenum, focus),
isRunning: lineutil.cmdStatusIsRunning(cmd.status),
customKeyHandler: this.termCustomKeyHandler.bind(this),
fontSize: this.getTermFontSize(),
ptyDataSource: getTermPtyData,
onUpdateContentHeight: (termContext : T.RendererContext, height : number) => { this.setContentHeight(termContext, height); },
});
this.terminals[lineId] = termWrap;
if (this.selectedLine.get() == line.linenum) {
termWrap.giveFocus();
}
return;
}
termCustomKeyHandler(e : any, termWrap : TermWrap) : boolean {
if (e.type != "keydown" || isModKeyPress(e)) {
return false;
}
e.stopPropagation();
e.preventDefault();
if (e.code == "ArrowUp") {
termWrap.terminal.scrollLines(-1);
return false;
}
if (e.code == "ArrowDown") {
termWrap.terminal.scrollLines(1);
return false;
}
if (e.code == "PageUp") {
termWrap.terminal.scrollPages(-1);
return false;
}
if (e.code == "PageDown") {
termWrap.terminal.scrollPages(1);
return false;
}
return false;
}
setTermFocus(lineNum : number, focus : boolean) : void {
}
getContentHeight(context : T.RendererContext) : number {
let key = context.lineId;
return this.contentHeightCache[key];
}
setContentHeight(context : T.RendererContext, height : number) : void {
let key = context.cmdId;
this.contentHeightCache[key] = height;
}
unloadRenderer(lineId : string) : void {
let rmodel = this.renderers[lineId];
if (rmodel != null) {
rmodel.dispose();
delete this.renderers[lineId];
}
let term = this.terminals[lineId];
if (term != null) {
term.dispose();
delete this.terminals[lineId];
}
}
getUsedRows(context : T.RendererContext, line : T.WebLine, cmd : T.WebCmd, width : number) : number {
if (cmd == null) {
return 0;
}
let termOpts = cmd.termopts;
if (!termOpts.flexrows) {
return termOpts.rows;
}
let termWrap = this.getTermWrap(cmd.lineid);
if (termWrap == null) {
let cols = windowWidthToCols(width, this.getTermFontSize());
let usedRows = this.getContentHeight(context);
if (usedRows != null) {
return usedRows;
}
if (line.contentheight != null && line.contentheight != -1) {
return line.contentheight;
}
return (lineutil.cmdStatusIsRunning(cmd.status) ? 1 : 0);
}
return termWrap.getUsedRows();
}
getTermWrap(lineId : string) : TermWrap {
return this.terminals[lineId];
}
getRenderer(lineId : string) : T.RendererModel {
return this.renderers[lineId];
}
loadFullScreenData() : void {
@ -52,7 +179,13 @@ class WebShareModelClass {
let usp = new URLSearchParams({screenid: this.screenId, viewkey: this.viewKey});
let url = new URL(getBaseUrl() + "/webshare/screen?" + usp.toString());
fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
mobx.action(() => this.screen.set(data))();
mobx.action(() => {
let screen : T.WebFullScreen = data;
this.screen.set(screen);
if (screen.lines != null && screen.lines.length > 0) {
this.selectedLine.set(screen.lines[screen.lines.length-1].linenum);
}
})();
}).catch((err) => {
this.errMessage.set("Cannot get screen: " + err.message);
});
@ -60,6 +193,28 @@ class WebShareModelClass {
}
}
function getTermPtyData(termContext : T.TermContextUnion) : Promise<T.PtyDataType> {
if ("remoteId" in termContext) {
throw new Error("remote term ptydata is not supported in webshare");
}
let ptyOffset = 0;
let viewKey = WebShareModel.viewKey;
let usp = new URLSearchParams({screenid: termContext.screenId, viewkey: viewKey, lineid: termContext.lineId});
let url = new URL(getBaseUrl() + "/webshare/ptydata?" + usp.toString());
return fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => {
if (!resp.ok) {
throw new Error(sprintf("Bad fetch response for /webshare/ptydata: %d %s", resp.status, resp.statusText));
}
let ptyOffsetStr = resp.headers.get("X-PtyDataOffset");
if (ptyOffsetStr != null && !isNaN(parseInt(ptyOffsetStr))) {
ptyOffset = parseInt(ptyOffsetStr);
}
return resp.arrayBuffer();
}).then((buf) => {
return {pos: ptyOffset, data: new Uint8Array(buf)};
});
}
let WebShareModel : WebShareModelClass = null;
if ((window as any).WebShareModel == null) {
WebShareModel = new WebShareModelClass();