selected lines, webshare url, refactor linesview into its own component

This commit is contained in:
sawka
2023-03-30 18:14:52 -07:00
parent 516761815b
commit 4d3ca62519
9 changed files with 691 additions and 507 deletions
+4 -5
View File
@@ -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<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
type OMap<K,V> = mobx.ObservableMap<K,V>;
type HeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void;
type RendererComponentProps = {screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : HeightChangeCallbackType, collapsed : boolean};
type RendererComponentProps = {screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : LineHeightChangeCallbackType, collapsed : boolean};
type RendererComponentType = { new(props : RendererComponentProps) : React.Component<RendererComponentProps, {}> };
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<boolean>, onHeightChange : HeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV<boolean>, noSelect? : boolean, showHints? : boolean}, {}> {
class LineCmd extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : LineHeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV<boolean>, noSelect? : boolean, showHints? : boolean}, {}> {
lineRef : React.RefObject<any> = React.createRef();
cmdTextRef : React.RefObject<any> = React.createRef();
rtnStateDiff : mobx.IObservableValue<string> = 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<boolean>, onHeightChange : HeightChangeCallbackType, overrideCollapsed : OV<boolean>, topBorder : boolean, renderMode : RenderModeType, noSelect? : boolean}, {}> {
class Line extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : LineHeightChangeCallbackType, overrideCollapsed : OV<boolean>, topBorder : boolean, renderMode : RenderModeType, noSelect? : boolean}, {}> {
render() {
let line = this.props.line;
if (line.archived) {
+483
View File
@@ -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<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
type OMap<K,V> = mobx.ObservableMap<K,V>;
const LinesVisiblePadding = 500;
type ScreenInterface = {
setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) : void,
getSelectedLine() : number,
getAnchor() : {anchorLine : number, anchorOffset : number},
}
// <Line key={line.lineid} line={line} screen={screen} width={width} visible={this.visibleMap.get(lineNumStr)} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange} overrideCollapsed={this.collapsedMap.get(lineNumStr)} topBorder={topBorder} renderMode={renderMode}/>;
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<any>;
staticRender : OV<boolean> = mobx.observable.box(true, {name: "static-render"});
lastOffsetHeight : number = 0;
lastOffsetWidth : number = 0;
ignoreNextScroll : boolean = false;
visibleMap : Map<string, OV<boolean>>; // linenum => OV<vis>
collapsedMap : Map<string, OV<boolean>>; // linenum => OV<collapsed>
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<string, boolean>();
// console.log("computevismap", linesElem.scrollTop, linesElem.clientHeight, containerTop + "-" + containerBot);
for (let i=0; i<lineElemArr.length; i++) {
let lineElem = lineElemArr[i];
let lineTop = lineElem.offsetTop;
let lineBot = lineElem.offsetTop + lineElem.offsetHeight;
let isVis = false;
if (lineTop >= 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<lines.length; i++) {
let linenum = String(lines[i].linenum);
if (visMap.get(linenum).get()) {
visLines.push(linenum);
}
}
console.log("vislines", visLines);
}
restoreAnchorOffset(reason : string) : void {
let {lines} = this.props;
let linesElem = this.linesRef.current;
if (linesElem == null) {
return;
}
let anchor = this.getAnchor();
let anchorElem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", anchor.anchorLine));
if (anchorElem == null) {
return;
}
let isLastLine = (anchor.anchorIndex == lines.length-1);
let scrollTop = linesElem.scrollTop;
let height = linesElem.clientHeight;
let containerBottom = scrollTop + height;
let curAnchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight);
let newAnchorOffset = anchor.anchorOffset;
if (isLastLine && newAnchorOffset == 0) {
newAnchorOffset = 10;
}
if (curAnchorOffset != newAnchorOffset) {
let offsetDiff = curAnchorOffset - newAnchorOffset;
let newScrollTop = scrollTop - offsetDiff;
// console.log("update scrolltop", reason, "line=" + anchor.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", 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<lines.length; idx++) {
let line = lines[idx];
if (line.linenum >= 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<lines.length; i++) {
let key = String(lines[i].linenum);
let visObs = this.visibleMap.get(key);
if (visObs == null) {
this.visibleMap.set(key, mobx.observable.box(false, {name: "lines-vis-map"}));
}
let collObs = this.collapsedMap.get(key);
if (collObs == null) {
this.collapsedMap.set(key, mobx.observable.box(false, {name: "lines-collapsed-map"}));
}
}
let lineElements : any = [];
let todayStr = util.getTodayStr();
let yesterdayStr = util.getYesterdayStr();
let prevDateStr : string = null;
let anchor = this.getAnchor();
let startIdx = util.boundInt(anchor.anchorIndex-50, 0, lines.length-1);
let endIdx = util.boundInt(anchor.anchorIndex+50, 0, lines.length-1);
// console.log("render", anchor, "[" + startIdx + "," + endIdx + "]");
for (let idx=startIdx; idx <= endIdx; idx++) {
let line = lines[idx];
let lineNumStr = String(line.linenum);
let dateSepStr = null;
let curDateStr = lineutil.getLineDateStr(todayStr, yesterdayStr, line.ts);
if (curDateStr != prevDateStr) {
dateSepStr = curDateStr;
}
prevDateStr = curDateStr;
if (dateSepStr != null) {
let sepElem = (<div key={"sep-" + line.lineid} className="line-sep">{dateSepStr}</div>);
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 = <Line key={line.lineid} line={line} screen={screen} width={width} visible={this.visibleMap.get(lineNumStr)} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange} overrideCollapsed={this.collapsedMap.get(lineNumStr)} topBorder={topBorder} renderMode={renderMode}/>;
lineElements.push(lineElem);
}
return (
<div key="lines" className="lines" onScroll={this.scrollHandler} ref={this.linesRef}>
<div className="lines-spacer"></div>
{lineElements}
</div>
);
}
}
export {LinesView};
+26 -482
View File
File diff suppressed because it is too large Load Diff
+17 -3
View File
@@ -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);
}
}
}
+6
View File
@@ -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 {
+10 -1
View File
@@ -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;
}
+25 -1
View File
@@ -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<boolean>,
staticRender : boolean,
onHeightChange : LineHeightChangeCallbackType,
overrideCollapsed : OV<boolean>,
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};
+91 -6
View File
@@ -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<V> = mobx.IObservableValue<V>;
type OArr<V> = mobx.IObservableArray<V>;
type OMap<K,V> = mobx.ObservableMap<K,V>;
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 (
<div className={mainCn}>
<div className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum}>
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
<div className="line-header">
<LineAvatar line={line} cmd={cmd}/>
@@ -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 (
<div className={mainCn}>
<div className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum}>
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
<div className="line-header">
<LineAvatar line={line} cmd={null}/>
@@ -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<any> = React.createRef();
linesRef : React.RefObject<any> = React.createRef();
width : OV<number> = 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 (
<div className="web-screen-view" ref={this.viewRef}>
<div className="web-lines lines">
<div className="web-lines lines" ref={this.linesRef}>
<div key="spacer" className="lines-spacer"></div>
</div>
</div>
@@ -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<lines.length; idx++) {
let line = lines[idx];
let lineNumStr = String(line.linenum);
@@ -468,7 +553,7 @@ class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> {
}
return (
<div className="web-screen-view" ref={this.viewRef}>
<div className="web-lines lines">
<div className="web-lines lines" ref={this.linesRef}>
<div key="spacer" className="lines-spacer"></div>
{lineElements}
</div>
+29 -9
View File
@@ -33,7 +33,6 @@ class WebShareModelClass {
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"});
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<fullScreen.lines.length; i++) {
let line = fullScreen.lines[i];
if (line.linenum == lineNum) {
return i;
}
}
return -1;
}
getNumLines() : number {
let fullScreen = this.screen.get();
if (fullScreen == null) {
return 0;
}
return fullScreen.lines.length;
}
}