diff --git a/src/components/App.js b/src/components/App.js index fb4e67e9..72af5bc2 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -11,8 +11,9 @@ import theme from 'styles/theme' import i18n from 'renderer/i18n' -import Wrapper from 'components/Wrapper' -import PrintWrapper from 'components/PrintWrapper' +import Default from 'components/layout/Default' +import Dev from 'components/layout/Dev' +import Print from 'components/layout/Print' export default ({ store, @@ -28,8 +29,9 @@ export default ({ - - + {__DEV__ && } + + diff --git a/src/components/DevToolbar.js b/src/components/DevToolbar.js index 6c44cfdc..a9255799 100644 --- a/src/components/DevToolbar.js +++ b/src/components/DevToolbar.js @@ -1,7 +1,7 @@ // @flow import React, { PureComponent } from 'react' -import { ipcRenderer } from 'electron' +import { remote, ipcRenderer } from 'electron' import { translate } from 'react-i18next' import { AreaChart, Area } from 'recharts' import takeRight from 'lodash/takeRight' @@ -10,15 +10,18 @@ import reduce from 'lodash/fp/reduce' import flow from 'lodash/fp/flow' import filter from 'lodash/fp/filter' import sortBy from 'lodash/fp/sortBy' +import chunk from 'lodash/chunk' import styled from 'styled-components' import color from 'color' import Box from 'components/base/Box' -import GrowScroll from 'components/base/GrowScroll' +import Bar from 'components/base/Bar' import CopyToClipboard from 'components/base/CopyToClipboard' import theme from 'styles/theme' +const mainWindow = remote.BrowserWindow.getAllWindows().find(w => w.name === 'MainWindow') + type HslColor = { color: Array, } @@ -53,40 +56,16 @@ const colors: Array = transform(theme.colors) const Container = styled(Box).attrs({ bg: 'night', - p: 1, + p: 5, + grow: true, color: 'white', - fontSize: 0, -})` - position: fixed; - bottom: 0; - right: 0; - z-index: 1; - border-top-left-radius: 3px; - transition: ease-in-out transform 300ms; - transform: translate3d(0, ${p => (p.isOpened ? '0' : '100%')}, 0); -` - -const Handle = styled(Box).attrs({ - bg: 'night', - justify: 'center', - px: 1, -})` - cursor: pointer; - position: absolute; - bottom: 100%; - right: 5px; - font-size: 9px; - height: 20px; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -` + fontSize: 3, +})`` const Colors = styled(Box).attrs({ horizontal: true, - align: 'flex-start', -})` - flex-wrap: wrap; -` + flow: 4, +})`` const Cl = styled(Box).attrs({ align: 'center', @@ -94,9 +73,8 @@ const Cl = styled(Box).attrs({ p: 2, })` border: 2px solid white; + flex: 1; cursor: pointer; - margin: 2px; - width: 80px; ` const Color = ({ onClick, color }: { onClick: Function, color: ColorType }) => ( @@ -106,29 +84,22 @@ const Color = ({ onClick, color }: { onClick: Function, color: ColorType }) => ( ) const Lang = styled(Box).attrs({ - bg: p => (p.current ? 'white' : 'night'), - color: p => (p.current ? 'night' : 'white'), + bg: 'night', + color: 'white', borderColor: 'white', - borderWidth: 1, - p: 1, + borderWidth: 2, + borderRadius: 1, + p: 2, })` - border-radius: 3px; cursor: pointer; ` -const LANGUAGES = { - fr: 'français', - en: 'english', -} - type State = { - isOpened: boolean, cpuUsage: Object, } class DevToolbar extends PureComponent { state = { - isOpened: false, cpuUsage: {}, } @@ -159,32 +130,34 @@ class DevToolbar extends PureComponent { } } - handleToggle = () => this.setState({ isOpened: !this.state.isOpened }) - handleChangeLanguage = lang => () => this.props.i18n.changeLanguage(lang) + handleChangeLanguage = lang => () => { + mainWindow.webContents.send('msg', { + type: 'application.changeLanguage', + data: lang, + }) + } render() { const { i18n } = this.props - const { isOpened, cpuUsage } = this.state + const { cpuUsage } = this.state return ( - - {'DEV'} - - - {Object.keys(LANGUAGES).map(lang => ( - - {LANGUAGES[lang]} + + + + {Object.keys(i18n.store.data).map(lang => ( + + {lang} ))} - - - - {colors.map(color => ( + + + {chunk(colors, 8).map((c, i) => ( + + {c.map(color => ( { /> ))} - + ))} - + + {Object.keys(cpuUsage) .sort() .map(k => ( - - - {k} - {last(cpuUsage[k]).value}% + + + {last(cpuUsage[k]).value}% + {k} p.theme.sizes.topBarHeight + p.theme.space[7]}px; ` -class Wrapper extends Component<{}> { +class Default extends Component<{}> { componentDidMount() { window.requestAnimationFrame( () => (this._timeout = setTimeout(() => ipcRenderer.send('app-finish-rendering'), 300)), @@ -44,7 +43,6 @@ class Wrapper extends Component<{}> { return ( {process.platform === 'darwin' && } - {__DEV__ && } {Object.entries(modals).map(([name, ModalComponent]: [string, any]) => ( @@ -70,4 +68,4 @@ class Wrapper extends Component<{}> { } } -export default translate()(Wrapper) +export default translate()(Default) diff --git a/src/components/layout/Dev.js b/src/components/layout/Dev.js new file mode 100644 index 00000000..b0dd5b1e --- /dev/null +++ b/src/components/layout/Dev.js @@ -0,0 +1,25 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' + +import Box from 'components/base/Box' +import DevToolbar from 'components/DevToolbar' + +const Container = styled(Box).attrs({ + grow: true, +})` + height: 100%; +` + +class Dev extends PureComponent<{}> { + render() { + return ( + + + + ) + } +} + +export default Dev diff --git a/src/components/PrintWrapper.js b/src/components/layout/Print.js similarity index 92% rename from src/components/PrintWrapper.js rename to src/components/layout/Print.js index da4e4fe9..53501912 100644 --- a/src/components/PrintWrapper.js +++ b/src/components/layout/Print.js @@ -12,7 +12,7 @@ type State = { data: Object | null, } -class PrintWrapper extends PureComponent { +class Print extends PureComponent { state = { data: null, } @@ -49,4 +49,4 @@ class PrintWrapper extends PureComponent { } } -export default PrintWrapper +export default Print diff --git a/src/internals/index.js b/src/internals/index.js index 2040becc..2c700f30 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -69,6 +69,7 @@ if (__DEV__) { value: cpuPercent, }, { + window: 'DevWindow', kill: false, }, ) diff --git a/src/main/app.js b/src/main/app.js index 80ac2ba6..18470cf2 100644 --- a/src/main/app.js +++ b/src/main/app.js @@ -8,14 +8,15 @@ import db from 'helpers/db' // necessary to prevent win from being garbage collected let mainWindow +let devWindow let preloadWindow let forceClose = false const devTools = __DEV__ -const getWindowPosition = (height, width) => { - const { bounds } = screen.getPrimaryDisplay() +const getWindowPosition = (height, width, display = screen.getPrimaryDisplay()) => { + const { bounds } = display return { x: Math.ceil(bounds.x + (bounds.width - width) / 2), @@ -23,10 +24,35 @@ const getWindowPosition = (height, width) => { } } +const getDefaultUrl = () => + __DEV__ + ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT || ''}` + : `file://${__dirname}/index.html` + +const saveWindowSettings = window => { + window.on( + 'resize', + debounce(() => { + const [width, height] = window.getSize() + db.setIn('settings', `window.${window.name}.dimensions`, { width, height }) + }, 100), + ) + + window.on( + 'move', + debounce(() => { + const [x, y] = window.getPosition() + db.setIn('settings', `window.${window.name}.positions`, { x, y }) + }, 100), + ) +} + const defaultWindowOptions = { backgroundColor: '#fff', webPreferences: { devTools, + // Enable, among other things, the ResizeObserver + experimentalFeatures: true, }, } @@ -34,8 +60,8 @@ function createMainWindow() { const MIN_HEIGHT = 768 const MIN_WIDTH = 1024 - const savedDimensions = db.getIn('settings', 'window.dimensions', {}) - const savedPositions = db.getIn('settings', 'window.positions', null) + const savedDimensions = db.getIn('settings', 'window.MainWindow.dimensions', {}) + const savedPositions = db.getIn('settings', 'window.MainWindow.positions', null) const width = savedDimensions.width || MIN_WIDTH const height = savedDimensions.height || MIN_HEIGHT @@ -54,23 +80,20 @@ function createMainWindow() { minWidth: MIN_WIDTH, minHeight: MIN_HEIGHT, show: false, - webPreferences: { - ...defaultWindowOptions.webPreferences, - // Enable, among other things, the ResizeObserver - experimentalFeatures: true, - }, } const window = new BrowserWindow(windowOptions) - const url = __DEV__ - ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT || ''}` - : `file://${__dirname}/index.html` + window.name = 'MainWindow' + + const url = getDefaultUrl() if (devTools) { window.webContents.openDevTools() } + saveWindowSettings(window) + window.loadURL(url) window.on('close', e => { @@ -83,22 +106,6 @@ function createMainWindow() { } }) - window.on( - 'resize', - debounce(() => { - const [width, height] = window.getSize() - db.setIn('settings', 'window.dimensions', { width, height }) - }, 100), - ) - - window.on( - 'move', - debounce(() => { - const [x, y] = window.getPosition() - db.setIn('settings', 'window.positions', { x, y }) - }, 100), - ) - window.webContents.on('devtools-opened', () => { window.focus() setImmediate(() => { @@ -109,16 +116,64 @@ function createMainWindow() { return window } +function createDevWindow() { + const MIN_HEIGHT = 400 + const MIN_WIDTH = 600 + + const savedDimensions = db.getIn('settings', 'window.DevWindow.dimensions', {}) + const savedPositions = db.getIn('settings', 'window.DevWindow.positions', null) + + const width = savedDimensions.width || MIN_WIDTH + const height = savedDimensions.height || MIN_HEIGHT + + const windowOptions = { + ...defaultWindowOptions, + ...(savedPositions !== null ? savedPositions : {}), + fullscreenable: false, + height, + show: false, + skipTaskbar: true, + width, + } + + const window = new BrowserWindow(windowOptions) + + window.name = 'DevWindow' + + const url = getDefaultUrl() + + if (devTools) { + window.webContents.openDevTools() + } + + saveWindowSettings(window) + + window.loadURL(`${url}/#/dev`) + + window.on('ready-to-show', () => { + window.show() + }) + + return window +} + function createPreloadWindow() { // Preload renderer of main windows mainWindow = createMainWindow() + const [x, y] = mainWindow.getPosition() + + if (__DEV__) { + devWindow = createDevWindow() + } + const height = 144 const width = 256 const windowOptions = { ...defaultWindowOptions, - ...getWindowPosition(height, width), + ...getWindowPosition(height, width, screen.getDisplayNearestPoint({ x, y })), + alwaysOnTop: true, closable: false, frame: false, fullscreenable: false, @@ -131,6 +186,8 @@ function createPreloadWindow() { const window = new BrowserWindow(windowOptions) + window.name = 'PreloadWindow' + window.loadURL(`file://${__static}/preload-window.html`) window.on('ready-to-show', () => { @@ -197,4 +254,8 @@ ipcMain.on('app-finish-rendering', () => { mainWindow.show() setImmediate(() => mainWindow !== null && mainWindow.focus()) } + + if (devWindow !== null) { + devWindow.show() + } }) diff --git a/src/main/bridge.js b/src/main/bridge.js index 14286441..e4f6f62d 100644 --- a/src/main/bridge.js +++ b/src/main/bridge.js @@ -1,7 +1,7 @@ // @flow import { fork } from 'child_process' -import { ipcMain } from 'electron' +import { BrowserWindow, ipcMain } from 'electron' import objectPath from 'object-path' import { resolve } from 'path' @@ -34,11 +34,17 @@ function onForkChannel(forkType, callType) { const onMessage = payload => { const { type, data, options = {} } = payload - if (callType === 'async') { - event.sender.send('msg', { type, data }) - } - if (callType === 'sync') { - event.returnValue = { type, data } + + if (options.window) { + const devWindow = BrowserWindow.getAllWindows().find(w => w.name === options.window) + devWindow.webContents.send('msg', { type, data }) + } else { + if (callType === 'async') { + event.sender.send('msg', { type, data }) + } + if (callType === 'sync') { + event.returnValue = { type, data } + } } if (options.kill && compute) { kill() @@ -72,5 +78,5 @@ ipcMain.on('msg', (event: any, payload) => { return } const send = (type: string, data: *) => event.sender.send('msg', { type, data }) - handler(send, data) + handler(send, data, type) }) diff --git a/src/renderer/events.js b/src/renderer/events.js index 807f75c6..70e24b4c 100644 --- a/src/renderer/events.js +++ b/src/renderer/events.js @@ -14,6 +14,8 @@ import { updateAccount } from 'actions/accounts' import { setUpdateStatus } from 'reducers/update' import { getAccountData, getAccounts } from 'reducers/accounts' +import i18n from 'renderer/i18n' + const { DISABLED_SYNC, DISABLED_AUTO_SYNC } = process.env type MsgPayload = { @@ -64,6 +66,10 @@ export function checkUpdates() { export default ({ store, locked }: { store: Object, locked: boolean }) => { const handlers = { + dispatch: (type, payload) => store.dispatch({ type, payload }), + application: { + changeLanguage: lang => i18n.changeLanguage(lang), + }, account: { sync: { success: account => { diff --git a/src/renderer/index.js b/src/renderer/index.js index 6529d14b..1a230513 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -4,6 +4,7 @@ import 'env' import React from 'react' import Raven from 'raven-js' +import { remote } from 'electron' import { render } from 'react-dom' import { AppContainer } from 'react-hot-loader' import createHistory from 'history/createHashHistory' @@ -35,8 +36,6 @@ const history = createHistory() const store = createStore(history) const rootNode = document.getElementById('app') -global.__PRINT_MODE__ = history.location.pathname.startsWith('/print') - store.dispatch(fetchSettings()) const state = store.getState() || {} @@ -55,7 +54,8 @@ function r(Comp) { r() -if (!__PRINT_MODE__) { +// Only init events on MainWindow +if (remote.getCurrentWindow().name === 'MainWindow') { events({ store, locked }) }