diff --git a/src/model.ts b/src/model.ts
index d309ef1ab..95389c1cc 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -1,25 +1,35 @@
import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
import {boundMethod} from "autobind-decorator";
-import {handleJsonFetchResponse, base64ToArray} from "./util";
+import {handleJsonFetchResponse, base64ToArray, genMergeData} from "./util";
import {TermWrap} from "./term";
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";
var GlobalUser = "sawka";
type OV
= mobx.IObservableValue;
type OArr = mobx.IObservableArray;
+type OMap = mobx.ObservableMap;
function isBlank(s : string) {
return (s == null || s == "");
}
+type KeyModsType = {
+ meta? : boolean,
+ ctrl? : boolean,
+ alt? : boolean,
+ shift? : boolean,
+};
+
type ElectronApi = {
getId : () => string,
- onCmdT : (callback : () => void) => void,
- onSwitchScreen : (callback : (event : any, arg : {relative? : number, absolute? : number}) => void) => void,
+ onTCmd : (callback : (mods : KeyModsType) => 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 {
@@ -163,6 +173,7 @@ class Cmd {
class Screen {
sessionId : string;
screenId : string;
+ screenIdx : OV;
opts : OV;
name : OV;
activeWindowId : OV;
@@ -172,6 +183,7 @@ class Screen {
this.sessionId = sdata.sessionid;
this.screenId = sdata.screenid;
this.name = mobx.observable.box(sdata.name);
+ this.screenIdx = mobx.observable.box(sdata.screenidx);
this.opts = mobx.observable.box(sdata.screenopts);
this.activeWindowId = mobx.observable.box(ces(sdata.activewindowid));
let swArr : ScreenWindow[] = [];
@@ -183,12 +195,10 @@ class Screen {
this.windows = mobx.observable.array(swArr, {deep: false})
}
- getActiveWindow() : Window {
- let session = GlobalModel.getSessionById(this.sessionId);
- if (session == null) {
- return null;
- }
- return session.getWindowById(this.activeWindowId.get());
+ dispose() {
+ }
+
+ mergeData(data : ScreenDataType) {
}
updatePtyData(ptyMsg : PtyDataUpdateType) {
@@ -216,34 +226,6 @@ class Screen {
}
return null;
}
-
- deactivate() {
- for (let i=0; i = {};
- let activeWindowId = this.activeWindowId.get();
- if (activeWindowId != null) {
- GlobalModel.loadWindow(this.sessionId, activeWindowId, false);
- loadedMap[activeWindowId] = true;
- }
- for (let i=0; i;
+ curRemote : OV = mobx.observable.box(null);
loaded : OV = mobx.observable.box(false);
+ loadError : OV = mobx.observable.box(null);
lines : OArr = mobx.observable.array([], {deep: false});
- linesLoaded : OV = mobx.observable.box(false);
history : any[] = [];
cmds : Record = {};
remoteInstances : OArr = mobx.observable.array([]);
- constructor(wdata : WindowDataType) {
- this.sessionId = wdata.sessionid;
- this.windowId = wdata.windowid;
- this.curRemote = mobx.observable.box(wdata.curremote);
+ constructor(sessionId : string, windowId : string) {
+ this.sessionId = sessionId;
+ this.windowId = windowId;
}
getNumHistoryItems() : number {
@@ -307,15 +287,14 @@ class Window {
cmd.updatePtyData(ptyMsg);
}
- updateWindow(win : WindowDataType, isActive : boolean) {
+ updateWindow(win : WindowDataType, load : boolean) {
mobx.action(() => {
if (!isBlank(win.curremote)) {
this.curRemote.set(win.curremote);
}
- if (!isActive) {
- return;
+ if (load) {
+ this.loaded.set(true);
}
- this.linesLoaded.set(true);
this.lines.replace(win.lines || []);
this.history = win.history || [];
let cmds = win.cmds || [];
@@ -325,15 +304,16 @@ class Window {
})();
}
- deactivate() {
+ setWindowLoadError(errStr : string) {
mobx.action(() => {
- this.linesLoaded.set(false);
- this.lines.replace([]);
- this.history = [];
- this.cmds = {};
+ this.loaded.set(true);
+ this.loadError.set(errStr);
})();
}
+ dispose() {
+ }
+
getCmd(cmdId : string) {
return this.cmds[cmdId];
}
@@ -369,7 +349,7 @@ class Window {
}
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
- if (!this.linesLoaded.get()) {
+ if (!this.loaded.get()) {
return;
}
mobx.action(() => {
@@ -401,21 +381,15 @@ class Session {
sessionId : string;
name : OV;
activeScreenId : OV;
+ sessionIdx : OV;
screens : OArr;
- windows : OArr;
notifyNum : OV = mobx.observable.box(0);
remoteInstances : OArr = mobx.observable.array([]);
constructor(sdata : SessionDataType) {
this.sessionId = sdata.sessionid;
this.name = mobx.observable.box(sdata.name);
- let winData = sdata.windows || [];
- let wins : Window[] = [];
- for (let i=0; i {
- for (let i=0; i {
+ if (!isBlank(sdata.name)) {
+ this.name.set(sdata.name);
}
- }
- return null;
+ if (sdata.sessionidx > 0) {
+ 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 {
@@ -492,13 +465,6 @@ class Session {
}
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 {
@@ -509,6 +475,7 @@ class Model {
ws : WSControl;
remotes : OArr = mobx.observable.array([], {deep: false});
remotesLoaded : OV = mobx.observable.box(false);
+ windows : OMap = mobx.observable.map({}, {deep: false});
constructor() {
this.clientId = getApi().getId();
@@ -516,16 +483,29 @@ class Model {
this.loadSessionList();
this.ws = new WSControl(this.clientId, this.onWSMessage.bind(this))
this.ws.reconnect();
- getApi().onCmdT(this.onCmdT.bind(this));
- getApi().onSwitchScreen(this.onSwitchScreen.bind(this));
+ getApi().onTCmd(this.onTCmd.bind(this));
+ getApi().onICmd(this.onICmd.bind(this));
+ getApi().onBracketCmd(this.onBracketCmd.bind(this));
+ getApi().onDigitCmd(this.onDigitCmd.bind(this));
}
- onCmdT() {
- console.log("got cmd-t");
+ onTCmd(mods : KeyModsType) {
+ console.log("got cmd-t", mods);
}
- onSwitchScreen(e : any, arg : {relative? : number, absolute? : number}) {
- console.log("switch screen", arg);
+ onICmd(mods : KeyModsType) {
+ 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 {
@@ -542,9 +522,26 @@ class Model {
activeScreen.updatePtyData(ptyMsg);
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);
}
+ removeSession(sessionId : string) {
+ console.log("removeSession not implemented");
+ }
+
getActiveSession() : Session {
return this.getSessionById(this.activeSessionId.get());
}
@@ -561,12 +558,39 @@ class Model {
return null;
}
+ deactivateWindows() {
+ mobx.action(() => {
+ this.windows.clear();
+ })();
+ }
+
getWindowById(sessionId : string, windowId : string) : Window {
- let session = this.getSessionById(sessionId);
- if (session == null) {
- return null;
- }
- return session.getWindowById(windowId);
+ return this.windows.get(sessionId + "/" + windowId);
+ }
+
+ updateWindow(win : WindowDataType, load : boolean) {
+ 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 {
@@ -582,7 +606,8 @@ class Model {
if (screen == null) {
return null;
}
- return screen.getActiveWindow();
+ let activeWindowId = screen.activeWindowId.get();
+ return this.windows.get(screen.sessionId + "/" + activeWindowId);
}
getActiveScreen() : Screen {
@@ -594,10 +619,11 @@ class Model {
}
addLineCmd(line : LineType, cmd : CmdDataType, interactive : boolean) {
- let session = this.getSessionById(line.sessionid);
- if (session != null) {
- session.addLineCmd(line, cmd, interactive);
+ let win = this.getWindowById(line.sessionid, line.windowid);
+ if (win == null) {
+ return;
}
+ win.addLineCmd(line, cmd, interactive);
}
submitCommand(cmdStr : string) {
@@ -607,7 +633,20 @@ class Model {
this.errorHandler("cannot submit command, no active window", null)
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();
if (rstate == 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() {
let url = new URL("http://localhost:8080/api/get-all-sessions");
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
@@ -663,15 +693,15 @@ class Model {
});
}
- activateScreen(sessionId : string, screenId : string) {
- let oldActiveScreen = this.getActiveScreen();
+ activateScreen(sessionId : string, screenId : string, oldActiveScreen? : Screen) {
+ if (!oldActiveScreen) {
+ oldActiveScreen = this.getActiveScreen();
+ }
if (oldActiveScreen && oldActiveScreen.sessionId == sessionId && oldActiveScreen.screenId == screenId) {
return;
}
mobx.action(() => {
- if (oldActiveScreen != null) {
- oldActiveScreen.deactivate();
- }
+ this.deactivateWindows();
this.activeSessionId.set(sessionId);
this.getActiveSession().activeScreenId.set(screenId);
})();
@@ -680,7 +710,6 @@ class Model {
return;
}
this.ws.pushMessage({type: "watchscreen", sessionid: curScreen.sessionId, screenid: curScreen.screenId});
- curScreen.loadWindows(false);
}
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 url = new URL(sprintf("http://localhost:8080/api/get-window?") + usp.toString());
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
@@ -708,11 +739,12 @@ class Model {
console.log("null window returned from get-window");
return;
}
- this.updateWindow(data.data);
+ this.updateWindow(data.data, true);
return;
}).catch((err) => {
this.errorHandler(sprintf("getting window=%s", windowId), err);
});
+ return newWin;
}
loadRemotes() {
@@ -750,7 +782,7 @@ class Model {
if (session == null) {
return null;
}
- let window = session.getWindowById(line.windowid);
+ let window = this.getWindowById(line.sessionid, line.windowid);
if (window == null) {
return null;
}
diff --git a/src/preload.js b/src/preload.js
index b8efa84b6..cabd11790 100644
--- a/src/preload.js
+++ b/src/preload.js
@@ -2,6 +2,8 @@ let {contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld("api", {
getId: () => ipcRenderer.sendSync("get-id"),
- onCmdT: (callback) => ipcRenderer.on("cmd-t", callback),
- onSwitchScreen: (callback) => ipcRenderer.on("switch-screen", callback),
+ onTCmd: (callback) => ipcRenderer.on("t-cmd", callback),
+ onICmd: (callback) => ipcRenderer.on("i-cmd", callback),
+ onBracketCmd: (callback) => ipcRenderer.on("bracket-cmd", callback),
+ onDigitCmd: (callback) => ipcRenderer.on("digit-cmd", callback),
});
diff --git a/src/sh2.less b/src/sh2.less
index 7fb767af5..51e5c5706 100644
--- a/src/sh2.less
+++ b/src/sh2.less
@@ -33,7 +33,7 @@ html, body, #main {
min-width: 80px;
width: 150px;
flex-shrink: 1;
- background-color: darken(#4e9a06, 15%);
+ background-color: darken(#4e9a06, 10%);
display: flex;
justify-content: center;
align-items: center;
@@ -43,16 +43,28 @@ html, body, #main {
padding-left: 10px;
padding-right: 10px;
cursor: pointer;
+ position: relative;
&:hover {
background-color: #4e9a06;
}
&.is-active {
- background-color: #4e9a06;
+ background-color: lighten(#4e9a06, 10%);
}
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 {
diff --git a/src/types.ts b/src/types.ts
index 567c28c72..381885ef2 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,12 +3,14 @@ import * as mobx from "mobx";
type SessionDataType = {
sessionid : string,
name : string,
+ notifynum : number,
activescreenid : string,
- windows : WindowDataType[],
+ sessionidx : number,
screens : ScreenDataType[],
- screenwindows : ScreenWindowType[],
- cmds : CmdDataType[],
- remove : boolean,
+
+ // for updates
+ remove? : boolean,
+ full? : boolean,
};
type LineType = {
@@ -34,6 +36,10 @@ type ScreenDataType = {
name : string,
windows : ScreenWindowType[],
screenopts : ScreenOptsType,
+
+ // for updates
+ remove? : boolean,
+ full? : boolean,
};
type LayoutType = {
@@ -55,6 +61,9 @@ type ScreenWindowType = {
windowid : string,
name : string,
layout : LayoutType,
+
+ // for updates
+ remove? : boolean,
};
type RemoteType = {
@@ -88,7 +97,9 @@ type WindowDataType = {
history : HistoryItem[],
cmds : CmdDataType[],
remotes : RemoteInstanceType[],
- remove : boolean,
+
+ // for updates
+ remove? : boolean,
};
type HistoryItem = {
@@ -104,6 +115,7 @@ type CmdRemoteStateType = {
type FeCmdPacketType = {
type : string,
sessionid : string,
+ screenid : string,
windowid : string,
userid : string,
cmdstr : string,
@@ -161,4 +173,13 @@ type PtyDataUpdateType = {
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};
diff --git a/src/util.ts b/src/util.ts
index c43f0b817..bfb7306b8 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,3 +1,4 @@
+import * as mobx from "mobx";
import {sprintf} from "sprintf-js";
function fetchJsonData(resp : any, ctErr : boolean) : Promise {
@@ -42,4 +43,62 @@ function base64ToArray(b64 : string) : Uint8Array {
return rtnArr;
}
-export {handleJsonFetchResponse, base64ToArray};
+interface IDataType {
+ remove? : boolean;
+ full? : boolean;
+}
+
+interface IObjType {
+ dispose : () => void;
+ mergeData : (data : DataType) => void,
+}
+
+function genMergeData, DataType extends IDataType>(
+ objs : mobx.IObservableArray,
+ 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 = {};
+ for (let i=0; i {
+ return sortIdxFn(a) - sortIdxFn(b);
+ });
+ }
+ objs.replace(newObjs);
+}
+
+export {handleJsonFetchResponse, base64ToArray, genMergeData};