cables_dev/cables_electron/src/electron/main.js
import { app, BrowserWindow, dialog, Menu, shell, clipboard, nativeTheme, nativeImage } from "electron";
import path from "path";
import localShortcut from "electron-localshortcut";
import fs from "fs";
import os from "os";
import electronEndpoint from "./electron_endpoint.js";
import electronApi from "./electron_api.js";
import logger from "../utils/logger.js";
import settings from "./electron_settings.js";
import doc from "../utils/doc_util.js";
import projectsUtil from "../utils/projects_util.js";
import filesUtil from "../utils/files_util.js";
import helper from "../utils/helper_util.js";
// this needs to be imported like this to not have to asarUnpack the entire nodejs world - sm,25.07.2024
import Npm from "../../node_modules/npm/lib/npm.js";
import opsUtil from "../utils/ops_util.js";
app.commandLine.appendSwitch("disable-http-cache", "true");
app.commandLine.appendSwitch("force_high_performance_gpu", "true");
app.commandLine.appendSwitch("lang", "EN");
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
app.commandLine.appendSwitch("no-user-gesture-required", "true");
app.commandLine.appendSwitch("disable-hid-blocklist", "true");
app.commandLine.appendSwitch("enable-web-bluetooth");
app.disableDomainBlockingFor3DAPIs();
logger.info("--- starting");
class ElectronApp
{
constructor()
{
this._log = logger;
this.appName = "name" in app ? app.name : app.getName();
this.appIcon = nativeImage.createFromPath("../../resources/cables.png");
this._defaultWindowBounds = { "width": 1920, "height": 1080 };
this.editorWindow = null;
settings.set("uiLoadStart", this._log.loadStart);
this._log.logStartup("started electron");
process.on("uncaughtException", (error) =>
{
this._handleError(this.appName + " encountered an error", error);
});
process.on("unhandledRejection", (error) =>
{
this._handleError(this.appName + " encountered an error", error);
});
app.on("browser-window-created", (event, win) =>
{
if (settings.get(settings.OPEN_DEV_TOOLS_FIELD))
{
win.webContents.once("dom-ready", this._toggleDevTools.bind(this));
}
});
nativeTheme.themeSource = "dark";
}
init()
{
this._createWindow();
this._createMenu();
this._loadNpm();
}
_loadNpm(cb = null)
{
try
{
this._npm = new Npm({
"argv": [
"--no-save",
"--no-package-lock",
"--legacy-peer-deps",
"--no-progress",
"--no-color",
"--yes",
"--no-fund",
"--no-audit"
],
"excludeNpmCwd": true,
});
this._npm.load().then(() =>
{
this._log.info("loaded npm", this._npm.version);
});
}
catch (e)
{
this._log.error("failed to load npm", e);
}
}
async installPackages(targetDir, packageNames, opName = null)
{
if (!targetDir || !packageNames || packageNames.length === 0) return { "stdout": "nothing to install", "packages": [] };
const result = await this._installNpmPackages(packageNames, targetDir, opName);
if (opName) result.opName = opName;
if (fs.existsSync(path.join(targetDir, "package.json"))) fs.rmSync(path.join(targetDir, "package.json"));
if (fs.existsSync(path.join(targetDir, "package-lock.json"))) fs.rmSync(path.join(targetDir, "package-lock.json"));
return result;
}
async addOpPackage(targetDir, opPackageLocation)
{
if (!targetDir || !opPackageLocation) return { "stdout": "nothing to install", "packages": [] };
const dirName = path.join(os.tmpdir(), "cables-oppackage-");
const tmpDir = fs.mkdtempSync(dirName);
const result = await this._installNpmPackages([opPackageLocation], tmpDir);
const nodeModulesDir = path.join(tmpDir, "node_modules");
if (fs.existsSync(nodeModulesDir))
{
const importedDocs = doc.getOpDocsInDir(nodeModulesDir);
Object.keys(importedDocs).forEach((opDocFile) =>
{
const opDoc = importedDocs[opDocFile];
const opName = opDoc.name;
const sourceDir = path.join(nodeModulesDir, path.dirname(opDocFile));
let opTargetDir = path.join(targetDir, opsUtil.getOpTargetDir(opName, true));
fs.cpSync(sourceDir, opTargetDir, { "recursive": true });
result.packages.push(opName);
});
fs.rmSync(tmpDir, { "recursive": true });
}
return result;
}
async _installNpmPackages(packageNames, targetDir, opName = null)
{
this._npm.config.localPrefix = targetDir;
let result = { "stdout": "", "stderr": "", "packages": packageNames, "targetDir": targetDir };
const oldConsole = console.log;
const logToVariable = (level, ...args) =>
{
switch (level)
{
case "standard":
args.forEach((arg) =>
{
result.stdout += arg;
});
break;
case "error":
args.forEach((arg) =>
{
result.error = true;
result.stderr += arg;
});
break;
case "buffer":
case "flush":
default:
}
};
process.on("output", logToVariable);
console.log = (l) => { result.stdout += l; };
this._log.debug("installing", packageNames, "to", targetDir);
try
{
await this._npm.exec("install", packageNames);
}
catch (e)
{
result.exception = String(e);
result.error = true;
result.stderr += e + e.stderr;
if (e.script && e.script.includes("gyp")) result.nativeCompile = true;
}
process.off("output", logToVariable);
console.log = oldConsole;
if (result.exception && result.exception === "Error: command failed")
{
if (result.nativeCompile)
{
if (targetDir.includes(" "))
{
result.stderr = "tried to compile native module <a href=\"https://github.com/nodejs/node-gyp/issues/65\" target=\"_blank\">with a space in the pathname</a>, try moving your op...";
}
else
{
result.stderr = "failed to natively compile using node-gyp";
if (opName)
{
const onClick = "CABLES.CMD.STANDALONE.openOpDir('', '" + opName + "');";
const opDir = opsUtil.getOpSourceDir(opName);
result.stderr += ", try running `npm --prefix ./ install " + packageNames.join(" ") + "` manually <a onclick=\"" + onClick + "\">in the op dir</a>: `" + opDir + "`";
}
}
}
}
return result;
}
_createWindow()
{
let patchFile = null;
const openLast = settings.getUserSetting("openlastproject", false) || this._initialPatchFile;
if (openLast)
{
const projectFile = this._initialPatchFile || settings.getCurrentProjectFile();
if (fs.existsSync(projectFile)) patchFile = projectFile;
this._initialPatchFile = null;
}
const defaultWindowOptions = {
"width": 1920,
"height": 1080,
"backgroundColor": "#222",
"icon": this.appIcon,
"autoHideMenuBar": true,
"webPreferences": {
"defaultEncoding": "utf-8",
"partition": settings.SESSION_PARTITION,
"nodeIntegration": true,
"nodeIntegrationInWorker": true,
"nodeIntegrationInSubFrames": true,
"contextIsolation": false,
"sandbox": false,
"webSecurity": false,
"allowRunningInsecureContent": true,
"plugins": true,
"experimentalFeatures": true,
"v8CacheOptions": "none",
"backgroundThrottling": false,
"autoplayPolicy": "no-user-gesture-required"
}
};
this.editorWindow = new BrowserWindow(defaultWindowOptions);
let windowBounds = this._defaultWindowBounds;
if (settings.getUserSetting("storeWindowBounds", true))
{
const userWindowBounds = settings.get(settings.WINDOW_BOUNDS);
if (userWindowBounds) windowBounds = userWindowBounds;
}
this.editorWindow.setBounds(windowBounds);
this._initCaches(() =>
{
this._registerListeners();
this._registerShortcuts();
this.openPatch(patchFile, false).then(() =>
{
this._log.logStartup("electron loaded");
});
});
}
async pickProjectFileDialog()
{
let title = "select patch";
let properties = ["openFile"];
return this._projectFileDialog(title, properties);
}
async pickFileDialog(filePath, asUrl = false, filter = [])
{
let title = "select file";
let properties = ["openFile"];
return this._fileDialog(title, filePath, asUrl, filter, properties);
}
async saveFileDialog(defaultPath, title = null, properties = [], filters = [])
{
title = title || "select directory";
properties = properties || ["createDirectory"];
return dialog.showSaveDialog(this.editorWindow, {
"title": title,
"defaultPath": defaultPath,
"properties": properties,
"filters": filters
}).then((result) =>
{
if (!result.canceled)
{
return result.filePath;
}
else
{
return null;
}
});
}
async pickDirDialog(defaultPath = null)
{
let title = "select file";
let properties = ["openDirectory", "createDirectory"];
return this._dirDialog(title, properties, defaultPath);
}
async exportProjectFileDialog(exportName)
{
const extensions = [];
extensions.push("zip");
let title = "select directory";
let properties = ["createDirectory"];
return dialog.showSaveDialog(this.editorWindow, {
"title": title,
"defaultPath": exportName,
"properties": properties,
"filters": [{
"name": "cables project",
"extensions": extensions,
}]
}).then((result) =>
{
if (!result.canceled)
{
return result.filePath;
}
else
{
return null;
}
});
}
async saveProjectFileDialog(defaultPath)
{
const extensions = [];
extensions.push(projectsUtil.CABLES_PROJECT_FILE_EXTENSION);
let title = "select patch";
let properties = ["createDirectory"];
return dialog.showSaveDialog(this.editorWindow, {
"title": title,
"properties": properties,
"defaultPath": defaultPath,
"filters": [{
"name": "cables project",
"extensions": extensions,
}]
}).then((result) =>
{
if (!result.canceled)
{
let patchFile = result.filePath;
if (!patchFile.endsWith(projectsUtil.CABLES_PROJECT_FILE_EXTENSION))
{
patchFile += "." + projectsUtil.CABLES_PROJECT_FILE_EXTENSION;
}
const currentProject = settings.getCurrentProject();
if (currentProject)
{
currentProject.name = path.basename(patchFile);
currentProject.summary = currentProject.summary || {};
currentProject.summary.title = currentProject.name;
projectsUtil.writeProjectToFile(patchFile, currentProject);
}
return patchFile;
}
else
{
return null;
}
});
}
async pickOpDirDialog()
{
const title = "select op directory";
const properties = ["openDirectory", "createDirectory"];
return this._dirDialog(title, properties);
}
_createMenu()
{
const isOsX = process.platform === "darwin";
let devToolsAcc = "CmdOrCtrl+Shift+I";
let inspectElementAcc = "CmdOrCtrl+Shift+C";
let consoleAcc = "CmdOrCtrl+Shift+J";
if (isOsX)
{
devToolsAcc = "CmdOrCtrl+Option+I";
inspectElementAcc = "CmdOrCtrl+Option+C";
consoleAcc = "CmdOrCtrl+Option+J";
}
const aboutMenu = [];
aboutMenu.push({
"label": "About Cables",
"click": () => { this._showAbout(); }
});
aboutMenu.push({ "type": "separator" });
if (isOsX)
{
aboutMenu.push({ "role": "services" });
aboutMenu.push({ "type": "separator" });
aboutMenu.push({ "role": "hide", "label": "Hide Cables" });
aboutMenu.push({ "role": "hideOthers" });
aboutMenu.push({ "role": "unhide" });
aboutMenu.push({ "type": "separator" });
}
aboutMenu.push({
"role": "quit",
"label": "Quit",
"accelerator": "CmdOrCtrl+Q",
"click": () => { app.quit(); }
});
const menuTemplate = [
{
"role": "appMenu",
"label": "Cables",
"submenu": aboutMenu
},
{
"label": "File",
"submenu": [
{
"label": "New patch",
"accelerator": "CmdOrCtrl+N",
"click": () =>
{
this.openPatch();
}
},
{
"label": "Open patch",
"accelerator": "CmdOrCtrl+O",
"click": () =>
{
this.pickProjectFileDialog();
}
},
{
"label": "Open Recent",
"role": "recentdocuments",
"submenu": [
{
"label": "Clear Recent",
"role": "clearrecentdocuments"
}
]
}
]
},
{
"label": "Edit",
"submenu": [
{ "role": "undo" }, { "role": "redo" },
{ "type": "separator" },
{ "role": "cut" },
{ "role": "copy" },
{ "role": "paste" },
{ "role": "selectAll" },
]
},
{
"label": "Window",
"submenu": [
{
"role": "minimize",
},
{
"role": "zoom",
"visible": isOsX
},
{ "role": "togglefullscreen" },
{
"label": "Reset Size and Position",
"click": () =>
{
this._resetSizeAndPostion();
}
},
{ "type": "separator" },
{
"label": "Zoom In",
"accelerator": "CmdOrCtrl+Plus",
"click": () =>
{
this._zoomIn();
}
},
{
"label": "Zoom Out",
"accelerator": "CmdOrCtrl+-",
"click": () =>
{
this._zoomOut();
}
},
{
"label": "Reset Zoom",
"click": () =>
{
this._resetZoom();
}
},
{ "type": "separator" },
{
"label": "Developer Tools",
"accelerator": devToolsAcc,
"click": () =>
{
this._toggleDevTools();
}
},
{
"label": "Insepect Elements",
"accelerator": inspectElementAcc,
"click": () =>
{
this._inspectElements();
}
},
{
"label": "JavaScript Console",
"accelerator": consoleAcc,
"click": () =>
{
this._toggleDevTools();
}
},
{ "role": "close", "visible": false }
]
}
];
// prevent osx from showin currently running process as name (e.g. `npm`)
if (process.platform == "darwin") { menuTemplate.unshift({ "label": "" }); }
let menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
}
openFile(patchFile)
{
if (this.editorWindow)
{
this.openPatch(patchFile, true);
}
else
{
// opened by double-clicking and starting the app
this._initialPatchFile = patchFile;
}
}
async openPatch(patchFile, rebuildCache = true)
{
this._unsavedContentLeave = false;
const open = async () =>
{
electronApi.loadProject(patchFile, null, rebuildCache);
this.updateTitle();
await this.editorWindow.loadFile("index.html");
const userZoom = settings.get(settings.WINDOW_ZOOM_FACTOR); // maybe set stored zoom later
this._resetZoom();
if (rebuildCache) doc.rebuildOpCaches(() => { this._log.logStartup("rebuild op caches"); }, ["core", "teams", "extensions"], true);
};
if (this.isDocumentEdited())
{
const leave = this._unsavedContentDialog();
if (leave)
{
await open();
}
}
else
{
await open();
}
}
updateTitle()
{
const buildInfo = settings.getBuildInfo();
let title = "cables";
if (buildInfo && buildInfo.api)
{
if (buildInfo.api.version)
{
title += " - " + buildInfo.api.version;
}
}
const projectFile = settings.getCurrentProjectFile();
if (projectFile)
{
title = title + " - " + projectFile;
}
const project = settings.getCurrentProject();
if (project)
{
this.sendTalkerMessage("updatePatchName", { "name": project.name });
this.sendTalkerMessage("updatePatchSummary", project.summary);
}
this.editorWindow.setTitle(title);
}
_dirDialog(title, properties, defaultPath = null)
{
const options = {
"title": title,
"properties": properties
};
if (defaultPath) options.defaultPath = defaultPath;
return dialog.showOpenDialog(this.editorWindow, options).then((result) =>
{
if (!result.canceled)
{
return result.filePaths[0];
}
else
{
return null;
}
});
}
_fileDialog(title, filePath = null, asUrl = false, extensions = ["*"], properties = null)
{
if (extensions)
{
extensions.forEach((ext, i) =>
{
if (ext.startsWith(".")) extensions[i] = ext.replace(".", "");
});
}
const options = {
"title": title,
"properties": properties,
"filters": [{ "name": "Assets", "extensions": extensions }]
};
if (filePath) options.defaultPath = filePath;
return dialog.showOpenDialog(this.editorWindow, options).then((result) =>
{
if (!result.canceled)
{
if (!asUrl) return result.filePaths[0];
return helper.pathToFileURL(result.filePaths[0]);
}
else
{
return null;
}
});
}
_projectFileDialog(title, properties)
{
const extensions = [];
extensions.push(projectsUtil.CABLES_PROJECT_FILE_EXTENSION);
return dialog.showOpenDialog(this.editorWindow, {
"title": title,
"properties": properties,
"filters": [{
"name": "cables project",
"extensions": extensions,
}]
}).then((result) =>
{
if (!result.canceled)
{
let projectFile = result.filePaths[0];
this.openPatch(projectFile);
return projectFile;
}
else
{
return null;
}
});
}
reload()
{
const projectFile = settings.getCurrentProjectFile();
this.openPatch(projectFile, false).then(() => { this._log.debug("reloaded", projectFile); });
}
setDocumentEdited(edited)
{
this._contentChanged = edited;
}
isDocumentEdited()
{
return this._contentChanged || this.editorWindow.isDocumentEdited();
}
cycleFullscreen()
{
if (this.editorWindow.isFullScreen())
{
this.editorWindow.setMenuBarVisibility(true);
this.editorWindow.setFullScreen(false);
}
else
{
this.editorWindow.setMenuBarVisibility(false);
this.editorWindow.setFullScreen(true);
}
}
sendTalkerMessage(cmd, data)
{
this.editorWindow.webContents.send("talkerMessage", { "cmd": cmd, "data": data });
}
_registerShortcuts()
{
let devToolsAcc = "CmdOrCtrl+Shift+I";
let inspectElementAcc = "CmdOrCtrl+Shift+C";
if (process.platform === "darwin") devToolsAcc = "CmdOrCtrl+Option+I";
// https://github.com/sindresorhus/electron-debug/blob/main/index.js
localShortcut.register(this.editorWindow, inspectElementAcc, this._inspectElements.bind(this));
localShortcut.register(this.editorWindow, devToolsAcc, this._toggleDevTools.bind(this));
localShortcut.register(this.editorWindow, "F12", this._toggleDevTools.bind(this));
localShortcut.register(this.editorWindow, "CommandOrControl+R", this._reloadWindow.bind(this));
localShortcut.register(this.editorWindow, "F5", this._reloadWindow.bind(this));
localShortcut.register(this.editorWindow, "CmdOrCtrl+O", this.pickProjectFileDialog.bind(this));
localShortcut.register(this.editorWindow, "CmdOrCtrl+=", this._zoomIn.bind(this));
localShortcut.register(this.editorWindow, "CmdOrCtrl+Plus", this._zoomIn.bind(this));
localShortcut.register(this.editorWindow, "CmdOrCtrl+-", this._zoomOut.bind(this));
}
_toggleDevTools()
{
if (this.editorWindow.webContents.isDevToolsOpened())
{
this.editorWindow.webContents.closeDevTools();
}
else
{
this.editorWindow.webContents.openDevTools({ "mode": "previous" });
}
}
_inspectElements()
{
const inspect = () =>
{
this.editorWindow.devToolsWebContents.executeJavaScript("DevToolsAPI.enterInspectElementMode()");
};
if (this.editorWindow.webContents.isDevToolsOpened())
{
inspect();
}
else
{
this.editorWindow.webContents.once("devtools-opened", inspect);
this.editorWindow.openDevTools();
}
}
_reloadWindow()
{
this.editorWindow.webContents.reloadIgnoringCache();
}
_registerListeners()
{
app.on("browser-window-created", (e, win) =>
{
win.setMenuBarVisibility(false);
});
this.editorWindow.on("close", () =>
{
if (settings.getUserSetting("storeWindowBounds", true)) settings.set(settings.WINDOW_BOUNDS, this.editorWindow.getBounds());
});
this.editorWindow.webContents.on("will-prevent-unload", (event) =>
{
if (!this._unsavedContentLeave && this.isDocumentEdited())
{
const leave = this._unsavedContentDialog();
if (leave) event.preventDefault();
}
else
{
event.preventDefault();
}
});
this.editorWindow.webContents.setWindowOpenHandler(({ url }) =>
{
if (url && url.startsWith("http"))
{
shell.openExternal(url);
return { "action": "deny" };
}
return { "action": "allow" };
});
this.editorWindow.webContents.on("devtools-opened", (event, win) =>
{
settings.set(settings.OPEN_DEV_TOOLS_FIELD, true);
});
this.editorWindow.webContents.on("devtools-closed", (event, win) =>
{
settings.set(settings.OPEN_DEV_TOOLS_FIELD, false);
});
this.editorWindow.webContents.session.on("will-download", (event, item, webContents) =>
{
if (item)
{
const filename = item.getFilename();
const savePath = path.join(settings.getDownloadPath(), filename);
// Set the save path, making Electron not to prompt a save dialog.
item.setSavePath(savePath);
const fileUrl = helper.pathToFileURL(savePath);
const cablesUrl = fileUrl.replace("file:", "cables:///openDir/");
const link = "<a href=\"" + cablesUrl + "\" download>" + savePath + "</a>";
this.sendTalkerMessage("notify", { "msg": "File saved to " + link });
}
});
}
_zoomIn()
{
let newZoom = this.editorWindow.webContents.getZoomFactor() + 0.2;
this.editorWindow.webContents.setZoomFactor(newZoom);
settings.set(settings.WINDOW_ZOOM_FACTOR, newZoom);
}
_zoomOut()
{
let newZoom = this.editorWindow.webContents.getZoomFactor() - 0.2;
newZoom = Math.round(newZoom * 100) / 100;
if (newZoom > 0)
{
this.editorWindow.webContents.setZoomFactor(newZoom);
settings.set(settings.WINDOW_ZOOM_FACTOR, newZoom);
}
}
_resetZoom()
{
this.editorWindow.webContents.setZoomFactor(1.0);
}
_resetSizeAndPostion()
{
if (this.editorWindow)
{
this.editorWindow.setBounds(this._defaultWindowBounds);
this.editorWindow.center();
}
}
_initCaches(cb)
{
doc.addOpsToLookup([], true);
cb();
}
_handleError(title, error)
{
this._log.error(title, error);
if (app.isReady())
{
const buttons = [
"&Reload",
"&New Patch",
"&Quit",
process.platform === "darwin" ? "Copy Error" : "Copy error",
];
const buttonIndex = dialog.showMessageBoxSync({
"type": "error",
buttons,
"defaultId": 0,
"noLink": true,
"message": title,
"detail": error.stack,
"normalizeAccessKeys": true
});
if (buttonIndex === 0)
{
this.reload();
}
if (buttonIndex === 1)
{
this.openPatch(null);
}
if (buttonIndex === 2)
{
app.quit();
}
if (buttonIndex === 3)
{
clipboard.writeText(title + "\n" + error.stack);
}
}
else
{
dialog.showErrorBox(title, (error.stack));
}
}
_unsavedContentDialog()
{
if (this._unsavedContentLeave) return true;
const choice = dialog.showMessageBoxSync(this.editorWindow, {
"type": "question",
"buttons": ["Leave", "Stay"],
"title": "unsaved content!",
"message": "unsaved content!",
"defaultId": 0,
"cancelId": 1
});
this._unsavedContentLeave = (choice === 0);
return this._unsavedContentLeave;
}
_showAbout()
{
const options = {
"icon": this.appIcon,
"type": "info",
"buttons": [],
"message": "cables standalone",
};
const buildInfo = settings.getBuildInfo();
if (buildInfo)
{
let versionText = "";
if (buildInfo.api.git)
{
if (buildInfo.api.version)
{
versionText += "version: " + buildInfo.api.version + "\n";
}
else
{
versionText += "local build" + "\n\n";
if (buildInfo.api.git)
{
versionText += "branch: " + buildInfo.api.git.branch + "\n";
versionText += "message: " + buildInfo.api.git.message + "\n";
}
}
if (buildInfo.api.git.tag) versionText += "tag: " + buildInfo.api.git.tag + "\n";
}
if (buildInfo.api.platform)
{
versionText += "\nbuilt with:\n";
if (buildInfo.api.platform.node) versionText += "node: " + buildInfo.api.platform.node + "\n";
if (buildInfo.api.platform.npm) versionText += "npm: " + buildInfo.api.platform.npm;
}
if (process.versions)
{
versionText += "\n\nrunning in:\n";
if (process.versions.electron) versionText += "electron: " + process.versions.electron + "\n";
if (process.versions.chrome) versionText += "chrome: " + process.versions.chrome + "\n";
if (process.versions.v8) versionText += "v8: " + process.versions.v8 + "\n";
if (process.versions.node) versionText += "node: " + process.versions.node + "\n";
if (buildInfo.api.platform.npm) versionText += "npm: " + buildInfo.api.platform.npm;
}
options.detail = versionText;
}
dialog.showMessageBox(options);
}
}
Menu.setApplicationMenu(null);
const electronApp = new ElectronApp();
app.on("open-file", (e, p) =>
{
if (p.endsWith("." + projectsUtil.CABLES_PROJECT_FILE_EXTENSION) && fs.existsSync(p))
{
electronApp.openFile(p);
}
});
app.on("window-all-closed", () =>
{
app.quit();
});
app.on("will-quit", (event) =>
{
event.preventDefault();
filesUtil.unregisterChangeListeners().then(() =>
{
process.exit(0);
}).catch((e) =>
{
console.error("error during shutdown", e);
process.exit(1);
});
});
Menu.setApplicationMenu(null);
app.whenReady().then(() =>
{
electronApp.init();
electronApi.init();
electronEndpoint.init();
app.on("activate", () =>
{
if (BrowserWindow.getAllWindows().length === 0) electronApp.init();
});
});
export default electronApp;