diff --git a/src/main.tsx b/src/main.tsx index 1ce98b8d4..166e81c32 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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 = 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 ( +
+
+ {message} +
+
+ ); + } render() { - let model = GlobalModel; - let win = model.getActiveWindow(); + let win = this.getWindow(); if (win == null) { - return
(no active window)
; + return this.renderError("(no window)"); } if (!win.linesLoaded.get()) { - return
(loading)
; + return this.renderError("(loading)"); } let idx = 0; let line : LineType = null; return ( -
-
+
+
- +
+
+ ); + } +} + +@mobxReact.observer +class SessionView extends React.Component<{}, {}> { + render() { + let model = GlobalModel; + let session = model.getActiveSession(); + if (session == null) { + return
(no active session)
; + } + let curWindowId = session.curWindowId.get(); + return ( +
+
); diff --git a/src/model.ts b/src/model.ts index e63cd328e..3604fbb0d 100644 --- a/src/model.ts +++ b/src/model.ts @@ -28,6 +28,7 @@ class Cmd { watching : boolean = false; isFocused : OV = mobx.observable.box(false, {name: "focus"}); usedRows : OV; + 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 = mobx.observable.box(false); history : any[] = []; cmds : Record = {}; + shouldFollow : OV = 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); } diff --git a/src/sh2.less b/src/sh2.less index dbcf76052..bc5a99728 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -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; + } } } } diff --git a/src/term.ts b/src/term.ts index d8349d9a4..8f159ca1f 100644 --- a/src/term.ts +++ b/src/term.ts @@ -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;