mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
lazy loading, cached TUR values
This commit is contained in:
parent
b4e41bc36d
commit
7af1d5cee6
146
src/main.tsx
146
src/main.tsx
@ -14,6 +14,38 @@ import {GlobalModel, GlobalInput, Session, Cmd, Window, Screen, ScreenWindow} fr
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
type InterObsValue = {
|
||||
sessionid : string,
|
||||
windowid : string,
|
||||
lineid : string,
|
||||
cmdid : string,
|
||||
visible : mobx.IObservableValue<boolean>,
|
||||
timeoutid? : any,
|
||||
};
|
||||
|
||||
let globalLineWeakMap = new WeakMap<any, InterObsValue>();
|
||||
|
||||
function interObsCallback(entries) {
|
||||
let now = Date.now();
|
||||
entries.forEach((entry) => {
|
||||
let line = globalLineWeakMap.get(entry.target);
|
||||
if ((line.timeoutid != null) && (line.visible.get() == entry.isIntersecting)) {
|
||||
clearTimeout(line.timeoutid);
|
||||
line.timeoutid = null;
|
||||
return;
|
||||
}
|
||||
if (line.visible.get() != entry.isIntersecting && line.timeoutid == null) {
|
||||
line.timeoutid = setTimeout(() => {
|
||||
line.timeoutid = null;
|
||||
mobx.action(() => {
|
||||
line.visible.set(entry.isIntersecting);
|
||||
})();
|
||||
}, 250);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLineId(line : LineType) : string {
|
||||
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
|
||||
}
|
||||
@ -107,30 +139,78 @@ class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}>
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width: number}, {}> {
|
||||
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> {
|
||||
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
lineRef : React.RefObject<any> = React.createRef();
|
||||
iobsVal : InterObsValue = null;
|
||||
autorunDisposer : () => void = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let line = props.line;
|
||||
let ival : InterObsValue = {
|
||||
sessionid: line.sessionid,
|
||||
windowid: line.windowid,
|
||||
lineid: line.lineid,
|
||||
cmdid: line.cmdid,
|
||||
visible: mobx.observable.box(this.props.initVis),
|
||||
};
|
||||
this.iobsVal = ival;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
visibilityChanged(vis : boolean) : void {
|
||||
if (vis && !this.termLoaded.get()) {
|
||||
this.loadTerminal();
|
||||
}
|
||||
else if (!vis && this.termLoaded.get()) {
|
||||
let {line} = this.props;
|
||||
}
|
||||
}
|
||||
|
||||
loadTerminal() : void {
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
let termElem = document.getElementById("term-" + getLineId(line));
|
||||
cmd.connectElem(termElem, sw.screenId, sw.windowId, this.props.width);
|
||||
mobx.action(() => this.termLoaded.set(true))();
|
||||
if (cmd == null) {
|
||||
return;
|
||||
}
|
||||
let termId = "term-" + getLineId(line);
|
||||
let termElem = document.getElementById(termId);
|
||||
if (termElem == null) {
|
||||
console.log("cannot load terminal, no term elem found", termId);
|
||||
return;
|
||||
}
|
||||
sw.connectElem(termElem, cmd, this.props.width);
|
||||
mobx.action(() => this.termLoaded.set(true))();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let {line} = this.props;
|
||||
if (this.lineRef.current == null || this.props.interObs == null) {
|
||||
console.log("LineCmd lineRef current is null or interObs is null", line, this.lineRef.current, this.props.interObs);
|
||||
}
|
||||
else {
|
||||
globalLineWeakMap.set(this.lineRef.current, this.iobsVal);
|
||||
this.props.interObs.observe(this.lineRef.current);
|
||||
this.autorunDisposer = mobx.autorun(() => {
|
||||
let vis = this.iobsVal.visible.get();
|
||||
this.visibilityChanged(vis);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
cmd.disconnectElem(sw.screenId, sw.windowId);
|
||||
if (this.termLoaded.get()) {
|
||||
sw.disconnectElem(line.cmdid);
|
||||
}
|
||||
if (this.lineRef.current != null && this.props.interObs != null) {
|
||||
this.props.interObs.unobserve(this.lineRef.current);
|
||||
}
|
||||
if (this.autorunDisposer != null) {
|
||||
this.autorunDisposer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,12 +223,9 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
||||
doRefresh() {
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
let termWrap = cmd.getTermWrap(sw.screenId, sw.windowId);
|
||||
if (termWrap != null) {
|
||||
termWrap.reloadTerminal(500);
|
||||
}
|
||||
let termWrap = sw.getTermWrap(line.cmdid);
|
||||
if (termWrap != null) {
|
||||
termWrap.reloadTerminal(500);
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,28 +247,32 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
||||
}
|
||||
|
||||
render() {
|
||||
let {sw, line} = this.props;
|
||||
let {sw, line, width} = this.props;
|
||||
let model = GlobalModel;
|
||||
let lineid = line.lineid.toString();
|
||||
let formattedTime = getLineDateStr(line.ts);
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd == null) {
|
||||
return <div className="line line-invalid">[cmd not found '{line.cmdid}']</div>;
|
||||
return (
|
||||
<div className="line line-invalid" id={"line-" + getLineId(line)} ref={this.lineRef}>
|
||||
[cmd not found '{line.cmdid}']
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let termLoaded = this.termLoaded.get();
|
||||
let cellHeightPx = 16;
|
||||
let cellWidthPx = 8;
|
||||
let termWidth = Math.max(Math.trunc((this.props.width - 20)/cellWidthPx), 10);
|
||||
let usedRows = cmd.getUsedRows(sw.screenId, sw.windowId);
|
||||
let termWidth = Math.max(Math.trunc((width - 20)/cellWidthPx), 10);
|
||||
let usedRows = sw.getUsedRows(cmd, width);
|
||||
let totalHeight = cellHeightPx * usedRows;
|
||||
let remote = model.getRemote(cmd.remoteId);
|
||||
let status = cmd.getStatus();
|
||||
let running = (status == "running");
|
||||
let detached = (status == "detached");
|
||||
let termOpts = cmd.getTermOpts();
|
||||
let isFocused = cmd.getIsFocused(sw.screenId, sw.windowId);
|
||||
let isFocused = sw.getIsFocused(line.cmdid);
|
||||
return (
|
||||
<div className={cn("line", "line-cmd", {"focus": isFocused})} id={"line-" + getLineId(line)}>
|
||||
<div className={cn("line", "line-cmd", {"focus": isFocused})} id={"line-" + getLineId(line)} ref={this.lineRef} style={{position: "relative"}}>
|
||||
<div className="line-header">
|
||||
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}>
|
||||
{lineid}
|
||||
@ -212,6 +293,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
||||
</div>
|
||||
<div className={cn("terminal-wrapper", {"focus": isFocused})} style={{overflowY: "hidden"}}>
|
||||
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
|
||||
<If condition={!termLoaded}><div style={{position: "absolute", top: 60, left: 30}}>(loading)</div></If>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -219,7 +301,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number}, {}> {
|
||||
class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> {
|
||||
render() {
|
||||
let line = this.props.line;
|
||||
if (line.linetype == "text") {
|
||||
@ -237,7 +319,6 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
lastTab : boolean = false;
|
||||
lastHistoryUpDown : boolean = false;
|
||||
lastTabCurLine : mobx.IObservableValue<string> = mobx.observable.box(null);
|
||||
textareaRef : React.RefObject<any> = React.createRef();
|
||||
|
||||
isModKeyPress(e : any) {
|
||||
return e.code.match(/^(Control|Meta|Alt|Shift)(Left|Right)$/);
|
||||
@ -423,7 +504,7 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
<div className="button is-static">{promptStr}</div>
|
||||
</div>
|
||||
<div className="control cmd-input-control is-expanded">
|
||||
<textarea id="main-cmd-input" ref={this.textareaRef} rows={displayLines} value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className="textarea"></textarea>
|
||||
<textarea id="main-cmd-input" rows={displayLines} value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className="textarea"></textarea>
|
||||
</div>
|
||||
<div className="control cmd-exec">
|
||||
<div onClick={this.doSubmitCmd} className="button">
|
||||
@ -441,7 +522,8 @@ class CmdInput extends React.Component<{}, {}> {
|
||||
@mobxReact.observer
|
||||
class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
||||
mutObs : any;
|
||||
rszObs : any
|
||||
rszObs : any;
|
||||
interObs : IntersectionObserver;
|
||||
randomId : string;
|
||||
width : mobx.IObservableValue<number> = mobx.observable.box(0);
|
||||
lastHeight : number = null;
|
||||
@ -462,7 +544,7 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
||||
let target = event.target;
|
||||
let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight));
|
||||
if (sw && sw.shouldFollow.get() != atBottom) {
|
||||
mobx.action(() => sw.shouldFollow.set(atBottom));
|
||||
mobx.action(() => sw.shouldFollow.set(atBottom))();
|
||||
}
|
||||
// console.log("scroll-handler>", atBottom, target.scrollTop, target.scrollHeight);
|
||||
}
|
||||
@ -477,6 +559,11 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
||||
if (sw && sw.shouldFollow.get()) {
|
||||
setTimeout(() => this.scrollToBottom("mount"), 0);
|
||||
}
|
||||
this.interObs = new IntersectionObserver(interObsCallback, {
|
||||
root: elem,
|
||||
rootMargin: "800px",
|
||||
threshold: 0.0,
|
||||
});
|
||||
}
|
||||
let wvElem = document.getElementById(this.getWindowViewDOMId());
|
||||
if (wvElem != null) {
|
||||
@ -498,6 +585,9 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
||||
if (this.rszObs) {
|
||||
this.rszObs.disconnect();
|
||||
}
|
||||
if (this.interObs) {
|
||||
this.interObs.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
handleResize(entries : any) {
|
||||
@ -619,7 +709,7 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
||||
</div>
|
||||
<div key="lines" className="lines" onScroll={this.scrollHandler} id={this.getLinesDOMId()} style={linesStyle}>
|
||||
<For each="line" of={win.lines} index="idx">
|
||||
<Line key={line.lineid} line={line} sw={sw} width={this.width.get()}/>
|
||||
<Line key={line.lineid} line={line} sw={sw} width={this.width.get()} interObs={this.interObs} initVis={idx > win.lines.length-1-7}/>
|
||||
</For>
|
||||
</div>
|
||||
<If condition={win.lines.length == 0}>
|
||||
|
139
src/model.ts
139
src/model.ts
@ -51,7 +51,6 @@ class Cmd {
|
||||
cmdId : string;
|
||||
data : OV<CmdDataType>;
|
||||
watching : boolean = false;
|
||||
instances : Record<string, TermWrap> = {};
|
||||
|
||||
constructor(cmd : CmdDataType) {
|
||||
this.sessionId = cmd.sessionid;
|
||||
@ -60,58 +59,6 @@ class Cmd {
|
||||
this.data = mobx.observable.box(cmd, {deep: false});
|
||||
}
|
||||
|
||||
connectElem(elem : Element, screenId : string, windowId : string, width : number) {
|
||||
let termWrap = this.getTermWrap(screenId, windowId);
|
||||
if (termWrap != null) {
|
||||
console.log("term-wrap already exists for", screenId, windowId);
|
||||
return;
|
||||
}
|
||||
termWrap = new TermWrap(elem, this.sessionId, this.cmdId, 0, this.getTermOpts(), {height: 0, width: width}, this.handleKey.bind(this));
|
||||
this.instances[screenId + "/" + windowId] = termWrap;
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectElem(screenId : string, windowId : string) {
|
||||
let key = screenId + "/" + windowId;
|
||||
let termWrap = this.instances[key];
|
||||
if (termWrap != null) {
|
||||
termWrap.dispose();
|
||||
delete this.instances[key];
|
||||
}
|
||||
}
|
||||
|
||||
updatePtyData(ptyMsg : PtyDataUpdateType) {
|
||||
for (let key in this.instances) {
|
||||
let tw = this.instances[key];
|
||||
let data = base64ToArray(ptyMsg.ptydata64);
|
||||
tw.updatePtyData(ptyMsg.ptypos, data);
|
||||
}
|
||||
}
|
||||
|
||||
getTermWrap(screenId : string, windowId : string) : TermWrap {
|
||||
return this.instances[screenId + "/" + windowId];
|
||||
}
|
||||
|
||||
getUsedRows(screenId : string, windowId : string) : number {
|
||||
let termOpts = this.getTermOpts();
|
||||
if (!termOpts.flexrows) {
|
||||
return termOpts.rows;
|
||||
}
|
||||
let termWrap = this.getTermWrap(screenId, windowId);
|
||||
if (termWrap == null) {
|
||||
return 2;
|
||||
}
|
||||
return termWrap.usedRows.get();
|
||||
}
|
||||
|
||||
getIsFocused(screenId : string, windowId : string) : boolean {
|
||||
let termWrap = this.getTermWrap(screenId, windowId);
|
||||
if (termWrap == null) {
|
||||
return false;
|
||||
}
|
||||
return termWrap.isFocused.get();
|
||||
}
|
||||
|
||||
setCmd(cmd : CmdDataType) {
|
||||
mobx.action(() => {
|
||||
this.data.set(cmd);
|
||||
@ -205,10 +152,7 @@ class Screen {
|
||||
updatePtyData(ptyMsg : PtyDataUpdateType) {
|
||||
for (let i=0; i<this.windows.length; i++) {
|
||||
let sw = this.windows[i];
|
||||
let win = sw.getWindow();
|
||||
if (win != null) {
|
||||
win.updatePtyData(ptyMsg);
|
||||
}
|
||||
sw.updatePtyData(ptyMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +181,9 @@ class ScreenWindow {
|
||||
layout : OV<LayoutType>;
|
||||
shouldFollow : OV<boolean> = mobx.observable.box(true);
|
||||
|
||||
// cmdid => TermWrap
|
||||
terms : Record<string, TermWrap> = {};
|
||||
|
||||
constructor(swdata : ScreenWindowType) {
|
||||
this.sessionId = swdata.sessionid;
|
||||
this.screenId = swdata.screenid;
|
||||
@ -245,6 +192,65 @@ class ScreenWindow {
|
||||
this.layout = mobx.observable.box(swdata.layout);
|
||||
}
|
||||
|
||||
updatePtyData(ptyMsg : PtyDataUpdateType) {
|
||||
let cmdId = ptyMsg.cmdid;
|
||||
let term = this.terms[cmdId];
|
||||
if (term == null) {
|
||||
return;
|
||||
}
|
||||
let data = base64ToArray(ptyMsg.ptydata64);
|
||||
term.updatePtyData(ptyMsg.ptypos, data);
|
||||
}
|
||||
|
||||
getTermWrap(cmdId : string) : TermWrap {
|
||||
return this.terms[cmdId];
|
||||
}
|
||||
|
||||
connectElem(elem : Element, cmd : Cmd, width : number) {
|
||||
let cmdId = cmd.cmdId;
|
||||
let termWrap = this.getTermWrap(cmdId);
|
||||
if (termWrap != null) {
|
||||
console.log("term-wrap already exists for", this.screenId, this.windowId, cmdId);
|
||||
return;
|
||||
}
|
||||
let usedRows = GlobalModel.getTUR(this.sessionId, cmdId, width);
|
||||
termWrap = new TermWrap(elem, this.sessionId, cmdId, usedRows, cmd.getTermOpts(), {height: 0, width: width}, cmd.handleKey.bind(cmd));
|
||||
this.terms[cmdId] = termWrap;
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectElem(cmdId : string) {
|
||||
let termWrap = this.terms[cmdId];
|
||||
if (cmdId != null) {
|
||||
termWrap.dispose();
|
||||
delete this.terms[cmdId];
|
||||
}
|
||||
}
|
||||
|
||||
getUsedRows(cmd : Cmd, width : number) : number {
|
||||
let termOpts = cmd.getTermOpts();
|
||||
if (!termOpts.flexrows) {
|
||||
return termOpts.rows;
|
||||
}
|
||||
let termWrap = this.getTermWrap(cmd.cmdId);
|
||||
if (termWrap == null) {
|
||||
let usedRows = GlobalModel.getTUR(this.sessionId, cmd.cmdId, width);
|
||||
if (usedRows != null) {
|
||||
return usedRows;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
return termWrap.usedRows.get();
|
||||
}
|
||||
|
||||
getIsFocused(cmdId : string) : boolean {
|
||||
let termWrap = this.getTermWrap(cmdId);
|
||||
if (termWrap == null) {
|
||||
return false;
|
||||
}
|
||||
return termWrap.isFocused.get();
|
||||
}
|
||||
|
||||
reset() {
|
||||
mobx.action(() => {
|
||||
this.shouldFollow.set(true);
|
||||
@ -271,14 +277,6 @@ class Window {
|
||||
this.windowId = windowId;
|
||||
}
|
||||
|
||||
updatePtyData(ptyMsg : PtyDataUpdateType) {
|
||||
let cmd = this.cmds[ptyMsg.cmdid];
|
||||
if (cmd == null) {
|
||||
return;
|
||||
}
|
||||
cmd.updatePtyData(ptyMsg);
|
||||
}
|
||||
|
||||
updateWindow(win : WindowDataType, load : boolean) {
|
||||
mobx.action(() => {
|
||||
if (!isBlank(win.curremote)) {
|
||||
@ -652,6 +650,7 @@ class Model {
|
||||
infoMsg : OV<InfoType> = mobx.observable.box(null);
|
||||
infoTimeoutId : any = null;
|
||||
inputModel : InputModel;
|
||||
termUsedRowsCache : Record<string, number> = {};
|
||||
|
||||
constructor() {
|
||||
this.clientId = getApi().getId();
|
||||
@ -666,6 +665,16 @@ class Model {
|
||||
getApi().onDigitCmd(this.onDigitCmd.bind(this));
|
||||
}
|
||||
|
||||
getTUR(sessionId : string, cmdId : string, width : number) : number {
|
||||
let key = sessionId + "/" + cmdId + "/" + width;
|
||||
return this.termUsedRowsCache[key];
|
||||
}
|
||||
|
||||
setTUR(sessionId : string, cmdId : string, width : number, usedRows : number) : void {
|
||||
let key = sessionId + "/" + cmdId + "/" + width;
|
||||
this.termUsedRowsCache[key] = usedRows;
|
||||
}
|
||||
|
||||
contextScreen(e : any, screenId : string) {
|
||||
console.log("model", screenId);
|
||||
getApi().contextScreen({screenId: screenId}, {x: e.x, y: e.y});
|
||||
|
@ -46,15 +46,17 @@ class TermWrap {
|
||||
reloading : boolean = false;
|
||||
dataUpdates : DataUpdate[] = [];
|
||||
loadError : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
winSize : WindowSize;
|
||||
|
||||
constructor(elem : Element, sessionId : string, cmdId : string, usedRows : number, termOpts : TermOptsType, winSize : WindowSize, keyHandler : (event : any) => void) {
|
||||
this.sessionId = sessionId;
|
||||
this.cmdId = cmdId;
|
||||
this.connectedElem = elem;
|
||||
this.flexRows = termOpts.flexrows ?? false;
|
||||
this.winSize = winSize;
|
||||
if (this.flexRows) {
|
||||
this.atRowMax = false;
|
||||
this.usedRows = mobx.observable.box(usedRows || 2);
|
||||
this.usedRows = mobx.observable.box(usedRows ?? 2);
|
||||
}
|
||||
else {
|
||||
this.atRowMax = true;
|
||||
@ -141,6 +143,7 @@ class TermWrap {
|
||||
mobx.action(() => {
|
||||
let oldUsedRows = this.usedRows.get();
|
||||
this.usedRows.set(tur);
|
||||
GlobalModel.setTUR(this.sessionId, this.cmdId, this.winSize.width, tur);
|
||||
if (this.connectedElem) {
|
||||
let resizeEvent = new CustomEvent("termresize", {
|
||||
bubbles: true,
|
||||
|
Loading…
Reference in New Issue
Block a user