diff --git a/app/index.js b/app/index.js index afa9910b068..a761e6a5dca 100644 --- a/app/index.js +++ b/app/index.js @@ -4,21 +4,18 @@ 'use strict' const electron = require('electron') +const BrowserWindow = electron.BrowserWindow const ipcMain = electron.ipcMain const app = electron.app -const BrowserWindow = electron.BrowserWindow const Menu = require('./menu') -const LocalShortcuts = require('./localShortcuts') const Updater = require('./updater') const messages = require('../js/constants/messages') +const AppActions = require('../js/actions/appActions') +require('../js/stores/appStore') // Report crashes electron.crashReporter.start() -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let windows = [] - app.on('window-all-closed', function () { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q @@ -27,36 +24,8 @@ app.on('window-all-closed', function () { } }) -const spawnWindow = () => { - let mainWindow = new BrowserWindow({ - width: 1360, - height: 800, - minWidth: 400, - // A frame but no title bar and windows buttons in titlebar. - // This only currently has an effect on 10.10 OSX and up and is - // ignore on other platforms. - 'title-bar-style': 'hidden' - }) - if (process.env.NODE_ENV === 'development') { - mainWindow.loadURL('file://' + __dirname + '/index-dev.html') - } else { - mainWindow.loadURL('file://' + __dirname + '/index.html') - } - mainWindow.on('closed', function () { - LocalShortcuts.unregister(mainWindow) - - var index = windows.indexOf(mainWindow) - if (index > -1) { - windows.splice(index, 1) - } - }) - - LocalShortcuts.register(mainWindow) - return mainWindow -} - app.on('ready', function () { - windows.push(spawnWindow()) + AppActions.newWindow() ipcMain.on(messages.QUIT_APPLICATION, () => { app.quit() @@ -66,12 +35,6 @@ app.on('ready', function () { BrowserWindow.getFocusedWindow().webContents.send(messages.CONTEXT_MENU_OPENED, nodeName) }) - ipcMain.on(messages.NEW_WINDOW, () => windows.push(spawnWindow())) - process.on(messages.NEW_WINDOW, () => windows.push(spawnWindow())) - - ipcMain.on(messages.CLOSE_WINDOW, () => BrowserWindow.getFocusedWindow().close()) - process.on(messages.CLOSE_WINDOW, () => BrowserWindow.getFocusedWindow().close()) - Menu.init() ipcMain.on(messages.UPDATE_REQUESTED, () => { diff --git a/app/menu.js b/app/menu.js index ad1e672f5be..8ca3a8e5471 100644 --- a/app/menu.js +++ b/app/menu.js @@ -7,6 +7,7 @@ const app = electron.app const Menu = require('menu') const messages = require('../js/constants/messages') const dialog = electron.dialog +const AppActions = require('../js/actions/appActions') /** * Sends a message to the web contents of the focused window. @@ -40,7 +41,7 @@ const init = () => { click: function (item, focusedWindow) { if (!sendToFocusedWindow(focusedWindow, [messages.SHORTCUT_NEW_FRAME])) { // no active windows - process.emit(messages.NEW_WINDOW) + AppActions.newWindow() } } }, { @@ -52,11 +53,11 @@ const init = () => { }, { label: 'New Window', accelerator: 'CmdOrCtrl+N', - click: () => process.emit(messages.NEW_WINDOW) + click: () => AppActions.newWindow() }, { label: 'New Private Window', accelerator: 'CmdOrCtrl+Alt+N', - click: () => process.emit(messages.NEW_WINDOW) + click: () => AppActions.newWindow() }, { type: 'separator' }, { @@ -105,7 +106,7 @@ const init = () => { accelerator: 'CmdOrCtrl+Shift+W', click: function (item, focusedWindow) { if (focusedWindow) { - process.emit(messages.CLOSE_WINDOW) + AppActions.closeWindow(focusedWindow.id) } } }, { diff --git a/js/actions/appActions.js b/js/actions/appActions.js index cfc088d75f2..d2ef3474c1f 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -3,10 +3,8 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict' - const AppDispatcher = require('../dispatcher/appDispatcher') const AppConstants = require('../constants/appConstants') -const ipc = global.require('electron').ipcRenderer const messages = require('../constants/messages') const AppActions = { @@ -14,15 +12,25 @@ const AppActions = { * Dispatches an event to the main process to create a new window */ newWindow: function () { - ipc.send(messages.NEW_WINDOW) + AppDispatcher.dispatch({ + actionType: AppConstants.APP_NEW_WINDOW + }) + }, + + closeWindow: function (appWindowId) { + AppDispatcher.dispatch({ + actionType: AppConstants.APP_CLOSE_WINDOW, + appWindowId + }) }, /** * Dispatches an event to the main process to update the browser */ updateRequested: function () { + // TODO - change to dispatcher console.log('appActions updateRequested') - ipc.send(messages.UPDATE_REQUESTED) + global.require('electron').ipcRenderer.send(messages.UPDATE_REQUESTED) }, /** diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js index f47a2dcc38a..a8c65cc9a2f 100644 --- a/js/actions/windowActions.js +++ b/js/actions/windowActions.js @@ -8,10 +8,13 @@ const WindowDispatcher = require('../dispatcher/windowDispatcher') const WindowConstants = require('../constants/windowConstants') const Config = require('../constants/config') const UrlUtil = require('../../node_modules/urlutil.js/dist/node-urlutil.js') -const ipc = global.require('electron').ipcRenderer +const electron = global.require('electron') +const ipc = electron.ipcRenderer +const remote = electron.remote const messages = require('../constants/messages') +const AppActions = require('./appActions') -const AppActions = { +const WindowActions = { /** * Dispatches a message to the store to load a new URL for the active frame. * Both the frame's src and location properties will be updated accordingly. @@ -144,17 +147,10 @@ const AppActions = { frameProps }) } else { - this.closeWindow() + AppActions.closeWindow(remote.getCurrentWindow().id) } }, - /** - * Dispatches an event to the main process to close the current window - */ - closeWindow: function () { - ipc.send(messages.CLOSE_WINDOW) - }, - /** * Dispatches a message to the store to undo a closed frame * The new frame is expected to appear at the index it was last closed at @@ -447,4 +443,4 @@ const AppActions = { } } -module.exports = AppActions +module.exports = WindowActions diff --git a/js/components/window.js b/js/components/window.js index 145d2ba61b5..a74ece73b1e 100644 --- a/js/components/window.js +++ b/js/components/window.js @@ -7,20 +7,25 @@ const React = require('react') const Immutable = require('immutable') const WindowStore = require('../stores/windowStore') -const AppStore = require('../stores/appStore') const Main = require('./main') +const ipc = global.require('electron').ipcRenderer +const messages = require('../constants/messages') class Window extends React.Component { - constructor () { - super() + constructor (props) { + super(props) + + // initialize appState from props + // and then listen for updates + this.appState = this.props.appState this.state = { immutableData: { windowState: WindowStore.getState(), - appState: AppStore.getState() + appState: this.appState } } + ipc.on(messages.APP_STATE_CHANGE, this.onAppStateChange.bind(this)) WindowStore.addChangeListener(this.onChange.bind(this)) - AppStore.addChangeListener(this.onChange.bind(this)) } render () { @@ -31,6 +36,7 @@ class Window extends React.Component { componentWillUnmount () { WindowStore.removeChangeListener(this.onChange.bind(this)) + ipc.removeListener(this.onAppStateChange) } shouldComponentUpdate (nextProps, nextState) { @@ -41,11 +47,16 @@ class Window extends React.Component { this.setState({ immutableData: { windowState: WindowStore.getState(), - appState: AppStore.getState() + appState: this.appState } }) } + onAppStateChange (appState) { + this.appState = appState + this.onChange() + } } +Window.propTypes = { appState: React.PropTypes.object } module.exports = Window diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 61ee7b3c631..a99c912b74f 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -6,6 +6,8 @@ const mapValuesByKeys = require('../lib/functional').mapValuesByKeys const _ = null const AppConstants = { + APP_NEW_WINDOW: _, + APP_CLOSE_WINDOW: _, APP_ADD_SITE: _, APP_REMOVE_SITE: _ } diff --git a/js/constants/messages.js b/js/constants/messages.js index eff636f25f3..9c01496dc6e 100644 --- a/js/constants/messages.js +++ b/js/constants/messages.js @@ -29,9 +29,6 @@ const messages = { SHORTCUT_FRAME_RELOAD: _, /** @arg {number} key of frame */ SHORTCUT_NEXT_TAB: _, SHORTCUT_PREV_TAB: _, - // Window management - CLOSE_WINDOW: _, - NEW_WINDOW: _, QUIT_APPLICATION: _, // Updates UPDATE_REQUESTED: _, @@ -44,7 +41,9 @@ const messages = { ZOOM_RESET: _, PRINT_PAGE: _, SET_AD_DIV_CANDIDATES: _, /** @arg {Array} adDivCandidates, @arg {string} placeholderUrl */ - CONTEXT_MENU_OPENED: _ /** @arg {string} nodeName of node being clicked */ + CONTEXT_MENU_OPENED: _, /** @arg {string} nodeName of node being clicked */ + APP_STATE_CHANGE: _, + APP_ACTION: _ } module.exports = mapValuesByKeys(messages) diff --git a/js/dispatcher/appDispatcher.js b/js/dispatcher/appDispatcher.js index 135f812fa07..fdff7c2e05d 100644 --- a/js/dispatcher/appDispatcher.js +++ b/js/dispatcher/appDispatcher.js @@ -2,7 +2,22 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -var Dispatcher = require('./dispatcher') +'use strict' +const messages = require('../constants/messages') -const appDispatcher = new Dispatcher() +class AppDispatcher { + /** + * dispatch + * @param {object} payload The data from the action. + */ + dispatch (payload) { + if (process.type === 'renderer') { + global.require('electron').ipcRenderer.send(messages.APP_ACTION, payload) + } else { + process.emit(messages.APP_ACTION, payload) + } + } +} + +const appDispatcher = new AppDispatcher() module.exports = appDispatcher diff --git a/js/entry.js b/js/entry.js index 61a8a050f61..5415657983c 100644 --- a/js/entry.js +++ b/js/entry.js @@ -10,10 +10,15 @@ require('../less/navigationBar.less') require('../less/tabs.less') require('../node_modules/font-awesome/css/font-awesome.css') +const URL = require('url') +const Immutable = require('immutable') const React = require('react') const ReactDOM = require('react-dom') const Window = require('./components/window') +// get appStore from url +var appState = Immutable.fromJS(JSON.parse(URL.parse(window.location.href, true).query.appState)) + ReactDOM.render( - , + , document.getElementById('windowContainer')) diff --git a/js/lib/siteUtil.js b/js/lib/siteUtil.js index 93789539a24..a00faa8a7fd 100644 --- a/js/lib/siteUtil.js +++ b/js/lib/siteUtil.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -import Immutable from 'immutable' +const Immutable = require('immutable') /** * Obtains the index of the location in sites diff --git a/js/state/siteUtil.js b/js/state/siteUtil.js index 4f612a85b05..34750ec6275 100644 --- a/js/state/siteUtil.js +++ b/js/state/siteUtil.js @@ -2,7 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -import Immutable from 'immutable' +'use strict' +const Immutable = require('immutable') + +var exports = {} /** * Obtains the index of the location in sites @@ -11,7 +14,7 @@ import Immutable from 'immutable' * @param location The frameProps of the page in question * @return index of the location or -1 if not found. */ -function getSiteUrlIndex (sites, location) { +exports.getSiteUrlIndex = function (sites, location) { return sites.findIndex(site => site.get('location') === location) } @@ -23,8 +26,8 @@ function getSiteUrlIndex (sites, location) { * @param tag The tag of the site to check * @return true if the location is already bookmarked */ -export function isSiteInList (sites, location, tag) { - let index = getSiteUrlIndex(sites, location) +exports.isSiteInList = function (sites, location, tag) { + let index = exports.getSiteUrlIndex(sites, location) if (index === -1) { return false } @@ -42,8 +45,8 @@ export function isSiteInList (sites, location, tag) { * Otherwise it's only considered to be a history item * @return The new sites Immutable object */ -export function addSite (sites, frameProps, tag) { - let index = getSiteUrlIndex(sites, frameProps.get('location')) +exports.addSite = function (sites, frameProps, tag) { + let index = exports.getSiteUrlIndex(sites, frameProps.get('location')) let tags = sites.getIn([index, 'tags']) || new Immutable.List() if (tag) { tags = tags.toSet().add(tag).toList() @@ -76,8 +79,8 @@ export function addSite (sites, frameProps, tag) { * @param frameProps The frameProps of the page in question * @return The new sites Immutable object */ -export function removeSite (sites, frameProps, tag) { - let index = getSiteUrlIndex(sites, frameProps.get('location')) +exports.removeSite = function (sites, frameProps, tag) { + let index = exports.getSiteUrlIndex(sites, frameProps.get('location')) if (index === -1) { return sites } @@ -85,3 +88,5 @@ export function removeSite (sites, frameProps, tag) { let tags = sites.getIn([index, 'tags']) return sites.setIn([index, 'tags'], tags.toSet().remove(tag).toList()) } + +module.exports = exports diff --git a/js/stores/appStore.js b/js/stores/appStore.js index 963a626a032..74dade91ca4 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -2,48 +2,79 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -const AppDispatcher = require('../dispatcher/appDispatcher') -const EventEmitter = require('events').EventEmitter +'use strict' const AppConstants = require('../constants/appConstants') const Immutable = require('immutable') const SiteUtil = require('../state/siteUtil') -const ipc = global.require('electron').ipcRenderer +const electron = require('electron') +const ipcMain = electron.ipcMain const messages = require('../constants/messages') +const BrowserWindow = electron.BrowserWindow +const LocalShortcuts = require('../../app/localShortcuts') -// For this simple example, store immutable data object for a simple counter. -// This is of course very silly, but this is just for an app template with top -// level immutable data. let appState = Immutable.fromJS({ + windows: [], sites: [], visits: [], updateAvailable: false }) -var CHANGE_EVENT = 'change' +const spawnWindow = () => { + let mainWindow = new BrowserWindow({ + width: 1360, + height: 800, + minWidth: 400, + // Neither a frame nor a titlebar + // frame: false, + // A frame but no title bar and windows buttons in titlebar 10.10 OSX and up only? + 'title-bar-style': 'hidden' + }) -class AppStore extends EventEmitter { - getState () { - return appState - } + // pass the appState into the query string for initialization + // This seems kind of hacky, maybe there is a better way to make + // sure that the window has the app state before it opens? + let queryString = 'appState=' + encodeURIComponent(JSON.stringify(appState)) - emitChange () { - this.emit(CHANGE_EVENT) + if (process.env.NODE_ENV === 'development') { + mainWindow.loadURL('file://' + __dirname + '/../../app/index-dev.html?' + queryString) + } else { + mainWindow.loadURL('file://' + __dirname + '/../../app/index.html?' + queryString) } + mainWindow.on('closed', function () { + LocalShortcuts.unregister(mainWindow) + mainWindow = null + }) - addChangeListener (callback) { - this.on(CHANGE_EVENT, callback) + LocalShortcuts.register(mainWindow) + return mainWindow +} + +class AppStore { + getState () { + return appState } - removeChangeListener (callback) { - this.removeListener(CHANGE_EVENT, callback) + emitChange () { + ipcMain.emit(messages.APP_STATE_CHANGE, this.getState()) } } const appStore = new AppStore() -// Register callback to handle all updates -AppDispatcher.register((action) => { +const handleAppAction = (action) => { switch (action.actionType) { + case AppConstants.APP_NEW_WINDOW: + appState = appState.set('windows', appState.get('windows').push(spawnWindow())) + appStore.emitChange() + break + case AppConstants.APP_CLOSE_WINDOW: + let appWindow = BrowserWindow.fromId(action.appWindowId) + appWindow.close() + + let windows = appState.get('windows') + appState = appState.set('windows', windows.delete(windows.indexOf(appWindow))) + appStore.emitChange() + break case AppConstants.APP_ADD_SITE: appState = appState.set('sites', SiteUtil.addSite(appState.get('sites'), action.frameProps, action.tag)) appStore.emitChange() @@ -54,9 +85,13 @@ AppDispatcher.register((action) => { break default: } -}) +} + +// Register callback to handle all updates +ipcMain.on(messages.APP_ACTION, (event, action) => handleAppAction(action)) +process.on(messages.APP_ACTION, handleAppAction) -ipc.on(messages.UPDATE_AVAILABLE, () => { +process.on(messages.UPDATE_AVAILABLE, () => { console.log('appStore update-available') appState = appState.merge({ updateAvailable: true diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index 54a681d9913..57531dc0481 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -10,9 +10,6 @@ const FrameStateUtil = require('../state/frameStateUtil') const ipc = global.require('electron').ipcRenderer const messages = require('../constants/messages') -// For this simple example, store immutable data object for a simple counter. -// This is of course very silly, but this is just for an app template with top -// level immutable data. let windowState = Immutable.fromJS({ activeFrameKey: null, frames: [],