diff --git a/src/emain.ts b/src/emain.ts index a53e66e0f..c05cb238a 100644 --- a/src/emain.ts +++ b/src/emain.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as fs from "fs"; let app = electron.app; -app.setAppLogsPath(__dirname, "../logs"); +app.setName("ScriptHaus"); let lock : File; try { @@ -14,7 +14,29 @@ catch (e) { app.exit(0); } -console.log("ACQUIRED LOCK"); +let menuTemplate = [ + { + role: "appMenu", + }, + { + role: "fileMenu", + }, + { + role: "editMenu", + }, + { + role: "viewMenu", + }, + { + role: "windowMenu", + }, + { + role: "help", + }, +]; + +let menu = electron.Menu.buildFromTemplate(menuTemplate); +electron.Menu.setApplicationMenu(menu); let MainWindow = null; @@ -27,12 +49,19 @@ function createWindow() { }, }); win.loadFile("../static/index.html"); + win.webContents.on("before-input-event", (e, input) => { + if (input.type != "keyDown") { + return; + } + if (input.code == "KeyT" && input.meta) { + win.webContents.send("cmt-t"); + } + }); return win; } app.whenReady().then(() => { MainWindow = createWindow(); - MainWindow.webContents.openDevTools(); app.on('activate', () => { if (electron.BrowserWindow.getAllWindows().length === 0) { @@ -45,10 +74,8 @@ app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() }); -electron.ipcMain.on("relaunch", (event) => { - console.log("RELAUNCH!"); - app.relaunch(); - app.exit(0); - console.log("test", event); +electron.ipcMain.on("get-id", (event) => { + return event.processId; }); + diff --git a/src/main.tsx b/src/main.tsx index 790ea10fe..66d2cbd00 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -7,9 +7,9 @@ import dayjs from 'dayjs' import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames" import {TermWrap} from "./term"; -import {getCurrentSession, getLineId, Session, newSession, getAllSessions, getCurrentSessionId} from "./session"; import type {SessionDataType, LineType, CmdDataType, RemoteType} from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; +import {GlobalMode, Cmd, Window} from "./model"; dayjs.extend(localizedFormat) @@ -47,7 +47,7 @@ function getLineDateStr(ts : number) : string { } @mobxReact.observer -class LineText extends React.Component<{line : LineType, session : Session}, {}> { +class LineText extends React.Component<{line : LineType}, {}> { render() { let line = this.props.line; let formattedTime = getLineDateStr(line.ts); @@ -71,13 +71,14 @@ class LineText extends React.Component<{line : LineType, session : Session}, {}> } @mobxReact.observer -class LineCmd extends React.Component<{line : LineType, session : Session, changeSizeCallback? : (term : TermWrap) => void}, {}> { +class LineCmd extends React.Component<{line : LineType}, {}> { constructor(props) { super(props); } componentDidMount() { - let {session, line} = this.props; + let {line} = this.props; + let model = GlobalModel; let termElem = document.getElementById("term-" + getLineId(line)); let termWrap = session.getTermWrapByLine(line); termWrap.changeSizeCallback = this.props.changeSizeCallback; @@ -100,22 +101,6 @@ class LineCmd extends React.Component<{line : LineType, session : Session, chang termWrap.reloadTerminal(true, 500); } - @boundMethod - singleLineCmdText(cmdText : string) { - if (cmdText == null) { - return "(none)"; - } - cmdText = cmdText.trim(); - let nlIdx = cmdText.indexOf("\n"); - if (nlIdx != -1) { - cmdText = cmdText.substr(0, nlIdx) + "..."; - } - if (cmdText.length > 80) { - cmdText = cmdText.substr(0, 77) + "..."; - } - return cmdText; - } - replaceHomePath(path : string, homeDir : string) : string { if (path == homeDir) { return "~"; @@ -126,7 +111,7 @@ class LineCmd extends React.Component<{line : LineType, session : Session, chang return path; } - renderCmdText(cmd : CmdDataType, remote : RemoteType) : any { + renderCmdText(cmd : Cmd, remote : RemoteType) : any { if (cmd == null) { return (
@@ -230,33 +215,15 @@ class Line extends React.Component<{line : LineType, session : Session, changeSi } @mobxReact.observer -class CmdInput extends React.Component<{session : Session, windowid : string}, {}> { +class CmdInput extends React.Component<{windowid : string}, {}> { historyIndex : mobx.IObservableValue = mobx.observable.box(0, {name: "history-index"}); modHistory : mobx.IObservableArray = mobx.observable.array([""], {name: "mod-history"}); - elistener : any; - - componentDidMount() { - this.elistener = this.handleKeyPress.bind(this); - document.addEventListener("keypress", this.elistener); - } - - componentWillUnmount() { - document.removeEventListener("keypress", this.elistener); - } - - handleKeyPress(event : any) { - if (event.code == "KeyI" && event.metaKey) { - let elem = document.getElementById("main-cmd-input"); - if (elem != null) { - elem.focus(); - } - } - } @mobx.action @boundMethod onKeyDown(e : any) { mobx.action(() => { - let {session} = this.props; + let model = GlobalModel; + let win = getActiveWindow(); let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift"); if (e.code == "Enter" && !ctrlMod) { e.preventDefault(); @@ -267,8 +234,8 @@ class CmdInput extends React.Component<{session : Session, windowid : string}, { e.preventDefault(); let hidx = this.historyIndex.get(); hidx += 1; - if (hidx > session.getNumHistoryItems()) { - hidx = session.getNumHistoryItems(); + if (hidx > win.getNumHistoryItems()) { + hidx = win.getNumHistoryItems(); } this.historyIndex.set(hidx); return; @@ -298,12 +265,12 @@ class CmdInput extends React.Component<{session : Session, windowid : string}, { @boundMethod getCurLine() : string { - let {session} = this.props; + let model = GlobalModel; let hidx = this.historyIndex.get(); if (hidx < this.modHistory.length && this.modHistory[hidx] != null) { return this.modHistory[hidx]; } - let hitem = session.getHistoryItem(-hidx); + let hitem = win.getHistoryItem(-hidx); if (hitem == null) { return ""; } @@ -325,12 +292,12 @@ class CmdInput extends React.Component<{session : Session, windowid : string}, { @boundMethod doSubmitCmd() { - let {session, windowid} = this.props; + let {windowid} = this.props; + let model = GlobalModel; let commandStr = this.getCurLine(); let hitem = {cmdtext: commandStr}; - session.addToHistory(hitem); this.clearCurLine(); - session.submitCommand(windowid, commandStr); + model.submitCommand(windowid, commandStr); } render() { @@ -363,7 +330,7 @@ class CmdInput extends React.Component<{session : Session, windowid : string}, { } @mobxReact.observer -class SessionView extends React.Component<{session : Session}, {}> { +class SessionView extends React.Component<{}, {}> { shouldFollow : mobx.IObservableValue = mobx.observable.box(true); @boundMethod @@ -389,12 +356,12 @@ class SessionView extends React.Component<{session : Session}, {}> { } render() { - let session = this.props.session; - let window = session.getActiveWindow(); - if (window == null) { + let model = GlobalModel; + let win = model.getActiveWindow(); + if (win == null) { return
(no active window {session.activeWindowId.get()})
; } - if (session.loading.get() || window.linesLoading.get()) { + if (!win.linesLoaded.get()) { return
(loading)
; } let idx = 0; @@ -402,11 +369,11 @@ class SessionView extends React.Component<{session : Session}, {}> { return (
- - + +
- +
); } @@ -427,15 +394,9 @@ class MainSideBar extends React.Component<{}, {}> { console.log("click session", sessionId); } - handleReload() { - console.log("reload"); - window.api.relaunch(); - } - render() { - let curSessionId = getCurrentSessionId(); - let sessions = getAllSessions(); - let session : SessionDataType = null; + let model = GlobalModel; + let curSessionId = model.curSessionId.get(); return (
@@ -449,10 +410,15 @@ class MainSideBar extends React.Component<{}, {}> { Private Sessions

Shared Sessions @@ -499,9 +465,6 @@ class MainSideBar extends React.Component<{}, {}> {

  • mike@test01.ec2
  • root@app01.ec2
  • -

    - Relaunch -

    @@ -516,7 +479,6 @@ class Main extends React.Component<{}, {}> { } render() { - let session = getCurrentSession(); return (

    @@ -525,7 +487,7 @@ class Main extends React.Component<{}, {}> {

    - +
    ); diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 000000000..4037b4aa3 --- /dev/null +++ b/src/model.ts @@ -0,0 +1,230 @@ +import * as mobx from "mobx"; +import {sprintf} from "sprintf-js"; +import {boundMethod} from "autobind-decorator"; +import {handleJsonFetchResponse} from "./util"; +import {TermWrap} from "./term"; +import {v4 as uuidv4} from "uuid"; +import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType} from "./types"; +import {WSControl} from "./ws"; + +type OV = mobx.IObservableValue; +type OArr = mobx.IObservableArray; + +class Cmd { + cmdId : string; + data : OV; + + terminal : any; + ptyPos : number = 0; + atRowMax : boolean = false; + usedRowsUpdated : () => void = null; + watching : boolean = false; + + constructor(cmd : CmdDataType) { + this.cmdId = cmd.cmdid; + this.data = mobx.observable.box(cmd, {deep: false}); + } + + setCmd(cmd : CmdDataType) { + mobx.action(() => { + this.data.set(cmd); + }); + } + + getSingleLineCmdText() { + let cmdText = this.data.get().cmdstr; + if (cmdText == null) { + return "(none)"; + } + cmdText = cmdText.trim(); + let nlIdx = cmdText.indexOf("\n"); + if (nlIdx != -1) { + cmdText = cmdText.substr(0, nlIdx) + "..."; + } + if (cmdText.length > 80) { + cmdText = cmdText.substr(0, 77) + "..."; + } + return cmdText; + } +}; + +class Window { + windowId : string; + name : OV; + curRemote : OV; + loaded : OV = mobx.observable.box(false); + lines : OArr = mobx.observable.array([]); + linesLoaded : OV = mobx.observable.box(false); + history : any[] = []; + + constructor(wdata : WindowDataType) { + this.windowId = wdata.windowid; + this.name = mobx.observable.box(wdata.name); + this.curRemote = mobx.observable.box(wdata.curremote); + } + + getNumHistoryItems() : number { + return 0; + } + + getHistoryItem() : any { + return null + } +}; + +class Session { + sessionId : string; + name : OV; + curWindowId : OV; + windows : OArr; + notifyNum : OV = mobx.observable.box(0); + + 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 = mobx.observable.box(null); + sessionListLoaded : OV = mobx.observable.box(false); + sessionList : OArr = mobx.observable.array([], {name: "SessionList"}); + cmds : Record = {}; + ws : WSControl; + + constructor() { + this.clientId = uuidv4(); + this.loadSessionList(); + this.ws = new WSControl(this.clientId, this.onMessage.bind(this)) + this.ws.reconnect(); + } + + isConnected() : boolean { + return this.ws.open.get(); + } + + onMessage(message : any) { + } + + getActiveSession() : Session { + let sid = this.curSessionId.get(); + if (sid == null) { + return null; + } + for (let i=0; i handleJsonFetchResponse(url, resp)).then((data) => { + mobx.action(() => { + let sdatalist : SessionDataType[] = data.data || []; + let slist : Session[] = []; + let defaultSessionId = null; + for (let i=0; i { + console.log("error getting session list"); + }); + } + + sendInputPacket(inputPacket : any) { + this.ws.pushMessage(inputPacket); + } +} + +let GlobalModel : Model = null; +if ((window as any).GlobalModal == null) { + (window as any).GlobalModel = new Model(); +} +GlobalModel = (window as any).GlobalModel; + +export {Model, Window, GlobalModel, Cmd}; + + +// GlobalWS.registerAndSendGetCmd(getCmdPacket, (dataPacket) => { +// let realData = atob(dataPacket.ptydata64); +// this.updatePtyData(this.ptyPos, realData, dataPacket.ptydatalen); +// }); + + +/* +reloadTerminal(startTail : boolean, delayMs : number) { + loadPtyOut(this.terminal, this.sessionId, this.cmdId, delayMs, (ptyoutLen) => { + mobx.action(() => { + this.incRenderVersion(); + this.ptyPos = ptyoutLen; + })(); + if (startTail) { + this.startPtyTail(); + } + }); +} + + setCmdStatus(status : string) { + if (this.cmdStatus == status) { + return; + } + this.cmdStatus = status; + if (!this.isRunning() && this.tailReqId) { + this.stopPtyTail(); + } + } +} + + isRunning() : boolean { + return this.cmdStatus == "running" || this.cmdStatus == "detached"; + } +*/ diff --git a/src/preload.js b/src/preload.js index f0f949d11..942150cdf 100644 --- a/src/preload.js +++ b/src/preload.js @@ -3,5 +3,6 @@ let {contextBridge, ipcRenderer} = require("electron"); console.log("RUNNING PRELOAD"); contextBridge.exposeInMainWorld("api", { - relaunch: () => ipcRenderer.send("relaunch"), + getId: () => ipcRenderer.sendSync("get-id"), + onCmdT: (callback) => ipcRenderer.on("cmd-t"), }); diff --git a/src/sh2.ts b/src/sh2.ts index ad0fdc801..c59c4ebfc 100644 --- a/src/sh2.ts +++ b/src/sh2.ts @@ -3,18 +3,14 @@ import {createRoot} from 'react-dom/client'; import {sprintf} from "sprintf-js"; import {Terminal} from 'xterm'; import {Main} from "./main"; -import {GlobalWS} from "./ws"; +import {WSControl} from "./ws"; +import {GlobalModel} from "./model"; import {v4 as uuidv4} from "uuid"; -import {loadSessionList} from "./session"; // @ts-ignore let VERSION = __SHVERSION__; -(window as any).ScriptHausClientId = uuidv4(); - document.addEventListener("DOMContentLoaded", () => { - loadSessionList(true); - GlobalWS.reconnect(); let reactElem = React.createElement(Main, null, null); let elem = document.getElementById("app"); let root = createRoot(elem); diff --git a/src/term.ts b/src/term.ts index cf8062614..063fa10ff 100644 --- a/src/term.ts +++ b/src/term.ts @@ -2,8 +2,8 @@ import * as mobx from "mobx"; import {Terminal} from 'xterm'; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; -import {GlobalWS} from "./ws"; import {v4 as uuidv4} from "uuid"; +import {GlobalModel} from "./model"; function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, delayMs : number, callback?: (number) => void) { term.clear() @@ -49,17 +49,6 @@ class TermWrap { } destroy() { - this.stopPtyTail(); - } - - setCmdStatus(status : string) { - if (this.cmdStatus == status) { - return; - } - this.cmdStatus = status; - if (!this.isRunning() && this.tailReqId) { - this.stopPtyTail(); - } } isRunning() : boolean { @@ -68,49 +57,17 @@ class TermWrap { @boundMethod onKeyHandler(event : any) { + console.log("onkey", event); + if (!this.isRunning()) { + return; + } let inputPacket = { type: "input", ck: this.sessionId + "/" + this.cmdId, inputdata: btoa(event.key), remoteid: this.remoteId, }; - GlobalWS.pushMessage(inputPacket); - } - - stopPtyTail() { - if (this.tailReqId == null) { - return; - } - let untailCmdPacket = { - type: "untailcmd", - reqid: uuidv4(), - ck: this.sessionId + "/" + this.cmdId, - }; - GlobalWS.sendMessage(untailCmdPacket); - GlobalWS.unregisterReq(this.tailReqId); - this.tailReqId = null; - } - - startPtyTail() { - if (this.tailReqId != null) { - return; - } - if (!this.isRunning()) { - return; - } - this.tailReqId = uuidv4(); - let getCmdPacket = { - type: "getcmd", - reqid: this.tailReqId, - ck: this.sessionId + "/" + this.cmdId, - ptypos: this.ptyPos, - tail: true, - ptyonly: true, - }; - GlobalWS.registerAndSendGetCmd(getCmdPacket, (dataPacket) => { - let realData = atob(dataPacket.ptydata64); - this.updatePtyData(this.ptyPos, realData, dataPacket.ptydatalen); - }); + GlobalModel.sendInputPacket(inputPacket); } // datalen is passed because data could be utf-8 and data.length is not the actual *byte* length @@ -189,18 +146,6 @@ class TermWrap { mobx.action(() => this.renderVersion.set(this.renderVersion.get() + 1))(); } - reloadTerminal(startTail : boolean, delayMs : number) { - loadPtyOut(this.terminal, this.sessionId, this.cmdId, delayMs, (ptyoutLen) => { - mobx.action(() => { - this.incRenderVersion(); - this.ptyPos = ptyoutLen; - })(); - if (startTail) { - this.startPtyTail(); - } - }); - } - connectToElem(elem : Element) { this.terminal.open(elem); if (this.isRunning()) { @@ -212,6 +157,9 @@ class TermWrap { }); this.terminal.onKey(this.onKeyHandler); } + else { + this.terminal.onKey(this.onKeyHandler); + } } } diff --git a/src/types.ts b/src/types.ts index 0a9ed3f26..37a42ed2a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -104,6 +104,7 @@ type CmdDataType = { startpk : CmdStartPacketType, donepk : CmdDonePacketType, runout : any[], + usedrows : number, }; export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDonePacketType, CmdDataType}; diff --git a/src/ws.ts b/src/ws.ts index f0d2f4e35..17d46610a 100644 --- a/src/ws.ts +++ b/src/ws.ts @@ -2,17 +2,18 @@ import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; -declare var window : any; - class WSControl { wsConn : any; open : mobx.IObservableValue; opening : boolean = false; reconnectTimes : number = 0; msgQueue : any[] = []; - reqMap : Record void> = {}; + clientId : string; + messageCallback : (any) => void = null; - constructor() { + constructor(clientId : string, messageCallback : (any) => void) { + this.messageCallback = messageCallback; + this.clientId = clientId; this.open = mobx.observable.box(false, {name: "WSOpen"}); setInterval(this.sendPing, 5000); } @@ -22,20 +23,9 @@ class WSControl { this.open.set(val); } - registerAndSendGetCmd(pk : any, callback : (dataPacket : any) => void) { - if (pk.reqid) { - this.reqMap[pk.reqid] = callback; - } - this.pushMessage(pk) - } - - unregisterReq(reqid : string) { - delete this.reqMap[reqid]; - } - reconnect() { if (this.open.get()) { - this.wsConn.close(); + this.wsConn.close(); // this will force a reconnect return; } this.reconnectTimes++; @@ -54,7 +44,7 @@ class WSControl { setTimeout(() => { console.log(sprintf("websocket reconnect(%d)", this.reconnectTimes)); this.opening = true; - this.wsConn = new WebSocket("ws://localhost:8081/ws?clientid=" + window.ScriptHausClientId); + this.wsConn = new WebSocket("ws://localhost:8081/ws?clientid=" + this.clientId); this.wsConn.onopen = this.onopen; this.wsConn.onmessage = this.onmessage; this.wsConn.onerror = this.onerror; @@ -126,16 +116,9 @@ class WSControl { this.reconnectTimes = 0; return; } - if (eventData.type == "cmddata") { - let cb = this.reqMap[eventData.respid]; - if (!cb) { - console.log(sprintf("websocket cmddata req=%s -- no callback", eventData.respid)); - return; - } - cb(eventData); - return; + if (this.messageCallback) { + this.messageCallback(eventData); } - console.log("websocket message", eventData); } @boundMethod @@ -162,10 +145,4 @@ class WSControl { } } -var GlobalWS : WSControl; -if (window.GlobalWS == null) { - GlobalWS = new WSControl(); - window.GlobalWS = GlobalWS; -} - -export {GlobalWS}; +export {WSControl};