mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-24 03:01:58 +01:00
screen switching
This commit is contained in:
parent
94fd29cbad
commit
8a710669ec
103
src/main.tsx
103
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 (
|
||||
<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>
|
||||
);
|
||||
|
92
src/model.ts
92
src/model.ts
@ -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());
|
||||
|
102
src/sh2.less
102
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;
|
||||
|
@ -137,6 +137,7 @@ class TermWrap {
|
||||
newUsedRows: tur,
|
||||
},
|
||||
});
|
||||
// console.log("resize-event", resizeEvent);
|
||||
this.connectedElem.dispatchEvent(resizeEvent);
|
||||
}
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user