From 4d3ca6251985333bc08f7cdfab2891a72bda8fa0 Mon Sep 17 00:00:00 2001 From: sawka Date: Thu, 30 Mar 2023 18:14:52 -0700 Subject: [PATCH] selected lines, webshare url, refactor linesview into its own component --- src/linecomps.tsx | 9 +- src/linesview.tsx | 483 +++++++++++++++++++++++++++++++++++++++ src/main.tsx | 508 +++-------------------------------------- src/model.ts | 20 +- src/sh2.less | 6 + src/term.ts | 11 +- src/types.ts | 26 ++- src/webshare-elems.tsx | 97 +++++++- src/webshare-model.ts | 38 ++- 9 files changed, 691 insertions(+), 507 deletions(-) create mode 100644 src/linesview.tsx diff --git a/src/linecomps.tsx b/src/linecomps.tsx index 56189a24..dd601d1a 100644 --- a/src/linecomps.tsx +++ b/src/linecomps.tsx @@ -8,7 +8,7 @@ import localizedFormat from 'dayjs/plugin/localizedFormat'; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; 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 type {LineType, CmdDataType, FeStateType, RemoteType, RemotePtrType, RenderModeType, RendererContext, RendererOpts, SimpleBlobRendererComponent, RendererPluginType, LineHeightChangeCallbackType} from "./types"; import cn from "classnames"; import {TermWrap} from "./term"; import type {LineContainerModel} from "./model"; @@ -24,8 +24,7 @@ type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; type OMap = mobx.ObservableMap; -type HeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void; -type RendererComponentProps = {screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : HeightChangeCallbackType, collapsed : boolean}; +type RendererComponentProps = {screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : LineHeightChangeCallbackType, collapsed : boolean}; type RendererComponentType = { new(props : RendererComponentProps) : React.Component }; function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string { @@ -98,7 +97,7 @@ class LineAvatar extends React.Component<{line : LineType, cmd : Cmd, onRightCli } @mobxReact.observer -class LineCmd extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : HeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV, noSelect? : boolean, showHints? : boolean}, {}> { +class LineCmd extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : LineHeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV, noSelect? : boolean, showHints? : boolean}, {}> { lineRef : React.RefObject = React.createRef(); cmdTextRef : React.RefObject = React.createRef(); rtnStateDiff : mobx.IObservableValue = mobx.observable.box(null, {name: "linecmd-rtn-state-diff"}); @@ -550,7 +549,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT } @mobxReact.observer -class Line extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : HeightChangeCallbackType, overrideCollapsed : OV, topBorder : boolean, renderMode : RenderModeType, noSelect? : boolean}, {}> { +class Line extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV, onHeightChange : LineHeightChangeCallbackType, overrideCollapsed : OV, topBorder : boolean, renderMode : RenderModeType, noSelect? : boolean}, {}> { render() { let line = this.props.line; if (line.archived) { diff --git a/src/linesview.tsx b/src/linesview.tsx new file mode 100644 index 00000000..499c7c61 --- /dev/null +++ b/src/linesview.tsx @@ -0,0 +1,483 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import {sprintf} from "sprintf-js"; +import {boundMethod} from "autobind-decorator"; +import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import localizedFormat from 'dayjs/plugin/localizedFormat'; +import {debounce, throttle} from "throttle-debounce"; +import * as T from "./types"; +import * as util from "./util"; +import * as lineutil from "./lineutil"; + +dayjs.extend(localizedFormat); +type OV = mobx.IObservableValue; +type OArr = mobx.IObservableArray; +type OMap = mobx.ObservableMap; + +const LinesVisiblePadding = 500; + +type ScreenInterface = { + setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) : void, + getSelectedLine() : number, + getAnchor() : {anchorLine : number, anchorOffset : number}, +} + +// ; + +type LineCompFactory = (props : T.LineFactoryProps) => JSX.Element; + +@mobxReact.observer +class LinesView extends React.Component<{screen : ScreenInterface, width : number, lines : T.LineInterface[], renderMode : T.RenderModeType, lineFactory : LineCompFactory}, {}> { + rszObs : ResizeObserver; + linesRef : React.RefObject; + staticRender : OV = mobx.observable.box(true, {name: "static-render"}); + lastOffsetHeight : number = 0; + lastOffsetWidth : number = 0; + ignoreNextScroll : boolean = false; + visibleMap : Map>; // linenum => OV + collapsedMap : Map>; // linenum => OV + lastLinesLength : number = 0; + lastSelectedLine : number = 0; + + computeAnchorLine_throttled : () => void; + computeVisibleMap_debounced : () => void; + + constructor(props) { + super(props); + this.linesRef = React.createRef(); + this.computeAnchorLine_throttled = throttle(100, this.computeAnchorLine.bind(this), {noLeading: true, noTrailing: false}); + this.visibleMap = new Map(); + this.collapsedMap = new Map(); + this.computeVisibleMap_debounced = debounce(1000, this.computeVisibleMap.bind(this)); + } + + @boundMethod + scrollHandler() { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return; + } + let heightDiff = linesElem.offsetHeight - this.lastOffsetHeight; + if (heightDiff > 0) { + this.ignoreNextScroll = true; + } + let fromBottom = linesElem.scrollHeight - linesElem.scrollTop - linesElem.offsetHeight; + // console.log("scroll", linesElem.scrollTop, (this.ignoreNextScroll ? "ignore" : "------"), "height-diff:" + heightDiff, "scroll-height:" + linesElem.scrollHeight, "from-bottom:" + fromBottom); + this.computeVisibleMap_debounced(); // always do this + if (this.ignoreNextScroll) { + this.ignoreNextScroll = false; + return; + } + this.computeAnchorLine_throttled(); // only do this when we're not ignoring the scroll + } + + computeAnchorLine() : void { + let {screen} = this.props; + let linesElem = this.linesRef.current; + if (linesElem == null) { + screen.setAnchorFields(null, 0, "no-lines"); + return; + } + let lineElemArr = linesElem.querySelectorAll(".line"); + if (lineElemArr == null || lineElemArr.length == 0) { + screen.setAnchorFields(null, 0, "no-line"); + return; + } + let scrollTop = linesElem.scrollTop; + let height = linesElem.clientHeight; + let containerBottom = scrollTop + height; + let anchorElem : HTMLElement = null; + for (let i=lineElemArr.length-1; i >= 0; i--) { + let lineElem = lineElemArr[i]; + let bottomPos = lineElem.offsetTop + lineElem.offsetHeight; + if (anchorElem == null && (bottomPos <= containerBottom || lineElem.offsetTop <= scrollTop)) { + anchorElem = lineElem; + } + } + if (anchorElem == null) { + anchorElem = lineElemArr[0]; + } + let anchorLineNum = parseInt(anchorElem.dataset.linenum); + let anchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight); + // console.log("compute-anchor-line", anchorLineNum, anchorOffset, "st:" + scrollTop); + screen.setAnchorFields(anchorLineNum, anchorOffset, "computeAnchorLine"); + } + + computeVisibleMap() : void { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return; + } + if (linesElem.offsetParent == null) { + return; // handles when parent is set to display:none (is-hidden) + } + let lineElemArr = linesElem.querySelectorAll(".line"); + if (lineElemArr == null) { + return; + } + if (linesElem.clientHeight == 0) { + return; // when linesElem is collapsed (or display:none) + } + let containerTop = linesElem.scrollTop - LinesVisiblePadding; + let containerBot = linesElem.scrollTop + linesElem.clientHeight + LinesVisiblePadding; + let newMap = new Map(); + // console.log("computevismap", linesElem.scrollTop, linesElem.clientHeight, containerTop + "-" + containerBot); + for (let i=0; i= containerTop && lineTop <= containerBot) { + isVis = true; + } + if (lineBot >= containerTop && lineBot <= containerBot) { + isVis = true + } + // console.log("line", lineElem.dataset.linenum, "top=" + lineTop, "bot=" + lineTop, isVis); + let lineNumInt = parseInt(lineElem.dataset.linenum); + newMap.set(lineElem.dataset.linenum, isVis); + // console.log("setvis", sprintf("%4d %4d-%4d (%4d) %s", lineElem.dataset.linenum, lineTop, lineBot, lineElem.offsetHeight, isVis)); + } + // console.log("compute vismap", "[" + this.firstVisLine + "," + this.lastVisLine + "]"); + mobx.action(() => { + for (let [k, v] of newMap) { + let oldVal = this.visibleMap.get(k); + if (oldVal == null) { + oldVal = mobx.observable.box(v, {name: "lines-vis-map"}); + this.visibleMap.set(k, oldVal); + } + if (oldVal.get() != v) { + oldVal.set(v); + } + } + for (let [k, v] of this.visibleMap) { + if (!newMap.has(k)) { + this.visibleMap.delete(k); + } + } + })(); + } + + printVisMap() : void { + let visMap = this.visibleMap; + let lines = this.props.lines; + let visLines : string[] = []; + for (let i=0; i", newScrollTop); + linesElem.scrollTop = newScrollTop; + this.ignoreNextScroll = true; + } + } + + componentDidMount() : void { + let {screen, lines} = this.props; + let linesElem = this.linesRef.current; + let anchor = this.getAnchor(); + if (anchor.anchorIndex == lines.length-1) { + if (linesElem != null) { + linesElem.scrollTop = linesElem.scrollHeight; + } + this.computeAnchorLine(); + } + else { + this.restoreAnchorOffset("re-mount"); + } + this.lastSelectedLine = screen.getSelectedLine(); + this.lastLinesLength = lines.length; + if (linesElem != null) { + this.lastOffsetHeight = linesElem.offsetHeight; + this.lastOffsetWidth = linesElem.offsetWidth; + this.rszObs = new ResizeObserver(this.handleResize.bind(this)); + this.rszObs.observe(linesElem); + } + mobx.action(() => { + this.staticRender.set(false) + this.computeVisibleMap(); + })(); + } + + getLineElem(lineNum : number) : HTMLElement { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return null; + } + let elem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", lineNum)); + return elem; + } + + getLineViewInfo(lineNum : number) : {height: number, topOffset: number, botOffset: number, anchorOffset: number} { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return null; + } + let lineElem = this.getLineElem(lineNum); + if (lineElem == null) { + return null; + } + let rtn = { + height: lineElem.offsetHeight, + topOffset: 0, + botOffset: 0, + anchorOffset: 0, + }; + let containerTop = linesElem.scrollTop; + let containerBot = linesElem.scrollTop + linesElem.clientHeight; + let lineTop = lineElem.offsetTop; + let lineBot = lineElem.offsetTop + lineElem.offsetHeight; + if (lineTop < containerTop) { + rtn.topOffset = lineTop - containerTop; + } + else if (lineTop > containerBot) { + rtn.topOffset = lineTop - containerBot; + } + if (lineBot < containerTop) { + rtn.botOffset = lineBot - containerTop; + } + else if (lineBot > containerBot) { + rtn.botOffset = lineBot - containerBot; + } + rtn.anchorOffset = containerBot - lineBot; + return rtn; + } + + updateSelectedLine() : void { + let {screen, lines} = this.props; + let linesElem = this.linesRef.current; + if (linesElem == null) { + return null; + } + let newLine = screen.getSelectedLine(); + if (newLine == 0) { + return; + } + let lidx = this.findClosestLineIndex(newLine); + this.setLineVisible(newLine, true); + // console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", screen.anchorLine, screen.anchorOffset)); + let viewInfo = this.getLineViewInfo(newLine); + let isFirst = (lidx.index == 0); + let isLast = (lidx.index == lines.length-1); + let offsetDelta = (isLast ? 10 : (isFirst ? -10 : 0)); + if (viewInfo == null) { + screen.setAnchorFields(newLine, 0+offsetDelta, "updateSelectedLine"); + } + else if (viewInfo.botOffset > 0) { + linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + offsetDelta; + this.ignoreNextScroll = true; + screen.setAnchorFields(newLine, offsetDelta, "updateSelectedLine"); + } + else if (viewInfo.topOffset < 0) { + linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + offsetDelta; + this.ignoreNextScroll = true; + let newOffset = linesElem.clientHeight - viewInfo.height; + screen.setAnchorFields(newLine, newOffset, "updateSelectedLine"); + } + else { + screen.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine"); + } + // console.log("new anchor", screen.getAnchorStr()); + } + + setLineVisible(lineNum : number, vis : boolean) : void { + mobx.action(() => { + let key = String(lineNum); + let visObj = this.visibleMap.get(key); + if (visObj == null) { + visObj = mobx.observable.box(true, {name: "lines-vis-map"}); + this.visibleMap.set(key, visObj); + } + else { + visObj.set(true); + } + })(); + } + + componentDidUpdate(prevProps, prevState, snapshot) : void { + let {screen, lines} = this.props; + if (screen.getSelectedLine() != this.lastSelectedLine) { + this.updateSelectedLine(); + this.lastSelectedLine = screen.getSelectedLine(); + } else if (lines.length != this.lastLinesLength) { + this.restoreAnchorOffset("line-length-change"); + } + } + + componentWillUnmount() : void { + if (this.rszObs != null) { + this.rszObs.disconnect(); + } + } + + handleResize(entries : any) { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return; + } + let heightDiff = linesElem.offsetHeight - this.lastOffsetHeight; + if (heightDiff != 0) { + this.lastOffsetHeight = linesElem.offsetHeight; + this.restoreAnchorOffset("resize"); + } + if (this.lastOffsetWidth != linesElem.offsetWidth) { + this.restoreAnchorOffset("resize-width"); + this.lastOffsetWidth = linesElem.offsetWidth; + } + this.computeVisibleMap_debounced(); + } + + @boundMethod + onHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void { + if (oldHeight == null) { + return; + } + // console.log("height-change", lineNum, oldHeight, "=>", newHeight); + this.restoreAnchorOffset("height-change"); + this.computeVisibleMap_debounced(); + } + + hasTopBorder(lines : T.LineInterface[], idx : number) : boolean { + if (idx == 0) { + return false; + } + let curLineNumStr = String(lines[idx].linenum); + let prevLineNumStr = String(lines[idx-1].linenum); + return !this.collapsedMap.get(curLineNumStr).get() || !this.collapsedMap.get(prevLineNumStr).get(); + } + + getDateSepStr(lines : T.LineInterface[], idx : number, prevStr : string, todayStr : string, yesterdayStr : string) : string { + let curLineDate = new Date(lines[idx].ts); + let curLineFormat = dayjs(curLineDate).format("ddd YYYY-MM-DD"); + if (idx == 0) { + return ; + } + let prevLineDate = new Date(lines[idx].ts); + let prevLineFormat = dayjs(prevLineDate).format("YYYY-MM-DD"); + return null; + } + + findClosestLineIndex(lineNum : number) : {line : T.LineInterface, index : number} { + let {lines} = this.props; + if (lines.length == 0) { + throw new Error("invalid lines, cannot have 0 length in LinesView"); + } + if (lineNum == null || lineNum == 0) { + return {line: lines[lines.length-1], index: lines.length-1}; + } + // todo: bsearch + // lines is sorted by linenum + for (let idx=0; idx= lineNum) { + return {line: line, index: idx}; + } + } + return {line: lines[lines.length-1], index: lines.length-1}; + } + + getAnchor() : {anchorLine : number, anchorOffset : number, anchorIndex : number} { + let {screen, lines} = this.props; + let anchor = screen.getAnchor(); + if (anchor.anchorLine == null || anchor.anchorLine == 0) { + return {anchorLine: lines[lines.length-1].linenum, anchorOffset: 0, anchorIndex: lines.length-1}; + } + let lidx = this.findClosestLineIndex(anchor.anchorLine); + if (lidx.line.linenum == anchor.anchorLine) { + return {anchorLine: anchor.anchorLine, anchorOffset: anchor.anchorOffset, anchorIndex: lidx.index}; + } + return {anchorLine: lidx.line.linenum, anchorOffset: 0, anchorIndex: lidx.index}; + } + render() { + let {screen, width, lines, renderMode} = this.props; + let selectedLine = screen.getSelectedLine(); // for re-rendering + let line : T.LineInterface = null; + for (let i=0; i{dateSepStr}); + lineElements.push(sepElem); + } + let topBorder = (dateSepStr == null) && this.hasTopBorder(lines, idx); + let lineProps = { + key: line.lineid, + line: line, + width: width, + visible: this.visibleMap.get(lineNumStr), + staticRender: this.staticRender.get(), + onHeightChange: this.onHeightChange, + overrideCollapsed: this.collapsedMap.get(lineNumStr), + topBorder: topBorder, + renderMode: renderMode, + }; + let lineElem = this.props.lineFactory(lineProps); + // let lineElem = ; + lineElements.push(lineElem); + } + return ( +
+
+ {lineElements} +
+ ); + } +} + +export {LinesView}; diff --git a/src/main.tsx b/src/main.tsx index 7a72480d..3676fec6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,7 +8,7 @@ import cn from "classnames"; import {debounce, throttle} from "throttle-debounce"; import {v4 as uuidv4} from "uuid"; import dayjs from "dayjs"; -import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType, ClientMigrationInfo} from "./types"; +import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType, ClientMigrationInfo, LineFactoryProps} from "./types"; import type * as T from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, TabColors, RemoteColors} from "./model"; @@ -21,6 +21,7 @@ import {HistoryView} from "./history"; import {Line, Prompt} from "./linecomps"; import {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal} from "./settings"; import {renderCmdText} from "./elements"; +import {LinesView} from "./linesview"; dayjs.extend(localizedFormat) @@ -52,16 +53,6 @@ function makeExternLink(url : string) : string { return "https://extern?" + encodeURIComponent(url); } -function getTodayStr() : string { - return getDateStr(new Date()); -} - -function getYesterdayStr() : string { - let d = new Date(); - d.setDate(d.getDate()-1); - return getDateStr(d); -} - function scrollDiv(div : any, amt : number) { if (div == null) { return; @@ -1316,6 +1307,7 @@ class InfoMsg extends React.Component<{}, {}> { remoteEditKey += (infoMsg.remoteedit.remoteid == null ? "-new" : "-" + infoMsg.remoteedit.remoteid); } } + let activeScreen = model.getActiveScreen(); return (
@@ -1325,7 +1317,17 @@ class InfoMsg extends React.Component<{}, {}> {
- {infoMsg.infomsg} + + + + + {infoMsg.infomsg} + +
+
+ +
+ screen is now shared at [link]
@@ -1667,474 +1669,6 @@ class CmdInput extends React.Component<{}, {}> { } } -const DOW_STRS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - -function getDateStr(d : Date) : string { - let yearStr = String(d.getFullYear()); - let monthStr = String(d.getMonth()+1); - if (monthStr.length == 1) { - monthStr = "0" + monthStr; - } - let dayStr = String(d.getDate()); - if (dayStr.length == 1) { - dayStr = "0" + dayStr; - } - let dowStr = DOW_STRS[d.getDay()]; - return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr; -} - -function getLineDateStr(todayDate : string, yesterdayDate : string, ts : number) : string { - let lineDate = new Date(ts); - let dateStr = getDateStr(lineDate); - if (dateStr == todayDate) { - return "today"; - } - if (dateStr == yesterdayDate) { - return "yesterday"; - } - return dateStr; -} - -@mobxReact.observer -class LinesView extends React.Component<{sessionId : string, screen : Screen, width : number, lines : LineType[], renderMode : RenderModeType}, {}> { - rszObs : ResizeObserver; - linesRef : React.RefObject; - staticRender : OV = mobx.observable.box(true, {name: "static-render"}); - lastOffsetHeight : number = 0; - lastOffsetWidth : number = 0; - ignoreNextScroll : boolean = false; - visibleMap : Map>; // linenum => OV - collapsedMap : Map>; // linenum => OV - lastLinesLength : number = 0; - lastSelectedLine : number = 0; - - computeAnchorLine_throttled : () => void; - computeVisibleMap_debounced : () => void; - - constructor(props) { - super(props); - this.linesRef = React.createRef(); - this.computeAnchorLine_throttled = throttle(100, this.computeAnchorLine.bind(this), {noLeading: true, noTrailing: false}); - this.visibleMap = new Map(); - this.collapsedMap = new Map(); - this.computeVisibleMap_debounced = debounce(1000, this.computeVisibleMap.bind(this)); - } - - @boundMethod - scrollHandler() { - let linesElem = this.linesRef.current; - if (linesElem == null) { - return; - } - let heightDiff = linesElem.offsetHeight - this.lastOffsetHeight; - if (heightDiff > 0) { - this.ignoreNextScroll = true; - } - let fromBottom = linesElem.scrollHeight - linesElem.scrollTop - linesElem.offsetHeight; - // console.log("scroll", linesElem.scrollTop, (this.ignoreNextScroll ? "ignore" : "------"), "height-diff:" + heightDiff, "scroll-height:" + linesElem.scrollHeight, "from-bottom:" + fromBottom); - this.computeVisibleMap_debounced(); // always do this - if (this.ignoreNextScroll) { - this.ignoreNextScroll = false; - return; - } - this.computeAnchorLine_throttled(); // only do this when we're not ignoring the scroll - } - - computeAnchorLine() : void { - let {screen} = this.props; - let linesElem = this.linesRef.current; - if (linesElem == null) { - screen.setAnchorFields(null, 0, "no-lines"); - return; - } - let lineElemArr = linesElem.querySelectorAll(".line"); - if (lineElemArr == null || lineElemArr.length == 0) { - screen.setAnchorFields(null, 0, "no-line"); - return; - } - let scrollTop = linesElem.scrollTop; - let height = linesElem.clientHeight; - let containerBottom = scrollTop + height; - let anchorElem : HTMLElement = null; - for (let i=lineElemArr.length-1; i >= 0; i--) { - let lineElem = lineElemArr[i]; - let bottomPos = lineElem.offsetTop + lineElem.offsetHeight; - if (anchorElem == null && (bottomPos <= containerBottom || lineElem.offsetTop <= scrollTop)) { - anchorElem = lineElem; - } - } - if (anchorElem == null) { - anchorElem = lineElemArr[0]; - } - let anchorLineNum = parseInt(anchorElem.dataset.linenum); - let anchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight); - // console.log("compute-anchor-line", anchorLineNum, anchorOffset, "st:" + scrollTop); - screen.setAnchorFields(anchorLineNum, anchorOffset, "computeAnchorLine"); - } - - computeVisibleMap() : void { - let linesElem = this.linesRef.current; - if (linesElem == null) { - return; - } - if (linesElem.offsetParent == null) { - return; // handles when parent is set to display:none (is-hidden) - } - let lineElemArr = linesElem.querySelectorAll(".line"); - if (lineElemArr == null) { - return; - } - if (linesElem.clientHeight == 0) { - return; // when linesElem is collapsed (or display:none) - } - let containerTop = linesElem.scrollTop - LinesVisiblePadding; - let containerBot = linesElem.scrollTop + linesElem.clientHeight + LinesVisiblePadding; - let newMap = new Map(); - // console.log("computevismap", linesElem.scrollTop, linesElem.clientHeight, containerTop + "-" + containerBot); - for (let i=0; i= containerTop && lineTop <= containerBot) { - isVis = true; - } - if (lineBot >= containerTop && lineBot <= containerBot) { - isVis = true - } - // console.log("line", lineElem.dataset.linenum, "top=" + lineTop, "bot=" + lineTop, isVis); - let lineNumInt = parseInt(lineElem.dataset.linenum); - newMap.set(lineElem.dataset.linenum, isVis); - // console.log("setvis", sprintf("%4d %4d-%4d (%4d) %s", lineElem.dataset.linenum, lineTop, lineBot, lineElem.offsetHeight, isVis)); - } - // console.log("compute vismap", "[" + this.firstVisLine + "," + this.lastVisLine + "]"); - mobx.action(() => { - for (let [k, v] of newMap) { - let oldVal = this.visibleMap.get(k); - if (oldVal == null) { - oldVal = mobx.observable.box(v, {name: "lines-vis-map"}); - this.visibleMap.set(k, oldVal); - } - if (oldVal.get() != v) { - oldVal.set(v); - } - } - for (let [k, v] of this.visibleMap) { - if (!newMap.has(k)) { - this.visibleMap.delete(k); - } - } - })(); - } - - printVisMap() : void { - let visMap = this.visibleMap; - let lines = this.props.lines; - let visLines : string[] = []; - for (let i=0; i", newScrollTop); - linesElem.scrollTop = newScrollTop; - this.ignoreNextScroll = true; - } - } - - componentDidMount() : void { - let {screen, lines} = this.props; - let linesElem = this.linesRef.current; - let anchor = this.getAnchor(); - if (anchor.anchorIndex == lines.length-1) { - if (linesElem != null) { - linesElem.scrollTop = linesElem.scrollHeight; - } - this.computeAnchorLine(); - } - else { - this.restoreAnchorOffset("re-mount"); - } - this.lastSelectedLine = screen.getSelectedLine(); - this.lastLinesLength = lines.length; - if (linesElem != null) { - this.lastOffsetHeight = linesElem.offsetHeight; - this.lastOffsetWidth = linesElem.offsetWidth; - this.rszObs = new ResizeObserver(this.handleResize.bind(this)); - this.rszObs.observe(linesElem); - } - mobx.action(() => { - this.staticRender.set(false) - this.computeVisibleMap(); - })(); - } - - getLineElem(lineNum : number) : HTMLElement { - let linesElem = this.linesRef.current; - if (linesElem == null) { - return null; - } - let elem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", lineNum)); - return elem; - } - - getLineViewInfo(lineNum : number) : {height: number, topOffset: number, botOffset: number, anchorOffset: number} { - let linesElem = this.linesRef.current; - if (linesElem == null) { - return null; - } - let lineElem = this.getLineElem(lineNum); - if (lineElem == null) { - return null; - } - let rtn = { - height: lineElem.offsetHeight, - topOffset: 0, - botOffset: 0, - anchorOffset: 0, - }; - let containerTop = linesElem.scrollTop; - let containerBot = linesElem.scrollTop + linesElem.clientHeight; - let lineTop = lineElem.offsetTop; - let lineBot = lineElem.offsetTop + lineElem.offsetHeight; - if (lineTop < containerTop) { - rtn.topOffset = lineTop - containerTop; - } - else if (lineTop > containerBot) { - rtn.topOffset = lineTop - containerBot; - } - if (lineBot < containerTop) { - rtn.botOffset = lineBot - containerTop; - } - else if (lineBot > containerBot) { - rtn.botOffset = lineBot - containerBot; - } - rtn.anchorOffset = containerBot - lineBot; - return rtn; - } - - updateSelectedLine() : void { - let {screen, lines} = this.props; - let linesElem = this.linesRef.current; - if (linesElem == null) { - return null; - } - let newLine = screen.getSelectedLine(); - if (newLine == 0) { - return; - } - let lidx = this.findClosestLineIndex(newLine); - this.setLineVisible(newLine, true); - // console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", screen.anchorLine, screen.anchorOffset)); - let viewInfo = this.getLineViewInfo(newLine); - let isFirst = (lidx.index == 0); - let isLast = (lidx.index == lines.length-1); - let offsetDelta = (isLast ? 10 : (isFirst ? -10 : 0)); - if (viewInfo == null) { - screen.setAnchorFields(newLine, 0+offsetDelta, "updateSelectedLine"); - } - else if (viewInfo.botOffset > 0) { - linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + offsetDelta; - this.ignoreNextScroll = true; - screen.setAnchorFields(newLine, offsetDelta, "updateSelectedLine"); - } - else if (viewInfo.topOffset < 0) { - linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + offsetDelta; - this.ignoreNextScroll = true; - let newOffset = linesElem.clientHeight - viewInfo.height; - screen.setAnchorFields(newLine, newOffset, "updateSelectedLine"); - } - else { - screen.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine"); - } - // console.log("new anchor", screen.getAnchorStr()); - } - - setLineVisible(lineNum : number, vis : boolean) : void { - mobx.action(() => { - let key = String(lineNum); - let visObj = this.visibleMap.get(key); - if (visObj == null) { - visObj = mobx.observable.box(true, {name: "lines-vis-map"}); - this.visibleMap.set(key, visObj); - } - else { - visObj.set(true); - } - })(); - } - - componentDidUpdate(prevProps, prevState, snapshot) : void { - let {screen, lines} = this.props; - if (screen.getSelectedLine() != this.lastSelectedLine) { - this.updateSelectedLine(); - this.lastSelectedLine = screen.getSelectedLine(); - } else if (lines.length != this.lastLinesLength) { - this.restoreAnchorOffset("line-length-change"); - } - } - - componentWillUnmount() : void { - if (this.rszObs != null) { - this.rszObs.disconnect(); - } - } - - handleResize(entries : any) { - let linesElem = this.linesRef.current; - if (linesElem == null) { - return; - } - let heightDiff = linesElem.offsetHeight - this.lastOffsetHeight; - if (heightDiff != 0) { - this.lastOffsetHeight = linesElem.offsetHeight; - this.restoreAnchorOffset("resize"); - } - if (this.lastOffsetWidth != linesElem.offsetWidth) { - this.restoreAnchorOffset("resize-width"); - this.lastOffsetWidth = linesElem.offsetWidth; - } - this.computeVisibleMap_debounced(); - } - - @boundMethod - onHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void { - if (oldHeight == null) { - return; - } - // console.log("height-change", lineNum, oldHeight, "=>", newHeight); - this.restoreAnchorOffset("height-change"); - this.computeVisibleMap_debounced(); - } - - hasTopBorder(lines : LineType[], idx : number) : boolean { - if (idx == 0) { - return false; - } - let curLineNumStr = String(lines[idx].linenum); - let prevLineNumStr = String(lines[idx-1].linenum); - return !this.collapsedMap.get(curLineNumStr).get() || !this.collapsedMap.get(prevLineNumStr).get(); - } - - getDateSepStr(lines : LineType[], idx : number, prevStr : string, todayStr : string, yesterdayStr : string) : string { - let curLineDate = new Date(lines[idx].ts); - let curLineFormat = dayjs(curLineDate).format("ddd YYYY-MM-DD"); - if (idx == 0) { - return ; - } - let prevLineDate = new Date(lines[idx].ts); - let prevLineFormat = dayjs(prevLineDate).format("YYYY-MM-DD"); - return null; - } - - findClosestLineIndex(lineNum : number) : {line : LineType, index : number} { - let {lines} = this.props; - if (lines.length == 0) { - throw new Error("invalid lines, cannot have 0 length in LinesView"); - } - if (lineNum == null || lineNum == 0) { - return {line: lines[lines.length-1], index: lines.length-1}; - } - // todo: bsearch - // lines is sorted by linenum - for (let idx=0; idx= lineNum) { - return {line: line, index: idx}; - } - } - return {line: lines[lines.length-1], index: lines.length-1}; - } - - getAnchor() : {anchorLine : number, anchorOffset : number, anchorIndex : number} { - let {screen, lines} = this.props; - let anchor = screen.getAnchor(); - if (anchor.anchorLine == null || anchor.anchorLine == 0) { - return {anchorLine: lines[lines.length-1].linenum, anchorOffset: 0, anchorIndex: lines.length-1}; - } - let lidx = this.findClosestLineIndex(anchor.anchorLine); - if (lidx.line.linenum == anchor.anchorLine) { - return {anchorLine: anchor.anchorLine, anchorOffset: anchor.anchorOffset, anchorIndex: lidx.index}; - } - return {anchorLine: lidx.line.linenum, anchorOffset: 0, anchorIndex: lidx.index}; - } - - render() { - let {screen, width, lines, renderMode} = this.props; - let selectedLine = screen.getSelectedLine(); // for re-rendering - let line : LineType = null; - for (let i=0; i{dateSepStr}
- lineElements.push(sepElem); - } - let topBorder = (dateSepStr == null) && this.hasTopBorder(lines, idx); - let lineElem = ; - lineElements.push(lineElem); - } - return ( -
-
- {lineElements} -
- ); - } -} - // screen is not null @mobxReact.observer class ScreenWindowView extends React.Component<{screen : Screen}, {}> { @@ -2246,6 +1780,16 @@ class ScreenWindowView extends React.Component<{screen : Screen}, {}> { }, 600) } + @boundMethod + buildLineComponent(lineProps : LineFactoryProps) { + let {screen} = this.props; + let {line, ...restProps} = lineProps; + let realLine : LineType = (line as LineType); + return ( + + ); + } + render() { let {screen} = this.props; let win = this.getScreenLines(); @@ -2287,11 +1831,11 @@ class ScreenWindowView extends React.Component<{screen : Screen}, {}> { 0}> - +
diff --git a/src/model.ts b/src/model.ts index a5e1f23f..fee49999 100644 --- a/src/model.ts +++ b/src/model.ts @@ -262,7 +262,21 @@ class Screen { } isWebShare() : boolean { - return this.shareMode.get() == "web"; + return (this.shareMode.get() == "web") && (this.webShareOpts.get() != null); + } + + getWebShareUrl() : string { + let viewKey : string = null; + if (this.webShareOpts.get() != null) { + viewKey = this.webShareOpts.get().viewkey; + } + if (viewKey == null) { + return null; + } + if (GlobalModel.isDev) { + return sprintf("http://devtest.getprompt.com:9001/static/index.html?screenid=%s&viewkey=%s", this.screenId, viewKey); + } + return sprintf("https://share.getprompt.dev/s/%s?viewkey=%s", this.screenId, viewKey); } mergeData(data : ScreenDataType) { @@ -324,7 +338,7 @@ class Screen { return session.getRemoteInstance(this.screenId, rptr); } - setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) { + setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) : void { mobx.action(() => { this.anchor.set({anchorLine: anchorLine, anchorOffset: anchorOffset}); })(); @@ -2574,7 +2588,7 @@ class Model { } let term = screen.getTermWrap(cmdId); if (term != null) { - term.cmdDone(); + setTimeout(() => term.cmdDone(), 300); } } } diff --git a/src/sh2.less b/src/sh2.less index 7b1d5ee4..ca77d606 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -141,12 +141,14 @@ body::-webkit-scrollbar { z-index: 11; font-size: 12px; border-bottom-right-radius: 5px; + opacity: 0.8; .share-tag-link { display: none; } &:hover { + opacity: 1.0; padding: 20px; .share-tag-link { @@ -2025,6 +2027,10 @@ body .xterm .xterm-viewport { .mono-font(14px, 400); color: @soft-blue; padding-bottom: 2px; + + a { + color: @term-blue; + } } .info-title { diff --git a/src/term.ts b/src/term.ts index a3f3ade5..a0c541d1 100644 --- a/src/term.ts +++ b/src/term.ts @@ -265,10 +265,19 @@ class TermWrap { } } + getLineNum() : number { + let context = this.getRendererContext(); + if (context == null) { + return 0; + } + return context.lineNum; + } + reload(delayMs : number) { if (this.terminal == null) { return; } + // console.log("reload-term", this.getLineNum()); this.reloading = true; this.terminal.reset(); let rtnp = this.ptyDataSource(this.termContext); @@ -285,7 +294,7 @@ class TermWrap { } receiveData(pos : number, data : Uint8Array, reason? : string) { - // console.log("update-pty-data", pos, data.length, reason); + // console.log("update-pty-data", reason, this.getLineNum(), data.length, "|", pos, "=>", pos + data.length); if (this.terminal == null) { return; } diff --git a/src/types.ts b/src/types.ts index b60ca693..2ff0e6c6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -321,6 +321,8 @@ type RemoteEditType = { type InfoType = { infotitle? : string, infomsg? : string, + infomsghtml? : boolean, + websharelink? : boolean, infoerror? : string, infolines? : string[], infocomps? : string[], @@ -490,6 +492,7 @@ type WebScreen = { screenid : string, sharename : string, vts : number, + selectedline : number, }; type WebLine = { @@ -560,4 +563,25 @@ type WebShareWSMessage = { viewkey : string, } -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, WebScreenUpdate, PtyDataUpdate, WebShareWSMessage}; +type LineInterface = { + lineid : string, + linenum : number, + ts : number, +} + +type LineFactoryProps = { + key : string, + line : LineInterface, + width : number, + visible : OV, + staticRender : boolean, + onHeightChange : LineHeightChangeCallbackType, + overrideCollapsed : OV, + topBorder : boolean, + renderMode : RenderModeType, + noSelect? : boolean, +} + +type LineHeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void; + +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, WebScreenUpdate, PtyDataUpdate, WebShareWSMessage, LineHeightChangeCallbackType, LineFactoryProps, LineInterface}; diff --git a/src/webshare-elems.tsx b/src/webshare-elems.tsx index d9fc5200..9ae20e6f 100644 --- a/src/webshare-elems.tsx +++ b/src/webshare-elems.tsx @@ -13,14 +13,15 @@ import * as lineutil from "./lineutil"; import * as util from "./util"; import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure"; import {debounce, throttle} from "throttle-debounce"; +import {LinesView} from "./linesview"; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; type OMap = mobx.ObservableMap; +let foo = LinesView; + // TODO selection -// TODO bug with finishing up the ptydata -// TODO bug with ptydata late -- not updating usedrows // TODO scroll screen when new cmds arrive (selection) // TODO archived should delete line // TODO implement linedel @@ -217,7 +218,7 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, width = 1024; } return ( -
+
@@ -237,7 +238,7 @@ class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get(); let mainCn = cn("web-line line line-text", {"top-border": topBorder}); return ( -
+
@@ -383,9 +384,12 @@ class WebLineView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, top @mobxReact.observer class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> { viewRef : React.RefObject = React.createRef(); + linesRef : React.RefObject = React.createRef(); width : OV = mobx.observable.box(0, {name: "webScreenView-width"}); rszObs : ResizeObserver; handleResize_debounced : (entries : any) => void; + lastSelectedLine : number = 0; + ignoreNextScroll : boolean = false; constructor(props : any) { super(props); @@ -404,6 +408,86 @@ class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> { })(); } } + this.lastSelectedLine = WebShareModel.getSelectedLine(); + } + + getLineElem(lineNum : number) : HTMLElement { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return null; + } + let elem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", lineNum)); + return elem; + } + + getLineViewInfo(lineNum : number) : {height: number, topOffset: number, botOffset: number, anchorOffset: number} { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return null; + } + let lineElem = this.getLineElem(lineNum); + if (lineElem == null) { + return null; + } + let rtn = { + height: lineElem.offsetHeight, + topOffset: 0, + botOffset: 0, + anchorOffset: 0, + }; + let containerTop = linesElem.scrollTop; + let containerBot = linesElem.scrollTop + linesElem.clientHeight; + let lineTop = lineElem.offsetTop; + let lineBot = lineElem.offsetTop + lineElem.offsetHeight; + if (lineTop < containerTop) { + rtn.topOffset = lineTop - containerTop; + } + else if (lineTop > containerBot) { + rtn.topOffset = lineTop - containerBot; + } + if (lineBot < containerTop) { + rtn.botOffset = lineBot - containerTop; + } + else if (lineBot > containerBot) { + rtn.botOffset = lineBot - containerBot; + } + rtn.anchorOffset = containerBot - lineBot; + return rtn; + } + + updateSelectedLine() : void { + let linesElem = this.linesRef.current; + if (linesElem == null) { + return null; + } + let newLine = WebShareModel.getSelectedLine(); + let lineIdx = WebShareModel.getLineIndex(newLine); + if (lineIdx == -1) { + return; + } + let viewInfo = this.getLineViewInfo(newLine); + if (viewInfo == null) { + return; + } + let numLines = WebShareModel.getNumLines(); + let isFirst = (lineIdx == 0); + let isLast = (lineIdx == numLines-1); + let offsetDelta = (isLast ? 10 : (isFirst ? -10 : 0)); + if (viewInfo.botOffset > 0) { + linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + offsetDelta; + this.ignoreNextScroll = true; + } + else if (viewInfo.topOffset < 0) { + linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + offsetDelta; + this.ignoreNextScroll = true; + } + this.lastSelectedLine = newLine; + } + + componentDidUpdate(prevProps, prevState, snapshot) : void { + if (WebShareModel.getSelectedLine() != this.lastSelectedLine) { + this.updateSelectedLine(); + } } handleResize(entries : any) : void { @@ -425,7 +509,7 @@ class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> { renderEmpty() : any { return (
-
+
@@ -449,6 +533,7 @@ class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> { if (width == 0) { return this.renderEmpty(); } + let selectedLine = WebShareModel.getSelectedLine(); // for re-rendering for (let idx=0; idx { } return (
-
+
{lineElements}
diff --git a/src/webshare-model.ts b/src/webshare-model.ts index ceaa1df8..9b351f36 100644 --- a/src/webshare-model.ts +++ b/src/webshare-model.ts @@ -33,7 +33,6 @@ class WebShareModelClass { terminals : Record = {}; // lineid => TermWrap renderers : Record = {}; // lineid => RendererModel contentHeightCache : Record = {}; // lineid => height - selectedLine : OV = mobx.observable.box(0, {name: "selectedLine"}); wsControl : WebShareWSControl; constructor() { @@ -51,7 +50,11 @@ class WebShareModelClass { } getSelectedLine() : number { - return this.selectedLine.get(); + let fullScreen = this.screen.get(); + if (fullScreen != null) { + return fullScreen.screen.selectedline; + } + return 0; } getTermFontSize() : number { @@ -150,8 +153,7 @@ class WebShareModelClass { continue; } let dataArr = base64ToArray(data.data); - termWrap.receiveData(data.ptypos, dataArr); - console.log("receivedata", data.lineid, data.ptypos + dataArr.length); + termWrap.receiveData(data.ptypos, dataArr, "ws:ptydata"); } } if (msg.removedlines != null && msg.removedlines.length > 0) { @@ -179,9 +181,6 @@ class WebShareModelClass { screen.cmds = []; } this.screen.set(screen); - if (screen.lines != null && screen.lines.length > 0) { - this.selectedLine.set(screen.lines[screen.lines.length-1].linenum); - } this.wsControl.reconnect(true); })(); @@ -214,7 +213,7 @@ class WebShareModelClass { onUpdateContentHeight: (termContext : T.RendererContext, height : number) => { this.setContentHeight(termContext, height); }, }); this.terminals[lineId] = termWrap; - if (this.selectedLine.get() == line.linenum) { + if (this.getSelectedLine() == line.linenum) { termWrap.giveFocus(); } return; @@ -321,7 +320,28 @@ class WebShareModelClass { }).catch((err) => { this.errMessage.set("Cannot get screen: " + err.message); }); - + } + + getLineIndex(lineNum : number) : number { + let fullScreen = this.screen.get(); + if (fullScreen == null) { + return -1; + } + for (let i=0; i