get terminal follow scrolling working. create windowview

This commit is contained in:
sawka 2022-07-11 22:43:58 -07:00
parent 49c8b34a7c
commit 9af5edd451
4 changed files with 141 additions and 34 deletions

View File

@ -86,7 +86,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
let cmd = model.getCmd(line);
if (cmd != null) {
let termElem = document.getElementById("term-" + getLineId(line));
cmd.connectToElem(termElem);
cmd.connectElem(termElem);
}
if (line.isnew) {
setTimeout(() => this.scrollIntoView(), 100);
@ -94,6 +94,15 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
}
}
componentWillUnmount() {
let {line} = this.props;
let model = GlobalModel;
let cmd = model.getCmd(line);
if (cmd != null) {
cmd.disconnectElem();
}
}
scrollIntoView() {
let lineElem = document.getElementById("line-" + getLineId(this.props.line));
lineElem.scrollIntoView({block: "end"});
@ -104,7 +113,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
let model = GlobalModel;
let cmd = model.getCmd(this.props.line);
if (cmd != null) {
cmd.reloadTerminal(true, 500);
cmd.reloadTerminal(500);
}
}
@ -203,7 +212,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
}
@mobxReact.observer
class Line extends React.Component<{line : LineType, changeSizeCallback? : (term : TermWrap) => void}, {}> {
class Line extends React.Component<{line : LineType}, {}> {
render() {
let line = this.props.line;
if (line.linetype == "text") {
@ -335,48 +344,119 @@ class CmdInput extends React.Component<{}, {}> {
}
@mobxReact.observer
class SessionView extends React.Component<{}, {}> {
shouldFollow : mobx.IObservableValue<boolean> = mobx.observable.box(true);
class WindowView extends React.Component<{windowId : string}, {}> {
mutObs : any;
scrollToBottom() {
let elem = document.getElementById(this.getLinesId());
let oldST = elem.scrollTop;
elem.scrollTop = elem.scrollHeight;
// console.log("scroll-elem", oldST, elem.scrollHeight, elem.scrollTop, elem.scrollLeft, elem);
}
@boundMethod
scrollHandler(event : any) {
let target = event.target;
let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight));
mobx.action(() => this.shouldFollow.set(atBottom))();
let win = this.getWindow();
if (win.shouldFollow.get() != atBottom) {
mobx.action(() => win.shouldFollow.set(atBottom));
}
// console.log("scroll-handler>", atBottom, target.scrollTop, target.scrollHeight);
}
componentDidMount() {
let elem = document.getElementById(this.getLinesId());
if (elem == null) {
return;
}
this.mutObs = new MutationObserver(this.handleDomMutation.bind(this));
this.mutObs.observe(elem, {childList: true});
elem.addEventListener("termresize", this.handleTermResize)
}
componentWillUnmount() {
this.mutObs.disconnect();
}
handleDomMutation(mutations, mutObs) {
let win = this.getWindow();
if (win && win.shouldFollow.get()) {
setTimeout(() => this.scrollToBottom(), 0);
}
}
getWindow() : Window {
let {windowId} = this.props;
if (windowId == null) {
return null;
}
let model = GlobalModel;
let session = model.getActiveSession();
if (session == null) {
return null;
}
let win = session.getWindowById(windowId);
return win;
}
getLinesId() {
let {windowId} = this.props;
return "window-lines-" + windowId;
}
@boundMethod
changeSizeCallback(term : TermWrap) {
if (this.shouldFollow.get()) {
let window = GlobalModel.getActiveWindow();
let lines = window.lines;
if (lines == null || lines.length == 0) {
return;
}
let lastLine = lines[lines.length-1];
let lineElem = document.getElementById("line-" + getLineId(lastLine));
setTimeout(() => lineElem.scrollIntoView({block: "end"}), 0);
handleTermResize(e : any) {
let win = this.getWindow();
if (win && win.shouldFollow.get()) {
setTimeout(() => this.scrollToBottom(), 0);
}
}
renderError(message : string) {
return (
<div className="window-view">
<div className="lines" onScroll={this.scrollHandler} id={this.getLinesId()}>
{message}
</div>
</div>
);
}
render() {
let model = GlobalModel;
let win = model.getActiveWindow();
let win = this.getWindow();
if (win == null) {
return <div className="session-view">(no active window)</div>;
return this.renderError("(no window)");
}
if (!win.linesLoaded.get()) {
return <div className="session-view">(loading)</div>;
return this.renderError("(loading)");
}
let idx = 0;
let line : LineType = null;
return (
<div className="session-view">
<div className="lines" onScroll={this.scrollHandler}>
<div className="window-view">
<div className="lines" onScroll={this.scrollHandler} id={this.getLinesId()}>
<For each="line" of={win.lines} index="idx">
<Line key={line.lineid} line={line} changeSizeCallback={this.changeSizeCallback}/>
<Line key={line.lineid} line={line}/>
</For>
</div>
</div>
);
}
}
@mobxReact.observer
class SessionView extends React.Component<{}, {}> {
render() {
let model = GlobalModel;
let session = model.getActiveSession();
if (session == null) {
return <div className="session-view">(no active session)</div>;
}
let curWindowId = session.curWindowId.get();
return (
<div className="session-view">
<WindowView windowId={curWindowId}/>
<CmdInput/>
</div>
);

View File

@ -28,6 +28,7 @@ class Cmd {
watching : boolean = false;
isFocused : OV<boolean> = mobx.observable.box(false, {name: "focus"});
usedRows : OV<number>;
connectedElem : Element;
constructor(cmd : CmdDataType, windowId : string) {
this.sessionId = cmd.sessionid;
@ -45,14 +46,26 @@ class Cmd {
}
}
connectToElem(elem : Element) {
disconnectElem() {
this.connectedElem = null;
}
connectElem(elem : Element) {
if (this.connectedElem != null) {
console.log("WARNING element already connected to cmd", this.cmdId, this.connectedElem);
}
this.connectedElem = elem;
if (this.termWrap == null) {
this.termWrap = new TermWrap(this.getTermOpts());
this.reloadTerminal(0);
}
this.termWrap.connectToElem(elem, {
setFocus: this.setFocus.bind(this),
handleKey: this.handleKey.bind(this),
});
}
reloadTerminal(startTail : boolean, delayMs : number) {
reloadTerminal(delayMs : number) {
if (this.termWrap == null) {
return;
}
@ -106,7 +119,17 @@ class Cmd {
let data = this.data.get();
let oldUsedRows = this.usedRows.get();
this.usedRows.set(tur);
GlobalModel.termChangeSize(this.sessionId, this.windowId, this.cmdId, oldUsedRows, tur);
if (this.connectedElem) {
let resizeEvent = new CustomEvent("termresize", {
bubbles: true,
detail: {
cmdId: this.cmdId,
oldUsedRows: oldUsedRows,
newUsedRows: tur,
},
});
this.connectedElem.dispatchEvent(resizeEvent);
}
})();
}
}
@ -174,6 +197,7 @@ class Window {
linesLoaded : OV<boolean> = mobx.observable.box(false);
history : any[] = [];
cmds : Record<string, Cmd> = {};
shouldFollow : OV<boolean> = mobx.observable.box(true);
constructor(wdata : WindowDataType) {
this.sessionId = wdata.sessionid;
@ -286,7 +310,7 @@ class Model {
this.clientId = uuidv4();
this.loadRemotes();
this.loadSessionList();
this.ws = new WSControl(this.clientId, this.onMessage.bind(this))
this.ws = new WSControl(this.clientId, this.onWSMessage.bind(this))
this.ws.reconnect();
}
@ -294,7 +318,8 @@ class Model {
return this.ws.open.get();
}
onMessage(message : any) {
onWSMessage(message : any) {
console.log("ws-message", message);
}
getActiveSession() : Session {
@ -322,6 +347,7 @@ class Model {
}
submitCommand(cmdStr : string) {
console.log("submit-command>", cmdStr);
}
updateWindow(win : WindowDataType) {
@ -409,10 +435,6 @@ class Model {
return window.getCmd(line.cmdid);
}
termChangeSize(sessionId : string, windowId : string, cmdId : string, oldUsedRows : number, newUsedRows : number) {
console.log("change-size", sessionId + "/" + windowId + "/" + cmdId, oldUsedRows, "=>", newUsedRows);
}
errorHandler(str : string, err : any) {
console.log("[error]", str, err);
}

View File

@ -15,9 +15,14 @@ html, body, #main {
.session-view {
flex-grow: 1;
display: flex;
flex-direction: column;
.window-view {
flex-grow: 1;
display: flex;
flex-direction: column;
}
}
}
}

View File

@ -44,7 +44,7 @@ class TermWrap {
let termBuf = term._core.buffer;
let termNumLines = termBuf.lines.length;
let termYPos = termBuf.y;
if (termNumLines >= term.rows) {
if (termNumLines > term.rows) {
return term.rows;
}
let usedRows = 2;