checkpoint on updates. can switch screens using commands

This commit is contained in:
sawka 2022-07-14 18:41:49 -07:00
parent 879cb03da0
commit 8cdf514bb9
7 changed files with 306 additions and 154 deletions

View File

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

View File

@ -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">&#x2318;{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}>

View File

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

View File

@ -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),
}); });

View File

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

View File

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

View File

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