From eaa59bfcfdc7fd0208dc364adb495d9e34e5688a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Jim=C3=A9nez?= Date: Thu, 21 Jul 2022 03:09:10 +0200 Subject: [PATCH] Upgrade Cerebro core update packages and improve project build (#559) * fix: removeNoise function not working correctly * style: fix linter errors * chore(deps): use exact dependencies * CI: try build gh action * ci: fix gh token * ci: fix repo * chore: small refactor * chore: rm deprecated proptypes - use prop-types * chore: small refactor * fix: can not read getPrimarydisplay of undefined * chore: update electron to v19 * chore: update deps * chore: update deps * chore: use new electron whenReady * chore: lint webpack configs * CI: delete unused files * CI: run on workflow dispatch * chore: update dev deps * chore: update deps * deps: update url-loader * chore: fix plugin installation errors chore: move to rimraf * chore: fix building errors * CI: run scripts in parallel * chore: update babel * chore: update webpack dev middleware * chore: update cross-env * chore: update redux * chore: update linter config * fix: quit app not working * chore: remove unused code * fix: remote module in background window * chore: update deps, autoinstall app deps * docs: update readme * chore: minor fixes * docs: two package.json msg + delete broken links * ci: update workflows, work when tags pushed * ci: test CI * chore: fix babel target * CI: test new release * CI: test on release * CI: run on gh release * Update build.yml * Update build.yml * docs: update release configuration * docs: target info * docs: how to publish a release * CI: run on git tags * chore: move to @cerebroapp/cerebro-ui * chore: add @cerebroapp/cerebro-ui dependency * feat: support @cerebroapp in plugin names * fix: handle @cerebroapp directory * chore: refactor promises * chore: small refactor * chore: fix tests script * CI: add tests to publish workflow * chore: adjusting json package description and version * chore: add new rule in eslint * doc: update readme * feat: add template string * refactor: fix lint problems * chore: remove comments Co-authored-by: oguhpereira --- .babelrc | 8 - .eslintignore | 2 - .eslintrc.json | 10 +- .github/workflows/build.yml | 41 + .travis.yml | 52 - README.md | 37 +- __mocks__/@electron/remote.js | 5 + __mocks__/electron.js | 8 + __mocks__/fileMock.js | 1 + __mocks__/lib/config.js | 3 - __mocks__/lib/plugins.js | 3 - __mocks__/plugins.js | 5 - app/background/background.js | 16 +- app/background/createWindow.js | 1 + app/background/index.html | 4 +- app/initAutoUpdater.js | 15 +- app/lib/config.js | 13 +- app/lib/getWindowPosition.js | 18 +- app/lib/initializePlugins.js | 1 + app/lib/plugins/index.js | 6 +- app/lib/plugins/npm.js | 134 +- app/lib/plugins/settings/get.js | 2 + app/lib/rpc.js | 7 +- app/lib/trackEvent.js | 9 +- app/main.development.js | 75 +- app/main/actions/search.js | 23 +- app/main/actions/search.spec.js | 9 +- app/main/components/Cerebro/index.js | 57 +- app/main/components/MainInput/index.js | 6 +- app/main/components/ResultsList/Row/index.js | 20 +- app/main/components/ResultsList/index.js | 5 +- app/main/components/StatusBar/index.js | 3 +- app/main/createWindow.js | 6 +- app/main/createWindow/AppTray.js | 35 +- app/main/createWindow/autoStart.js | 13 +- app/main/createWindow/buildMenu.js | 93 +- app/main/createWindow/checkForUpdates.js | 55 +- app/main/createWindow/donateDialog.js | 15 +- app/main/css/global.css | 6 +- app/main/index.html | 4 +- app/main/main.js | 5 +- app/main/reducers/search.js | 4 +- app/main/reducers/statusBar.js | 2 +- app/package.json | 19 +- app/plugins/core/autocomplete/index.js | 19 +- app/plugins/core/index.js | 4 +- .../core/plugins/Preview/ActionButton.js | 9 +- app/plugins/core/plugins/Preview/FormItem.js | 13 +- app/plugins/core/plugins/Preview/Settings.js | 7 +- app/plugins/core/plugins/Preview/index.js | 42 +- app/plugins/core/plugins/Preview/styles.css | 3 +- app/plugins/core/plugins/StatusBar/index.js | 3 +- app/plugins/core/plugins/format.js | 27 +- .../core/plugins/getAvailablePlugins.js | 28 +- .../core/plugins/getDebuggingPlugins.js | 21 +- .../core/plugins/getInstalledPlugins.js | 17 +- app/plugins/core/plugins/getReadme.js | 2 +- app/plugins/core/plugins/index.js | 39 +- app/plugins/core/plugins/initializeAsync.js | 60 +- app/plugins/core/plugins/loadPlugins.js | 67 +- app/plugins/core/quit/index.js | 16 +- app/plugins/core/reload/index.js | 5 +- app/plugins/core/settings/Settings/Hotkey.js | 15 +- app/plugins/core/settings/Settings/index.js | 32 +- app/plugins/core/version/index.js | 5 +- app/plugins/externalPlugins.js | 21 +- app/plugins/index.js | 2 +- app/yarn.lock | 1270 +-- appveyor.yml | 33 - babel.config.js | 16 + jest.config.js | 7 + jsconfig.json | 6 + mocha-webpack.opts | 5 - package.json | 137 +- server.js | 8 +- webpack.config.base.js | 28 +- webpack.config.development.js | 25 +- webpack.config.electron.js | 20 +- webpack.config.production.js | 54 +- yarn.lock | 7536 +++++++++-------- 80 files changed, 5216 insertions(+), 5242 deletions(-) delete mode 100644 .babelrc delete mode 100644 .eslintignore create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml create mode 100644 __mocks__/@electron/remote.js create mode 100644 __mocks__/electron.js create mode 100644 __mocks__/fileMock.js delete mode 100644 __mocks__/lib/config.js delete mode 100644 __mocks__/lib/plugins.js delete mode 100644 __mocks__/plugins.js delete mode 100644 appveyor.yml create mode 100644 babel.config.js create mode 100644 jest.config.js create mode 100644 jsconfig.json delete mode 100644 mocha-webpack.opts diff --git a/.babelrc b/.babelrc deleted file mode 100644 index bfd5a4e5..00000000 --- a/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": ["cerebro-plugin"], - "env": { - "development": { - "presets": ["react-hmre"] - } - } -} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 8216e149..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -./main.js -webpack.config.*.js diff --git a/.eslintrc.json b/.eslintrc.json index 705bebd5..5b716de5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,5 @@ { - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", "extends": "airbnb", "env": { "browser": true, @@ -16,6 +16,7 @@ "no-useless-escape": 0, "no-mixed-operators": "off", "no-continue": "off", + "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], "import/no-extraneous-dependencies": "off", "import/imports-first": "off", "import/extensions": "off", @@ -32,7 +33,8 @@ "prefer-spread": "off", "class-methods-use-this": "off", "jsx-a11y/no-static-element-interactions": "off", - "jsx-a11y/label-has-for": "off" + "jsx-a11y/label-has-for": "off", + "linebreak-style": 0 }, "plugins": [ "jsx-a11y", @@ -43,8 +45,8 @@ "settings": { "import/core-modules": "electron", "import/resolver": { - "webpack": { - "config": "../webpack.config.base.js" + "node": { + "paths": ["app"] } } } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..01b978dc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: Build/release + +on: + push: + tags: + - '*' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + - run: yarn + - run: yarn test --detectOpenHandles --forceExit + + release: + needs: test + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Install Node.js, NPM and Yarn + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Build & Release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + github_token: ${{ secrets.github_token }} + release: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index daf1b4a1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -sudo: required -dist: trusty - -language: node_js - -branches: - only: - - master - -matrix: - include: - - os: linux - node_js: 14 - env: CC=clang CXX=clang++ npm_config_clang=1 - compiler: clang - -addons: - apt: - packages: - - libgnome-keyring-dev - - libicns - - graphicsmagick - - xz-utils - - rpm - - bsdtar - -before_install: - - npm install -g yarn - -cache: yarn - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-6 - -install: -- export CXX="g++-6" -- yarn -- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" - -before_script: -- export DISPLAY=:99.0 -- sh -e /etc/init.d/xvfb start & -- sleep 3 - -script: -- yarn run lint -- yarn run test -- yarn run build diff --git a/README.md b/README.md index 2f2f17ba..dbc98ff9 100644 --- a/README.md +++ b/README.md @@ -46,19 +46,26 @@ First, clone the repo via git: ```bash $ git clone https://github.com/cerebroapp/cerebro.git cerebro ``` +Open the project + +```bash +$ cd cerebro +``` And then install dependencies: + ```bash -$ cd cerebro && yarn && cd ./app && yarn && cd ../ +yarn ``` ### Run + ```bash $ yarn run dev ``` -> Note: requires a node version >=6.x +> Note: requires a node version >=16.x ### Resolve common issues 1. `AssertionError: Current node version is not supported for development` on npm postinstall. @@ -90,19 +97,26 @@ Use this command to build `.app` file: $ yarn build ``` +## For developers + +### Publish a release -## Be in touch -Follow to be notified about new releases or learn some productivity tips with Cerebro: +CerebroApp is using GH actions to build the app and publish it to a release. To publish a new release follow the steps below: -* [Twitter](https://twitter.com/cerebro_app) -* [Facebook](https://www.facebook.com/cerebroapp) -* [Google+](https://plus.google.com/104292436165594177472) -* [VK.com](https://vk.com/cerebroapp) – channel in Russian +1. Update the version on both `package.json` and `app/package.json` files. +2. Create a release with from GH and publish it. 🚧 The release **tag** SHOULD NOT contain the `v` prefix (❌ `v0.1.2` → ✅`0.1.2`). +3. Complete the name with a name and a description of the release. +4. The GH action is triggered and the release is updated when executables are built. -Or [subscribe to newsletter](http://eepurl.com/coiKU9) to be notified only about big releases. +### Add dependencies + +CerebroApp was created from an [old version of electron-react-boilerplate](https://github.com/cerebroapp/cerebro/commit/57b6e28c0f64beae8948cf17f099fa5d6236ae3c) and uses a two package.json file structure. If you are interested in developing a new feature, you should read about this structure in the [electron-react-boilerplate documentation](https://www.electron.build/tutorials/two-package-structure.html). + + +# Support -## Support ### Backers + Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/cerebro#backer)] @@ -171,4 +185,5 @@ Become a sponsor and get your logo on our README on Github with a link to your s ## License -MIT © [Alexandr Subbotin](https://github.com/KELiON) + +MIT © [Cerebro App](https://github.com/cerebroapp/cerebro/blob/master/LICENSE) diff --git a/__mocks__/@electron/remote.js b/__mocks__/@electron/remote.js new file mode 100644 index 00000000..e90881b1 --- /dev/null +++ b/__mocks__/@electron/remote.js @@ -0,0 +1,5 @@ +module.exports = { + app: { + getPath: jest.fn(), + } +} diff --git a/__mocks__/electron.js b/__mocks__/electron.js new file mode 100644 index 00000000..34a8edb5 --- /dev/null +++ b/__mocks__/electron.js @@ -0,0 +1,8 @@ +module.exports = { + app: { + getPath: jest.fn(), + }, + ipcRenderer: { + on: jest.fn(), + } +} diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js new file mode 100644 index 00000000..b0c50903 --- /dev/null +++ b/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = '' diff --git a/__mocks__/lib/config.js b/__mocks__/lib/config.js deleted file mode 100644 index 7acaac2b..00000000 --- a/__mocks__/lib/config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - -} diff --git a/__mocks__/lib/plugins.js b/__mocks__/lib/plugins.js deleted file mode 100644 index cdca2b57..00000000 --- a/__mocks__/lib/plugins.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - get: () => undefined -} diff --git a/__mocks__/plugins.js b/__mocks__/plugins.js deleted file mode 100644 index 6f336ba0..00000000 --- a/__mocks__/plugins.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - 'test-plugin': { - fn: () => {} - } -} diff --git a/app/background/background.js b/app/background/background.js index 5329c3c7..dd950457 100644 --- a/app/background/background.js +++ b/app/background/background.js @@ -3,8 +3,9 @@ import ReactDOM from 'react-dom' import plugins from 'plugins' import { on, send } from 'lib/rpc' import { settings as pluginSettings, modulesDirectory } from 'lib/plugins' +import fixPath from 'fix-path' -require('fix-path')() +fixPath() global.React = React global.ReactDOM = ReactDOM @@ -12,26 +13,25 @@ global.isBackground = true on('initializePluginAsync', ({ name }) => { console.group(`Initialize async plugin ${name}`) + try { const { initializeAsync } = plugins[name] ? plugins[name] : window.require(`${modulesDirectory}/${name}`) + if (!initializeAsync) { console.log('no `initializeAsync` function, skipped') return } + console.log('running `initializeAsync`') initializeAsync((data) => { console.log('Done! Sending data back to main window') // Send message back to main window with initialization result - send('plugin.message', { - name, - data, - }) + send('plugin.message', { name, data }) }, pluginSettings.getUserSettings(name)) - } catch (err) { - console.log('Failed', err) - } + } catch (err) { console.log('Failed', err) } + console.groupEnd() }) diff --git a/app/background/createWindow.js b/app/background/createWindow.js index 4e537f51..2086e1a9 100644 --- a/app/background/createWindow.js +++ b/app/background/createWindow.js @@ -10,6 +10,7 @@ export default ({ src }) => { contextIsolation: false }, }) + backgroundWindow.loadURL(src) return backgroundWindow } diff --git a/app/background/index.html b/app/background/index.html index ed56370c..888ceb9b 100644 --- a/app/background/index.html +++ b/app/background/index.html @@ -5,7 +5,9 @@ (function() { const script = document.createElement('script'); script.async = true; - script.src = (process.env.HOT) ? 'http://localhost:3000/dist/background.bundle.js' : '../dist/background.bundle.js'; + script.src = (process.env.HOT) + ? 'http://localhost:3000/dist/background.bundle.js' + : '../dist/background.bundle.js'; document.write(script.outerHTML); }()); diff --git a/app/initAutoUpdater.js b/app/initAutoUpdater.js index dd4953b3..14b63a2d 100644 --- a/app/initAutoUpdater.js +++ b/app/initAutoUpdater.js @@ -1,11 +1,12 @@ -import * as os from "os"; -import { dialog, } from 'electron' -import { autoUpdater } from "electron-updater"; +import { autoUpdater } from 'electron-updater' const event = 'update-downloaded' +const TEN_SECONDS = 10 * 1000 +const ONE_HOUR = 60 * 60 * 1000 + export default (w) => { - if (process.env.NODE_ENV === 'development' || os.platform() === "linux") { + if (process.env.NODE_ENV === 'development' || process.platform === 'linux') { return } @@ -18,9 +19,9 @@ export default (w) => { setTimeout(() => { autoUpdater.checkForUpdates() - }, 10 * 1000) + }, TEN_SECONDS) setInterval(() => { autoUpdater.checkForUpdates() - }, 60 * 60 * 1000) -} \ No newline at end of file + }, ONE_HOUR) +} diff --git a/app/lib/config.js b/app/lib/config.js index 40450bb4..e1fe07cb 100644 --- a/app/lib/config.js +++ b/app/lib/config.js @@ -1,9 +1,13 @@ -import { app, remote, ipcRenderer } from 'electron' +import { app, ipcRenderer } from 'electron' import fs from 'fs' import { memoize } from 'cerebro-tools' import { trackEvent } from './trackEvent' import loadThemes from './loadThemes' +const remote = process.type === 'browser' + ? undefined + : require('@electron/remote') + const electronApp = remote ? remote.app : app // initiate portable mode @@ -55,13 +59,13 @@ const readConfig = () => { */ const get = (key) => { let config + if (!fs.existsSync(CONFIG_FILE)) { // Save default config to local storage config = defaultSettings() fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)) - } else { - config = readConfig() - } + } else { config = readConfig() } + return config[key] } @@ -87,6 +91,7 @@ const set = (key, value) => { event: `Change ${key}`, label: value }) + if (ipcRenderer) { console.log('notify main process', key, value) // Notify main process about settings changes diff --git a/app/lib/getWindowPosition.js b/app/lib/getWindowPosition.js index 866f06e2..813bee64 100644 --- a/app/lib/getWindowPosition.js +++ b/app/lib/getWindowPosition.js @@ -1,17 +1,27 @@ -import { screen } from 'electron' - +import { ipcRenderer, screen } from 'electron' import { WINDOW_WIDTH, INPUT_HEIGHT, RESULT_HEIGHT, MIN_VISIBLE_RESULTS, -} from '../main/constants/ui' +} from 'main/constants/ui' /** * Computes window position */ export default ({ width, heightWithResults }) => { + if (process.type === 'renderer') { + const [x, y] = ipcRenderer.sendSync('get-window-position', { width, heightWithResults }) + return [x, y] + } + + const [x, y] = getWhenMainProcess({ width, heightWithResults }) + return [x, y] +} + +const getWhenMainProcess = ({ width, heightWithResults }) => { const winWidth = typeof width !== 'undefined' ? width : WINDOW_WIDTH + const winHeight = typeof heightWithResults !== 'undefined' ? heightWithResults : MIN_VISIBLE_RESULTS * RESULT_HEIGHT + INPUT_HEIGHT @@ -20,6 +30,6 @@ export default ({ width, heightWithResults }) => { const x = parseInt(display.bounds.x + (display.workAreaSize.width - winWidth) / 2, 10) const y = parseInt(display.bounds.y + (display.workAreaSize.height - winHeight) / 2, 10) + return [x, y] } - diff --git a/app/lib/initializePlugins.js b/app/lib/initializePlugins.js index 201630f0..8a733ccc 100644 --- a/app/lib/initializePlugins.js +++ b/app/lib/initializePlugins.js @@ -4,6 +4,7 @@ import { settings as pluginSettings } from 'lib/plugins' export const initializePlugin = (name) => { const { initialize, initializeAsync } = plugins[name] + if (initialize) { // Foreground plugin initialization try { diff --git a/app/lib/plugins/index.js b/app/lib/plugins/index.js index ec4d798d..e0e8d5d3 100644 --- a/app/lib/plugins/index.js +++ b/app/lib/plugins/index.js @@ -1,4 +1,4 @@ -import { app, remote } from 'electron' +import getAppDataPath from 'appdata-path' import path from 'path' import fs from 'fs' import npm from './npm' @@ -20,9 +20,9 @@ const EMPTY_PACKAGE_JSON = JSON.stringify({ dependencies: {} }, null, 2) -const electronApp = remote ? remote.app : app -export const pluginsPath = path.join(electronApp.getPath('userData'), 'plugins') +export const pluginsPath = path.join(getAppDataPath('Cerebro'), 'plugins') export const modulesDirectory = path.join(pluginsPath, 'node_modules') +export const cerebroappModulesDirectory = path.join(pluginsPath, 'node_modules', '@cerebroapp') export const packageJsonPath = path.join(pluginsPath, 'package.json') export const ensureFiles = () => { diff --git a/app/lib/plugins/npm.js b/app/lib/plugins/npm.js index dbfde714..306de6bc 100644 --- a/app/lib/plugins/npm.js +++ b/app/lib/plugins/npm.js @@ -1,20 +1,13 @@ import fs from 'fs' import os from 'os' -import rmdir from 'rmdir' +import rimraf from 'rimraf' import path from 'path' import tar from 'tar-fs' import zlib from 'zlib' import https from 'https' -import mv from 'mv' +import moveFile from 'move-file' -/** - * Promise-wrapper for rmdir - * @param {String} dir - * @return {Promise} - */ -const removeDir = dir => new Promise((resolve, reject) => { - rmdir(dir, err => err ? reject(err) : resolve()) -}) +const removeDir = (dir) => rimraf.sync(dir) /** * Base url of npm API @@ -30,33 +23,35 @@ const API_BASE = 'http://registry.npmjs.org/' * @param {Object} header * @return {Object} */ -const formatPackageFile = header => ({ +const formatPackageFile = (header) => ({ ...header, name: header.name.replace(/^package\//, '') }) -const installPackage = (tarPath, destination, middleware) => { +const installPackage = async (tarPath, destination, middleware) => { console.log(`Extract ${tarPath} to ${destination}`) - return new Promise((resolve, reject) => { - const packageName = path.parse(destination).name - const tempPath = `${os.tmpdir()}/${packageName}` - console.log(`Download and extract to temp path: ${tempPath}`) + + const packageName = path.parse(destination).name + const tempPath = path.join(os.tmpdir(), packageName) + + console.log(`Download and extract to temp path: ${tempPath}`) + + await new Promise((resolve, reject) => { https.get(tarPath, (stream) => { const result = stream - // eslint-disable-next-line new-cap .pipe(zlib.Unzip()) .pipe(tar.extract(tempPath, { map: formatPackageFile })) result.on('error', reject) result.on('finish', () => { - middleware().then(() => { - console.log(`Move ${tempPath} to ${destination}`) - mv(tempPath, destination, err => err ? reject(err) : resolve()) - }) + middleware().then(resolve) }) }) }) + + console.log(`Move ${tempPath} to ${destination}`) + moveFile.sync(tempPath, destination) } /** @@ -68,7 +63,7 @@ const installPackage = (tarPath, destination, middleware) => { */ export default (dir) => { const packageJson = path.join(dir, 'package.json') - const setConfig = config => ( + const setConfig = (config) => ( fs.writeFileSync(packageJson, JSON.stringify(config, null, 2)) ) const getConfig = () => JSON.parse(fs.readFileSync(packageJson)) @@ -84,35 +79,37 @@ export default (dir) => { * to temp folder, but before moving to real location * @return {Promise} */ - install(name, options = {}) { + async install(name, options = {}) { let versionToInstall const version = options.version || null const middleware = options.middleware || (() => Promise.resolve()) + console.group('[npm] Install package', name) - return fetch(`${API_BASE}${name}`) - .then(response => response.json()) - .then((json) => { - versionToInstall = version || json['dist-tags'].latest - console.log('Version:', versionToInstall) - return installPackage( - json.versions[versionToInstall].dist.tarball, - path.join(dir, 'node_modules', name), - middleware - ) - }) - .then(() => { - const json = getConfig() - json.dependencies[name] = versionToInstall - console.log('Add package to dependencies') - setConfig(json) - console.groupEnd() - }) - .catch((err) => { - console.log('Error in package installation') - console.log(err) - console.groupEnd() - }) + + try { + const resJson = await fetch(`${API_BASE}${name}`).then((response) => response.json()) + + versionToInstall = version || resJson['dist-tags'].latest + console.log('Version:', versionToInstall) + await installPackage( + resJson.versions[versionToInstall].dist.tarball, + path.join(dir, 'node_modules', name), + middleware + ) + + const json = getConfig() + json.dependencies[name] = versionToInstall + console.log('Add package to dependencies') + setConfig(json) + console.log('Finished installing', name) + console.groupEnd() + } catch (err) { + console.log('Error in package installation') + console.log(err) + console.groupEnd() + } }, + update(name) { // Plugin update is downloading `.tar` and unarchiving it to temp folder // Only if this part was succeeded, current version of plugin is uninstalled @@ -120,41 +117,34 @@ export default (dir) => { const middleware = () => this.uninstall(name) return this.install(name, { middleware }) }, + /** * Uninstall npm package * * @param {String} name * @return {Promise} */ - uninstall(name) { + async uninstall(name) { const modulePath = path.join(dir, 'node_modules', name) console.group('[npm] Uninstall package', name) console.log('Remove package directory ', modulePath) - return removeDir(modulePath) - .then(() => { - const json = getConfig() - console.log('Update package.json') - json.dependencies = Object - .keys(json.dependencies || {}) - .reduce((acc, key) => { - if (key !== name) { - return { - ...acc, - [key]: json.dependencies[key] - } - } - return acc - }, {}) - console.log('Rewrite package.json') - setConfig(json) - console.groupEnd() - return true - }) - .catch((err) => { - console.log('Error in package uninstallation') - console.log(err) - console.groupEnd() - }) + try { + removeDir(modulePath) // sync method + + const json = getConfig() + console.log('Update package.json') + delete json.dependencies?.[name] + + console.log('Rewrite package.json') + setConfig(json) + + console.groupEnd() + return true + } catch (err) { + console.log('Error in package uninstallation') + console.log(err) + console.groupEnd() + } } } } diff --git a/app/lib/plugins/settings/get.js b/app/lib/plugins/settings/get.js index b823e9bd..dc7685d4 100644 --- a/app/lib/plugins/settings/get.js +++ b/app/lib/plugins/settings/get.js @@ -5,12 +5,14 @@ const getSettings = pluginName => config.get('plugins')[pluginName] || {} const getUserSettings = (pluginName) => { const settings = getSettings(pluginName) + if (plugins[pluginName].settings) { // Provide default values if nothing is set by user Object.keys(plugins[pluginName].settings).forEach((key) => { settings[key] = settings[key] || plugins[pluginName].settings[key].defaultValue }) } + return settings } diff --git a/app/lib/rpc.js b/app/lib/rpc.js index e4eeaeb7..a9577bfb 100644 --- a/app/lib/rpc.js +++ b/app/lib/rpc.js @@ -18,14 +18,11 @@ ipcRenderer.on(CHANNEL, (_, { message, payload }) => { /** * Send message to rpc-channel * @param {String} message - * @param {} payload + * @param {any} payload */ export const send = (message, payload) => { console.log(`[rpc] send ${message}`) - ipcRenderer.send(CHANNEL, { - message, - payload - }) + ipcRenderer.send(CHANNEL, { message, payload }) } export const on = emitter.on.bind(emitter) diff --git a/app/lib/trackEvent.js b/app/lib/trackEvent.js index 20e76884..c22a11e7 100644 --- a/app/lib/trackEvent.js +++ b/app/lib/trackEvent.js @@ -5,16 +5,13 @@ import config from './config' const DEFAULT_CATEGORY = 'Cerebro App' -const isTrackingEnabled = () => ( - process.env.NODE_ENV === 'production' && config.get('trackingEnabled') -) +const isTrackingEnabled = () => (process.env.NODE_ENV === 'production' && config.get('trackingEnabled')) let visitorCache = null const visitor = () => { - if (visitorCache) { - return visitorCache - } + if (visitorCache) return visitorCache + if (isTrackingEnabled()) { try { visitorCache = ua('UA-87361302-1', machineIdSync(), { strictCidFormat: false }) diff --git a/app/main.development.js b/app/main.development.js index f741b0ac..0a77c2c7 100644 --- a/app/main.development.js +++ b/app/main.development.js @@ -1,4 +1,12 @@ -import { app, ipcMain, crashReporter } from 'electron' +import { + app, ipcMain, crashReporter, screen +} from 'electron' +import { + WINDOW_WIDTH, + INPUT_HEIGHT, + RESULT_HEIGHT, + MIN_VISIBLE_RESULTS, +} from 'main/constants/ui' import createMainWindow from './main/createWindow' import createBackgroundWindow from './background/createWindow' @@ -7,16 +15,15 @@ import AppTray from './main/createWindow/AppTray' import autoStart from './main/createWindow/autoStart' import initAutoUpdater from './initAutoUpdater' -let trayIconSrc = `${__dirname}/tray_icon.png` -if (process.platform === 'darwin') { - trayIconSrc = `${__dirname}/tray_iconTemplate@2x.png` -} else if (process.platform === 'win32') { - trayIconSrc = `${__dirname}/tray_icon.ico` +const iconSrc = { + DEFAULT: `${__dirname}/tray_icon.png`, + darwin: `${__dirname}/tray_iconTemplate@2x.png`, + win32: `${__dirname}/tray_icon.ico` } -const isDev = () => ( - process.env.NODE_ENV === 'development' || config.get('developerMode') -) +const trayIconSrc = iconSrc[process.platform] || iconSrc.DEFAULT + +const isDev = () => (process.env.NODE_ENV === 'development' || config.get('developerMode')) let mainWindow let backgroundWindow @@ -37,17 +44,23 @@ if (process.env.NODE_ENV !== 'development') { } } -app.on('ready', () => { +app.whenReady().then(() => { mainWindow = createMainWindow({ isDev, - // Main window html - src: `file://${__dirname}/main/index.html`, + src: `file://${__dirname}/main/index.html`, // Main window html }) + // eslint-disable-next-line global-require + require('@electron/remote/main').initialize() + // eslint-disable-next-line global-require + require('@electron/remote/main').enable(mainWindow.webContents) backgroundWindow = createBackgroundWindow({ src: `file://${__dirname}/background/index.html`, }) + // eslint-disable-next-line global-require + require('@electron/remote/main').enable(backgroundWindow.webContents) + tray = new AppTray({ src: trayIconSrc, isDev: isDev(), @@ -56,11 +69,9 @@ app.on('ready', () => { }) // Show tray icon if it is set in configuration - if (config.get('showInTray')) { - tray.show() - } + if (config.get('showInTray')) { tray.show() } - autoStart.isEnabled().then(enabled => { + autoStart.isEnabled().then((enabled) => { if (config.get('openAtLogin') !== enabled) { autoStart.set(config.get('openAtLogin')) } @@ -81,20 +92,36 @@ ipcMain.on('updateSettings', (event, key, value) => { // Show or hide menu bar icon when it is changed in setting if (key === 'showInTray') { - value ? tray.show() : tray.hide() + value + ? tray.show() + : tray.hide() } // Show or hide "development" section in tray menu - if (key === 'developerMode') { - tray.setIsDev(isDev()) - } + if (key === 'developerMode') { tray.setIsDev(isDev()) } // Enable or disable auto start if (key === 'openAtLogin') { - autoStart.isEnabled().then(enabled => { - if (value !== enabled) { - autoStart.set(value) - } + autoStart.isEnabled().then((enabled) => { + if (value !== enabled) autoStart.set(value) }) } }) + +ipcMain.on('get-window-position', (event, { width, heightWithResults }) => { + const winWidth = typeof width !== 'undefined' ? width : WINDOW_WIDTH + + const winHeight = typeof heightWithResults !== 'undefined' + ? heightWithResults + : MIN_VISIBLE_RESULTS * RESULT_HEIGHT + INPUT_HEIGHT + + const display = screen.getPrimaryDisplay() + + const x = parseInt(display.bounds.x + (display.workAreaSize.width - winWidth) / 2, 10) + const y = parseInt(display.bounds.y + (display.workAreaSize.height - winHeight) / 2, 10) + + // eslint-disable-next-line no-param-reassign + event.returnValue = [x, y] +}) + +ipcMain.on('quit', () => app.quit()) diff --git a/app/main/actions/search.js b/app/main/actions/search.js index 1c8e4246..4fb70476 100644 --- a/app/main/actions/search.js +++ b/app/main/actions/search.js @@ -1,10 +1,8 @@ import plugins from 'plugins' import config from 'lib/config' -import { shell, clipboard, remote } from 'electron' -import { settings as pluginSettings } from 'lib/plugins' - -import store from '../store' +import { shell, clipboard } from 'electron' +import { settings as pluginSettings } from 'lib/plugins' import { UPDATE_TERM, MOVE_CURSOR, @@ -14,7 +12,13 @@ import { UPDATE_RESULT, RESET, CHANGE_VISIBLE_RESULTS, -} from '../constants/actionTypes' +} from 'main/constants/actionTypes' + +import store from '../store' + +const remote = process.type === 'browser' + ? undefined + : require('@electron/remote') /** * Default scope object would be first argument for plugins @@ -81,9 +85,7 @@ function onResultFound(term, result) { * @return {Object} redux action */ export function reset() { - return { - type: RESET, - } + return { type: RESET } } /** @@ -93,9 +95,8 @@ export function reset() { * @return {Object} redux action */ export function updateTerm(term) { - if (term === '') { - return reset() - } + if (term === '') return reset() + return (dispatch) => { dispatch({ type: UPDATE_TERM, diff --git a/app/main/actions/search.spec.js b/app/main/actions/search.spec.js index dcb1e1dd..c3743cc6 100644 --- a/app/main/actions/search.spec.js +++ b/app/main/actions/search.spec.js @@ -1,11 +1,16 @@ -import * as actions from './search' +/** + * @jest-environment jsdom + */ + import { MOVE_CURSOR, SELECT_ELEMENT, UPDATE_RESULT, HIDE_RESULT, RESET, -} from '../constants/actionTypes' +} from 'main/constants/actionTypes' + +import * as actions from './search' describe('reset', () => { it('returns valid action', () => { diff --git a/app/main/components/Cerebro/index.js b/app/main/components/Cerebro/index.js index c07f6a1d..521641b8 100644 --- a/app/main/components/Cerebro/index.js +++ b/app/main/components/Cerebro/index.js @@ -1,29 +1,33 @@ /* eslint default-case: 0 */ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import { clipboard, remote } from 'electron' -import { focusableSelector } from 'cerebro-ui' +import { clipboard, BrowserWindow } from 'electron' +import { focusableSelector } from '@cerebroapp/cerebro-ui' import escapeStringRegexp from 'escape-string-regexp' import debounce from 'lodash/debounce' import { trackEvent } from 'lib/trackEvent' import getWindowPosition from 'lib/getWindowPosition' +import { + WINDOW_WIDTH, + INPUT_HEIGHT, + RESULT_HEIGHT, + MIN_VISIBLE_RESULTS, +} from 'main/constants/ui' +import * as searchActions from 'main/actions/search' import MainInput from '../MainInput' import ResultsList from '../ResultsList' import StatusBar from '../StatusBar' import styles from './styles.css' -import * as searchActions from '../../actions/search' -import { - WINDOW_WIDTH, - INPUT_HEIGHT, - RESULT_HEIGHT, - MIN_VISIBLE_RESULTS, -} from '../../constants/ui' +const remote = process.type === 'browser' + ? { getCurrentWindow: BrowserWindow.getFocusedWindow } + : require('@electron/remote') const SHOW_EVENT = { category: 'Window', @@ -36,7 +40,7 @@ const SELECT_EVENT = { } const trackShowWindow = () => trackEvent(SHOW_EVENT) -const trackSelectItem = label => trackEvent({ ...SELECT_EVENT, label }) +const trackSelectItem = (label) => trackEvent({ ...SELECT_EVENT, label }) /** * Wrap click or mousedown event to custom `select-item` event, @@ -60,9 +64,7 @@ const wrapEvent = (realEvent) => { const focusPreview = () => { const previewDom = document.getElementById('preview') const firstFocusable = previewDom.querySelector(focusableSelector) - if (firstFocusable) { - firstFocusable.focus() - } + if (firstFocusable) { firstFocusable.focus() } } /** @@ -97,10 +99,7 @@ class Cerebro extends Component { this.focusMainInput = this.focusMainInput.bind(this) this.selectItem = this.selectItem.bind(this) - - this.state = { - mainInputFocused: false - } + this.state = { mainInputFocused: false } } componentWillMount() { @@ -115,10 +114,12 @@ class Cerebro extends Component { this.electronWindow.on('show', this.updateElectronWindow) this.electronWindow.on('show', trackShowWindow) } + componentDidMount() { this.focusMainInput() this.updateElectronWindow() } + componentDidUpdate(prevProps) { const { results } = this.props if (results.length !== prevProps.results.length) { @@ -126,6 +127,7 @@ class Cerebro extends Component { this.updateElectronWindow() } } + componentWillUnmount() { this.cleanup() } @@ -160,14 +162,11 @@ class Cerebro extends Component { if (highlighted && highlighted.onKeyDown) { highlighted.onKeyDown(event) } - if (event.defaultPrevented) { - return - } + if (event.defaultPrevented) { return } const keyActions = { - select: () => { - this.selectCurrent(event) - }, + select: () => this.selectCurrent(event), + arrowRight: () => { if (cursorInEndOfInut(event.target)) { if (this.autocompleteValue()) { @@ -179,10 +178,12 @@ class Cerebro extends Component { } } }, + arrowDown: () => { this.props.actions.moveCursor(1) event.preventDefault() }, + arrowUp: () => { if (this.props.results.length > 0) { this.props.actions.moveCursor(-1) @@ -193,7 +194,6 @@ class Cerebro extends Component { } } - if (event.metaKey || event.ctrlKey) { if (event.keyCode === 67) { // Copy to clipboard on cmd+c @@ -213,6 +213,7 @@ class Cerebro extends Component { return this.selectItem(result) } } + // Lightweight vim-mode: cmd/ctrl + jklo switch (event.keyCode) { case 74: @@ -229,6 +230,7 @@ class Cerebro extends Component { break } } + switch (event.keyCode) { case 9: this.autocomplete(event) @@ -306,6 +308,7 @@ class Cerebro extends Component { event.preventDefault() } } + /** * Select highlighted element */ @@ -350,6 +353,7 @@ class Cerebro extends Component { } return '' } + /** * Render autocomplete suggestion from selected item * @return {React} @@ -360,6 +364,7 @@ class Cerebro extends Component { return
{term}
} } + render() { const { mainInputFocused } = this.state return ( @@ -408,7 +413,7 @@ Cerebro.propTypes = { function mapStateToProps(state) { return { selected: state.search.selected, - results: state.search.resultIds.map(id => state.search.resultsById[id]), + results: state.search.resultIds.map((id) => state.search.resultsById[id]), term: state.search.term, statusBarText: state.statusBar.text, prevTerm: state.search.prevTerm, diff --git a/app/main/components/MainInput/index.js b/app/main/components/MainInput/index.js index 36b603cc..1954081a 100644 --- a/app/main/components/MainInput/index.js +++ b/app/main/components/MainInput/index.js @@ -1,10 +1,12 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import styles from './styles.css' class MainInput extends Component { focus() { this.refs.input.focus() } + render() { return ( this.props.onChange(e.target.value)} + onChange={(e) => this.props.onChange(e.target.value)} onKeyDown={this.props.onKeyDown} onFocus={this.props.onFocus} onBlur={this.props.onBlur} diff --git a/app/main/components/ResultsList/Row/index.js b/app/main/components/ResultsList/Row/index.js index 315a57df..803cda5d 100644 --- a/app/main/components/ResultsList/Row/index.js +++ b/app/main/components/ResultsList/Row/index.js @@ -1,5 +1,6 @@ -import React, { PropTypes, Component } from 'react' -import { SmartIcon } from 'cerebro-ui' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { SmartIcon } from '@cerebroapp/cerebro-ui' import styles from './styles.css' class Row extends Component { @@ -9,11 +10,13 @@ class Row extends Component { this.props.selected ? styles.selected : null ].join(' ') } + renderIcon() { const { icon } = this.props if (!icon) return null return } + render() { const { title, @@ -21,12 +24,21 @@ class Row extends Component { onMouseMove, subtitle } = this.props + return (
{this.renderIcon()}
- {title &&
{title}
} - {subtitle &&
{subtitle}
} + {title && ( +
+ {` ${title} `} +
+ )} + {subtitle && ( +
+ {` ${subtitle} `} +
+ )}
) diff --git a/app/main/components/ResultsList/index.js b/app/main/components/ResultsList/index.js index 943e7ecc..0e88bddc 100644 --- a/app/main/components/ResultsList/index.js +++ b/app/main/components/ResultsList/index.js @@ -1,9 +1,10 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { VirtualScroll } from 'react-virtualized' +import { RESULT_HEIGHT } from 'main/constants/ui' import Row from './Row' import styles from './styles.css' -import { RESULT_HEIGHT } from '../../constants/ui' class ResultsList extends Component { diff --git a/app/main/components/StatusBar/index.js b/app/main/components/StatusBar/index.js index bee0af06..bb706e47 100644 --- a/app/main/components/StatusBar/index.js +++ b/app/main/components/StatusBar/index.js @@ -1,4 +1,5 @@ import React from 'react' +import PropTypes from 'prop-types' import styles from './styles.css' const StatusBar = ({ value }) => ( @@ -6,7 +7,7 @@ const StatusBar = ({ value }) => ( ) StatusBar.propTypes = { - value: React.PropTypes.string + value: PropTypes.string } export default StatusBar diff --git a/app/main/createWindow.js b/app/main/createWindow.js index 3921c162..f346394d 100644 --- a/app/main/createWindow.js +++ b/app/main/createWindow.js @@ -1,7 +1,9 @@ import { BrowserWindow, globalShortcut, app, screen, shell } from 'electron' import debounce from 'lodash/debounce' import EventEmitter from 'events' -import { trackEvent, screenView } from '../lib/trackEvent' +import { trackEvent, screenView } from 'lib/trackEvent' +import config from 'lib/config' +import getWindowPosition from 'lib/getWindowPosition' import { INPUT_HEIGHT, @@ -11,8 +13,6 @@ import { import buildMenu from './createWindow/buildMenu' import toggleWindow from './createWindow/toggleWindow' import handleUrl from './createWindow/handleUrl' -import config from '../lib/config' -import getWindowPosition from '../lib/getWindowPosition' import * as donateDialog from './createWindow/donateDialog' export default ({ src, isDev }) => { diff --git a/app/main/createWindow/AppTray.js b/app/main/createWindow/AppTray.js index b865e187..490ffb48 100644 --- a/app/main/createWindow/AppTray.js +++ b/app/main/createWindow/AppTray.js @@ -19,6 +19,7 @@ export default class AppTray { this.tray = null this.options = options } + /** * Show application icon in menu bar */ @@ -28,12 +29,14 @@ export default class AppTray { tray.setContextMenu(this.buildMenu()) this.tray = tray } + setIsDev(isDev) { this.options.isDev = isDev if (this.tray) { this.tray.setContextMenu(this.buildMenu()) } } + buildMenu() { const { mainWindow, backgroundWindow, isDev } = this.options const separator = { type: 'separator' } @@ -68,20 +71,23 @@ export default class AppTray { template.push(separator) template.push({ label: 'Development', - submenu: [{ - label: 'DevTools (main)', - click: () => mainWindow.webContents.openDevTools({ mode: 'detach' }) - }, { - label: 'DevTools (background)', - click: () => backgroundWindow.webContents.openDevTools({ mode: 'detach' }) - }, { - label: 'Reload', - click: () => { - mainWindow.reload() - backgroundWindow.reload() - backgroundWindow.hide() - } - }] + submenu: [ + { + label: 'DevTools (main)', + click: () => mainWindow.webContents.openDevTools({ mode: 'detach' }) + }, + { + label: 'DevTools (background)', + click: () => backgroundWindow.webContents.openDevTools({ mode: 'detach' }) + }, + { + label: 'Reload', + click: () => { + mainWindow.reload() + backgroundWindow.reload() + backgroundWindow.hide() + } + }] }) } @@ -92,6 +98,7 @@ export default class AppTray { }) return Menu.buildFromTemplate(template) } + /** * Hide icon in menu bar */ diff --git a/app/main/createWindow/autoStart.js b/app/main/createWindow/autoStart.js index 64936368..e28fa0fe 100644 --- a/app/main/createWindow/autoStart.js +++ b/app/main/createWindow/autoStart.js @@ -5,20 +5,21 @@ let appLauncher const isLinux = ['win32', 'darwin'].indexOf(process.platform) === -1 -if (isLinux) { - appLauncher = new AutoLaunch({ name: 'Cerebro' }) -} +if (isLinux) { appLauncher = new AutoLaunch({ name: 'Cerebro' }) } -const isEnabled = () => ( +const isEnabled = async () => ( isLinux ? appLauncher.isEnabled() - : Promise.resolve(app.getLoginItemSettings().openAtLogin) + : app.getLoginItemSettings().openAtLogin ) const set = (openAtLogin) => { if (isLinux) { - return openAtLogin ? appLauncher.enable() : appLauncher.disable() + return openAtLogin + ? appLauncher.enable() + : appLauncher.disable() } + return app.setLoginItemSettings({ openAtLogin }) } diff --git a/app/main/createWindow/buildMenu.js b/app/main/createWindow/buildMenu.js index 973cbf77..d315bfd3 100644 --- a/app/main/createWindow/buildMenu.js +++ b/app/main/createWindow/buildMenu.js @@ -6,63 +6,69 @@ export default (mainWindow) => { submenu: [{ label: 'About ElectronReact', selector: 'orderFrontStandardAboutPanel:' - }, { - type: 'separator' - }, { + }, + { type: 'separator' }, + { label: 'Services', submenu: [] - }, { - type: 'separator' - }, { + }, + { type: 'separator' }, + { label: 'Hide ElectronReact', accelerator: 'Command+H', selector: 'hide:' - }, { + }, + { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' - }, { + }, + { label: 'Show All', selector: 'unhideAllApplications:' - }, { - type: 'separator' - }, { + }, + { type: 'separator' }, + { label: 'Quit', accelerator: 'Command+Q', - click() { - app.quit() - } + click() { app.quit() } }] - }, { + }, + { label: 'Edit', submenu: [{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' - }, { + }, + { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' - }, { - type: 'separator' - }, { + }, + { type: 'separator' }, + { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' - }, { + }, + { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' - }, { + }, + { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' - }, { + }, + { label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' }] - }, { + }, + { label: 'View', submenu: [{ label: 'Toggle Full Screen', @@ -71,44 +77,43 @@ export default (mainWindow) => { mainWindow.setFullScreen(!mainWindow.isFullScreen()) } }] - }, { + }, + { label: 'Window', submenu: [{ label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' - }, { + }, + { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' - }, { - type: 'separator' - }, { + }, + { type: 'separator' }, + { label: 'Bring All to Front', selector: 'arrangeInFront:' }] - }, { + }, + + { label: 'Help', submenu: [{ label: 'Learn More', - click() { - shell.openExternal('http://electron.atom.io') - } - }, { + click() { shell.openExternal('http://electron.atom.io') } + }, + { label: 'Documentation', - click() { - shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme') - } - }, { + click() { shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme') } + }, + { label: 'Community Discussions', - click() { - shell.openExternal('https://discuss.atom.io/c/electron') - } - }, { + click() { shell.openExternal('https://discuss.atom.io/c/electron') } + }, + { label: 'Search Issues', - click() { - shell.openExternal('https://github.com/atom/electron/issues') - } + click() { shell.openExternal('https://github.com/atom/electron/issues') } }] }] diff --git a/app/main/createWindow/checkForUpdates.js b/app/main/createWindow/checkForUpdates.js index ec1e3dc5..e81e6c4c 100644 --- a/app/main/createWindow/checkForUpdates.js +++ b/app/main/createWindow/checkForUpdates.js @@ -1,7 +1,5 @@ import { dialog, app, shell } from 'electron' -import os from 'os' -import semver from 'semver' -import https from 'https' +import { autoUpdater } from 'electron-updater' const currentVersion = app.getVersion() const DEFAULT_DOWNLOAD_URL = 'https://github.com/cerebroapp/cerebro/releases' @@ -13,43 +11,25 @@ const PLATFORM_EXTENSIONS = { linux: 'AppImage', win32: 'exe' } -const platform = os.platform() + +const { platform } = process const installerExtension = PLATFORM_EXTENSIONS[platform] const findInstaller = (assets) => { - if (!installerExtension) { - return DEFAULT_DOWNLOAD_URL - } + if (!installerExtension) return DEFAULT_DOWNLOAD_URL + const regexp = new RegExp(`\.${installerExtension}$`) const downloadUrl = assets - .map(a => a.browser_download_url) - .find(url => url.match(regexp)) + .find(({ url }) => url.match(regexp)) + return downloadUrl || DEFAULT_DOWNLOAD_URL } -const getLatestRelease = () => ( - new Promise((resolve, reject) => { - const opts = { - host: 'api.github.com', - path: '/repos/cerebro/cerebro/releases', - headers: { - 'User-Agent': `CerebroApp v${currentVersion}` - } - } - https.get(opts, (res) => { - let json = '' - res.on('data', (chunk) => { - json += chunk - }) - res.on('end', () => resolve(JSON.parse(json)[0])) - }).on('error', () => reject()) - }) -) - -export default () => { - getLatestRelease().then((release) => { - const version = semver.valid(release.tag_name) - if (version && semver.gt(version, currentVersion)) { +export default async () => { + try { + const release = await autoUpdater.checkForUpdates() + if (release) { + const { updateInfo: { version, files } } = release dialog.showMessageBox({ buttons: ['Skip', 'Download'], defaultId: 1, @@ -59,7 +39,7 @@ export default () => { detail: 'Click download to get it now', }, (response) => { if (response === 1) { - const url = findInstaller(release.assets) + const url = findInstaller(files) shell.openExternal(url) } }) @@ -70,11 +50,8 @@ export default () => { buttons: [] }) } - }).catch((err) => { + } catch (err) { console.log('Catch error!', err) - dialog.showErrorBox( - TITLE, - 'Error fetching latest version', - ) - }) + dialog.showErrorBox(TITLE, 'Error fetching latest version') + } } diff --git a/app/main/createWindow/donateDialog.js b/app/main/createWindow/donateDialog.js index aa84453b..a09bde1e 100644 --- a/app/main/createWindow/donateDialog.js +++ b/app/main/createWindow/donateDialog.js @@ -20,19 +20,12 @@ export const shouldShow = () => { return !lastShow || twoWeeksAgo() >= lastShow } -/* eslint-disable max-len */ const messages = [ 'Developers try to make you happy with this free and open-source app. Make them happy too with your donation!' ] -const buttons = [ - ['Close', 'Make them happy'] -] - -const skipMessages = [ - "I don't want to see this message again", -] -/* eslint-enable max-len */ +const buttons = [['Close', 'Make them happy']] +const skipMessages = ["I don't want to see this message again"] export const show = () => { config.set('lastShownDonateDialog', now()) @@ -65,9 +58,7 @@ export const show = () => { if (id === 1) { track('choose-donate') donate() - } else { - track('cancel') - } + } else { track('cancel') } } setTimeout(() => { diff --git a/app/main/css/global.css b/app/main/css/global.css index aa7b9b91..b91396f9 100644 --- a/app/main/css/global.css +++ b/app/main/css/global.css @@ -1,7 +1,7 @@ @import "system-font.css"; -@import "../../../node_modules/normalize.css/normalize.css"; -@import "../../../node_modules/react-virtualized/styles.css"; -@import "../../../node_modules/react-select/dist/react-select.css"; +@import url("~normalize.css/normalize.css"); +@import url("~react-virtualized/styles.css"); +@import url("~react-select/dist/react-select.css"); html, body { margin: 0; diff --git a/app/main/index.html b/app/main/index.html index fafb86b4..a8b35220 100644 --- a/app/main/index.html +++ b/app/main/index.html @@ -22,7 +22,9 @@ (function() { const script = document.createElement('script'); script.async = true; - script.src = (process.env.HOT) ? 'http://localhost:3000/dist/main.bundle.js' : '../dist/main.bundle.js'; + script.src = (process.env.HOT) + ? 'http://localhost:3000/dist/main.bundle.js' + : '../dist/main.bundle.js'; document.write(script.outerHTML); }()); diff --git a/app/main/main.js b/app/main/main.js index 90938976..2ccf8c19 100644 --- a/app/main/main.js +++ b/app/main/main.js @@ -1,16 +1,17 @@ import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' +import fixPath from 'fix-path' import initializePlugins from 'lib/initializePlugins' import { on } from 'lib/rpc' +import config from 'lib/config' import { updateTerm } from './actions/search' -import config from '../lib/config' import store from './store' import Cerebro from './components/Cerebro' import './css/global.css' -require('fix-path')() +fixPath() global.React = React global.ReactDOM = ReactDOM diff --git a/app/main/reducers/search.js b/app/main/reducers/search.js index 41161388..be092d22 100644 --- a/app/main/reducers/search.js +++ b/app/main/reducers/search.js @@ -11,9 +11,9 @@ import { UPDATE_RESULT, RESET, CHANGE_VISIBLE_RESULTS -} from '../constants/actionTypes' +} from 'main/constants/actionTypes' -import { MIN_VISIBLE_RESULTS } from '../constants/ui' +import { MIN_VISIBLE_RESULTS } from 'main/constants/ui' const initialState = { // Search term in main input diff --git a/app/main/reducers/statusBar.js b/app/main/reducers/statusBar.js index b64f097a..0ba5614f 100644 --- a/app/main/reducers/statusBar.js +++ b/app/main/reducers/statusBar.js @@ -2,7 +2,7 @@ import { SET_STATUS_BAR_TEXT -} from '../constants/actionTypes' +} from 'main/constants/actionTypes' const initialState = { text: null diff --git a/app/package.json b/app/package.json index ebe1d667..8b91594b 100644 --- a/app/package.json +++ b/app/package.json @@ -1,8 +1,8 @@ { "name": "cerebro", "productName": "Cerebro", - "description": "Extendable electron-based open-source Spotlight and Alfred analogue", - "version": "0.5.0", + "description": "Cerebro is a open-source launcher to improve your productivity and efficiency", + "version": "0.6.0", "main": "./main.js", "license": "MIT", "author": { @@ -11,17 +11,20 @@ "url": "https://github.com/KELiON" }, "contributors": [ - "Gustavo Pereira" + "Gustavo Pereira", + "David Jiménez" ], "scripts": { "rebuild": "npm rebuild --runtime=electron --target=1.6.11 --disturl=https://atom.io/download/electron --abi=53" }, "dependencies": { - "chokidar": "^1.6.1", - "electron-updater": "^4.3.9", - "rmdir": "^1.2.0", - "semver": "^5.3.0", - "universal-analytics": "^0.4.8" + "move-file": "2.1.0", + "chokidar": "3.5.3", + "electron-updater": "5.0.1", + "fix-path": "3.0.0", + "rimraf": "3.0.2", + "semver": "7.3.7", + "universal-analytics": "0.5.3" }, "optionalDependencies": {}, "devDependencies": {} diff --git a/app/plugins/core/autocomplete/index.js b/app/plugins/core/autocomplete/index.js index 50d98405..18b4cdb4 100644 --- a/app/plugins/core/autocomplete/index.js +++ b/app/plugins/core/autocomplete/index.js @@ -1,13 +1,15 @@ import { search } from 'cerebro-tools' -import plugins from '../../index' -import { flow, filter, map, partialRight, values } from 'lodash/fp' +import { + flow, filter, map, partialRight, values +} from 'lodash/fp' +import plugins from 'plugins' -const toString = plugin => plugin.keyword -const notMatch = term => plugin => ( +const toString = (plugin) => plugin.keyword +const notMatch = (term) => (plugin) => ( plugin.keyword !== term && `${plugin.keyword} ` !== term ) -const pluginToResult = actions => res => ({ +const pluginToResult = (actions) => (res) => ({ title: res.name, icon: res.icon, term: `${res.keyword} `, @@ -25,14 +27,11 @@ const pluginToResult = actions => res => ({ */ const fn = ({ term, display, actions }) => flow( values, - filter(plugin => !!plugin.keyword), + filter((plugin) => !!plugin.keyword), partialRight(search, [term, toString]), filter(notMatch(term)), map(pluginToResult(actions)), display )(plugins) -export default { - fn, - name: 'Plugins autocomplete', -} +export default { fn, name: 'Plugins autocomplete' } diff --git a/app/plugins/core/index.js b/app/plugins/core/index.js index 78ec4a21..27139254 100644 --- a/app/plugins/core/index.js +++ b/app/plugins/core/index.js @@ -5,6 +5,4 @@ import settings from './settings' import version from './version' import reload from './reload' -export default { - autocomplete, quit, plugins, settings, version, reload -} +export default { autocomplete, quit, plugins, settings, version, reload } diff --git a/app/plugins/core/plugins/Preview/ActionButton.js b/app/plugins/core/plugins/Preview/ActionButton.js index 437e114a..474bc02a 100644 --- a/app/plugins/core/plugins/Preview/ActionButton.js +++ b/app/plugins/core/plugins/Preview/ActionButton.js @@ -1,9 +1,10 @@ -import React, { PropTypes } from 'react' -import { KeyboardNavItem } from 'cerebro-ui' +import React from 'react' +import PropTypes from 'prop-types' +import { KeyboardNavItem } from '@cerebroapp/cerebro-ui' -const ActionButton = ({ action, onComplete, text }) => { +function ActionButton({ action, onComplete, text }) { const onSelect = () => { - const timeout = new Promise(resolve => setTimeout(resolve, 1500)) + const timeout = new Promise((resolve) => setTimeout(resolve, 1500)) Promise.all([action(), timeout]).then(onComplete) } return ( diff --git a/app/plugins/core/plugins/Preview/FormItem.js b/app/plugins/core/plugins/Preview/FormItem.js index b592de77..709322e6 100644 --- a/app/plugins/core/plugins/Preview/FormItem.js +++ b/app/plugins/core/plugins/Preview/FormItem.js @@ -1,17 +1,18 @@ -import React, { PropTypes } from 'react' -import { Select, Text, Checkbox } from 'cerebro-ui/Form' +import React from 'react' +import PropTypes from 'prop-types' +import { FormComponents } from '@cerebroapp/cerebro-ui' + +const { Checkbox, Select, Text } = FormComponents const components = { bool: Checkbox, option: Select, } -const FormItem = ({ type, ...props }) => { +function FormItem({ type, ...props }) { const Component = components[type] || Text - return ( - - ) + return } FormItem.propTypes = { diff --git a/app/plugins/core/plugins/Preview/Settings.js b/app/plugins/core/plugins/Preview/Settings.js index 971c68a2..e1ef2881 100644 --- a/app/plugins/core/plugins/Preview/Settings.js +++ b/app/plugins/core/plugins/Preview/Settings.js @@ -1,4 +1,5 @@ -import React, { PropTypes, Component } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import config from 'lib/config' import FormItem from './FormItem' import styles from './styles.css' @@ -45,9 +46,7 @@ export default class Settings extends Component { render() { return (
- { - Object.keys(this.props.settings).map(this.renderSetting) - } + { Object.keys(this.props.settings).map(this.renderSetting) }
) } diff --git a/app/plugins/core/plugins/Preview/index.js b/app/plugins/core/plugins/Preview/index.js index d17850b3..115de4e5 100644 --- a/app/plugins/core/plugins/Preview/index.js +++ b/app/plugins/core/plugins/Preview/index.js @@ -1,14 +1,16 @@ -import React, { PropTypes, Component } from 'react' -import { KeyboardNav, KeyboardNavItem, Preload } from 'cerebro-ui' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { KeyboardNav, KeyboardNavItem, Preload } from '@cerebroapp/cerebro-ui' +import { trackEvent } from 'lib/trackEvent' +import { client } from 'lib/plugins' +import plugins from 'plugins' +import ReactMarkdown from 'react-markdown' + import ActionButton from './ActionButton.js' import Settings from './Settings' import getReadme from '../getReadme' -import ReactMarkdown from 'react-markdown' import styles from './styles.css' -import { trackEvent } from 'lib/trackEvent' import * as format from '../format' -import { client } from 'lib/plugins' -import plugins from 'plugins' const isRelative = (src) => !src.match(/^(https?:|data:)/) const urlTransform = (repo, src) => { @@ -77,37 +79,50 @@ class Preview extends Component { const settings = plugins[name] ? plugins[name].settings : null return (
-

{format.name(name)} ({version})

+

+ {format.name(name)} + {' '} + ( + {version} + ) +

{format.description(description)}

{ - settings && + settings + && ( this.setState({ showSettings: !this.state.showSettings })} > Settings + ) } {showSettings && } { - !isInstalled && !isDebugging && + !isInstalled && !isDebugging + && ( + ) } { - isInstalled && + isInstalled + && ( + ) } { - isUpdateAvailable && + isUpdateAvailable + && ( + ) } { - githubRepo && + githubRepo + && ( this.setState({ showDescription: !this.state.showDescription })} > Details + ) }
diff --git a/app/plugins/core/plugins/Preview/styles.css b/app/plugins/core/plugins/Preview/styles.css index da997e14..c1ea0080 100644 --- a/app/plugins/core/plugins/Preview/styles.css +++ b/app/plugins/core/plugins/Preview/styles.css @@ -9,11 +9,10 @@ } .markdown { - min-width: 100%; font-size: .8em; align-self: flex-start; font-size: 16px; - padding: 10px; + padding: 0 10px; p { font-size: 1em; margin: 0 0 10px; diff --git a/app/plugins/core/plugins/StatusBar/index.js b/app/plugins/core/plugins/StatusBar/index.js index fe11d79a..91b91e52 100644 --- a/app/plugins/core/plugins/StatusBar/index.js +++ b/app/plugins/core/plugins/StatusBar/index.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import styles from './styles.css' const StatusBar = ({ value }) => ( diff --git a/app/plugins/core/plugins/format.js b/app/plugins/core/plugins/format.js index d25ffb96..437bbf54 100644 --- a/app/plugins/core/plugins/format.js +++ b/app/plugins/core/plugins/format.js @@ -1,31 +1,40 @@ -import { flow, lowerCase, words, capitalize, trim, map, join } from 'lodash/fp' +import { flow, words, capitalize, trim, map, join } from 'lodash/fp' /** - * Remove unnecessary information from plugin name or description + * Remove unnecessary information from plugin description * like `Cerebro plugin for` * @param {String} str * @return {String} */ -const removeNoise = (str) => ( +const removeDescriptionNoise = str => ( (str || '').replace(/^cerebro\s?(plugin)?\s?(to|for)?/i, '') ) -export const name = flow( - lowerCase, - removeNoise, + +/** + * Remove unnecessary information from plugin name + * like `cerebro-plugin-` or `cerebro-` + * @param {String} str + * @return {String} + */ +const removeNameNoise = str => ( + (str || '').replace(/^cerebro-(plugin)?-?/i, '') +) + +export const name = (text = '') => flow( trim, words, map(capitalize), join(' ') -) +)(removeNameNoise(text.toLowerCase())) export const description = flow( - removeNoise, + removeDescriptionNoise, trim, capitalize, ) -export const version = (plugin) => ( +export const version = plugin => ( plugin.isUpdateAvailable ? `${plugin.installedVersion} → ${plugin.version}` : plugin.version diff --git a/app/plugins/core/plugins/getAvailablePlugins.js b/app/plugins/core/plugins/getAvailablePlugins.js index df7d5b40..475c9e43 100644 --- a/app/plugins/core/plugins/getAvailablePlugins.js +++ b/app/plugins/core/plugins/getAvailablePlugins.js @@ -10,18 +10,16 @@ const URL = 'https://registry.npmjs.com/-/v1/search?from=0&size=500&text=keyword * Get all available plugins for Cerebro * @return {Promise} */ -export default () => ( - fetch(URL) - .then(response => response.json()) - .then(json => flow( - sortBy(p => -p.score.detail.popularity), - map(p => ({ - name: p.package.name, - version: p.package.version, - description: p.package.description, - homepage: p.package.links.homepage, - repo: p.package.links.repository - })) - )(json.objects) - ) -) +export default async () => { + const json = await fetch(URL).then(res => res.json()) + return flow( + sortBy(p => -p.score.detail.popularity), + map(p => ({ + name: p.package.name, + version: p.package.version, + description: p.package.description, + homepage: p.package.links.homepage, + repo: p.package.links.repository + })) + )(json.objects) +} diff --git a/app/plugins/core/plugins/getDebuggingPlugins.js b/app/plugins/core/plugins/getDebuggingPlugins.js index 3b1a8f06..42079f3c 100644 --- a/app/plugins/core/plugins/getDebuggingPlugins.js +++ b/app/plugins/core/plugins/getDebuggingPlugins.js @@ -1,19 +1,16 @@ import path from 'path' import { modulesDirectory } from 'lib/plugins' -import { readdir, lstatSync } from 'fs' +import { lstatSync, readdirSync } from 'fs' -const isSymlink = file => lstatSync(path.join(modulesDirectory, file)).isSymbolicLink() +const isSymlink = (file) => lstatSync(path.join(modulesDirectory, file)).isSymbolicLink() -/* Get list of all plugins that are currently in debugging mode. +/** + * Get list of all plugins that are currently in debugging mode. * These plugins are symlinked by [create-cerebro-plugin](https://github.com/cerebroapp/create-cerebro-plugin) * - * @return {Promise} + * @return {Promise} */ -export default () => new Promise((resolve, reject) => { - readdir(modulesDirectory, (err, files) => { - if (err) { - return reject(err) - } - resolve(files.filter(isSymlink)) - }) -}) +export default async () => { + const files = readdirSync(modulesDirectory) + return files.filter(isSymlink) +} diff --git a/app/plugins/core/plugins/getInstalledPlugins.js b/app/plugins/core/plugins/getInstalledPlugins.js index 2256b3b1..1f2f1aec 100644 --- a/app/plugins/core/plugins/getInstalledPlugins.js +++ b/app/plugins/core/plugins/getInstalledPlugins.js @@ -1,19 +1,14 @@ import { packageJsonPath } from 'lib/plugins' -import { readFile } from 'fs' +import { readFileSync } from 'fs' -const readPackageJson = () => new Promise((resolve, reject) => { - readFile(packageJsonPath, (err, source) => ( - err ? reject(err) : resolve(source) - )) -}) +const readPackageJson = () => readFileSync(packageJsonPath, { encoding: 'utf8' }) /** * Get list of all installed plugins with versions * * @return {Promise} */ -export default () => ( - readPackageJson() - .then(JSON.parse) - .then(json => json.dependencies) -) +export default async () => { + const packageJson = JSON.parse(readPackageJson()) + return packageJson.dependencies +} diff --git a/app/plugins/core/plugins/getReadme.js b/app/plugins/core/plugins/getReadme.js index c25d10e3..b73dd1ad 100644 --- a/app/plugins/core/plugins/getReadme.js +++ b/app/plugins/core/plugins/getReadme.js @@ -4,7 +4,7 @@ * @param {String} repository Repository field from npm package * @return {Promise} */ -export default (repo) => ( +export default repo => ( fetch(`https://api.github.com/repos/${repo}/readme`) .then(response => response.json()) .then(json => Buffer.from(json.content, 'base64').toString()) diff --git a/app/plugins/core/plugins/index.js b/app/plugins/core/plugins/index.js index a13a6f0d..410ef7fa 100644 --- a/app/plugins/core/plugins/index.js +++ b/app/plugins/core/plugins/index.js @@ -1,15 +1,15 @@ import React from 'react' -import Preview from './Preview' import { search } from 'cerebro-tools' import { shell } from 'electron' +import { partition } from 'lodash' +import { flow, map, partialRight, tap } from 'lodash/fp' +import store from 'main/store' +import * as statusBar from 'main/actions/statusBar' import loadPlugins from './loadPlugins' import icon from '../icon.png' import * as format from './format' -import { flow, map, partialRight, tap } from 'lodash/fp' -import { partition } from 'lodash' +import Preview from './Preview' import initializeAsync from './initializeAsync' -import store from '../../../main/store' -import * as statusBar from '../../../main/actions/statusBar' const toString = ({ name, description }) => [name, description].join(' ') const categories = [ @@ -19,23 +19,22 @@ const categories = [ ['Available', plugin => plugin.name], ] -const updatePlugin = (update, name) => { - loadPlugins().then(plugins => { - const updatedPlugin = plugins.find(plugin => plugin.name === name) - update(name, { - title: `${format.name(updatedPlugin.name)} (${format.version(updatedPlugin)})`, - getPreview: () => ( - updatePlugin(update, name)} - /> - ) - }) +const updatePlugin = async (update, name) => { + const plugins = await loadPlugins() + const updatedPlugin = plugins.find(plugin => plugin.name === name) + update(name, { + title: `${format.name(updatedPlugin.name)} (${format.version(updatedPlugin)})`, + getPreview: () => ( + updatePlugin(update, name)} + /> + ) }) } -const pluginToResult = update => plugin => { +const pluginToResult = update => (plugin) => { if (typeof plugin === 'string') { return { title: plugin } } @@ -60,7 +59,7 @@ const categorize = (plugins, callback) => { const result = [] let remainder = plugins - categories.forEach(category => { + categories.forEach((category) => { const [title, filter] = category const [matched, others] = partition(remainder, filter) if (matched.length) result.push(title, ...matched) diff --git a/app/plugins/core/plugins/initializeAsync.js b/app/plugins/core/plugins/initializeAsync.js index 9f032c21..27d69654 100644 --- a/app/plugins/core/plugins/initializeAsync.js +++ b/app/plugins/core/plugins/initializeAsync.js @@ -1,11 +1,13 @@ -import loadPlugins from './loadPlugins' -import getInstalledPlugins from './getInstalledPlugins' import { client } from 'lib/plugins' import config from 'lib/config' -import { flow, filter, map, property } from 'lodash/fp' +import { + flow, filter, map, property +} from 'lodash/fp' +import loadPlugins from './loadPlugins' +import getInstalledPlugins from './getInstalledPlugins' const DEFAULT_PLUGINS = [ - process.platform === 'darwin' ? 'cerebro-mac-apps' : 'cerebro-basic-apps', + process.platform === 'darwin' ? 'cerebro-mac-apps' : '@cerebroapp/cerebro-basic-apps', 'cerebro-google', 'cerebro-math', 'cerebro-converter', @@ -16,19 +18,20 @@ const DEFAULT_PLUGINS = [ /** * Check plugins for updates and start plugins autoupdater */ -function checkForUpdates() { +async function checkForUpdates() { console.log('Run plugins autoupdate') - loadPlugins().then(flow( + const plugins = await loadPlugins() + + const updatePromises = flow( filter(property('isUpdateAvailable')), - map(plugin => client.update(plugin.name)) - )).then(promises => Promise.all(promises).then(() => promises.length)) - .then(updatedPlugins => { - console.log( - updatedPlugins > 0 - ? `${updatedPlugins} plugins are updated` - : 'All plugins are up to date' - ) - }) + map((plugin) => client.update(plugin.name)) + )(plugins) + + await Promise.all(updatePromises) + + console.log(updatePromises.length > 0 + ? `${updatePromises.length} plugins are updated` + : 'All plugins are up to date') // Run autoupdate every 12 hours setTimeout(checkForUpdates, 12 * 60 * 60 * 1000) @@ -38,29 +41,28 @@ function checkForUpdates() { * Migrate plugins: default plugins were extracted to separate packages * so if default plugins are not installed – start installation */ -function migratePlugins(sendMessage) { +async function migratePlugins(sendMessage) { if (config.get('isMigratedPlugins')) { // Plugins are already migrated return } console.log('Start installation of default plugins') + const installedPlugins = await getInstalledPlugins() - getInstalledPlugins().then(installedPlugins => { - const promises = flow( - filter(plugin => !installedPlugins[plugin]), - map(plugin => client.install(plugin)) - )(DEFAULT_PLUGINS) + const promises = flow( + filter((plugin) => !installedPlugins[plugin]), + map((plugin) => client.install(plugin)) + )(DEFAULT_PLUGINS) - if (promises.length > 0) { - sendMessage('plugins:start-installation') - } + if (promises.length > 0) { + sendMessage('plugins:start-installation') + } - Promise.all(promises).then(() => { - console.log('All default plugins are installed!') - config.set('isMigratedPlugins', true) - sendMessage('plugins:finish-installation') - }) + Promise.all(promises).then(() => { + console.log('All default plugins are installed!') + config.set('isMigratedPlugins', true) + sendMessage('plugins:finish-installation') }) } diff --git a/app/plugins/core/plugins/loadPlugins.js b/app/plugins/core/plugins/loadPlugins.js index d92c44ae..e59de02a 100644 --- a/app/plugins/core/plugins/loadPlugins.js +++ b/app/plugins/core/plugins/loadPlugins.js @@ -1,46 +1,49 @@ import { memoize } from 'cerebro-tools' +import validVersion from 'semver/functions/valid' +import compareVersions from 'semver/functions/gt' import availablePlugins from './getAvailablePlugins' import getInstalledPlugins from './getInstalledPlugins' import getDebuggingPlugins from './getDebuggingPlugins' -import semver from 'semver' -const maxAge = 5 * 60 * 1000 +const maxAge = 5 * 60 * 1000 // 5 minutes const getAvailablePlugins = memoize(availablePlugins, { maxAge }) -const parseVersion = (version) => ( - semver.valid((version || '').replace(/^\^/, '')) || '0.0.0' +const parseVersion = version => ( + validVersion((version || '').replace(/^\^/, '')) || '0.0.0' ) -export default () => ( - Promise.all([ +export default async () => { + const [available, installed, debuggingPlugins] = await Promise.all([ getAvailablePlugins(), getInstalledPlugins(), getDebuggingPlugins() - ]).then(([available, installed, debuggingPlugins]) => { - const listOfAvailablePlugins = available.map((plugin) => { - const installedVersion = parseVersion(installed[plugin.name]) - const isInstalled = !!installed[plugin.name] - const isUpdateAvailable = isInstalled && semver.gt(plugin.version, installedVersion) - return { - ...plugin, - installedVersion, - isInstalled, - isUpdateAvailable - } - }) - console.log(debuggingPlugins) - const listOfDebuggingPlugins = debuggingPlugins.map(name => ({ - name, - description: '', - version: 'dev', - isInstalled: false, - isUpdateAvailable: false, - isDebugging: true - })) - return [ - ...listOfAvailablePlugins, - ...listOfDebuggingPlugins - ] + ]) + + const listOfAvailablePlugins = available.map((plugin) => { + const installedVersion = parseVersion(installed[plugin.name]) + const isInstalled = !!installed[plugin.name] + const isUpdateAvailable = isInstalled && compareVersions(plugin.version, installedVersion) + return { + ...plugin, + installedVersion, + isInstalled, + isUpdateAvailable + } }) -) + console.log('Debugging Plugins: ', debuggingPlugins) + + const listOfDebuggingPlugins = debuggingPlugins.map(name => ({ + name, + description: '', + version: 'dev', + isInstalled: false, + isUpdateAvailable: false, + isDebugging: true + })) + + return [ + ...listOfAvailablePlugins, + ...listOfDebuggingPlugins + ] +} diff --git a/app/plugins/core/quit/index.js b/app/plugins/core/quit/index.js index 828e5eac..8f5dc490 100644 --- a/app/plugins/core/quit/index.js +++ b/app/plugins/core/quit/index.js @@ -1,14 +1,11 @@ -import { remote } from 'electron' +import { ipcRenderer } from 'electron' import { search } from 'cerebro-tools' import icon from '../icon.png' -const KEYWORDS = [ - 'Quit', - 'Exit' -] +const KEYWORDS = ['Quit', 'Exit'] const subtitle = 'Quit from Cerebro' -const onSelect = () => remote.app.quit() +const onSelect = () => ipcRenderer.send('quit') /** * Plugin to exit from Cerebro @@ -17,8 +14,11 @@ const onSelect = () => remote.app.quit() * @param {Function} options.display */ const fn = ({ term, display }) => { - const result = search(KEYWORDS, term).map(title => ({ - icon, title, subtitle, onSelect, + const result = search(KEYWORDS, term).map((title) => ({ + icon, + title, + subtitle, + onSelect, term: title, })) display(result) diff --git a/app/plugins/core/reload/index.js b/app/plugins/core/reload/index.js index ed49e4e0..a167e0ef 100644 --- a/app/plugins/core/reload/index.js +++ b/app/plugins/core/reload/index.js @@ -24,7 +24,4 @@ const fn = ({ term, display }) => { } } -export default { - keyword, fn, icon, - name: 'Reload' -} +export default { keyword, fn, icon, name: 'Reload' } diff --git a/app/plugins/core/settings/Settings/Hotkey.js b/app/plugins/core/settings/Settings/Hotkey.js index 5ccaf3a7..e6252f7f 100644 --- a/app/plugins/core/settings/Settings/Hotkey.js +++ b/app/plugins/core/settings/Settings/Hotkey.js @@ -1,4 +1,5 @@ -import React, { PropTypes, Component } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import styles from './styles.css' const ASCII = { @@ -83,12 +84,11 @@ const charCodeToSign = ({ keyCode, shiftKey }) => { if (KEYCODES[keyCode]) { return KEYCODES[keyCode] } - const valid = - (keyCode > 47 && keyCode < 58) || // number keys - (keyCode > 64 && keyCode < 91) || // letter keys - (keyCode > 95 && keyCode < 112) || // numpad keys - (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) - (keyCode > 218 && keyCode < 223) // [\]' (in order) + const valid = (keyCode > 47 && keyCode < 58) // number keys + || (keyCode > 64 && keyCode < 91) // letter keys + || (keyCode > 95 && keyCode < 112) // numpad keys + || (keyCode > 185 && keyCode < 193) // ;=,-./` (in order) + || (keyCode > 218 && keyCode < 223) // [\]' (in order) if (!valid) { return null } @@ -123,6 +123,7 @@ class Hotkey extends Component { keys.push(key) this.props.onChange(keys.join('+')) } + render() { const { hotkey } = this.props const keys = hotkey.split('+').map(keyToSign).join(osKeyDelimiter) diff --git a/app/plugins/core/settings/Settings/index.js b/app/plugins/core/settings/Settings/index.js index 35f375e4..ef7b471a 100644 --- a/app/plugins/core/settings/Settings/index.js +++ b/app/plugins/core/settings/Settings/index.js @@ -1,10 +1,16 @@ -import React, { PropTypes, Component } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { FormComponents } from '@cerebroapp/cerebro-ui' +import loadThemes from 'lib/loadThemes' + import Hotkey from './Hotkey' import countries from './countries' -import { Select, Checkbox, Wrapper, Text } from 'cerebro-ui/Form' -import loadThemes from 'lib/loadThemes' import styles from './styles.css' +const { + Select, Checkbox, Wrapper, Text +} = FormComponents + class Settings extends Component { constructor(props) { super(props) @@ -24,12 +30,14 @@ class Settings extends Component { } this.changeConfig = this.changeConfig.bind(this) } + changeConfig(key, value) { this.props.set(key, value) this.setState({ [key]: value }) } + render() { const { hotkey, showInTray, country, theme, proxy, developerMode, cleanOnHide, openAtLogin, @@ -49,48 +57,48 @@ class Settings extends Component { description="Choose your country so Cerebro can better choose currency, language, etc." value={country} options={countries} - onChange={value => this.changeConfig('country', value)} + onChange={(value) => this.changeConfig('country', value)} />