mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
selected lines, webshare url, refactor linesview into its own component
This commit is contained in:
+4
-5
@@ -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) {
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+17
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user