screen switching

This commit is contained in:
sawka 2022-07-13 23:11:45 -07:00
parent 94fd29cbad
commit 8a710669ec
4 changed files with 264 additions and 34 deletions

View File

@ -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 (
<div className="line line-cmd" id={"line-" + getLineId(line)}>
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})}>
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}>
{lineid}
</div>
<div className="line-content">
@ -209,9 +210,6 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}>
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
</div>
</div>
<div onClick={this.doRefresh} className="button refresh-button has-background-black is-small">
<span className="icon"><i className="fa fa-refresh"/></span>
</div>
</div>
);
}
@ -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 (
<div className="window-view" style={this.getWindowViewStyle()}>
<div className="lines" onScroll={this.scrollHandler} id={this.getLinesId()}>
{message}
<div key="window-tag" className="window-tag">
<If condition={sw != null}>
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
</If>
</div>
<div key="lines" className="lines" id={this.getLinesId()}></div>
<div key="window-empty" className="window-empty">
<div>{message}</div>
</div>
</div>
);
@ -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 (
<div className="window-view" style={this.getWindowViewStyle()}>
<div className="lines" onScroll={this.scrollHandler} id={this.getLinesId()}>
<div key="window-tag" className="window-tag">
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
</div>
<div key="lines" className="lines" onScroll={this.scrollHandler} id={this.getLinesId()} style={linesStyle}>
<For each="line" of={win.lines} index="idx">
<Line key={line.lineid} line={line} sw={sw}/>
</For>
</div>
<If condition={win.lines.length == 0}>
<div key="window-empty" className="window-empty">
<div><code>[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]</code></div>
</div>
</If>
</div>
);
}
@ -461,23 +499,52 @@ class ScreenView extends React.Component<{screen : Screen}, {}> {
let sw = screen.getActiveSW();
return (
<div className="screen-view">
<ScreenWindowView sw={sw}/>
<ScreenWindowView key={sw.windowId} sw={sw}/>
</div>
);
}
}
@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 (
<div className="screen-tabs">
tabs!
<For each="screen" of={session.screens}>
<div key={screen.screenId} className={cn("screen-tab", {"is-active": session.activeScreenId.get() == screen.screenId})} onClick={() => this.handleSwitchScreen(screen.screenId)}>
{screen.name.get()}
</div>
</For>
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
+
</div>
</div>
);
}
@ -495,7 +562,7 @@ class SessionView extends React.Component<{}, {}> {
return (
<div className="session-view">
<ScreenView screen={activeScreen}/>
<ScreenTabs/>
<ScreenTabs session={session}/>
<CmdInput/>
</div>
);

View File

@ -217,6 +217,17 @@ class Screen {
return null;
}
deactivate() {
for (let i=0; i<this.windows.length; i++) {
let sw = this.windows[i];
sw.reset();
let win = sw.getWindow();
if (win != null) {
win.deactivate();
}
}
}
loadWindows(force : boolean) {
let loadedMap : Record<string, boolean> = {};
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<sdatalist.length; i++) {
let sdata = sdatalist[i];
if (sdata.name == "default") {
defaultSessionId = sdata.sessionid;
}
let s = new Session(sdata);
if (s.name.get() == "default") {
activeSessionId = s.sessionId;
activeScreenId = s.activeScreenId.get();
}
slist.push(s);
}
this.sessionList.replace(slist);
this.sessionListLoaded.set(true)
this.activeSessionId.set(defaultSessionId);
let screen = this.getActiveScreen();
if (screen != null) {
this.ws.pushMessage({type: "watchscreen", sessionid: screen.sessionId, screenid: screen.screenId});
screen.loadWindows(false);
if (activeScreenId != null) {
this.activateScreen(activeSessionId, activeScreenId);
}
})();
}).catch((err) => {
@ -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<string, string> = {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());

View File

@ -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;

View File

@ -137,6 +137,7 @@ class TermWrap {
newUsedRows: tur,
},
});
// console.log("resize-event", resizeEvent);
this.connectedElem.dispatchEvent(resizeEvent);
}
})();