diff --git a/src/main.tsx b/src/main.tsx index 09b36683..d33e6f5c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,6 +3,7 @@ import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; +import {v4 as uuidv4} from "uuid"; import dayjs from 'dayjs' import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames" @@ -190,7 +191,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> let isFocused = cmd.getIsFocused(sw.screenId, sw.windowId); return (
-
= 5}, {"running": running}, {"detached": detached})}> +
= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}> {lineid}
@@ -209,9 +210,6 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}>
-
- -
); } @@ -352,9 +350,13 @@ class CmdInput extends React.Component<{}, {}> { @mobxReact.observer class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { mutObs : any; + randomId : string; - scrollToBottom() { + scrollToBottom(reason : string) { let elem = document.getElementById(this.getLinesId()); + if (elem == null) { + return; + } let oldST = elem.scrollTop; elem.scrollTop = elem.scrollHeight; // console.log("scroll-elem", oldST, elem.scrollHeight, elem.scrollTop, elem.scrollLeft, elem); @@ -378,17 +380,23 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { } this.mutObs = new MutationObserver(this.handleDomMutation.bind(this)); this.mutObs.observe(elem, {childList: true}); - elem.addEventListener("termresize", this.handleTermResize) + elem.addEventListener("termresize", this.handleTermResize); + let {sw} = this.props; + if (sw && sw.shouldFollow.get()) { + setTimeout(() => this.scrollToBottom("mount"), 0); + } } componentWillUnmount() { - this.mutObs.disconnect(); + if (this.mutObs) { + this.mutObs.disconnect(); + } } handleDomMutation(mutations, mutObs) { let {sw} = this.props; if (sw && sw.shouldFollow.get()) { - setTimeout(() => this.scrollToBottom(), 0); + setTimeout(() => this.scrollToBottom("mut"), 0); } } @@ -399,6 +407,12 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { getLinesId() { let {sw} = this.props; + if (sw == null) { + if (!this.randomId) { + this.randomId = uuidv4(); + } + return "window-lines-" + this.randomId; + } return "window-lines-" + sw.windowId; } @@ -406,7 +420,7 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { handleTermResize(e : any) { let {sw} = this.props; if (sw && sw.shouldFollow.get()) { - setTimeout(() => this.scrollToBottom(), 0); + setTimeout(() => this.scrollToBottom("termresize"), 0); } } @@ -415,10 +429,17 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { } renderError(message : string) { + let {sw} = this.props; return (
-
- {message} +
+ + {sw.name.get()}{sw.shouldFollow.get() ? "*" : ""} + +
+
+
+
{message}
); @@ -430,18 +451,35 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { return this.renderError("(no screen window)"); } let win = this.getWindow(); + if (win == null) { + return this.renderError("(no window)"); + } if (!win.linesLoaded.get()) { return this.renderError("(loading)"); } let idx = 0; let line : LineType = null; + let screen = GlobalModel.getScreenById(sw.sessionId, sw.screenId); + let session = GlobalModel.getSessionById(sw.sessionId); + let linesStyle : any = {}; + if (win.lines.length == 0) { + linesStyle.display = "none"; + } return (
-
+
+ {sw.name.get()}{sw.shouldFollow.get() ? "*" : ""} +
+
+ +
+
[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]
+
+
); } @@ -461,23 +499,52 @@ class ScreenView extends React.Component<{screen : Screen}, {}> { let sw = screen.getActiveSW(); return (
- +
); } } @mobxReact.observer -class ScreenTabs extends React.Component<{}, {}> { +class ScreenTabs extends React.Component<{session : Session}, {}> { + @boundMethod + handleNewScreen() { + let {session} = this.props; + GlobalModel.createNewScreen(session, null, true); + } + + @boundMethod + handleSwitchScreen(screenId : string) { + let {session} = this.props; + if (session == null) { + return; + } + if (session.activeScreenId.get() == screenId) { + return; + } + let screen = session.getScreenById(screenId); + if (screen == null) { + return; + } + GlobalModel.activateScreen(screen.sessionId, screen.screenId); + } + render() { - let model = GlobalModel; - let session = model.getActiveSession(); + let {session} = this.props; if (session == null) { return null; } + let screen : Screen = null; return (
- tabs! + +
this.handleSwitchScreen(screen.screenId)}> + {screen.name.get()} +
+
+
+ + +
); } @@ -495,7 +562,7 @@ class SessionView extends React.Component<{}, {}> { return (
- +
); diff --git a/src/model.ts b/src/model.ts index dd54431a..86bcd1a1 100644 --- a/src/model.ts +++ b/src/model.ts @@ -217,6 +217,17 @@ class Screen { return null; } + deactivate() { + for (let i=0; i = {}; let activeWindowId = this.activeWindowId.get(); @@ -251,6 +262,12 @@ class ScreenWindow { this.layout = mobx.observable.box(swdata.layout); } + reset() { + mobx.action(() => { + this.shouldFollow.set(true); + })(); + } + getWindow() : Window { return GlobalModel.getWindowById(this.sessionId, this.windowId); } @@ -308,6 +325,15 @@ class Window { })(); } + deactivate() { + mobx.action(() => { + this.linesLoaded.set(false); + this.lines.replace([]); + this.history = []; + this.cmds = {}; + })(); + } + getCmd(cmdId : string) { return this.cmds[cmdId]; } @@ -436,6 +462,10 @@ class Session { return this.getScreenById(this.activeScreenId.get()); } + setActiveScreenId(screenId : string) { + this.activeScreenId.set(screenId); + } + getScreenById(screenId : string) : Screen { if (screenId == null) { return null; @@ -539,6 +569,14 @@ class Model { return session.getWindowById(windowId); } + getScreenById(sessionId : string, screenId : string) : Screen { + let session = this.getSessionById(sessionId); + if (session == null) { + return null; + } + return session.getScreenById(screenId); + } + getActiveWindow() : Window { let screen = this.getActiveScreen(); if (screen == null) { @@ -603,22 +641,21 @@ class Model { mobx.action(() => { let sdatalist : SessionDataType[] = data.data || []; let slist : Session[] = []; - let defaultSessionId = null; + let activeSessionId = null; + let activeScreenId = null; for (let i=0; i { @@ -626,6 +663,43 @@ class Model { }); } + activateScreen(sessionId : string, screenId : string) { + let oldActiveScreen = this.getActiveScreen(); + if (oldActiveScreen && oldActiveScreen.sessionId == sessionId && oldActiveScreen.screenId == screenId) { + return; + } + mobx.action(() => { + if (oldActiveScreen != null) { + oldActiveScreen.deactivate(); + } + this.activeSessionId.set(sessionId); + this.getActiveSession().activeScreenId.set(screenId); + })(); + let curScreen = this.getActiveScreen(); + if (curScreen == null) { + return; + } + this.ws.pushMessage({type: "watchscreen", sessionid: curScreen.sessionId, screenid: curScreen.screenId}); + curScreen.loadWindows(false); + } + + createNewScreen(session : Session, name : string, activate : boolean) { + let params : Record = {sessionid: session.sessionId}; + if (name != null) { + params.name = name; + } + if (activate) { + params.activate = "1"; + } + let usp = new URLSearchParams(params); + let url = new URL("http://localhost:8080/api/create-screen?" + usp.toString()); + fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { + console.log("created screen", data.data); + }).catch((err) => { + this.errorHandler(sprintf("creating screen session=%s", session.sessionId), err); + }); + } + loadWindow(sessionId : string, windowId : string, force : boolean) { let usp = new URLSearchParams({sessionid: sessionId, windowid: windowId}); let url = new URL(sprintf("http://localhost:8080/api/get-window?") + usp.toString()); diff --git a/src/sh2.less b/src/sh2.less index 90d7a8fc..b54b222f 100644 --- a/src/sh2.less +++ b/src/sh2.less @@ -20,17 +20,91 @@ html, body, #main { } .screen-tabs { - height: 50px; - background-color: red; + height: 30px; + display: flex; + flex-direction: row; + border-top: 1px solid #eee; + border-right: 1px solid #eee; + + .screen-tab { + height: 30px; + min-width: 80px; + width: 150px; + flex-shrink: 1; + background-color: darken(#4e9a06, 15%); + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + font-weight: bold; + color: black; + padding-left: 10px; + padding-right: 10px; + cursor: pointer; + + &:hover { + background-color: #4e9a06; + } + + &.is-active { + background-color: #4e9a06; + } + + border-right: 1px solid #ccc; + } + + .screen-tab.new-screen { + width: 30px; + min-width: 30px; + background-color: black; + border-right: none; + color: #ccc; + cursor: pointer; + + &:hover { + background-color: #666; + border-right: 1px solid #ccc; + } + } } .screen-view { flex-grow: 1; + border-right: 1px solid #ccc; } .window-view { display: flex; flex-direction: column; + position: relative; + + .window-tag { + position: absolute; + top: 0; + right: 0; + background-color: rgba(78, 154, 6, 0.5); + /* background-color: rgba(0, 255, 0, 0.5); */ + color: white; + padding: 2px 4px 2px 4px; + border-bottom-left-radius: 5px; + z-index: 10; + font-size: 12px; + } + + .window-empty { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding: 10px; + height: 100%; + color: #ccc; + + code { + background-color: black; + color: #4e9a06; + } + } } } } @@ -198,12 +272,17 @@ html, body, #main { } } +.line.line-text { + flex-direction: row; +} + +.line.line-cmd { +} + .line { padding: 10px 5px 5px 5px; margin: 0px 5px 5px 5px; - border-radius: 5px; display: flex; - flex-direction: row; line-height: 1.25; border-top: 1px solid #777; @@ -244,6 +323,10 @@ html, body, #main { } } + &.line-cmd .avatar { + cursor: pointer; + } + .line-content { display: flex; flex-direction: column; @@ -319,9 +402,11 @@ body .xterm .xterm-viewport { } .line.line-cmd { - .button.refresh-button { + .refresh-button { + cursor: pointer; border-color: #777; color: #777; + margin-right: 10px; &:hover { border-color: #fff; @@ -345,13 +430,16 @@ body .xterm .xterm-viewport { overflow-y: scroll; padding-bottom: 10px; padding-top: 10px; - padding-right: 15px; + padding-right: 0px; flex-grow: 1; } .cmd-input { border-radius: 0; - border-top: 2px solid white; + border-top: 2px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-bottom-right-radius: 10px; .cmd-input-context { color: #fff; diff --git a/src/term.ts b/src/term.ts index db8b3ec1..d81c7ab7 100644 --- a/src/term.ts +++ b/src/term.ts @@ -137,6 +137,7 @@ class TermWrap { newUsedRows: tur, }, }); + // console.log("resize-event", resizeEvent); this.connectedElem.dispatchEvent(resizeEvent); } })();