From 00881bcbfddab8b06ccd2f45d200fe7bbcfad15a Mon Sep 17 00:00:00 2001 From: davemfish Date: Fri, 2 Jun 2023 12:27:16 -0400 Subject: [PATCH 01/19] use electron-store for settings and manage in main process --- workbench/package.json | 1 + workbench/src/main/ipcMainChannels.js | 5 +- workbench/src/main/main.js | 4 +- workbench/src/main/settingsStore.js | 21 ++++ workbench/src/main/setupInvestHandlers.js | 6 +- workbench/src/renderer/app.jsx | 65 ++--------- .../renderer/components/InvestTab/index.jsx | 13 +-- .../SettingsModal/SettingsStorage.js | 83 -------------- .../components/SettingsModal/index.jsx | 77 ++++++------- .../renderer/components/SetupTab/index.jsx | 10 +- workbench/yarn.lock | 106 +++++++++++++++++- 11 files changed, 195 insertions(+), 196 deletions(-) create mode 100644 workbench/src/main/settingsStore.js delete mode 100644 workbench/src/renderer/components/SettingsModal/SettingsStorage.js diff --git a/workbench/package.json b/workbench/package.json index e35115e541..170f367719 100644 --- a/workbench/package.json +++ b/workbench/package.json @@ -39,6 +39,7 @@ "dependencies": { "@babel/runtime": "^7.13.10", "electron-log": "^4.3.5", + "electron-store": "^8.1.0", "i18next": "^22.4.9", "localforage": "^1.9.0", "node-fetch": "^2.6.7", diff --git a/workbench/src/main/ipcMainChannels.js b/workbench/src/main/ipcMainChannels.js index e1ff8b78ef..2fe0828406 100644 --- a/workbench/src/main/ipcMainChannels.js +++ b/workbench/src/main/ipcMainChannels.js @@ -1,8 +1,10 @@ export const ipcMainChannels = { + CHANGE_LANGUAGE: 'change-language', CHECK_FILE_PERMISSIONS: 'check-file-permissions', CHECK_STORAGE_TOKEN: 'check-storage-token', DOWNLOAD_URL: 'download-url', GET_N_CPUS: 'get-n-cpus', + GET_SETTING: 'get-setting', INVEST_KILL: 'invest-kill', INVEST_READ_LOG: 'invest-read-log', INVEST_RUN: 'invest-run', @@ -10,8 +12,9 @@ export const ipcMainChannels = { IS_FIRST_RUN: 'is-first-run', OPEN_EXTERNAL_URL: 'open-external-url', OPEN_LOCAL_HTML: 'open-local-html', + RESET_SETTINGS: 'reset-settings', + SET_SETTING: 'set-setting', SHOW_ITEM_IN_FOLDER: 'show-item-in-folder', SHOW_OPEN_DIALOG: 'show-open-dialog', SHOW_SAVE_DIALOG: 'show-save-dialog', - CHANGE_LANGUAGE: 'change-language', }; diff --git a/workbench/src/main/main.js b/workbench/src/main/main.js index 06dc81868d..c41cb89bfb 100644 --- a/workbench/src/main/main.js +++ b/workbench/src/main/main.js @@ -30,13 +30,14 @@ import setupGetNCPUs from './setupGetNCPUs'; import setupOpenExternalUrl from './setupOpenExternalUrl'; import setupOpenLocalHtml from './setupOpenLocalHtml'; import setupChangeLanguage from './setupChangeLanguage'; +import { setupSettingsHandlers } from './settingsStore'; import { ipcMainChannels } from './ipcMainChannels'; import menuTemplate from './menubar'; import ELECTRON_DEV_MODE from './isDevMode'; import BASE_URL from './baseUrl'; import { getLogger } from './logger'; -import pkg from '../../package.json'; import i18n from './i18n/i18n'; +import pkg from '../../package.json'; const logger = getLogger(__filename.split('/').slice(-1)[0]); @@ -84,6 +85,7 @@ export const createWindow = async () => { setupCheckFirstRun(); setupCheckStorageToken(); setupChangeLanguage(); + setupSettingsHandlers(); await getFlaskIsReady(); const devModeArg = ELECTRON_DEV_MODE ? '--devMode' : ''; diff --git a/workbench/src/main/settingsStore.js b/workbench/src/main/settingsStore.js new file mode 100644 index 0000000000..a9f3e3575e --- /dev/null +++ b/workbench/src/main/settingsStore.js @@ -0,0 +1,21 @@ +import { ipcMain } from 'electron'; +import Store from 'electron-store'; + +import { ipcMainChannels } from './ipcMainChannels'; + +const defaults = { + nWorkers: '-1', + taskgraphLoggingLevel: 'INFO', + loggingLevel: 'INFO', + sampleDataDir: '', + language: 'en' +}; + +export const settingsStore = new Store({ defaults: defaults }); + +export function setupSettingsHandlers() { + ipcMain.handle(ipcMainChannels.GET_SETTING, (event, key) => settingsStore.get(key)); + ipcMain.on(ipcMainChannels.SET_SETTING, (event, key, value) => { + settingsStore.set(key, value); + }); +} diff --git a/workbench/src/main/setupInvestHandlers.js b/workbench/src/main/setupInvestHandlers.js index 37783fd882..885aadab7e 100644 --- a/workbench/src/main/setupInvestHandlers.js +++ b/workbench/src/main/setupInvestHandlers.js @@ -11,6 +11,7 @@ import ELECTRON_DEV_MODE from './isDevMode'; import investUsageLogger from './investUsageLogger'; import markupMessage from './investLogMarkup'; import writeInvestParameters from './writeInvestParameters'; +import { settingsStore } from './settingsStore'; const logger = getLogger(__filename.split('/').slice(-1)[0]); @@ -45,12 +46,15 @@ export function setupInvestRunHandlers(investExe) { }); ipcMain.on(ipcMainChannels.INVEST_RUN, async ( - event, modelRunName, pyModuleName, args, loggingLevel, taskgraphLoggingLevel, language, tabID + event, modelRunName, pyModuleName, args, tabID ) => { let investRun; let investStarted = false; let investStdErr = ''; const usageLogger = investUsageLogger(); + const loggingLevel = settingsStore.get('loggingLevel'); + const taskgraphLoggingLevel = settingsStore.get('taskgraphLoggingLevel'); + const language = settingsStore.get('language'); // Write a temporary datastack json for passing to invest CLI try { diff --git a/workbench/src/renderer/app.jsx b/workbench/src/renderer/app.jsx index 9b875a1874..8243bde21e 100644 --- a/workbench/src/renderer/app.jsx +++ b/workbench/src/renderer/app.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import i18n from 'i18next'; import TabPane from 'react-bootstrap/TabPane'; import TabContent from 'react-bootstrap/TabContent'; @@ -25,8 +26,7 @@ import { import { getInvestModelNames } from './server_requests'; import InvestJob from './InvestJob'; import { dragOverHandlerNone } from './utils'; -import { ipcMainChannels } from '../main/ipcMainChannels'; -import i18n from 'i18next'; +// import { ipcMainChannels } from '../main/ipcMainChannels'; const { ipcRenderer } = window.Workbench.electron; const logger = window.Workbench.getLogger('app.jsx'); @@ -44,18 +44,15 @@ export default class App extends React.Component { openJobs: {}, investList: null, recentJobs: [], - investSettings: null, showDownloadModal: false, downloadedNofN: null, }; - this.saveSettings = this.saveSettings.bind(this); this.switchTabs = this.switchTabs.bind(this); this.openInvestModel = this.openInvestModel.bind(this); this.closeInvestModel = this.closeInvestModel.bind(this); this.updateJobProperties = this.updateJobProperties.bind(this); this.saveJob = this.saveJob.bind(this); this.clearRecentJobs = this.clearRecentJobs.bind(this); - this.storeDownloadDir = this.storeDownloadDir.bind(this); this.showDownloadModal = this.showDownloadModal.bind(this); } @@ -63,17 +60,13 @@ export default class App extends React.Component { async componentDidMount() { const investList = await getInvestModelNames(); const recentJobs = await InvestJob.getJobStore(); - const investSettings = await getAllSettings(); this.setState({ investList: investList, recentJobs: recentJobs, - investSettings: investSettings, showDownloadModal: this.props.isFirstRun, }); - await i18n.changeLanguage(investSettings.language); - await ipcRenderer.invoke( - ipcMainChannels.CHANGE_LANGUAGE, investSettings.language - ); + const language = await ipcRenderer.invoke('getSettingsValue', 'language'); + await i18n.changeLanguage(language); ipcRenderer.on('download-status', (downloadedNofN) => { this.setState({ downloadedNofN: downloadedNofN, @@ -95,33 +88,6 @@ export default class App extends React.Component { ); } - async saveSettings(settings) { - const { investSettings } = this.state; - await saveSettingsStore(settings); - this.setState({ investSettings: settings }); - // if language has changed, refresh the app - if (settings.language !== investSettings.language) { - // change language in the renderer process - await i18n.changeLanguage(settings.language); - // change language in the main process - await ipcRenderer.invoke( - ipcMainChannels.CHANGE_LANGUAGE, settings.language - ); - // rerender for changes to take effect - window.location.reload(); - } - } - - /** Store a sampledata filepath in localforage. - * - * @param {string} dir - the path to the user-selected dir - */ - storeDownloadDir(dir) { - const { investSettings } = this.state; - investSettings.sampleDataDir = dir; - this.saveSettings(investSettings); - } - showDownloadModal(shouldShow) { this.setState({ showDownloadModal: shouldShow, @@ -212,7 +178,6 @@ export default class App extends React.Component { render() { const { investList, - investSettings, recentJobs, openJobs, openTabIDs, @@ -289,7 +254,6 @@ export default class App extends React.Component { @@ -344,21 +308,12 @@ export default class App extends React.Component { ) :
} - { - // don't render until after we fetched the data - (investSettings) - ? ( - this.showDownloadModal(true)} - nCPU={this.props.nCPU} - /> - ) - :
- } + this.showDownloadModal(true)} + nCPU={this.props.nCPU} + /> diff --git a/workbench/src/renderer/components/InvestTab/index.jsx b/workbench/src/renderer/components/InvestTab/index.jsx index 764954d067..94dbec1a4d 100644 --- a/workbench/src/renderer/components/InvestTab/index.jsx +++ b/workbench/src/renderer/components/InvestTab/index.jsx @@ -147,7 +147,6 @@ class InvestTab extends React.Component { const { job, tabID, - investSettings, updateJobProperties, } = this.props; const args = { ...argsValues }; @@ -162,9 +161,6 @@ class InvestTab extends React.Component { job.modelRunName, this.state.modelSpec.pyname, args, - investSettings.loggingLevel, - investSettings.taskgraphLoggingLevel, - investSettings.language, tabID ); this.switchTabs('log'); @@ -205,7 +201,7 @@ class InvestTab extends React.Component { logfile, } = this.props.job; - const { tabID, investSettings, t } = this.props; + const { tabID, t } = this.props; // Don't render the model setup & log until data has been fetched. if (!modelSpec) { @@ -279,7 +275,6 @@ class InvestTab extends React.Component { uiSpec={uiSpec} argsInitValues={argsValues} investExecute={this.investExecute} - nWorkers={investSettings.nWorkers} sidebarSetupElementId={sidebarSetupElementId} sidebarFooterElementId={sidebarFooterElementId} executeClicked={executeClicked} @@ -313,12 +308,6 @@ InvestTab.propTypes = { status: PropTypes.string, }).isRequired, tabID: PropTypes.string.isRequired, - investSettings: PropTypes.shape({ - nWorkers: PropTypes.string, - taskgraphLoggingLevel: PropTypes.string, - loggingLevel: PropTypes.string, - language: PropTypes.string, - }).isRequired, saveJob: PropTypes.func.isRequired, updateJobProperties: PropTypes.func.isRequired, }; diff --git a/workbench/src/renderer/components/SettingsModal/SettingsStorage.js b/workbench/src/renderer/components/SettingsModal/SettingsStorage.js deleted file mode 100644 index 185af59e9b..0000000000 --- a/workbench/src/renderer/components/SettingsModal/SettingsStorage.js +++ /dev/null @@ -1,83 +0,0 @@ -import localforage from 'localforage'; - -const logger = window.Workbench.getLogger('SettingsStorage.js'); - -const investSettingsStore = localforage.createInstance({ - name: 'InvestSettings', -}); - -/** Getter function for global default settings. - * - * @returns {object} to destructure into: - * {String} nWorkers - TaskGraph number of workers - * {String} taskgraphLoggingLevel - InVEST taskgraph logging level - * {String} loggingLevel - InVEST model logging level - * {String} sampleDataDir - default location for sample datastack downloads - */ -export function getDefaultSettings() { - const defaultSettings = { - nWorkers: '-1', - taskgraphLoggingLevel: 'INFO', - loggingLevel: 'INFO', - sampleDataDir: '', - language: 'en' - }; - return defaultSettings; -} - -/** Getter function for settings store value. - * - * @param {object} obj.argsValues - an invest "args dict" with initial values - * @param {string} key - setting key to get value - * - * @returns {string} - value of the setting key. - */ -export async function getSettingsValue(key) { - const value = await investSettingsStore.getItem(key); - if (!value) { - return getDefaultSettings()[key]; - } - return value; -} - -/** Getter function for entire contents of store. - * - * @returns {Object} - key: value pairs of settings - */ -export async function getAllSettings() { - try { - const promises = []; - const keys = Object.keys(getDefaultSettings()); - keys.forEach((key) => { - promises.push(getSettingsValue(key)); - }); - const values = await Promise.all(promises); - const settings = Object.fromEntries(keys.map( - (_, i) => [keys[i], values[i]] - )); - return settings; - } catch (err) { - logger.error(err.message); - return getDefaultSettings(); - } -} - -/** Clear the settings store. */ -export async function clearSettingsStore() { - await investSettingsStore.clear(); -} - -/** Setter function for saving store values. - * - * @param {object} settingsObj - object with one or more key:value pairs - * - */ -export async function saveSettingsStore(settingsObj) { - try { - for (const [setting, value] of Object.entries(settingsObj)) { - await investSettingsStore.setItem(setting, value); - } - } catch (err) { - logger.error(`Error saving settings: ${err}`); - } -} diff --git a/workbench/src/renderer/components/SettingsModal/index.jsx b/workbench/src/renderer/components/SettingsModal/index.jsx index b3972c43fc..a5d8b205bb 100644 --- a/workbench/src/renderer/components/SettingsModal/index.jsx +++ b/workbench/src/renderer/components/SettingsModal/index.jsx @@ -16,7 +16,6 @@ import { import { BsChevronExpand } from 'react-icons/bs'; import { withTranslation } from 'react-i18next'; -import { getDefaultSettings } from './SettingsStorage'; import { ipcMainChannels } from '../../../main/ipcMainChannels'; import { getSupportedLanguages } from '../../server_requests'; @@ -29,11 +28,15 @@ class SettingsModal extends React.Component { this.state = { show: false, languageOptions: null, + language: null, + loggingLevel: null, + taskgraphLoggingLevel: null, + nWorkers: null }; this.handleShow = this.handleShow.bind(this); this.handleClose = this.handleClose.bind(this); this.handleChange = this.handleChange.bind(this); - this.handleReset = this.handleReset.bind(this); + this.setSettings = this.setSettings.bind(this); this.switchToDownloadModal = this.switchToDownloadModal.bind(this); } @@ -42,6 +45,7 @@ class SettingsModal extends React.Component { this.setState({ languageOptions: languageOptions, }); + this.setSettings(); } handleClose() { @@ -54,17 +58,27 @@ class SettingsModal extends React.Component { this.setState({ show: true }); } - handleReset(event) { - event.preventDefault(); - const resetSettings = getDefaultSettings(); - this.props.saveSettings(resetSettings); - } - handleChange(event) { - const newSettings = { ...this.props.investSettings }; const { name, value } = event.currentTarget; - newSettings[name] = value; - this.props.saveSettings(newSettings); + this.setState({ [name]: value }); + ipcRenderer.send(ipcMainChannels.SET_SETTING, name, value); + } + + async setSettings() { + const language = await ipcRenderer + .invoke(ipcMainChannels.GET_SETTING, 'language'); + const loggingLevel = await ipcRenderer + .invoke(ipcMainChannels.GET_SETTING, 'loggingLevel'); + const taskgraphLoggingLevel = await ipcRenderer + .invoke(ipcMainChannels.GET_SETTING, 'taskgraphLoggingLevel'); + const nWorkers = await ipcRenderer + .invoke(ipcMainChannels.GET_SETTING, 'nWorkers'); + this.setState({ + language: language, + loggingLevel: loggingLevel, + taskgraphLoggingLevel: taskgraphLoggingLevel, + nWorkers: nWorkers + }); } switchToDownloadModal() { @@ -73,8 +87,15 @@ class SettingsModal extends React.Component { } render() { - const { show, languageOptions } = this.state; - const { investSettings, clearJobsStorage, nCPU, t } = this.props; + const { + show, + languageOptions, + language, + loggingLevel, + taskgraphLoggingLevel, + nWorkers, + } = this.state; + const { clearJobsStorage, nCPU, t } = this.props; const nWorkersOptions = [ [-1, `${t('Synchronous')} (-1)`], @@ -83,7 +104,7 @@ class SettingsModal extends React.Component { for (let i = 1; i <= nCPU; i += 1) { nWorkersOptions.push([i, `${i} ${t('CPUs')}`]); } - const logLevelOptions = { // map value to display name + const logLevelOptions = { // map value to display name 'DEBUG': t('DEBUG'), 'INFO': t('INFO'), 'WARNING': t('WARNING'), @@ -134,7 +155,7 @@ class SettingsModal extends React.Component { id="language-select" as="select" name="language" - value={investSettings.language} + value={language} onChange={this.handleChange} > {Object.entries(languageOptions).map((entry) => { @@ -155,7 +176,7 @@ class SettingsModal extends React.Component { id="logging-select" as="select" name="loggingLevel" - value={investSettings.loggingLevel} + value={loggingLevel} onChange={this.handleChange} > {Object.entries(logLevelOptions).map( @@ -173,7 +194,7 @@ class SettingsModal extends React.Component { id="taskgraph-logging-select" as="select" name="taskgraphLoggingLevel" - value={investSettings.taskgraphLoggingLevel} + value={taskgraphLoggingLevel} onChange={this.handleChange} > {Object.entries(logLevelOptions).map( @@ -197,7 +218,7 @@ class SettingsModal extends React.Component { as="select" name="nWorkers" type="text" - value={investSettings.nWorkers} + value={nWorkers} onChange={this.handleChange} > {nWorkersOptions.map( @@ -233,18 +254,6 @@ class SettingsModal extends React.Component { ) :
} - - - - -