diff --git a/electron/main.ts b/electron/main.ts index 9b8d42a92..e02596559 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,30 +1,41 @@ -import { - app, - BrowserWindow, - screen as electronScreen, - ipcMain, - dialog, - shell, -} from "electron"; +import { app, BrowserWindow, ipcMain, dialog, shell } from "electron"; import { readdirSync } from "fs"; import { resolve, join, extname } from "path"; import { rmdir, unlink, createWriteStream } from "fs"; -import isDev = require("electron-is-dev"); import { init } from "./core/plugin-manager/pluginMgr"; -const { autoUpdater } = require("electron-updater"); -const Store = require("electron-store"); +import { setupMenu } from "./utils/menu"; + +import isDev = require("electron-is-dev"); // @ts-ignore import request = require("request"); // @ts-ignore import progress = require("request-progress"); +const { autoUpdater } = require("electron-updater"); +const Store = require("electron-store"); let mainWindow: BrowserWindow | undefined = undefined; -const store = new Store(); -autoUpdater.autoDownload = false; -autoUpdater.autoInstallOnAppQuit = true; +app + .whenReady() + .then(migratePlugins) + .then(setupPlugins) + .then(setupMenu) + .then(handleIPCs) + .then(handleAppUpdates) + .then(createMainWindow) + .then(() => { + app.on("activate", () => { + if (!BrowserWindow.getAllWindows().length) { + createMainWindow(); + } + }); + }); -const createMainWindow = () => { +app.on("window-all-closed", () => { + app.quit(); +}); + +function createMainWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 800, @@ -37,6 +48,61 @@ const createMainWindow = () => { }, }); + const startURL = isDev + ? "http://localhost:3000" + : `file://${join(__dirname, "../renderer/index.html")}`; + + mainWindow.loadURL(startURL); + + mainWindow.once("ready-to-show", () => mainWindow?.show()); + mainWindow.on("closed", () => { + if (process.platform !== "darwin") app.quit(); + }); + + if (isDev) mainWindow.webContents.openDevTools(); +} + +function handleAppUpdates() { + /*New Update Available*/ + autoUpdater.on("update-available", async (_info: any) => { + const action = await dialog.showMessageBox({ + message: `Update available. Do you want to download the latest update?`, + buttons: ["Download", "Later"], + }); + if (action.response === 0) await autoUpdater.downloadUpdate(); + }); + + /*App Update Completion Message*/ + autoUpdater.on("update-downloaded", async (_info: any) => { + mainWindow?.webContents.send("APP_UPDATE_COMPLETE", {}); + const action = await dialog.showMessageBox({ + message: `Update downloaded. Please restart the application to apply the updates.`, + buttons: ["Restart", "Later"], + }); + if (action.response === 0) { + autoUpdater.quitAndInstall(); + } + }); + + /*App Update Error */ + autoUpdater.on("error", (info: any) => { + dialog.showMessageBox({ message: info.message }); + mainWindow?.webContents.send("APP_UPDATE_ERROR", {}); + }); + + /*App Update Progress */ + autoUpdater.on("download-progress", (progress: any) => { + console.log("app update progress: ", progress.percent); + mainWindow?.webContents.send("APP_UPDATE_PROGRESS", { + percent: progress.percent, + }); + }); + autoUpdater.autoDownload = false; + autoUpdater.autoInstallOnAppQuit = true; + autoUpdater.checkForUpdates(); +} + +function handleIPCs() { ipcMain.handle( "invokePluginFunc", async (_event, modulePath, method, ...args) => { @@ -56,152 +122,81 @@ const createMainWindow = () => { .catch((err) => console.log(err)); } ); - - const startURL = isDev - ? "http://localhost:3000" - : `file://${join(__dirname, "../renderer/index.html")}`; - - mainWindow.loadURL(startURL); - - mainWindow.once("ready-to-show", () => mainWindow?.show()); - mainWindow.on("closed", () => { - if (process.platform !== "darwin") app.quit(); + ipcMain.handle("basePlugins", async (_event) => { + const basePluginPath = join( + __dirname, + "../", + isDev ? "/core/pre-install" : "../app.asar.unpacked/core/pre-install" + ); + return readdirSync(basePluginPath) + .filter((file) => extname(file) === ".tgz") + .map((file) => join(basePluginPath, file)); }); - if (isDev) mainWindow.webContents.openDevTools(); -}; + ipcMain.handle("pluginPath", async (_event) => { + return join(app.getPath("userData"), "plugins"); + }); + ipcMain.handle("appVersion", async (_event) => { + return app.getVersion(); + }); + ipcMain.handle("openExternalUrl", async (_event, url) => { + shell.openExternal(url); + }); -app - .whenReady() - .then(migratePlugins) - .then(() => { - createMainWindow(); - setupPlugins(); - autoUpdater.checkForUpdates(); + /** + * Used to delete a file from the user data folder + */ + ipcMain.handle("deleteFile", async (_event, filePath) => { + const userDataPath = app.getPath("userData"); + const fullPath = join(userDataPath, filePath); - ipcMain.handle("basePlugins", async (event) => { - const basePluginPath = join( - __dirname, - "../", - isDev ? "/core/pre-install" : "../app.asar.unpacked/core/pre-install" - ); - return readdirSync(basePluginPath) - .filter((file) => extname(file) === ".tgz") - .map((file) => join(basePluginPath, file)); - }); - - ipcMain.handle("pluginPath", async (event) => { - return join(app.getPath("userData"), "plugins"); - }); - ipcMain.handle("appVersion", async (event) => { - return app.getVersion(); - }); - ipcMain.handle("openExternalUrl", async (event, url) => { - shell.openExternal(url); - }); - - /** - * Used to delete a file from the user data folder - */ - ipcMain.handle("deleteFile", async (_event, filePath) => { - const userDataPath = app.getPath("userData"); - const fullPath = join(userDataPath, filePath); - - let result = "NULL"; - unlink(fullPath, function (err) { - if (err && err.code == "ENOENT") { - result = `File not exist: ${err}`; - } else if (err) { - result = `File delete error: ${err}`; - } else { - result = "File deleted successfully"; - } - console.log( - `Delete file ${filePath} from ${fullPath} result: ${result}` - ); - }); - - return result; - }); - - /** - * Used to download a file from a given url - */ - ipcMain.handle("downloadFile", async (_event, url, fileName) => { - const userDataPath = app.getPath("userData"); - const destination = resolve(userDataPath, fileName); - - progress(request(url), {}) - .on("progress", function (state: any) { - mainWindow?.webContents.send("FILE_DOWNLOAD_UPDATE", { - ...state, - fileName, - }); - }) - .on("error", function (err: Error) { - mainWindow?.webContents.send("FILE_DOWNLOAD_ERROR", { - fileName, - err, - }); - }) - .on("end", function () { - mainWindow?.webContents.send("FILE_DOWNLOAD_COMPLETE", { - fileName, - }); - }) - .pipe(createWriteStream(destination)); - }); - - app.on("activate", () => { - if (!BrowserWindow.getAllWindows().length) { - createMainWindow(); + let result = "NULL"; + unlink(fullPath, function (err) { + if (err && err.code == "ENOENT") { + result = `File not exist: ${err}`; + } else if (err) { + result = `File delete error: ${err}`; + } else { + result = "File deleted successfully"; } + console.log(`Delete file ${filePath} from ${fullPath} result: ${result}`); }); + + return result; }); -/*New Update Available*/ -autoUpdater.on("update-available", async (info: any) => { - const action = await dialog.showMessageBox({ - message: `Update available. Do you want to download the latest update?`, - buttons: ["Download", "Later"], + /** + * Used to download a file from a given url + */ + ipcMain.handle("downloadFile", async (_event, url, fileName) => { + const userDataPath = app.getPath("userData"); + const destination = resolve(userDataPath, fileName); + + progress(request(url), {}) + .on("progress", function (state: any) { + mainWindow?.webContents.send("FILE_DOWNLOAD_UPDATE", { + ...state, + fileName, + }); + }) + .on("error", function (err: Error) { + mainWindow?.webContents.send("FILE_DOWNLOAD_ERROR", { + fileName, + err, + }); + }) + .on("end", function () { + mainWindow?.webContents.send("FILE_DOWNLOAD_COMPLETE", { + fileName, + }); + }) + .pipe(createWriteStream(destination)); }); - if (action.response === 0) await autoUpdater.downloadUpdate(); -}); - -/*App Update Completion Message*/ -autoUpdater.on("update-downloaded", async (info: any) => { - mainWindow?.webContents.send("APP_UPDATE_COMPLETE", {}); - const action = await dialog.showMessageBox({ - message: `Update downloaded. Please restart the application to apply the updates.`, - buttons: ["Restart", "Later"], - }); - if (action.response === 0) { - autoUpdater.quitAndInstall(); - } -}); - -/*App Update Error */ -autoUpdater.on("error", (info: any) => { - dialog.showMessageBox({ message: info.message }); - mainWindow?.webContents.send("APP_UPDATE_ERROR", {}); -}); - -/*App Update Progress */ -autoUpdater.on("download-progress", (progress: any) => { - console.log("app update progress: ", progress.percent); - mainWindow?.webContents.send("APP_UPDATE_PROGRESS", { - percent: progress.percent, - }); -}); - -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - } -}); +} function migratePlugins() { return new Promise((resolve) => { + const store = new Store(); if (store.get("migrated_version") !== app.getVersion()) { console.log("start migration:", store.get("migrated_version")); const userDataPath = app.getPath("userData"); @@ -217,12 +212,12 @@ function migratePlugins() { resolve(undefined); } }); -}; +} function setupPlugins() { init({ // Function to check from the main process that user wants to install a plugin - confirmInstall: async (plugins: string[]) => { + confirmInstall: async (_plugins: string[]) => { return true; }, // Path to install plugin to diff --git a/electron/utils/menu.ts b/electron/utils/menu.ts new file mode 100644 index 000000000..90c5d55c5 --- /dev/null +++ b/electron/utils/menu.ts @@ -0,0 +1,98 @@ +// @ts-nocheck +const { app, Menu } = require("electron"); +const isMac = process.platform === "darwin"; +const { autoUpdater } = require("electron-updater"); + +const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [ + ...(isMac + ? [ + { + label: app.name, + submenu: [ + { role: "about" }, + { + label: "Check for Updates...", + click: () => autoUpdater.checkForUpdates(), + }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { role: "hide" }, + { role: "hideOthers" }, + { role: "unhide" }, + { type: "separator" }, + { role: "quit" }, + ], + }, + ] + : []), + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + ...(isMac + ? [ + { role: "pasteAndMatchStyle" }, + { role: "delete" }, + { role: "selectAll" }, + { type: "separator" }, + { + label: "Speech", + submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }], + }, + ] + : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + { + label: "Window", + submenu: [ + { role: "minimize" }, + { role: "zoom" }, + ...(isMac + ? [ + { type: "separator" }, + { role: "front" }, + { type: "separator" }, + { role: "window" }, + ] + : [{ role: "close" }]), + ], + }, + { + role: "help", + submenu: [ + { + label: "Learn More", + click: async () => { + const { shell } = require("electron"); + await shell.openExternal("https://jan.ai/"); + }, + }, + ], + }, +]; + +export const setupMenu = () => { + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +};