diff --git a/gui/src/main/electron.js b/gui/src/main/electron.js index d33a9210..0f57d6cd 100644 --- a/gui/src/main/electron.js +++ b/gui/src/main/electron.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow, ipcMain, dialog, shell, nativeTheme } = require('electron') +const { app, BrowserWindow, Menu, ipcMain, dialog, shell, nativeTheme } = require('electron') const osu = require('node-os-utils') const path = require("path"); const writeYamlFile = require('write-yaml-file') @@ -7,7 +7,9 @@ const os = require('os') const { handleGetSingleFolder,handleGetMultipleFolders, handleGetSingleFile, handleGetMultipleFiles} = require('./modules/dialogHandler') const { discoverWorkflows, workflowToConfig } = require('./modules/workflows') -const { getEnvironmentStatus, lineBreakTransform, CondaEnvironment} = require('./modules/cmd'); +const { CondaEnvironment} = require('./modules/cmd'); +const { buildMenu } = require('./modules/menu') +const { Profile } = require('./modules/profile') // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (require('electron-squirrel-startup')) app.quit(); @@ -15,6 +17,7 @@ if (require('electron-squirrel-startup')) app.quit(); let mainWindow; let workflows; let environment; +let profile; function createWindow() { // Create the browser window. @@ -32,14 +35,18 @@ function createWindow() { }, }); mainWindow.setTitle("alphaDIA"); - mainWindow.removeMenu() mainWindow.loadFile(path.join(__dirname, "../dist/index.html")); + profile = new Profile() + + Menu.setApplicationMenu(buildMenu(mainWindow, profile)); + // Open the DevTools if NODE_ENV=dev if (process.env.NODE_ENV === "dev"){ mainWindow.webContents.openDevTools({ mode: "detach" }); } - environment = new CondaEnvironment("alpha") + + environment = new CondaEnvironment(profile) workflows = discoverWorkflows(mainWindow) } @@ -82,7 +89,7 @@ function handleStartWorkflow(workflow) { // save config.yaml in workflow folder writeYamlFile.sync(configPath, config, {lineWidth: -1}) - return environment.spawn(`conda run -n ${environment.envName} --no-capture-output alphadia extract --config ${configPath}`) + return environment.spawn(`conda run -n ${profile.config.conda.envName} --no-capture-output alphadia extract --config ${configPath}`) } // This method will be called when Electron has finished @@ -122,15 +129,6 @@ app.whenReady().then(() => { powerMonitor.on("suspend", () => { powerSaveBlocker.start("prevent-app-suspension"); }); - - mainWindow.webContents.on('render-process-gone', function (event, detailed) { - console.log("!crashed, reason: " + detailed.reason + ", exitCode = " + detailed.exitCode) - if (detailed.reason == "crashed"){ - // relaunch app - app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) }) - app.exit(0) - } - }) }); app.on('window-all-closed', () => { diff --git a/gui/src/main/modules/cmd.js b/gui/src/main/modules/cmd.js index 5f17ce43..91da17e1 100644 --- a/gui/src/main/modules/cmd.js +++ b/gui/src/main/modules/cmd.js @@ -46,7 +46,6 @@ function testCommand(command, pathUpdate){ const CondaEnvironment = class { pathUpdate = "" - envName = ""; exists = { conda: false, python: false, @@ -65,8 +64,8 @@ const CondaEnvironment = class { std = []; pid = null; - constructor(envName){ - this.envName = envName; + constructor(profile){ + this.profile = profile; this.initPromise = this.discoverCondaPATH().then((pathUpdate) => { this.pathUpdate = pathUpdate; @@ -81,7 +80,7 @@ const CondaEnvironment = class { // check if info exist and active_prefix is not null if (info != null){ if (info["active_prefix"] != null){ - if (path.basename(info["active_prefix"]) == this.envName){ + if (path.basename(info["active_prefix"]) == profile.config.conda.envName){ //dialog.showErrorBox("Conda environment already activated", "The conda environment " + this.envName + " is already activated. Please deactivate the environment and restart alphaDIA.") return Promise.reject("Conda environment already activated") } @@ -103,8 +102,8 @@ const CondaEnvironment = class { discoverCondaPATH(){ return new Promise((resolve, reject) => { - - const paths = ["", ...condaPATH(os.userInfo().username, os.platform())] + + const paths = [this.profile.config.conda.path, ...condaPATH(os.userInfo().username, os.platform())] Promise.all(paths.map((path) => { return testCommand("conda", path) })).then((codes) => { @@ -124,7 +123,6 @@ const CondaEnvironment = class { this.exec('conda info --json', (err, stdout, stderr) => { if (err) {console.log(err); reject(err); return;} const info = JSON.parse(stdout); - console.log(info) this.versions.conda = info["conda_version"]; this.exists.conda = true; resolve(info); @@ -134,7 +132,8 @@ const CondaEnvironment = class { checkPythonVersion(){ return new Promise((resolve, reject) => { - this.exec(`conda run -n ${this.envName} python --version`, (err, stdout, stderr) => { + + this.exec(`conda run -n ${this.profile.config.conda.envName} python --version`, (err, stdout, stderr) => { if (err) {console.log(err); reject(err); return;} const versionPattern = /\d+\.\d+\.\d+/; const versionList = stdout.match(versionPattern); @@ -150,7 +149,7 @@ const CondaEnvironment = class { } checkAlphadiaVersion(){ return new Promise((resolve, reject) => { - this.exec(`conda list -n ${this.envName} --json`, (err, stdout, stderr) => { + this.exec(`conda list -n ${this.profile.config.conda.envName} --json`, (err, stdout, stderr) => { if (err) {console.log(err); reject(err); return;} const info = JSON.parse(stdout); const packageInfo = info.filter((p) => p.name == "alphadia"); @@ -170,7 +169,7 @@ const CondaEnvironment = class { buildEnvironmentStatus(){ return { - envName: this.envName, + envName: this.profile.config.conda.envName, versions: this.versions, exists: this.exists, ready: this.ready @@ -189,6 +188,7 @@ const CondaEnvironment = class { spawn(cmd){ console.log(cmd) + this.std = []; return new Promise((resolve, reject) => { if (!this.ready){ reject("Environment not ready"); @@ -240,94 +240,6 @@ const CondaEnvironment = class { } - -function getCondaInfo() { - return new Promise((resolve, reject) => { - exec('conda info --json', (err, stdout, stderr) => { - if (err) { - reject(err); - } - const info = JSON.parse(stdout); - resolve(info); - }); - }); -} - -function getPythonVersion(envName){ - return new Promise((resolve, reject) => { - exec(`conda run -n ${envName} python --version`, (err, stdout, stderr) => { - if (err) { - reject(err); - return; - } - const versionPattern = /\d+\.\d+\.\d+/; - const versionList = stdout.match(versionPattern); - // check if versionList is null - if (versionList == null){ - reject("Python version not found"); - return; - } - if (versionList.length == 0){ - reject("Python version not found"); - return; - } - resolve(versionList[0]); - }); - }); -} - -function getPackageVersion(envName, packageName){ - return new Promise((resolve, reject) => { - exec(`conda list -n ${envName} --json`, (err, stdout, stderr) => { - if (err) { - reject(err); - } - const info = JSON.parse(stdout); - const packageInfo = info.filter((package) => package.name == packageName); - if (packageInfo.length == 0){ - reject(`Package ${packageName} not found in environment ${envName}`); - } - resolve(packageInfo[0].version); - }); - }); -} - -function getEnvironmentStatus(envName){ - environment = { - envName: envName, - hasConda: false, - condaVersion: "", - hasEnv: false, - hasPython: false, - pythonVersion: "", - hasAlphadia: false, - alphadiaVersion: "", - ready: false - } - - return getCondaInfo().then((info) => { - environment.hasConda = true; - environment.condaVersion = info["conda_version"]; - environment.hasEnv = info["envs"].map((env) => path.basename(env)).includes(envName); - return getPythonVersion(envName).then((version) => { - environment.hasPython = true; - environment.pythonVersion = version; - return getPackageVersion(envName, "alphadia").then((version) => { - environment.hasAlphadia = true; - environment.alphadiaVersion = version; - environment.ready = [environment.hasConda, environment.hasEnv, environment.hasPython, environment.hasAlphadia].every(Boolean); - return environment - }).catch((error) => { - return environment - }) - }).catch((error) => { - return environment - }) - }).catch((error) => { - return environment - }) -} - function lineBreakTransform () { // https://stackoverflow.com/questions/40781713/getting-chunks-by-newline-in-node-js-data-stream @@ -359,12 +271,6 @@ function lineBreakTransform () { module.exports = { - getCondaInfo, - getPythonVersion, - getPackageVersion, - getEnvironmentStatus, - lineBreakTransform, - testCommand, CondaEnvironment } diff --git a/gui/src/main/modules/menu.js b/gui/src/main/modules/menu.js new file mode 100644 index 00000000..4926aaa4 --- /dev/null +++ b/gui/src/main/modules/menu.js @@ -0,0 +1,69 @@ +const {app, Menu} = require('electron') +const path = require('path') +const {shell} = require('electron') + +function buildMenu(mainWindow, profile) { + + const isMac = process.platform === 'darwin' + + const template = [ + ...(isMac + ? [{ + label: app.name, + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { id: 'preferences', label: 'Preferences', click: () => { profile.openProfile() } }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + }] + : []), + { + label: 'File', + submenu: [ + isMac ? { role: 'close' } : { role: 'quit' }, + ...(isMac ? []: [{ id: 'preferences', label: 'Preferences', click: () => { profile.openProfile() } }]) + ] + }, + { + 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://electronjs.org') + } + } + ] + } + ] + + return Menu.buildFromTemplate(template) +} + +module.exports = { + buildMenu +} \ No newline at end of file diff --git a/gui/src/main/modules/profile.js b/gui/src/main/modules/profile.js new file mode 100644 index 00000000..0960c65e --- /dev/null +++ b/gui/src/main/modules/profile.js @@ -0,0 +1,48 @@ + +const fs = require("fs") +const path = require("path") +const { app, shell} = require("electron") + +const Profile = class { + + config = { + "version": "1.3.0", + "conda": { + "envName": "alpha", + "path": "" + }, + "clippy": false, + } + + constructor() { + + // check if profile exists + if (!fs.existsSync(this.getProfilePath())) { + this.saveProfile() + } else { + this.loadProfile() + } + + console.log(this.getProfilePath()) + } + + saveProfile() { + fs.writeFileSync(this.getProfilePath(), JSON.stringify(this.config, null, 4)) + } + + loadProfile() { + this.config = JSON.parse(fs.readFileSync(this.getProfilePath())) + } + + getProfilePath() { + return path.join(app.getPath("userData"), "profile.json") + } + + openProfile() { + shell.openPath(this.getProfilePath()) + } +} + +module.exports = { + Profile +} \ No newline at end of file diff --git a/gui/src/renderer/components/MenuDrawer.js b/gui/src/renderer/components/MenuDrawer.js index 2316cc0e..9edd2d53 100644 --- a/gui/src/renderer/components/MenuDrawer.js +++ b/gui/src/renderer/components/MenuDrawer.js @@ -9,6 +9,7 @@ import HomeIcon from '@mui/icons-material/Home'; import FolderIcon from '@mui/icons-material/Folder'; import SettingsApplicationsIcon from '@mui/icons-material/SettingsApplications'; import SaveIcon from '@mui/icons-material/Save'; +import TerminalIcon from '@mui/icons-material/Terminal'; const DrawerContainer = styled('div')(({ theme }) => ({ width: 240, @@ -133,11 +134,24 @@ const MenuDrawer = ({ + + + + + + + + + - + ) diff --git a/gui/src/renderer/components/RunButton.js b/gui/src/renderer/components/RunButton.js index 7547de40..15d18639 100644 --- a/gui/src/renderer/components/RunButton.js +++ b/gui/src/renderer/components/RunButton.js @@ -69,7 +69,6 @@ const RunButton = ({ function handleRunClick() { const validation = validateMethod(method); if (profile.running) { - navigate("/run"); return; } if (!validation.valid) { diff --git a/gui/src/renderer/pages/Run.js b/gui/src/renderer/pages/Run.js index 40f69264..40f6ed1e 100644 --- a/gui/src/renderer/pages/Run.js +++ b/gui/src/renderer/pages/Run.js @@ -87,15 +87,21 @@ const Output = () => { } React.useEffect(() => { + setItems([]); + currentLengthRef.current = 0; + let isMounted = true; const interval = setInterval(() => { window.electronAPI.getOutputLength().then((length) => { if (isMounted){ if (length > currentLengthRef.current) { - console.log("updating items") updateItems(currentLengthRef.current); } + if (length < currentLengthRef.current) { + setItems([]); + currentLengthRef.current = 0; + } } }); }, 100); @@ -104,6 +110,8 @@ const Output = () => { isMounted = false; clearInterval(interval); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []);