From 99cf937ffdc16f88c1fb66883ce89d7d43dc7ae3 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 29 Mar 2023 01:05:36 -0700 Subject: [PATCH] got terminal rendering working --- scripthaus.md | 6 ++ src/linecomps.tsx | 8 +- src/lineutil.ts | 26 +++++- src/model.ts | 16 +--- src/sh2.less | 23 ++++- src/types.ts | 2 +- src/webshare-elems.tsx | 189 +++++++++++++++++++++++++++++++++++------ src/webshare-model.ts | 161 ++++++++++++++++++++++++++++++++++- 8 files changed, 382 insertions(+), 49 deletions(-) diff --git a/scripthaus.md b/scripthaus.md index 572eff79..7ff3fe49 100644 --- a/scripthaus.md +++ b/scripthaus.md @@ -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 diff --git a/src/linecomps.tsx b/src/linecomps.tsx index 087ab4ff..7e562ca7 100644 --- a/src/linecomps.tsx +++ b/src/linecomps.tsx @@ -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 ( diff --git a/src/lineutil.ts b/src/lineutil.ts index 8f25210b..8bd20931 100644 --- a/src/lineutil.ts +++ b/src/lineutil.ts @@ -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}; diff --git a/src/model.ts b/src/model.ts index e045baab..a5e1f23f 100644 --- a/src/model.ts +++ b/src/model.ts @@ -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}; diff --git a/src/sh2.less b/src/sh2.less index 3bc80609..d13d6b8b 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -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; diff --git a/src/types.ts b/src/types.ts index d104fd3f..caa45759 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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}; diff --git a/src/webshare-elems.tsx b/src/webshare-elems.tsx index 0305125a..ac6a871c 100644 --- a/src/webshare-elems.tsx +++ b/src/webshare-elems.tsx @@ -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 = mobx.IObservableValue; +type OArr = mobx.IObservableArray; +type OMap = mobx.ObservableMap; // 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 = mobx.observable.box(false, {name: "cmd-expanded"}); isOverflow : OV = mobx.observable.box(false, {name: "line-overflow"}); + cmdTextRef : React.RefObject = React.createRef(); renderSimple() { -
- -
+ let {line} = this.props; + return ( +
+ +
+ ); } - 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 (
@@ -140,7 +158,7 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd},
-
{getFullCmdText(cmd.cmdstr)}
+
{lineutil.getFullCmdText(cmd.cmdstr)}
); @@ -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 ( -
+
{this.renderMetaWrap()}
+
); } } @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 ( -
+
- +
{line.text}
@@ -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, onHeightChange : () => void}, {}> { + termLoaded : mobx.IObservableValue = mobx.observable.box(false, {name: "termrenderer-termLoaded"}); + elemRef : React.RefObject = React.createRef(); + termRef : React.RefObject = 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 ( +
+
+
...
+ +
+ ); + } +} + +@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 = {}; + let cmdMap : Record = {}; for (let i=0; i { @mobxReact.observer class WebShareMain extends React.Component<{}, {}> { renderCopy() { - return (
© 2023 Dashborg Inc
); + return (
© 2023 Dashborg Inc
); } render() { diff --git a/src/webshare-model.ts b/src/webshare-model.ts index 02d3c72d..9d81973f 100644 --- a/src/webshare-model.ts +++ b/src/webshare-model.ts @@ -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 = mobx.IObservableValue; type OArr = mobx.IObservableArray; @@ -21,6 +25,10 @@ class WebShareModelClass { screenId : string; errMessage : OV = mobx.observable.box(null, {name: "errMessage"}); screen : OV = mobx.observable.box(null, {name: "webScreen"}); + terminals : Record = {}; // lineid => TermWrap + renderers : Record = {}; // lineid => RendererModel + contentHeightCache : Record = {}; // lineid => height + selectedLine : OV = 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 { + 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();