mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
checkpoint on updates. can switch screens using commands
This commit is contained in:
parent
879cb03da0
commit
8cdf514bb9
28
src/emain.ts
28
src/emain.ts
@ -40,6 +40,10 @@ electron.Menu.setApplicationMenu(menu);
|
|||||||
|
|
||||||
let MainWindow = null;
|
let MainWindow = null;
|
||||||
|
|
||||||
|
function getMods(input : any) {
|
||||||
|
return {meta: input.meta, shift: input.shift, ctrl: input.ctrl, alt: input.alt};
|
||||||
|
}
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
let win = new electron.BrowserWindow({
|
let win = new electron.BrowserWindow({
|
||||||
width: 1800,
|
width: 1800,
|
||||||
@ -53,21 +57,33 @@ function createWindow() {
|
|||||||
if (input.type != "keyDown") {
|
if (input.type != "keyDown") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let mods = getMods(input);
|
||||||
if (input.meta) {
|
if (input.meta) {
|
||||||
console.log("before-input", input.code, input.modifiers);
|
console.log("before-input", input.code, input.modifiers);
|
||||||
}
|
}
|
||||||
if (input.code == "KeyT" && input.meta) {
|
if (input.code == "KeyT" && input.meta) {
|
||||||
win.webContents.send("cmd-t");
|
win.webContents.send("t-cmd", mods);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (input.code == "BracketRight" && input.meta) {
|
if (input.code == "KeyI" && input.meta) {
|
||||||
win.webContents.send("switch-screen", {relative: 1});
|
if (!input.alt) {
|
||||||
e.preventDefault();
|
win.webContents.send("i-cmd", mods);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (input.code == "BracketLeft" && input.meta) {
|
if (input.code.startsWith("Digit") && input.meta) {
|
||||||
win.webContents.send("switch-screen", {relative: -1});
|
let digitNum = parseInt(input.code.substr(5));
|
||||||
|
if (isNaN(digitNum) || digitNum < 1 || digitNum > 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
win.webContents.send("digit-cmd", {digit: digitNum}, mods);
|
||||||
|
}
|
||||||
|
if ((input.code == "BracketRight" || input.code == "BracketLeft") && input.meta) {
|
||||||
|
let rel = (input.code == "BracketRight" ? 1 : -1);
|
||||||
|
win.webContents.send("bracket-cmd", {relative: rel}, mods);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
24
src/main.tsx
24
src/main.tsx
@ -187,7 +187,6 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
|
|||||||
<div className="meta">
|
<div className="meta">
|
||||||
<div className="user" style={{display: "none"}}>{line.userid}</div>
|
<div className="user" style={{display: "none"}}>{line.userid}</div>
|
||||||
<div className="ts">{formattedTime}</div>
|
<div className="ts">{formattedTime}</div>
|
||||||
width={this.props.width}, cellwidth={termWidth}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="meta">
|
<div className="meta">
|
||||||
<div className="metapart-mono" style={{display: "none"}}>
|
<div className="metapart-mono" style={{display: "none"}}>
|
||||||
@ -417,7 +416,14 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
|||||||
|
|
||||||
getWindow() : Window {
|
getWindow() : Window {
|
||||||
let {sw} = this.props;
|
let {sw} = this.props;
|
||||||
return GlobalModel.getWindowById(sw.sessionId, sw.windowId);
|
if (sw == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let win = GlobalModel.getWindowById(sw.sessionId, sw.windowId);
|
||||||
|
if (win == null) {
|
||||||
|
win = GlobalModel.loadWindow(sw.sessionId, sw.windowId);
|
||||||
|
}
|
||||||
|
return win;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLinesDOMId() {
|
getLinesDOMId() {
|
||||||
@ -475,12 +481,12 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
|||||||
return this.renderError("(no screen window)");
|
return this.renderError("(no screen window)");
|
||||||
}
|
}
|
||||||
let win = this.getWindow();
|
let win = this.getWindow();
|
||||||
if (win == null) {
|
if (win == null || !win.loaded.get()) {
|
||||||
return this.renderError("(no window)");
|
|
||||||
}
|
|
||||||
if (!win.linesLoaded.get()) {
|
|
||||||
return this.renderError("(loading)");
|
return this.renderError("(loading)");
|
||||||
}
|
}
|
||||||
|
if (win.loadError.get() != null) {
|
||||||
|
return this.renderError(sprintf("(%s)", win.loadError.get()));
|
||||||
|
}
|
||||||
if (this.width.get() == 0) {
|
if (this.width.get() == 0) {
|
||||||
return this.renderError("");
|
return this.renderError("");
|
||||||
}
|
}
|
||||||
@ -562,11 +568,15 @@ class ScreenTabs extends React.Component<{session : Session}, {}> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let screen : Screen = null;
|
let screen : Screen = null;
|
||||||
|
let index = 0;
|
||||||
return (
|
return (
|
||||||
<div className="screen-tabs">
|
<div className="screen-tabs">
|
||||||
<For each="screen" of={session.screens}>
|
<For each="screen" index="index" of={session.screens}>
|
||||||
<div key={screen.screenId} className={cn("screen-tab", {"is-active": session.activeScreenId.get() == screen.screenId})} onClick={() => this.handleSwitchScreen(screen.screenId)}>
|
<div key={screen.screenId} className={cn("screen-tab", {"is-active": session.activeScreenId.get() == screen.screenId})} onClick={() => this.handleSwitchScreen(screen.screenId)}>
|
||||||
{screen.name.get()}
|
{screen.name.get()}
|
||||||
|
<If condition={index+1 <= 9}>
|
||||||
|
<div className="tab-index">⌘{index+1}</div>
|
||||||
|
</If>
|
||||||
</div>
|
</div>
|
||||||
</For>
|
</For>
|
||||||
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
|
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
|
||||||
|
292
src/model.ts
292
src/model.ts
@ -1,25 +1,35 @@
|
|||||||
import * as mobx from "mobx";
|
import * as mobx from "mobx";
|
||||||
import {sprintf} from "sprintf-js";
|
import {sprintf} from "sprintf-js";
|
||||||
import {boundMethod} from "autobind-decorator";
|
import {boundMethod} from "autobind-decorator";
|
||||||
import {handleJsonFetchResponse, base64ToArray} from "./util";
|
import {handleJsonFetchResponse, base64ToArray, genMergeData} from "./util";
|
||||||
import {TermWrap} from "./term";
|
import {TermWrap} from "./term";
|
||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType} from "./types";
|
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, SessionUpdateType, WindowUpdateType} from "./types";
|
||||||
import {WSControl} from "./ws";
|
import {WSControl} from "./ws";
|
||||||
|
|
||||||
var GlobalUser = "sawka";
|
var GlobalUser = "sawka";
|
||||||
|
|
||||||
type OV<V> = mobx.IObservableValue<V>;
|
type OV<V> = mobx.IObservableValue<V>;
|
||||||
type OArr<V> = mobx.IObservableArray<V>;
|
type OArr<V> = mobx.IObservableArray<V>;
|
||||||
|
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||||
|
|
||||||
function isBlank(s : string) {
|
function isBlank(s : string) {
|
||||||
return (s == null || s == "");
|
return (s == null || s == "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyModsType = {
|
||||||
|
meta? : boolean,
|
||||||
|
ctrl? : boolean,
|
||||||
|
alt? : boolean,
|
||||||
|
shift? : boolean,
|
||||||
|
};
|
||||||
|
|
||||||
type ElectronApi = {
|
type ElectronApi = {
|
||||||
getId : () => string,
|
getId : () => string,
|
||||||
onCmdT : (callback : () => void) => void,
|
onTCmd : (callback : (mods : KeyModsType) => void) => void,
|
||||||
onSwitchScreen : (callback : (event : any, arg : {relative? : number, absolute? : number}) => void) => void,
|
onICmd : (callback : (mods : KeyModsType) => void) => void,
|
||||||
|
onBracketCmd : (callback : (event : any, arg : {relative : number}, mods : KeyModsType) => void) => void,
|
||||||
|
onDigitCmd : (callback : (event : any, arg : {digit : number}, mods : KeyModsType) => void) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getApi() : ElectronApi {
|
function getApi() : ElectronApi {
|
||||||
@ -163,6 +173,7 @@ class Cmd {
|
|||||||
class Screen {
|
class Screen {
|
||||||
sessionId : string;
|
sessionId : string;
|
||||||
screenId : string;
|
screenId : string;
|
||||||
|
screenIdx : OV<number>;
|
||||||
opts : OV<ScreenOptsType>;
|
opts : OV<ScreenOptsType>;
|
||||||
name : OV<string>;
|
name : OV<string>;
|
||||||
activeWindowId : OV<string>;
|
activeWindowId : OV<string>;
|
||||||
@ -172,6 +183,7 @@ class Screen {
|
|||||||
this.sessionId = sdata.sessionid;
|
this.sessionId = sdata.sessionid;
|
||||||
this.screenId = sdata.screenid;
|
this.screenId = sdata.screenid;
|
||||||
this.name = mobx.observable.box(sdata.name);
|
this.name = mobx.observable.box(sdata.name);
|
||||||
|
this.screenIdx = mobx.observable.box(sdata.screenidx);
|
||||||
this.opts = mobx.observable.box(sdata.screenopts);
|
this.opts = mobx.observable.box(sdata.screenopts);
|
||||||
this.activeWindowId = mobx.observable.box(ces(sdata.activewindowid));
|
this.activeWindowId = mobx.observable.box(ces(sdata.activewindowid));
|
||||||
let swArr : ScreenWindow[] = [];
|
let swArr : ScreenWindow[] = [];
|
||||||
@ -183,12 +195,10 @@ class Screen {
|
|||||||
this.windows = mobx.observable.array(swArr, {deep: false})
|
this.windows = mobx.observable.array(swArr, {deep: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveWindow() : Window {
|
dispose() {
|
||||||
let session = GlobalModel.getSessionById(this.sessionId);
|
}
|
||||||
if (session == null) {
|
|
||||||
return null;
|
mergeData(data : ScreenDataType) {
|
||||||
}
|
|
||||||
return session.getWindowById(this.activeWindowId.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePtyData(ptyMsg : PtyDataUpdateType) {
|
updatePtyData(ptyMsg : PtyDataUpdateType) {
|
||||||
@ -216,34 +226,6 @@ class Screen {
|
|||||||
}
|
}
|
||||||
return null;
|
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();
|
|
||||||
if (activeWindowId != null) {
|
|
||||||
GlobalModel.loadWindow(this.sessionId, activeWindowId, false);
|
|
||||||
loadedMap[activeWindowId] = true;
|
|
||||||
}
|
|
||||||
for (let i=0; i<this.windows.length; i++) {
|
|
||||||
let win = this.windows[i];
|
|
||||||
if (loadedMap[win.windowId]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
loadedMap[win.windowId] = true;
|
|
||||||
GlobalModel.loadWindow(this.sessionId, win.windowId, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScreenWindow {
|
class ScreenWindow {
|
||||||
@ -273,22 +255,20 @@ class ScreenWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Window {
|
class Window {
|
||||||
sessionId : string;
|
sessionId : string;
|
||||||
windowId : string;
|
windowId : string;
|
||||||
curRemote : OV<string>;
|
curRemote : OV<string> = mobx.observable.box(null);
|
||||||
loaded : OV<boolean> = mobx.observable.box(false);
|
loaded : OV<boolean> = mobx.observable.box(false);
|
||||||
|
loadError : OV<string> = mobx.observable.box(null);
|
||||||
lines : OArr<LineType> = mobx.observable.array([], {deep: false});
|
lines : OArr<LineType> = mobx.observable.array([], {deep: false});
|
||||||
linesLoaded : OV<boolean> = mobx.observable.box(false);
|
|
||||||
history : any[] = [];
|
history : any[] = [];
|
||||||
cmds : Record<string, Cmd> = {};
|
cmds : Record<string, Cmd> = {};
|
||||||
remoteInstances : OArr<RemoteInstanceType> = mobx.observable.array([]);
|
remoteInstances : OArr<RemoteInstanceType> = mobx.observable.array([]);
|
||||||
|
|
||||||
constructor(wdata : WindowDataType) {
|
constructor(sessionId : string, windowId : string) {
|
||||||
this.sessionId = wdata.sessionid;
|
this.sessionId = sessionId;
|
||||||
this.windowId = wdata.windowid;
|
this.windowId = windowId;
|
||||||
this.curRemote = mobx.observable.box(wdata.curremote);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNumHistoryItems() : number {
|
getNumHistoryItems() : number {
|
||||||
@ -307,15 +287,14 @@ class Window {
|
|||||||
cmd.updatePtyData(ptyMsg);
|
cmd.updatePtyData(ptyMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWindow(win : WindowDataType, isActive : boolean) {
|
updateWindow(win : WindowDataType, load : boolean) {
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
if (!isBlank(win.curremote)) {
|
if (!isBlank(win.curremote)) {
|
||||||
this.curRemote.set(win.curremote);
|
this.curRemote.set(win.curremote);
|
||||||
}
|
}
|
||||||
if (!isActive) {
|
if (load) {
|
||||||
return;
|
this.loaded.set(true);
|
||||||
}
|
}
|
||||||
this.linesLoaded.set(true);
|
|
||||||
this.lines.replace(win.lines || []);
|
this.lines.replace(win.lines || []);
|
||||||
this.history = win.history || [];
|
this.history = win.history || [];
|
||||||
let cmds = win.cmds || [];
|
let cmds = win.cmds || [];
|
||||||
@ -325,15 +304,16 @@ class Window {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate() {
|
setWindowLoadError(errStr : string) {
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
this.linesLoaded.set(false);
|
this.loaded.set(true);
|
||||||
this.lines.replace([]);
|
this.loadError.set(errStr);
|
||||||
this.history = [];
|
|
||||||
this.cmds = {};
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
}
|
||||||
|
|
||||||
getCmd(cmdId : string) {
|
getCmd(cmdId : string) {
|
||||||
return this.cmds[cmdId];
|
return this.cmds[cmdId];
|
||||||
}
|
}
|
||||||
@ -369,7 +349,7 @@ class Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
|
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
|
||||||
if (!this.linesLoaded.get()) {
|
if (!this.loaded.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
@ -401,21 +381,15 @@ class Session {
|
|||||||
sessionId : string;
|
sessionId : string;
|
||||||
name : OV<string>;
|
name : OV<string>;
|
||||||
activeScreenId : OV<string>;
|
activeScreenId : OV<string>;
|
||||||
|
sessionIdx : OV<number>;
|
||||||
screens : OArr<Screen>;
|
screens : OArr<Screen>;
|
||||||
windows : OArr<Window>;
|
|
||||||
notifyNum : OV<number> = mobx.observable.box(0);
|
notifyNum : OV<number> = mobx.observable.box(0);
|
||||||
remoteInstances : OArr<RemoteInstanceType> = mobx.observable.array([]);
|
remoteInstances : OArr<RemoteInstanceType> = mobx.observable.array([]);
|
||||||
|
|
||||||
constructor(sdata : SessionDataType) {
|
constructor(sdata : SessionDataType) {
|
||||||
this.sessionId = sdata.sessionid;
|
this.sessionId = sdata.sessionid;
|
||||||
this.name = mobx.observable.box(sdata.name);
|
this.name = mobx.observable.box(sdata.name);
|
||||||
let winData = sdata.windows || [];
|
this.sessionIdx = mobx.observable.box(sdata.sessionidx);
|
||||||
let wins : Window[] = [];
|
|
||||||
for (let i=0; i<winData.length; i++) {
|
|
||||||
let win = new Window(winData[i]);
|
|
||||||
wins.push(win);
|
|
||||||
}
|
|
||||||
this.windows = mobx.observable.array(wins, {deep: false});
|
|
||||||
let screenData = sdata.screens || [];
|
let screenData = sdata.screens || [];
|
||||||
let screens : Screen[] = [];
|
let screens : Screen[] = [];
|
||||||
for (let i=0; i<screenData.length; i++) {
|
for (let i=0; i<screenData.length; i++) {
|
||||||
@ -426,36 +400,35 @@ class Session {
|
|||||||
this.activeScreenId = mobx.observable.box(ces(sdata.activescreenid));
|
this.activeScreenId = mobx.observable.box(ces(sdata.activescreenid));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWindow(win : WindowDataType, isActive : boolean) {
|
dispose() : void {
|
||||||
mobx.action(() => {
|
|
||||||
for (let i=0; i<this.windows.length; i++) {
|
|
||||||
let foundWin = this.windows[i];
|
|
||||||
if (foundWin.windowId != win.windowid) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (win.remove) {
|
|
||||||
this.windows.splice(i, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foundWin.updateWindow(win, isActive);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let newWindow = new Window(win);
|
|
||||||
newWindow.updateWindow(win, isActive);
|
|
||||||
this.windows.push(newWindow);
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getWindowById(windowId : string) : Window {
|
// session updates only contain screens (no windows)
|
||||||
if (windowId == null) {
|
mergeData(sdata : SessionDataType) {
|
||||||
return null;
|
if (sdata.sessionid != this.sessionId) {
|
||||||
|
throw new Error(sprintf("cannot merge session data, sessionids don't match sid=%s, data-sid=%s", this.sessionId, sdata.sessionid));
|
||||||
}
|
}
|
||||||
for (let i=0; i<this.windows.length; i++) {
|
mobx.action(() => {
|
||||||
if (this.windows[i].windowId == windowId) {
|
if (!isBlank(sdata.name)) {
|
||||||
return this.windows[i];
|
this.name.set(sdata.name);
|
||||||
}
|
}
|
||||||
}
|
if (sdata.sessionidx > 0) {
|
||||||
return null;
|
this.sessionIdx.set(sdata.sessionidx);
|
||||||
|
}
|
||||||
|
if (sdata.notifynum >= 0) {
|
||||||
|
this.notifyNum.set(sdata.notifynum);
|
||||||
|
}
|
||||||
|
genMergeData(this.screens, sdata.screens, (s : Screen) => s.screenId, (s : ScreenDataType) => s.screenid, (data : ScreenDataType) => new Screen(data), (s : Screen) => s.screenIdx.get());
|
||||||
|
if (!isBlank(sdata.activescreenid)) {
|
||||||
|
let screen = this.getScreenById(sdata.activescreenid);
|
||||||
|
if (screen == null) {
|
||||||
|
console.log(sprintf("got session update, activescreenid=%s, screen not found", sdata.activescreenid));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.activeScreenId.set(sdata.activescreenid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveScreen() : Screen {
|
getActiveScreen() : Screen {
|
||||||
@ -492,13 +465,6 @@ class Session {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
|
|
||||||
let win = this.getWindowById(line.windowid);
|
|
||||||
if (win != null) {
|
|
||||||
win.addLineCmd(line, cmd, interactive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Model {
|
class Model {
|
||||||
@ -509,6 +475,7 @@ class Model {
|
|||||||
ws : WSControl;
|
ws : WSControl;
|
||||||
remotes : OArr<RemoteType> = mobx.observable.array([], {deep: false});
|
remotes : OArr<RemoteType> = mobx.observable.array([], {deep: false});
|
||||||
remotesLoaded : OV<boolean> = mobx.observable.box(false);
|
remotesLoaded : OV<boolean> = mobx.observable.box(false);
|
||||||
|
windows : OMap<string, Window> = mobx.observable.map({}, {deep: false});
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.clientId = getApi().getId();
|
this.clientId = getApi().getId();
|
||||||
@ -516,16 +483,29 @@ class Model {
|
|||||||
this.loadSessionList();
|
this.loadSessionList();
|
||||||
this.ws = new WSControl(this.clientId, this.onWSMessage.bind(this))
|
this.ws = new WSControl(this.clientId, this.onWSMessage.bind(this))
|
||||||
this.ws.reconnect();
|
this.ws.reconnect();
|
||||||
getApi().onCmdT(this.onCmdT.bind(this));
|
getApi().onTCmd(this.onTCmd.bind(this));
|
||||||
getApi().onSwitchScreen(this.onSwitchScreen.bind(this));
|
getApi().onICmd(this.onICmd.bind(this));
|
||||||
|
getApi().onBracketCmd(this.onBracketCmd.bind(this));
|
||||||
|
getApi().onDigitCmd(this.onDigitCmd.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onCmdT() {
|
onTCmd(mods : KeyModsType) {
|
||||||
console.log("got cmd-t");
|
console.log("got cmd-t", mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwitchScreen(e : any, arg : {relative? : number, absolute? : number}) {
|
onICmd(mods : KeyModsType) {
|
||||||
console.log("switch screen", arg);
|
let elem = document.getElementById("main-cmd-input");
|
||||||
|
if (elem != null) {
|
||||||
|
elem.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBracketCmd(e : any, arg : {relative: number}, mods : KeyModsType) {
|
||||||
|
console.log("switch screen (bracket)", arg, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDigitCmd(e : any, arg : {digit: number}, mods : KeyModsType) {
|
||||||
|
console.log("switch screen (digit)", arg, mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected() : boolean {
|
isConnected() : boolean {
|
||||||
@ -542,9 +522,26 @@ class Model {
|
|||||||
activeScreen.updatePtyData(ptyMsg);
|
activeScreen.updatePtyData(ptyMsg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ("sessions" in message) {
|
||||||
|
let sessionUpdateMsg : SessionUpdateType = message;
|
||||||
|
console.log("update-sessions", sessionUpdateMsg.sessions);
|
||||||
|
mobx.action(() => {
|
||||||
|
let oldActiveScreen = this.getActiveScreen();
|
||||||
|
genMergeData(this.sessionList, sessionUpdateMsg.sessions, (s : Session) => s.sessionId, (sdata : SessionDataType) => sdata.sessionid, (sdata : SessionDataType) => new Session(sdata), (s : Session) => s.sessionIdx.get());
|
||||||
|
let newActiveScreen = this.getActiveScreen();
|
||||||
|
if (oldActiveScreen != newActiveScreen) {
|
||||||
|
this.activateScreen(newActiveScreen.sessionId, newActiveScreen.screenId, oldActiveScreen);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
}
|
||||||
console.log("ws-message", message);
|
console.log("ws-message", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeSession(sessionId : string) {
|
||||||
|
console.log("removeSession not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
getActiveSession() : Session {
|
getActiveSession() : Session {
|
||||||
return this.getSessionById(this.activeSessionId.get());
|
return this.getSessionById(this.activeSessionId.get());
|
||||||
}
|
}
|
||||||
@ -561,12 +558,39 @@ class Model {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deactivateWindows() {
|
||||||
|
mobx.action(() => {
|
||||||
|
this.windows.clear();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
getWindowById(sessionId : string, windowId : string) : Window {
|
getWindowById(sessionId : string, windowId : string) : Window {
|
||||||
let session = this.getSessionById(sessionId);
|
return this.windows.get(sessionId + "/" + windowId);
|
||||||
if (session == null) {
|
}
|
||||||
return null;
|
|
||||||
}
|
updateWindow(win : WindowDataType, load : boolean) {
|
||||||
return session.getWindowById(windowId);
|
mobx.action(() => {
|
||||||
|
let winKey = win.sessionid + "/" + win.windowid;
|
||||||
|
if (win.remove) {
|
||||||
|
this.windows.delete(winKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let existingWin = this.windows.get(winKey);
|
||||||
|
if (existingWin == null) {
|
||||||
|
if (!load) {
|
||||||
|
console.log("cannot update window that does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newWindow = new Window(win.sessionid, win.windowid);
|
||||||
|
this.windows.set(winKey, newWindow);
|
||||||
|
newWindow.updateWindow(win, load);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
existingWin.updateWindow(win, load);
|
||||||
|
existingWin.loaded.set(true);
|
||||||
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
getScreenById(sessionId : string, screenId : string) : Screen {
|
getScreenById(sessionId : string, screenId : string) : Screen {
|
||||||
@ -582,7 +606,8 @@ class Model {
|
|||||||
if (screen == null) {
|
if (screen == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return screen.getActiveWindow();
|
let activeWindowId = screen.activeWindowId.get();
|
||||||
|
return this.windows.get(screen.sessionId + "/" + activeWindowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveScreen() : Screen {
|
getActiveScreen() : Screen {
|
||||||
@ -594,10 +619,11 @@ class Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
|
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
|
||||||
let session = this.getSessionById(line.sessionid);
|
let win = this.getWindowById(line.sessionid, line.windowid);
|
||||||
if (session != null) {
|
if (win == null) {
|
||||||
session.addLineCmd(line, cmd, interactive);
|
return;
|
||||||
}
|
}
|
||||||
|
win.addLineCmd(line, cmd, interactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitCommand(cmdStr : string) {
|
submitCommand(cmdStr : string) {
|
||||||
@ -607,7 +633,20 @@ class Model {
|
|||||||
this.errorHandler("cannot submit command, no active window", null)
|
this.errorHandler("cannot submit command, no active window", null)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let data : FeCmdPacketType = {type: "fecmd", sessionid: win.sessionId, windowid: win.windowId, cmdstr: cmdStr, userid: GlobalUser, remotestate: null};
|
let screen = this.getActiveScreen();
|
||||||
|
if (screen == null) {
|
||||||
|
this.errorHandler("cannot submit command, no active screen", null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data : FeCmdPacketType = {
|
||||||
|
type: "fecmd",
|
||||||
|
sessionid: win.sessionId,
|
||||||
|
screenid: screen.screenId,
|
||||||
|
windowid: win.windowId,
|
||||||
|
cmdstr: cmdStr,
|
||||||
|
userid: GlobalUser,
|
||||||
|
remotestate: null
|
||||||
|
};
|
||||||
let rstate = win.getCurRemoteInstance();
|
let rstate = win.getCurRemoteInstance();
|
||||||
if (rstate == null) {
|
if (rstate == null) {
|
||||||
this.errorHandler("cannot submit command, no remote state found", null);
|
this.errorHandler("cannot submit command, no remote state found", null);
|
||||||
@ -626,15 +665,6 @@ class Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWindow(win : WindowDataType) {
|
|
||||||
let session = this.getSessionById(win.sessionid);
|
|
||||||
if (session == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let isActive = (win.sessionid == this.activeSessionId.get());
|
|
||||||
session.updateWindow(win, isActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSessionList() {
|
loadSessionList() {
|
||||||
let url = new URL("http://localhost:8080/api/get-all-sessions");
|
let url = new URL("http://localhost:8080/api/get-all-sessions");
|
||||||
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||||
@ -663,15 +693,15 @@ class Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
activateScreen(sessionId : string, screenId : string) {
|
activateScreen(sessionId : string, screenId : string, oldActiveScreen? : Screen) {
|
||||||
let oldActiveScreen = this.getActiveScreen();
|
if (!oldActiveScreen) {
|
||||||
|
oldActiveScreen = this.getActiveScreen();
|
||||||
|
}
|
||||||
if (oldActiveScreen && oldActiveScreen.sessionId == sessionId && oldActiveScreen.screenId == screenId) {
|
if (oldActiveScreen && oldActiveScreen.sessionId == sessionId && oldActiveScreen.screenId == screenId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mobx.action(() => {
|
mobx.action(() => {
|
||||||
if (oldActiveScreen != null) {
|
this.deactivateWindows();
|
||||||
oldActiveScreen.deactivate();
|
|
||||||
}
|
|
||||||
this.activeSessionId.set(sessionId);
|
this.activeSessionId.set(sessionId);
|
||||||
this.getActiveSession().activeScreenId.set(screenId);
|
this.getActiveSession().activeScreenId.set(screenId);
|
||||||
})();
|
})();
|
||||||
@ -680,7 +710,6 @@ class Model {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.ws.pushMessage({type: "watchscreen", sessionid: curScreen.sessionId, screenid: curScreen.screenId});
|
this.ws.pushMessage({type: "watchscreen", sessionid: curScreen.sessionId, screenid: curScreen.screenId});
|
||||||
curScreen.loadWindows(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewScreen(session : Session, name : string, activate : boolean) {
|
createNewScreen(session : Session, name : string, activate : boolean) {
|
||||||
@ -700,7 +729,9 @@ class Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadWindow(sessionId : string, windowId : string, force : boolean) {
|
loadWindow(sessionId : string, windowId : string) : Window {
|
||||||
|
let newWin = new Window(sessionId, windowId);
|
||||||
|
this.windows.set(sessionId + "/" + windowId, newWin);
|
||||||
let usp = new URLSearchParams({sessionid: sessionId, windowid: windowId});
|
let usp = new URLSearchParams({sessionid: sessionId, windowid: windowId});
|
||||||
let url = new URL(sprintf("http://localhost:8080/api/get-window?") + usp.toString());
|
let url = new URL(sprintf("http://localhost:8080/api/get-window?") + usp.toString());
|
||||||
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||||
@ -708,11 +739,12 @@ class Model {
|
|||||||
console.log("null window returned from get-window");
|
console.log("null window returned from get-window");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.updateWindow(data.data);
|
this.updateWindow(data.data, true);
|
||||||
return;
|
return;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.errorHandler(sprintf("getting window=%s", windowId), err);
|
this.errorHandler(sprintf("getting window=%s", windowId), err);
|
||||||
});
|
});
|
||||||
|
return newWin;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRemotes() {
|
loadRemotes() {
|
||||||
@ -750,7 +782,7 @@ class Model {
|
|||||||
if (session == null) {
|
if (session == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let window = session.getWindowById(line.windowid);
|
let window = this.getWindowById(line.sessionid, line.windowid);
|
||||||
if (window == null) {
|
if (window == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ let {contextBridge, ipcRenderer} = require("electron");
|
|||||||
|
|
||||||
contextBridge.exposeInMainWorld("api", {
|
contextBridge.exposeInMainWorld("api", {
|
||||||
getId: () => ipcRenderer.sendSync("get-id"),
|
getId: () => ipcRenderer.sendSync("get-id"),
|
||||||
onCmdT: (callback) => ipcRenderer.on("cmd-t", callback),
|
onTCmd: (callback) => ipcRenderer.on("t-cmd", callback),
|
||||||
onSwitchScreen: (callback) => ipcRenderer.on("switch-screen", callback),
|
onICmd: (callback) => ipcRenderer.on("i-cmd", callback),
|
||||||
|
onBracketCmd: (callback) => ipcRenderer.on("bracket-cmd", callback),
|
||||||
|
onDigitCmd: (callback) => ipcRenderer.on("digit-cmd", callback),
|
||||||
});
|
});
|
||||||
|
16
src/sh2.less
16
src/sh2.less
@ -33,7 +33,7 @@ html, body, #main {
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
background-color: darken(#4e9a06, 15%);
|
background-color: darken(#4e9a06, 10%);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -43,16 +43,28 @@ html, body, #main {
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #4e9a06;
|
background-color: #4e9a06;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
background-color: #4e9a06;
|
background-color: lighten(#4e9a06, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
|
|
||||||
|
.tab-index {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
font-weight: normal;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .screen-tab .tab-index {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-tab.new-screen {
|
.screen-tab.new-screen {
|
||||||
|
33
src/types.ts
33
src/types.ts
@ -3,12 +3,14 @@ import * as mobx from "mobx";
|
|||||||
type SessionDataType = {
|
type SessionDataType = {
|
||||||
sessionid : string,
|
sessionid : string,
|
||||||
name : string,
|
name : string,
|
||||||
|
notifynum : number,
|
||||||
activescreenid : string,
|
activescreenid : string,
|
||||||
windows : WindowDataType[],
|
sessionidx : number,
|
||||||
screens : ScreenDataType[],
|
screens : ScreenDataType[],
|
||||||
screenwindows : ScreenWindowType[],
|
|
||||||
cmds : CmdDataType[],
|
// for updates
|
||||||
remove : boolean,
|
remove? : boolean,
|
||||||
|
full? : boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type LineType = {
|
type LineType = {
|
||||||
@ -34,6 +36,10 @@ type ScreenDataType = {
|
|||||||
name : string,
|
name : string,
|
||||||
windows : ScreenWindowType[],
|
windows : ScreenWindowType[],
|
||||||
screenopts : ScreenOptsType,
|
screenopts : ScreenOptsType,
|
||||||
|
|
||||||
|
// for updates
|
||||||
|
remove? : boolean,
|
||||||
|
full? : boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type LayoutType = {
|
type LayoutType = {
|
||||||
@ -55,6 +61,9 @@ type ScreenWindowType = {
|
|||||||
windowid : string,
|
windowid : string,
|
||||||
name : string,
|
name : string,
|
||||||
layout : LayoutType,
|
layout : LayoutType,
|
||||||
|
|
||||||
|
// for updates
|
||||||
|
remove? : boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type RemoteType = {
|
type RemoteType = {
|
||||||
@ -88,7 +97,9 @@ type WindowDataType = {
|
|||||||
history : HistoryItem[],
|
history : HistoryItem[],
|
||||||
cmds : CmdDataType[],
|
cmds : CmdDataType[],
|
||||||
remotes : RemoteInstanceType[],
|
remotes : RemoteInstanceType[],
|
||||||
remove : boolean,
|
|
||||||
|
// for updates
|
||||||
|
remove? : boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
type HistoryItem = {
|
type HistoryItem = {
|
||||||
@ -104,6 +115,7 @@ type CmdRemoteStateType = {
|
|||||||
type FeCmdPacketType = {
|
type FeCmdPacketType = {
|
||||||
type : string,
|
type : string,
|
||||||
sessionid : string,
|
sessionid : string,
|
||||||
|
screenid : string,
|
||||||
windowid : string,
|
windowid : string,
|
||||||
userid : string,
|
userid : string,
|
||||||
cmdstr : string,
|
cmdstr : string,
|
||||||
@ -161,4 +173,13 @@ type PtyDataUpdateType = {
|
|||||||
ptydatalen : number,
|
ptydatalen : number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType};
|
type SessionUpdateType = {
|
||||||
|
sessions: SessionDataType[],
|
||||||
|
};
|
||||||
|
|
||||||
|
type WindowUpdateType = {
|
||||||
|
window: WindowDataType,
|
||||||
|
remove: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, SessionUpdateType, WindowUpdateType};
|
||||||
|
61
src/util.ts
61
src/util.ts
@ -1,3 +1,4 @@
|
|||||||
|
import * as mobx from "mobx";
|
||||||
import {sprintf} from "sprintf-js";
|
import {sprintf} from "sprintf-js";
|
||||||
|
|
||||||
function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
|
function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
|
||||||
@ -42,4 +43,62 @@ function base64ToArray(b64 : string) : Uint8Array {
|
|||||||
return rtnArr;
|
return rtnArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {handleJsonFetchResponse, base64ToArray};
|
interface IDataType {
|
||||||
|
remove? : boolean;
|
||||||
|
full? : boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IObjType<DataType> {
|
||||||
|
dispose : () => void;
|
||||||
|
mergeData : (data : DataType) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
function genMergeData<ObjType extends IObjType<DataType>, DataType extends IDataType>(
|
||||||
|
objs : mobx.IObservableArray<ObjType>,
|
||||||
|
dataArr : DataType[],
|
||||||
|
objIdFn : (obj : ObjType) => string,
|
||||||
|
dataIdFn : (data : DataType) => string,
|
||||||
|
ctorFn : (data : DataType) => ObjType,
|
||||||
|
sortIdxFn : (obj : ObjType) => number,
|
||||||
|
) {
|
||||||
|
if (dataArr == null || dataArr.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let objMap : Record<string, ObjType> = {};
|
||||||
|
for (let i=0; i<objs.length; i++) {
|
||||||
|
let obj = objs[i];
|
||||||
|
let id = objIdFn(obj);
|
||||||
|
objMap[id] = obj;
|
||||||
|
}
|
||||||
|
for (let i=0; i<dataArr.length; i++) {
|
||||||
|
let dataItem = dataArr[i];
|
||||||
|
let id = dataIdFn(dataItem);
|
||||||
|
let obj = objMap[id];
|
||||||
|
if (dataItem.remove) {
|
||||||
|
if (obj != null) {
|
||||||
|
obj.dispose();
|
||||||
|
delete objMap[id];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
if (!dataItem.full) {
|
||||||
|
console.log("cannot create object, dataitem is not full", objs, dataItem);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj = ctorFn(dataItem);
|
||||||
|
objMap[id] = obj;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
obj.mergeData(dataItem);
|
||||||
|
}
|
||||||
|
let newObjs = Object.values(objMap);
|
||||||
|
if (sortIdxFn) {
|
||||||
|
newObjs.sort((a, b) => {
|
||||||
|
return sortIdxFn(a) - sortIdxFn(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
objs.replace(newObjs);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {handleJsonFetchResponse, base64ToArray, genMergeData};
|
||||||
|
Loading…
Reference in New Issue
Block a user