diff --git a/__mocks__/electron.js b/__mocks__/electron.js index 6653cb6b..6b6219a3 100644 --- a/__mocks__/electron.js +++ b/__mocks__/electron.js @@ -2,6 +2,7 @@ module.exports = { app: jest.fn(), ipcRenderer: { send: jest.fn(), + on: jest.fn(), }, remote: { require: jest.fn(name => { diff --git a/app.js b/app.js index 09788889..7123f1da 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,7 @@ const omit = require('lodash').omit; // Electron Libs const { app, BrowserWindow, ipcMain } = require('electron'); +const { autoUpdater } = require('electron-updater'); // Place a BrowserWindow in center of primary display const centerOnPrimaryDisplay = require('./app/helpers/center-on-primary-display'); @@ -98,7 +99,6 @@ function createMainWindow() { slashes: true, }) ); - // Add Event Listeners mainWindow.on('show', event => { if (isDev) mainWindow.webContents.openDevTools({ mode: 'detach' }); @@ -294,9 +294,19 @@ function addEventListeners() { ipcMain.on('quit-app', () => { app.quit(); }); - // Use with autoUpdater - ipcMain.on('restart-app', () => { - app.relaunch(); + // Quit and install + // https://github.com/electron-userland/electron-builder/issues/1604#issuecomment-306709572 + ipcMain.on('quit-and-install', () => { + setImmediate(() => { + // Remove this listener + app.removeAllListeners("window-all-closed"); + // Force close all windows + tourWindow.destroy(); + mainWindow.destroy(); + previewWindow.destroy(); + // Start the quit and update sequence + autoUpdater.quitAndInstall(false); + }) }); } @@ -371,9 +381,11 @@ function initialize() { }); // Close all windows before quit the app app.on('before-quit', () => { - tourWindow.destroy(); - mainWindow.destroy(); - previewWindow.destroy(); + // Use condition in case quit sequence is initiated by autoUpdater + // which will destroy all there windows already before emitting this event + if (tourWindow !== null) tourWindow.destroy(); + if (mainWindow !== null) mainWindow.destroy(); + if (previewWindow !== null) previewWindow.destroy(); }); console.timeEnd('init'); } diff --git a/app/App.jsx b/app/App.jsx index 7e856831..33a5c12b 100644 --- a/app/App.jsx +++ b/app/App.jsx @@ -97,7 +97,6 @@ class App extends PureComponent { - ); } diff --git a/app/components/layout/AppNav.jsx b/app/components/layout/AppNav.jsx index 39a497d6..69a7e4ef 100644 --- a/app/components/layout/AppNav.jsx +++ b/app/components/layout/AppNav.jsx @@ -49,7 +49,7 @@ export const SideBar = styled.div` position: relative; display: flex; flex-direction: column; - justify-content: flex-start; + justify-content: space-between; height: 100%; width: 80px; min-width: 80px; @@ -95,6 +95,8 @@ export const ActiveIndicator = styled.div` } `; +import AppUpdate from './AppUpdate'; + function AppNav({ activeTab, changeTab }) { const marginTopValue = setMarginValue(activeTab); const allTabsComponent = allTabs.map(tab => ( @@ -104,19 +106,22 @@ function AppNav({ activeTab, changeTab }) { )); return ( - - {({ marginTop }) => ( - -
- - )} - - {allTabsComponent} +
+ + {({ marginTop }) => ( + +
+ + )} + + {allTabsComponent} +
+ ); } diff --git a/app/components/layout/AppUpdate.jsx b/app/components/layout/AppUpdate.jsx index d933f91a..ef46dfbf 100644 --- a/app/components/layout/AppUpdate.jsx +++ b/app/components/layout/AppUpdate.jsx @@ -1,62 +1,52 @@ // Libraries -import React, { Component } from 'react'; -const ipc = require('electron').ipcRenderer; +import React, { PureComponent } from 'react'; import openDialog from '../../renderers/dialog'; +import { Circle } from 'rc-progress'; +const ipc = require('electron').ipcRenderer -// Styles -import styled from 'styled-components'; +// Styled Components +import styled, { keyframes } from 'styled-components'; -const Wrapper = styled.div` - position: fixed; - height: auto; - bottom: 0; - width: 100%; - padding-left: 120px; - padding-right: 40px; -`; - -const Message = styled.p` - font-size: 12px; - letter-spacing: 0.5px; - color: white; - margin-bottom: 0px; - line-height: 1.75; +const breathing = keyframes` + 0% { opacity: 0.5; } + 100% { opacity: 1; } `; -const Content = styled.div` - background: #469fe5; - border-radius: 4px 4px 0 0; - padding: 10px 20px; - border: 1px solid rgba(0, 0, 0, 0.1); - text-transform: uppercase; - ${props => props.type === 'error' && `background: #EC476E;`}; - ${props => props.type === 'success' && `background: #6BBB69;`}; +const Indicator = styled.div` display: flex; - align-items: flex-start; - justify-content: space-between; - button { - color: white; + align-items: center; + justify-content: center; + height: 60px; + width: 100%; + color: #f2f3f4; + i { + font-size: 24px; + animation: ${breathing} 1s infinite alternate; + } + svg { + width: 24px; + height: 24px; } `; -import Button from '../../components/shared/Button'; - -class AppUpdate extends Component { +class AppUpdate extends PureComponent { constructor(props) { super(props); this.state = { - message: null, - type: 'info', + checking: false, + downloading: false, + progress: null, }; - this.removeMessage = this.removeMessage.bind(this); + this.hideIndicator = this.hideIndicator.bind(this); } componentDidMount() { - ipc.on('update-checking', event => { - this.setState({ message: 'Checking for update' }); + ipc.on('update-checking', () => { + this.setState({ checking: true }); }); ipc.on('update-available', () => { + this.hideIndicator(); openDialog( { type: 'info', @@ -74,7 +64,7 @@ class AppUpdate extends Component { title: 'No Updates', message: 'Current version is up-to-date', }); - this.removeMessage(); + this.hideIndicator(); }); ipc.on('update-error', (event, error) => { @@ -83,36 +73,33 @@ class AppUpdate extends Component { title: 'Update Error', message: error, }); - this.removeMessage(); + this.hideIndicator(); }); ipc.on('update-download-confirmed', (event, index) => { - // Start the download - if (index === 0) { - this.setState({ - message: 'Downloading update ...', - type: 'success', - }); - ipc.send('update-download-started'); - } // Cancel the download if (index === 1) { - this.removeMessage(); + this.hideIndicator(); + return; + } + // Start the download + if (index === 0) { + this.setState( + { + downloading: true, + }, + ipc.send('update-download-started') + ); } }); - ipc.on('update-download-progress', (event, progressMessage) => { + ipc.on('update-download-progress', (event, percentage) => { this.setState({ - message: progressMessage, + progress: percentage, }); }); ipc.on('update-downloaded', () => { - // Update Message - this.setState({ - message: 'Download Completed!', - }); - // Ask user to upgrade now or later openDialog( { type: 'info', @@ -122,19 +109,16 @@ class AppUpdate extends Component { }, 'upgrade-confirmed' ); + this.hideIndicator(); }); ipc.on('upgrade-confirmed', (event, index) => { if (index === 0) { - ipc.send('restart-app'); + ipc.send('quit-and-install'); } }); } - shouldComponentUpdate(nextProps, nextState) { - return this.state !== nextState; - } - componentWillUnmount() { ipc.removeAllListeners([ 'update-checking', @@ -148,25 +132,29 @@ class AppUpdate extends Component { ]); } - removeMessage() { + hideIndicator() { this.setState({ - message: null, - type: 'info', + checking: false, + downloading: false, + progress: null, }); } render() { - const { message, type } = this.state; - return message ? ( - - - {message} - - - - ) : null; + return ( + + {this.state.checking && } + {this.state.downloading && ( + + )} + + ); } } diff --git a/app/components/layout/__tests__/AppNav.spec.js b/app/components/layout/__tests__/AppNav.spec.js index 86cf70c3..bfe4450b 100644 --- a/app/components/layout/__tests__/AppNav.spec.js +++ b/app/components/layout/__tests__/AppNav.spec.js @@ -15,7 +15,7 @@ describe('Renders correctly to the DOM', () => { }); it('renders 4 tabs', () => { - // expect(wrapper.find('a')).toHaveLength(4); + expect(wrapper.find('a')).toHaveLength(4); }); it('has correct porps', () => { @@ -23,9 +23,9 @@ describe('Renders correctly to the DOM', () => { // expect(wrapper.prop('activeTab')).not.toEqual('settings'); }); - // it('contains active indicator', () => { + it('contains active indicator', () => { // const sideBar = shallow(); // expect(wrapper.contains(sideBar)).toEqual(true); - // }); - // it('shows active indicator at the correct position'); + }); + it('shows active indicator at the correct position'); }); diff --git a/main/updater.js b/main/updater.js index 91982577..c6297e3f 100644 --- a/main/updater.js +++ b/main/updater.js @@ -7,13 +7,19 @@ const isDev = require('electron-is-dev'); // Disable Auto Downloading update; autoUpdater.autoDownload = false; +// Check for update silently +let silentMode = true; + // Set mainWindow const mainWindowID = appConfig.get('mainWindowID'); const mainWindow = BrowserWindow.fromId(mainWindowID); // Check for Updates -ipcMain.on('check-for-updates', event => { - if(!isDev) checkForUpdate(); +ipcMain.on('check-for-updates', (event) => { + // Turn off silent mode + silentMode = false; + // if(!isDev) checkForUpdate(); + checkForUpdate(); }); // Start Download @@ -25,7 +31,10 @@ ipcMain.on('update-download-started', () => { // ==================================== // Checking for Update autoUpdater.on('checking-for-update', () => { - mainWindow.send('update-checking'); + // Only notice user when they checked manually + if (!silentMode) { + mainWindow.send('update-checking'); + } }); // Update Available @@ -35,7 +44,10 @@ autoUpdater.on('update-available', info => { // Update Not Available autoUpdater.on('update-not-available', () => { - mainWindow.send('update-not-available'); + // Only notice user when they checked manually + if (!silentMode) { + mainWindow.send('update-not-available'); + } }); // Update Error @@ -51,8 +63,7 @@ autoUpdater.on('error', error => { // Download Progress autoUpdater.on('download-progress', progressObj => { - const message = `Downloaded ${progressObj.percent} %`; - mainWindow.send('update-download-progress', message); + mainWindow.send('update-download-progress', progressObj.percent); }); // Update Downloaded diff --git a/package.json b/package.json index ec0e44da..8ff91de1 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "jimp": "^0.2.28", "moment": "^2.20.1", "pouchdb-browser": "6.2.0", + "rc-progress": "^2.2.5", "react": "^16.2.0", "react-addons-shallow-compare": "^15.6.2", "react-beautiful-dnd": "^2.4.1", diff --git a/yarn.lock b/yarn.lock index 2893827c..b4d61124 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1199,7 +1199,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@6.26.0, babel-runtime@6.x, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -6542,6 +6542,13 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" +rc-progress@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-2.2.5.tgz#e61d0544bf9d4208e5ba32fc50962159e7f952a3" + dependencies: + babel-runtime "6.x" + prop-types "^15.5.8" + rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"