diff --git a/build/resources/asar-ordering-hint.txt b/build/resources/asar-ordering-hint.txt
index 26eb1c60b..f75d39086 100644
--- a/build/resources/asar-ordering-hint.txt
+++ b/build/resources/asar-ordering-hint.txt
@@ -214,7 +214,7 @@
779612: package.json
779612: package.json
2197703: node_modules/season/lib/cson.js
-2798802: src/window-secondary-bootstrap.js
+2798802: src/secondary-window-bootstrap.js
2799486: node_modules/bluebird/js/main/bluebird.js
2799780: node_modules/bluebird/js/main/promise.js
2197703: node_modules/season/lib/cson.js
@@ -596,7 +596,7 @@
2834742: src/window-bootstrap.js
3183165: src/package-manager.js
2951334: src/nylas-env.js
-2798802: src/window-secondary-bootstrap.js
+2798802: src/secondary-window-bootstrap.js
3268435: node_modules/service-hub/lib/service-hub.js
3270068: node_modules/service-hub/node_modules/event-kit/lib/event-kit.js
3270257: node_modules/service-hub/node_modules/event-kit/lib/emitter.js
@@ -3092,7 +3092,7 @@
2765809: node_modules/semver/semver.js
779612: package.json
2197703: node_modules/season/lib/cson.js
-2798802: src/window-secondary-bootstrap.js
+2798802: src/secondary-window-bootstrap.js
2799486: node_modules/bluebird/js/main/bluebird.js
2799780: node_modules/bluebird/js/main/promise.js
2824131: node_modules/bluebird/js/main/util.js
@@ -3282,7 +3282,7 @@
3205643: node_modules/q/q.js
3183165: src/package-manager.js
2951334: src/nylas-env.js
-2798802: src/window-secondary-bootstrap.js
+2798802: src/secondary-window-bootstrap.js
3268435: node_modules/service-hub/lib/service-hub.js
3270068: node_modules/service-hub/node_modules/event-kit/lib/event-kit.js
3270257: node_modules/service-hub/node_modules/event-kit/lib/emitter.js
@@ -4584,7 +4584,7 @@
2765809: node_modules/semver/semver.js
779612: package.json
2197703: node_modules/season/lib/cson.js
-2798802: src/window-secondary-bootstrap.js
+2798802: src/secondary-window-bootstrap.js
2799486: node_modules/bluebird/js/main/bluebird.js
2799780: node_modules/bluebird/js/main/promise.js
2824131: node_modules/bluebird/js/main/util.js
@@ -4774,7 +4774,7 @@
3205643: node_modules/q/q.js
3183165: src/package-manager.js
2951334: src/nylas-env.js
-2798802: src/window-secondary-bootstrap.js
+2798802: src/secondary-window-bootstrap.js
3268435: node_modules/service-hub/lib/service-hub.js
3270068: node_modules/service-hub/node_modules/event-kit/lib/event-kit.js
3270257: node_modules/service-hub/node_modules/event-kit/lib/emitter.js
diff --git a/internal_packages/composer-scheduler/lib/calendar/proposed-time-picker.jsx b/internal_packages/composer-scheduler/lib/calendar/proposed-time-picker.jsx
index 5416c9eb7..1a6a9356e 100644
--- a/internal_packages/composer-scheduler/lib/calendar/proposed-time-picker.jsx
+++ b/internal_packages/composer-scheduler/lib/calendar/proposed-time-picker.jsx
@@ -28,6 +28,7 @@ export default class ProposedTimePicker extends React.Component {
pendingSave: ProposedTimeCalendarStore.pendingSave(),
});
})
+ NylasEnv.displayWindow()
}
shouldComponentUpdate(nextProps, nextState) {
diff --git a/internal_packages/composer-scheduler/lib/composer/new-event-card.jsx b/internal_packages/composer-scheduler/lib/composer/new-event-card.jsx
index 5f0d9d320..72751a2f1 100644
--- a/internal_packages/composer-scheduler/lib/composer/new-event-card.jsx
+++ b/internal_packages/composer-scheduler/lib/composer/new-event-card.jsx
@@ -9,6 +9,8 @@ import {
} from 'nylas-component-kit'
import {PLUGIN_ID} from '../scheduler-constants'
+import NewEventHelper from './new-event-helper'
+
import ProposedTimeList from './proposed-time-list'
import {
@@ -98,13 +100,7 @@ export default class NewEventCard extends React.Component {
}
_onProposeTimes = () => {
- NylasEnv.newWindow({
- title: "Calendar",
- windowType: "calendar",
- windowProps: {
- draftClientId: this.props.draft.clientId,
- },
- });
+ NewEventHelper.launchCalendarWindow(this.props.draft.clientId);
}
_eventStart() {
diff --git a/internal_packages/composer-scheduler/lib/composer/new-event-helper.es6 b/internal_packages/composer-scheduler/lib/composer/new-event-helper.es6
index daaf017d9..bffe8cd25 100644
--- a/internal_packages/composer-scheduler/lib/composer/new-event-helper.es6
+++ b/internal_packages/composer-scheduler/lib/composer/new-event-helper.es6
@@ -14,6 +14,15 @@ export default class NewEventHelper {
return moment()
}
+ static launchCalendarWindow(draftClientId) {
+ NylasEnv.newWindow({
+ title: "Calendar",
+ hidden: true, // Displayed by ProposedTimePicker::componentDidMount
+ windowType: "calendar",
+ windowProps: {draftClientId},
+ });
+ }
+
static addEventToSession(session) {
if (!session) { return }
const draft = session.draft()
diff --git a/internal_packages/composer-scheduler/lib/composer/scheduler-composer-button.jsx b/internal_packages/composer-scheduler/lib/composer/scheduler-composer-button.jsx
index 79ebc143e..4f4a25bc8 100644
--- a/internal_packages/composer-scheduler/lib/composer/scheduler-composer-button.jsx
+++ b/internal_packages/composer-scheduler/lib/composer/scheduler-composer-button.jsx
@@ -68,13 +68,7 @@ export default class SchedulerComposerButton extends React.Component {
NewEventHelper.addEventToSession(this.props.session)
if (item === PROPOSAL) {
- NylasEnv.newWindow({
- title: "Calendar",
- windowType: "calendar",
- windowProps: {
- draftClientId: this.props.draft.clientId,
- },
- });
+ NewEventHelper.launchCalendarWindow(this.props.draft.clientId)
}
Actions.closePopover()
}
diff --git a/internal_packages/composer/lib/decorators/inflate-draft-client-id.es6 b/internal_packages/composer/lib/decorators/inflate-draft-client-id.es6
index 56cd66f37..22d23b8b3 100644
--- a/internal_packages/composer/lib/decorators/inflate-draft-client-id.es6
+++ b/internal_packages/composer/lib/decorators/inflate-draft-client-id.es6
@@ -5,6 +5,10 @@ export default ComposedComponent => class extends React.Component {
static displayName = ComposedComponent.displayName;
static propTypes = {
draftClientId: React.PropTypes.string,
+ onDraftReady: React.PropTypes.func,
+ }
+ static defaultProps = {
+ onDraftReady: () => {},
}
static containerRequired = false;
@@ -54,6 +58,7 @@ export default ComposedComponent => class extends React.Component {
session: session,
draft: session.draft(),
});
+ this.props.onDraftReady()
});
}
diff --git a/internal_packages/composer/lib/main.es6 b/internal_packages/composer/lib/main.es6
index 091ddd37c..a13266fe2 100644
--- a/internal_packages/composer/lib/main.es6
+++ b/internal_packages/composer/lib/main.es6
@@ -21,40 +21,34 @@ class ComposerWithWindowProps extends React.Component {
constructor(props) {
super(props);
- this.state = NylasEnv.getWindowProps()
+
+ // We'll now always have windowProps by the time we construct this.
+ const windowProps = NylasEnv.getWindowProps()
+ const {draftJSON, draftClientId} = windowProps;
+ const draft = new Message().fromJSON(draftJSON);
+ DraftStore._createSession(draftClientId, draft);
+ this.state = windowProps
}
- componentDidMount() {
- if (this.state.draftClientId) {
- this.ready();
- }
-
- this.unlisten = NylasEnv.onWindowPropsReceived((windowProps) => {
- const {errorMessage, draftJSON, draftClientId} = windowProps;
-
- if (draftJSON) {
- const draft = new Message().fromJSON(draftJSON);
- DraftStore._createSession(draftClientId, draft);
- }
-
- this.setState({draftClientId});
- this.ready();
- if (errorMessage) {
- this._showInitialErrorDialog(errorMessage);
- }
- });
- }
-
- componentWillUnmount() {
- if (this.unlisten) {
- this.unlisten();
- }
- }
-
- ready = () => {
+ onDraftReady = () => {
this.refs.composer.focus().then(() => {
- NylasEnv.getCurrentWindow().show()
- NylasEnv.getCurrentWindow().focus()
+ NylasEnv.displayWindow()
+ if (this.state.errorMessage) {
+ this._showInitialErrorDialog(this.state.errorMessage);
+ }
+ NylasEnv.getCurrentWindow().updateLoadSettings({
+ windowType: "composer",
+ })
+
+ // The call to updateLoadSettings will start loading the remaining
+ // packages. Once those packages load it'll cause a change in the
+ // root Sheet-level InjectedComponentSet, which will cause
+ // everything to re-render losing our focus. We have to manually
+ // refocus it but defer it so the event loop of the package
+ // activation happens first.
+ _.defer(() => {
+ this.refs.composer.focus()
+ })
});
}
@@ -62,6 +56,7 @@ class ComposerWithWindowProps extends React.Component {
return (
@@ -91,28 +86,21 @@ export function activate() {
});
if (NylasEnv.isMainWindow()) {
- NylasEnv.registerHotWindow({
- windowType: 'composer',
- replenishNum: 2,
- });
ComponentRegistry.register(ComposeButton, {
location: WorkspaceStore.Location.RootSidebar.Toolbar,
});
- } else {
- NylasEnv.getCurrentWindow().setMinimumSize(480, 250);
- WorkspaceStore.defineSheet('Main', {root: true}, {
- popout: ['Center'],
- });
- ComponentRegistry.register(ComposerWithWindowProps, {
- location: WorkspaceStore.Location.Center,
- });
}
+
+ NylasEnv.getCurrentWindow().setMinimumSize(480, 250);
+ WorkspaceStore.defineSheet('Main', {root: true}, {
+ popout: ['Center'],
+ });
+ ComponentRegistry.register(ComposerWithWindowProps, {
+ location: WorkspaceStore.Location.Center,
+ });
}
export function deactivate() {
- if (NylasEnv.isMainWindow()) {
- NylasEnv.unregisterHotWindow('composer');
- }
ComponentRegistry.unregister(ComposerViewForDraftClientId);
ComponentRegistry.unregister(ComposeButton);
ComponentRegistry.unregister(ComposerWithWindowProps);
diff --git a/internal_packages/composer/package.json b/internal_packages/composer/package.json
index 9e4b8f34d..9dfb64905 100644
--- a/internal_packages/composer/package.json
+++ b/internal_packages/composer/package.json
@@ -14,6 +14,7 @@
},
"windowTypes": {
"default": true,
- "composer": true
+ "composer": true,
+ "composer-preload": true
}
}
diff --git a/internal_packages/onboarding/lib/page-router.cjsx b/internal_packages/onboarding/lib/page-router.cjsx
index 8f5271338..d600694af 100644
--- a/internal_packages/onboarding/lib/page-router.cjsx
+++ b/internal_packages/onboarding/lib/page-router.cjsx
@@ -36,7 +36,7 @@ class PageRouter extends React.Component
{width, height} = ReactDOM.findDOMNode(@refs.activePage).getBoundingClientRect()
NylasEnv.setSize(width, height)
NylasEnv.center()
- NylasEnv.show()
+ NylasEnv.displayWindow()
_updateWindowSize: =>
return if @_unmounted
diff --git a/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee b/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee
index c138e3b54..65c1582bb 100644
--- a/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee
+++ b/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee
@@ -4,8 +4,7 @@ _ = require 'underscore'
Actions,
AccountStore,
DatabaseStore,
- MailRulesProcessor,
- DatabaseObjectRegistry} = require 'nylas-exports'
+ MailRulesProcessor} = require 'nylas-exports'
NylasLongConnection = require './nylas-long-connection'
NylasSyncWorker = require './nylas-sync-worker'
diff --git a/menus/darwin.cson b/menus/darwin.cson
index 43d2fd9bc..3dc2893bd 100644
--- a/menus/darwin.cson
+++ b/menus/darwin.cson
@@ -73,7 +73,6 @@
{ label: 'Reload', command: 'window:reload' }
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
{ label: 'Toggle Component Regions', command: 'window:toggle-component-regions' }
- { label: 'Toggle React Remote', command: 'window:toggle-react-remote' }
{ label: 'Toggle Screenshot Mode', command: 'window:toggle-screenshot-mode' }
{ type: 'separator' }
{ label: 'Open Activity Window', command: 'application:show-work-window' }
diff --git a/menus/linux.cson b/menus/linux.cson
index f233e24d4..1e273be7c 100644
--- a/menus/linux.cson
+++ b/menus/linux.cson
@@ -54,7 +54,6 @@
{ label: 'Reload', command: 'window:reload' }
{ label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
{ label: 'Toggle Component Regions', command: 'window:toggle-component-regions' }
- { label: 'Toggle React Remote', command: 'window:toggle-react-remote' }
{ label: 'Toggle Screenshot Mode', command: 'window:toggle-screenshot-mode' }
{ type: 'separator' }
{ label: 'Open Activity Window', command: 'application:show-work-window' }
diff --git a/menus/win32.cson b/menus/win32.cson
index c52a92045..29c13bd18 100644
--- a/menus/win32.cson
+++ b/menus/win32.cson
@@ -37,7 +37,6 @@
{ label: '&Reload', command: 'window:reload' }
{ label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
{ label: 'Toggle Component Regions', command: 'window:toggle-component-regions' }
- { label: 'Toggle React Remote', command: 'window:toggle-react-remote' }
{ label: 'Toggle Screenshot Mode', command: 'window:toggle-screenshot-mode' }
{ type: 'separator' }
{ label: 'Open Activity Window', command: 'application:show-work-window' }
diff --git a/spec/database-object-registry-spec.coffee b/spec/database-object-registry-spec.coffee
index 4075b3d51..f4fa286e1 100644
--- a/spec/database-object-registry-spec.coffee
+++ b/spec/database-object-registry-spec.coffee
@@ -3,8 +3,6 @@ Model = require '../src/flux/models/model'
Attributes = require '../src/flux/attributes'
DatabaseObjectRegistry = require '../src/database-object-registry'
-class BadTest
-
class GoodTest extends Model
@attributes: _.extend {}, Model.attributes,
"foo": Attributes.String
@@ -15,25 +13,17 @@ describe 'DatabaseObjectRegistry', ->
beforeEach ->
DatabaseObjectRegistry.unregister("GoodTest")
- it "throws an error if the constructor isn't a Model", ->
- expect( -> DatabaseObjectRegistry.register()).toThrow()
- expect( -> DatabaseObjectRegistry.register(BadTest)).toThrow()
-
it "can register constructors", ->
- expect( -> DatabaseObjectRegistry.register(GoodTest)).not.toThrow()
- expect(DatabaseObjectRegistry._constructors["GoodTest"]).toBe GoodTest
-
- it "Retrurns a map of constructors", ->
- DatabaseObjectRegistry.register(GoodTest)
- map = DatabaseObjectRegistry.classMap()
- expect(map.GoodTest).toBe GoodTest
+ testFn = -> GoodTest
+ expect( -> DatabaseObjectRegistry.register("GoodTest", testFn)).not.toThrow()
+ expect(DatabaseObjectRegistry.get("GoodTest")).toBe GoodTest
it "Tests if a constructor is in the registry", ->
- DatabaseObjectRegistry.register(GoodTest)
+ DatabaseObjectRegistry.register("GoodTest", -> GoodTest)
expect(DatabaseObjectRegistry.isInRegistry("GoodTest")).toBe true
it "deserializes the objects for a constructor", ->
- DatabaseObjectRegistry.register(GoodTest)
+ DatabaseObjectRegistry.register("GoodTest", -> GoodTest)
obj = DatabaseObjectRegistry.deserialize("GoodTest", foo: "bar")
expect(obj instanceof GoodTest).toBe true
expect(obj.foo).toBe "bar"
diff --git a/spec/stores/draft-store-spec.es6 b/spec/stores/draft-store-spec.es6
index 76251a463..850c011d8 100644
--- a/spec/stores/draft-store-spec.es6
+++ b/spec/stores/draft-store-spec.es6
@@ -135,7 +135,9 @@ describe("DraftStore", () => {
runs(() => {
expect(NylasEnv.newWindow).toHaveBeenCalledWith({
title: 'Message',
- windowType: "composer",
+ hidden: true,
+ windowKey: `composer-A`,
+ windowType: "composer-preload",
windowProps: { draftClientId: "A", draftJSON: this.newDraft.toJSON() },
});
});
@@ -155,7 +157,9 @@ describe("DraftStore", () => {
runs(() => {
expect(NylasEnv.newWindow).toHaveBeenCalledWith({
title: 'Message',
- windowType: "composer",
+ hidden: true,
+ windowKey: `composer-A`,
+ windowType: "composer-preload",
windowProps: { draftClientId: "A", draftJSON: this.newDraft.toJSON() },
});
});
diff --git a/src/browser/application.coffee b/src/browser/application.coffee
index ad9f1d62d..04830b367 100644
--- a/src/browser/application.coffee
+++ b/src/browser/application.coffee
@@ -1,6 +1,7 @@
SystemTrayManager = require './system-tray-manager'
NylasWindow = require './nylas-window'
WindowManager = require './window-manager'
+FileListCache = require './file-list-cache'
ApplicationMenu = require './application-menu'
AutoUpdateManager = require './auto-update-manager'
NylasProtocolHandler = require './nylas-protocol-handler'
@@ -64,6 +65,8 @@ class Application
constructor: (options) ->
{@resourcePath, @configDirPath, @version, @devMode, @specMode, @safeMode} = options
+ @fileListCache = new FileListCache(options)
+
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@@ -129,7 +132,7 @@ class Application
@launchWithOptions(options)
getMainNylasWindow: ->
- @windowManager.mainWindow()
+ @windowManager.get(WindowManager.MAIN_WINDOW)
getMainWindow: ->
@getMainNylasWindow().browserWindow
@@ -202,10 +205,13 @@ class Application
openWindowsForTokenState: (loadingMessage) =>
hasAccount = @config.get('nylas.accounts')?.length > 0
if hasAccount
- @windowManager.showMainWindow(loadingMessage)
- @windowManager.ensureWorkWindow()
+ @windowManager.ensureWindow(WindowManager.MAIN_WINDOW, {loadingMessage})
+ @windowManager.ensureWindow(WindowManager.WORK_WINDOW)
else
- @windowManager.ensureOnboardingWindow(welcome: true)
+ @windowManager.ensureWindow(WindowManager.ONBOARDING_WINDOW, {
+ title: "Welcome to N1"
+ windowProps: page: "welcome"
+ })
# The onboarding window automatically shows when it's ready
_resetConfigAndRelaunch: =>
@@ -215,7 +221,10 @@ class Application
@config.set('nylas', null)
@config.set('edgehill', null)
@setDatabasePhase('setup')
- @windowManager.ensureOnboardingWindow(welcome: true)
+ @windowManager.ensureWindow(WindowManager.ONBOARDING_WINDOW, {
+ title: "Welcome to N1"
+ windowProps: page: "welcome"
+ })
_deleteDatabase: (callback) ->
@deleteFileWithRetry path.join(@configDirPath,'edgehill.db'), callback
@@ -232,9 +241,7 @@ class Application
return if phase is @_databasePhase
@_databasePhase = phase
- @windowManager.windows().forEach (nylasWindow) ->
- return unless nylasWindow.browserWindow.webContents
- nylasWindow.browserWindow.webContents.send('database-phase-change', phase)
+ @windowManager.sendToAllWindows("database-phase-change", {}, phase)
rebuildDatabase: =>
return if @_databasePhase is 'close'
@@ -279,18 +286,26 @@ class Application
nylasWindow?.browserWindow.inspectElement(x, y)
@on 'application:add-account', (provider) =>
- @windowManager.ensureOnboardingWindow({provider})
- @on 'application:new-message', => @windowManager.sendToMainWindow('new-message')
+ @windowManager.ensureWindow(WindowManager.ONBOARDING_WINDOW, {
+ title: "Add an Account"
+ windowProps:
+ page: "account-choose"
+ pageData: {provider}
+ })
+ @on 'application:new-message', => @windowManager.sendToWindow(WindowManager.MAIN_WINDOW, 'new-message')
@on 'application:view-help', =>
url = 'https://nylas.zendesk.com/hc/en-us/sections/203638587-N1'
require('electron').shell.openExternal(url)
- @on 'application:open-preferences', => @windowManager.sendToMainWindow('open-preferences')
+ @on 'application:open-preferences', => @windowManager.sendToWindow(WindowManager.MAIN_WINDOW, 'open-preferences')
@on 'application:show-main-window', => @openWindowsForTokenState()
- @on 'application:show-work-window', => @windowManager.showWorkWindow()
+ @on 'application:show-work-window', =>
+ win = @windowManager.get(WindowManager.WORK_WINDOW)
+ win.show()
+ win.focus()
@on 'application:check-for-update', => @autoUpdateManager.check()
@on 'application:install-update', =>
@quitting = true
- @windowManager.unregisterAllHotWindows()
+ @windowManager.cleanupBeforeAppQuit()
@autoUpdateManager.install()
@on 'application:toggle-dev', =>
@@ -326,7 +341,7 @@ class Application
@on 'application:zoom', -> @windowManager.focusedWindow()?.maximize()
app.on 'window-all-closed', =>
- @windowManager.windowClosedOrHidden()
+ @windowManager.quitWinLinuxIfNoWindows()
# Called before the app tries to close any windows.
app.on 'before-quit', =>
@@ -334,7 +349,7 @@ class Application
@quitting = true
# Destroy hot windows so that they can't block the app from quitting.
# (Electron will wait for them to finish loading before quitting.)
- @windowManager.unregisterAllHotWindows()
+ @windowManager.cleanupBeforeAppQuit()
@systemTrayManager.destroyTray()
# Called after the app has closed all windows.
@@ -362,19 +377,10 @@ class Application
app.dock?.setBadge?(value)
ipcMain.on 'new-window', (event, options) =>
- @windowManager.newWindow(options)
-
- ipcMain.on 'register-hot-window', (event, options) =>
- @windowManager.registerHotWindow(options)
-
- ipcMain.on 'unregister-hot-window', (event, windowType) =>
- @windowManager.unregisterHotWindow(windowType)
-
- ipcMain.on 'from-react-remote-window', (event, json) =>
- @windowManager.sendToMainWindow('from-react-remote-window', json)
-
- ipcMain.on 'from-react-remote-window-selection', (event, json) =>
- @windowManager.sendToMainWindow('from-react-remote-window-selection', json)
+ if options.windowKey
+ @windowManager.ensureWindow(options.windowKey, options)
+ else
+ @windowManager.newWindow(options)
ipcMain.on 'inline-style-parse', (event, {html, key}) =>
juice = require 'juice'
@@ -420,13 +426,10 @@ class Application
ipcMain.on 'action-bridge-rebroadcast-to-all', (event, args...) =>
win = BrowserWindow.fromWebContents(event.sender)
- @windowManager.windows().forEach (nylasWindow) ->
- return if nylasWindow.browserWindow == win
- return unless nylasWindow.browserWindow.webContents
- nylasWindow.browserWindow.webContents.send('action-bridge-message', args...)
+ @windowManager.sendToAllWindows('action-bridge-message', {except: win}, args...)
ipcMain.on 'action-bridge-rebroadcast-to-work', (event, args...) =>
- workWindow = @windowManager.workWindow()
+ workWindow = @windowManager.get(WindowManager.WORK_WINDOW)
return if not workWindow or not workWindow.browserWindow.webContents
return if BrowserWindow.fromWebContents(event.sender) is workWindow
workWindow.browserWindow.webContents.send('action-bridge-message', args...)
@@ -437,21 +440,21 @@ class Application
clipboard.writeText(selectedText, 'selection')
ipcMain.on 'account-setup-successful', (event) =>
- @windowManager.showMainWindow()
- @windowManager.ensureWorkWindow()
- @windowManager.onboardingWindow()?.close()
+ @windowManager.ensureWindow(WindowManager.MAIN_WINDOW)
+ @windowManager.ensureWindow(WindowManager.WORK_WINDOW)
+ @windowManager.get(WindowManager.ONBOARDING_WINDOW)?.close()
ipcMain.on 'new-account-added', (event) =>
- @windowManager.ensureWorkWindow()
+ @windowManager.ensureWindow(WindowManager.WORK_WINDOW)
ipcMain.on 'run-in-window', (event, params) =>
@_sourceWindows ?= {}
sourceWindow = BrowserWindow.fromWebContents(event.sender)
@_sourceWindows[params.taskId] = sourceWindow
if params.window is "work"
- targetWindow = @windowManager.workWindow()
+ targetWindow = @windowManager.get(WindowManager.WORK_WINDOW)
else if params.window is "main"
- targetWindow = @windowManager.mainWindow()
+ targetWindow = @windowManager.get(WindowManager.MAIN_WINDOW)
else throw new Error("We don't support running in that window")
return if not targetWindow or not targetWindow.browserWindow.webContents
targetWindow.browserWindow.webContents.send('run-in-window', params)
@@ -478,7 +481,7 @@ class Application
else
unless @sendCommandToFirstResponder(command)
focusedBrowserWindow = BrowserWindow.getFocusedWindow()
- mainWindow = @windowManager.mainWindow()
+ mainWindow = @windowManager.get(WindowManager.MAIN_WINDOW)
if focusedBrowserWindow
switch command
when 'window:reload' then focusedBrowserWindow.reload()
@@ -519,12 +522,12 @@ class Application
openUrl: (urlToOpen) ->
{protocol} = url.parse(urlToOpen)
if protocol is 'mailto:'
- @windowManager.sendToMainWindow('mailto', urlToOpen)
+ @windowManager.sendToWindow(WindowManager.MAIN_WINDOW, 'mailto', urlToOpen)
else
console.log "Ignoring unknown URL type: #{urlToOpen}"
openComposerWithFiles: (pathsToOpen) ->
- @windowManager.sendToMainWindow('mailfiles', pathsToOpen)
+ @windowManager.sendToWindow(WindowManager.MAIN_WINDOW, 'mailfiles', pathsToOpen)
# Opens up a new {NylasWindow} to run specs within.
#
@@ -538,7 +541,9 @@ class Application
# :specPath - The directory to load specs from.
# :safeMode - A Boolean that, if true, won't run specs from ~/.nylas/packages
# and ~/.nylas/dev/packages, defaults to false.
- runSpecs: ({exitWhenDone, showSpecsInWindow, resourcePath, specDirectory, specFilePattern, logFile, safeMode}) ->
+ runSpecs: (specWindowOptions) ->
+ {resourcePath} = specWindowOptions
+
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
@@ -547,12 +552,12 @@ class Application
catch error
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'spec-bootstrap'))
- isSpec = true
- devMode = true
- safeMode ?= false
-
# Important: Use .nylas-spec instead of .nylas to avoid overwriting the
# user's real email config!
configDirPath = path.join(app.getPath('home'), '.nylas-spec')
- new NylasWindow({bootstrapScript, configDirPath, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, specFilePattern, logFile, safeMode, showSpecsInWindow})
+ specWindowOptions.resourcePath = resourcePath
+ specWindowOptions.configDirPath = configDirPath
+ specWindowOptions.bootstrapScript = bootstrapScript
+
+ @windowManager.ensureWindow(WindowManager.SPEC_WINDOW, specWindowOptions)
diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee
index 8d6776a10..c41b76c9b 100644
--- a/src/browser/auto-update-manager.coffee
+++ b/src/browser/auto-update-manager.coffee
@@ -69,7 +69,7 @@ class AutoUpdateManager
autoUpdater.on 'update-downloaded', (event, @releaseNotes, @releaseVersion) =>
@setState(UpdateAvailableState)
- @emitUpdateAvailableEvent(@getWindows()...)
+ @emitUpdateAvailableEvent()
@check(hidePopups: true)
setInterval =>
@@ -82,10 +82,10 @@ class AutoUpdateManager
if autoUpdater.supportsUpdates?
@setState(UnsupportedState) unless autoUpdater.supportsUpdates()
- emitUpdateAvailableEvent: (windows...) ->
+ emitUpdateAvailableEvent: ->
return unless @releaseVersion
- for nylasWindow in windows
- nylasWindow.sendMessage('update-available', {@releaseVersion, @releaseNotes})
+ global.application.windowManager.sendToAllWindows("update-available",
+ {}, {@releaseVersion, @releaseNotes})
setState: (state) ->
return if @state is state
@@ -141,6 +141,3 @@ class AutoUpdateManager
message: 'There was an error checking for updates.'
title: 'Update Error'
detail: message
-
- getWindows: ->
- global.application.windowManager.windows()
diff --git a/src/browser/file-list-cache.es6 b/src/browser/file-list-cache.es6
new file mode 100644
index 000000000..f5f686087
--- /dev/null
+++ b/src/browser/file-list-cache.es6
@@ -0,0 +1,12 @@
+// File operations (like traversing directory trees) are extremely
+// expensive. If any window traverses a tree once, we keep a cache of it
+// on the backend process. That way any new windows don't need to spend
+// their precious load time performing the same expensive operation.
+export default class FileListCache {
+ constructor() {
+ this.imageData = "{}" // A JSON stringified hash
+ this.packagePaths = []
+ this.lessCacheImportPaths = []
+ this.lessCacheImportedFiles = []
+ }
+}
diff --git a/src/browser/nylas-window.coffee b/src/browser/nylas-window.coffee
index 8fc302640..4c6d45ead 100644
--- a/src/browser/nylas-window.coffee
+++ b/src/browser/nylas-window.coffee
@@ -6,6 +6,7 @@ _ = require 'underscore'
{EventEmitter} = require 'events'
WindowIconPath = null
+idNum = 0
module.exports =
class NylasWindow
@@ -27,13 +28,19 @@ class NylasWindow
showSpecsInWindow,
@isSpec,
@devMode,
+ @windowKey,
@safeMode,
@neverClose,
@mainWindow,
+ @windowType,
@resourcePath,
@exitWhenDone,
@configDirPath} = settings
+ if !@windowKey
+ @windowKey = "#{@windowType}-#{idNum}"
+ idNum += 1
+
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@@ -81,7 +88,7 @@ class NylasWindow
options.icon = WindowIconPath
@browserWindow = new BrowserWindow(options)
- global.application.windowManager.addWindow(this)
+ @browserWindow.updateLoadSettings = @updateLoadSettings
@handleEvents()
@@ -104,7 +111,7 @@ class NylasWindow
if fs.statSyncNoException(pathToOpen).isFile?()
loadSettings.initialPath = path.dirname(pathToOpen)
- @setLoadSettings(loadSettings)
+ @browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', =>
@loaded = true
@@ -117,14 +124,27 @@ class NylasWindow
@browserWindow.loadURL(@getURL(loadSettings))
@browserWindow.focusOnWebView() if @isSpec
+ # Let the applicationMenu know that there's a new window available.
+ # The applicationMenu automatically listens to the `closed` event of
+ # the browserWindow to unregister itself
+ global.application.applicationMenu?.addWindow(@browserWindow)
+
+ updateLoadSettings: (newSettings={}) =>
+ @loaded = true
+ @setLoadSettings(Object.assign({}, @browserWindow.loadSettings, newSettings))
+
loadSettings: ->
@browserWindow.loadSettings
+ # This gets called when we want to turn a WindowLauncher.EMPTY_WINDOW
+ # into a new kind of custom popout window.
+ #
+ # The windowType will change which will cause a new set of plugins to
+ # load.
setLoadSettings: (loadSettings) ->
@browserWindow.loadSettings = loadSettings
@browserWindow.loadSettingsChangedSinceGetURL = true
- if @loaded
- @browserWindow.webContents.send('load-settings-changed', loadSettings)
+ @browserWindow.webContents.send('load-settings-changed', loadSettings)
getURL: (loadSettingsObj) ->
# Ignore the windowState when passing loadSettings via URL, since it could
@@ -148,8 +168,17 @@ class NylasWindow
new ContextMenu(menuTemplate, this)
handleEvents: ->
+ # Also see logic in `NylasEnv::onBeforeUnload` and
+ # `WindowEventHandler::AddUnloadCallback`. Classes like the DraftStore
+ # and ActionBridge intercept the closing of windows and perform
+ # action.
+ #
+ # This uses the DOM's `beforeunload` event.
@browserWindow.on 'close', (event) =>
if @neverClose and !global.application.quitting
+
+ # For neverClose windows (like the main window) simply hide and
+ # take out of full screen.
event.preventDefault()
if @browserWindow.isFullScreen()
@browserWindow.once 'leave-full-screen', =>
@@ -157,10 +186,12 @@ class NylasWindow
@browserWindow.setFullScreen(false)
else
@browserWindow.hide()
- @emit 'window:close-prevented'
- @browserWindow.on 'closed', =>
- global.application.windowManager.removeWindow(this)
+ # HOWEVER! If the neverClose window is the last window open, and
+ # it looks like there's no windows actually quit the application
+ # on Linux & Windows.
+ if not @isSpec
+ global.application.windowManager.quitWinLinuxIfNoWindows()
@browserWindow.on 'scroll-touch-begin', =>
@browserWindow.webContents.send('scroll-touch-begin')
diff --git a/src/browser/window-launcher.es6 b/src/browser/window-launcher.es6
new file mode 100644
index 000000000..77315c0d9
--- /dev/null
+++ b/src/browser/window-launcher.es6
@@ -0,0 +1,98 @@
+import NylasWindow from './nylas-window'
+
+const DEBUG_SHOW_HOT_WINDOW = false;
+
+/**
+ * It takes a full second or more to bootup a Nylas window. Most of this
+ * is due to sheer amount of time it takes to parse all of the javascript
+ * and follow the require tree.
+ *
+ * Since popout windows need to be more responsive than that, we pre-load
+ * "hot" windows in the background that have most of the code loaded. Then
+ * all we need to do is load the handful of packages the window
+ * requires and show it.
+ */
+export default class WindowLauncher {
+ static EMPTY_WINDOW = "emptyWindow"
+
+ constructor(appOpts) {
+ this.defaultWindowOpts = {
+ hidden: false,
+ devMode: appOpts.devMode,
+ safeMode: appOpts.safeMode,
+ windowType: WindowLauncher.EMPTY_WINDOW,
+ resourcePath: appOpts.resourcePath,
+ configDirPath: appOpts.configDirPath,
+ }
+ this.hotWindow = new NylasWindow(this._hotWindowOpts());
+
+ if (DEBUG_SHOW_HOT_WINDOW) {
+ this.hotWindow.showWhenLoaded()
+ }
+ }
+
+ newWindow(options) {
+ const opts = Object.assign({}, this.defaultWindowOpts, options);
+ let win;
+ if (opts.bootstrapScript) {
+ win = new NylasWindow(opts)
+ } else {
+ opts.bootstrapScript = this._secondaryWindowBootstrap()
+ if (opts.coldStartOnly) {
+ // Useful for the Worker Window: A secondary window that shouldn't
+ // be hot-loaded
+ win = new NylasWindow(opts)
+ } else {
+ win = this.hotWindow;
+
+ // Regenerate the hot window.
+ this.hotWindow = new NylasWindow(this._hotWindowOpts());
+ if (DEBUG_SHOW_HOT_WINDOW) {
+ this.hotWindow.showWhenLoaded()
+ }
+
+ const newLoadSettings = Object.assign({}, win.loadSettings(), opts)
+ if (newLoadSettings.windowType === WindowLauncher.EMPTY_WINDOW) {
+ throw new Error("Must specify a windowType")
+ }
+
+ // Reset the loaded state and update the load settings.
+ // This will fire `NylasEnv::populateHotWindow` and reload the
+ // packages.
+ win.setLoadSettings(newLoadSettings);
+ }
+ }
+ if (!opts.hidden) {
+ // NOTE: In the case of a cold window, this will show it once
+ // loaded. If it's a hotWindow, since hotWindows have a
+ // `hidden:true` flag, nothing will show. When `setLoadSettings`
+ // starts populating the window in `populateHotWindow` we'll show or
+ // hide based on the windowOpts
+ win.showWhenLoaded()
+ }
+ return win
+ }
+
+ // Note: This method calls `browserWindow.destroy()` which closes
+ // windows without waiting for them to load or firing window lifecycle
+ // events. This is necessary for the app to quit promptly on Linux.
+ // https://phab.nylas.com/T1282
+ cleanupBeforeAppQuit() {
+ this.hotWindow.browserWindow.destroy()
+ }
+
+ _secondaryWindowBootstrap() {
+ if (!this._bootstrap) {
+ this._bootstrap = require.resolve("../secondary-window-bootstrap")
+ }
+ return this._bootstrap
+ }
+
+ _hotWindowOpts() {
+ const hotWindowOpts = Object.assign({}, this.defaultWindowOpts);
+ hotWindowOpts.packageLoadingDeferred = true;
+ hotWindowOpts.bootstrapScript = this._secondaryWindowBootstrap();
+ hotWindowOpts.hidden = DEBUG_SHOW_HOT_WINDOW;
+ return hotWindowOpts
+ }
+}
diff --git a/src/browser/window-manager.coffee b/src/browser/window-manager.coffee
index 5fae1cfdd..29f21b06f 100644
--- a/src/browser/window-manager.coffee
+++ b/src/browser/window-manager.coffee
@@ -1,450 +1,73 @@
_ = require 'underscore'
fs = require 'fs-plus'
NylasWindow = require './nylas-window'
+WindowLauncher = require './window-launcher'
{BrowserWindow, app} = require 'electron'
class WindowManager
- constructor: ({@devMode, @safeMode, @resourcePath, @configDirPath, @config, @initializeInBackground}) ->
- @_windows = []
- @_mainWindow = null
- @_workWindow = null
- @_hotWindows = {}
+ @MAIN_WINDOW: "default"
+ @WORK_WINDOW: "work"
+ @SPEC_WINDOW: "spec"
+ @ONBOARDING_WINDOW: "onboarding"
+
+ constructor: (appOpts) ->
+ {@initializeInBackground} = appOpts
+ @_windows = {}
+ @windowLauncher = new WindowLauncher(appOpts)
+
+ # Be sure to register the very first hot window! If you don't, then
+ # the first window (only) won't get window events (like being notified
+ # the database is setup), which causes components loaded in that
+ # window to not work and may even prevent window closure (like in the
+ # case of the composer)
+ @_registerWindow(@windowLauncher.hotWindow)
+
+ get: (winId) -> @_windows[winId]
+
+ newWindow: (options={}) ->
+ win = @windowLauncher.newWindow(options)
+ @_registerWindow(win)
+ return win
+
+ _registerWindow: (win) =>
+ @_windows[win.windowKey] = win
+ win.browserWindow.on "closed", =>
+ delete @_windows[win.windowKey]
+ @quitWinLinuxIfNoWindows()
+
+ ensureWindow: (winId, extraOpts) ->
+ win = @_windows[winId]
+ if win
+ return if win.loadSettings().hidden
+ if win.isMinimized()
+ win.restore()
+ win.focus()
+ else if !win.isVisible()
+ win.showWhenLoaded()
+ else
+ win.focus()
+ else
+ @newWindow(@_coreWindowOpts(winId, extraOpts))
+
+ sendToWindow: (winId, args...) ->
+ if not @_windows[winId]
+ throw new Error("Can't find window: #{winId}")
+ @_windows[winId].sendMessage(args...)
+
+ sendToAllWindows: (msg, {except}, args...) ->
+ for winId, win of @_windows
+ continue if win.browserWindow == except
+ continue unless win.browserWindow.webContents
+ win.browserWindow.webContents.send(msg, args...)
closeAllWindows: ->
- @closeMainWindow()
- @closeWorkWindow()
- @unregisterAllHotWindows()
- for win in @_windows
- win.close()
+ win.close() for winId, win of @_windows
- windows: ->
- @_windows
+ cleanupBeforeAppQuit: -> @windowLauncher.cleanupBeforeAppQuit()
- windowWithPropsMatching: (props) ->
- _.find @_windows, (nylasWindow) ->
- {windowProps} = nylasWindow.loadSettings()
- return false unless windowProps
- _.every Object.keys(props), (key) -> _.isEqual(props[key],windowProps[key])
-
- focusedWindow: ->
- _.find @_windows, (nylasWindow) -> nylasWindow.isFocused()
-
- visibleWindows: ->
- _.filter @_windows, (nylasWindow) -> nylasWindow.isVisible()
-
- ###
- Main Window
-
- The main window is different from the others, because only one can exist at any
- given time and it is hidden instead of closed so that mail processing still
- happens.
- ###
-
- mainWindow: ->
- @_mainWindow
-
- sendToMainWindow: ->
- return unless @_mainWindow
- @_mainWindow.sendMessage(arguments...)
-
- closeMainWindow: ->
- return unless @_mainWindow
- @_mainWindow.neverClose = false
- @_mainWindow.close()
- @_mainWindow = null
-
- showMainWindow: (loadingMessage) ->
- if @_mainWindow
- if @_mainWindow.isMinimized()
- @_mainWindow.restore()
- @_mainWindow.focus()
- else if !@_mainWindow.isVisible()
- @_mainWindow.showWhenLoaded()
- else
- @_mainWindow.focus()
-
- else
- if @devMode
- try
- bootstrapScript = require.resolve(path.join(@resourcePath, 'src', 'window-bootstrap'))
- resourcePath = @resourcePath
- bootstrapScript ?= require.resolve('../window-bootstrap')
- resourcePath ?= @resourcePath
-
- @_mainWindow = new NylasWindow _.extend {}, @defaultWindowOptions(),
- loadingMessage: loadingMessage
- bootstrapScript: bootstrapScript
- neverClose: true
- mainWindow: true
- windowType: 'default'
- initializeInBackground: @initializeInBackground
- # The position and resizable bit gets reset when the window
- # finishes loading. This represents the state of our "loading"
- # window.
- center: true
- width: 640
- height: 396
- resizable: false
-
- ###
- Work Window
- ###
-
- workWindow: ->
- @_workWindow
-
- closeWorkWindow: ->
- return unless @_workWindow
- @_workWindow.neverClose = false
- @_workWindow.close()
- @_workWindow = null
-
- ensureWorkWindow: ->
- @_workWindow ?= @newWindow
- windowType: 'work'
- title: 'Activity'
- toolbar: true
- neverClose: true
- width: 800
- height: 400
- hidden: true
-
- showWorkWindow: ->
- return unless @_workWindow
- if @_workWindow.isMinimized()
- @_workWindow.restore()
- @_workWindow.focus()
- else if !@_workWindow.isVisible()
- @_workWindow.showWhenLoaded()
- else
- @_workWindow.focus()
-
- ###
- Onboarding Window
-
- The onboarding window is a normal secondary window, but the WindowManager knows
- how to create it itself.
- ###
-
- onboardingWindow: ->
- @windowWithPropsMatching({uniqueId: 'onboarding'})
-
- # Returns a new onboarding window
- #
- ensureOnboardingWindow: ({welcome, provider}={}) ->
- existing = @onboardingWindow()
- if existing
- existing.focus()
- else
- options =
- title: "Add an Account"
- toolbar: false
- resizable: false
- hidden: true # The `PageRouter` will center and show on load
- windowType: 'onboarding'
- windowProps:
- page: 'account-choose'
- uniqueId: 'onboarding'
- pageData: {provider}
-
- if welcome
- options.title = "Welcome to N1"
- options.windowProps.page = 'welcome'
-
- @newWindow(options)
-
- # Makes a new window appear of a certain `windowType`.
- #
- # In almost all cases, instead of booting up a new window from scratch,
- # we pass in new `windowProps` to a pre-loaded "hot window".
- #
- # Individual packages declare what windowTypes they support. We use this
- # to determine what packages to load in a given `windowType`. Inside a
- # package's `package.json` we expect to find an entry of the form:
- #
- # "windowTypes": {
- # "myCustomWindowType": true
- # "someOtherWindowType": true
- # "composer": true
- # }
- #
- # Individual packages must also call `registerHotWindow` upon activation
- # to start the prepartion of `hotWindows` of various types.
- #
- # Once a hot window is registered, we'll have a hidden window with the
- # declared packages of that `windowType` pre-loaded.
- #
- # This means that when `newWindow` is called, instead of going through
- # the bootup process, it simply replaces key parameters and does a soft
- # reload.
- #
- # To listen for window props being sent to your existing hot-loaded window,
- # add a callback to `NylasEnv.onWindowPropsChanged`.
- #
- # Since the window is already loaded, there are only some options that
- # can be soft-reloaded. If you attempt to pass options that a soft
- # reload doesn't support, you'll be forced to load from a `coldStart`.
- #
- # Any options passed in here will be passed into the NylasWindow
- # constructor, which will eventually show up in the window's main
- # loadSettings, which is accessible via `NylasEnv.getLoadSettings()`
- #
- # REQUIRED options:
- # - windowType: defaults "popout". This eventually ends up as
- # NylasEnv.getWindowType()
- #
- # Valid options:
- # - coldStart: true
- # - windowProps: A good place to put any data components of the window
- # need to initialize properly. NOTE: You can only put JSON
- # serializable data. No functions!
- # - title: The title of the page
- #
- # Other non required options:
- # - All of the options of BrowserWindow
- # https://github.com/atom/electron/blob/master/docs/api/browser-window.md#new-browserwindowoptions
- #
- # Returns a new NylasWindow
- #
- newWindow: (options={}) ->
- if options.coldStart or not @_hotWindows[options.windowType]?
- return @newColdWindow(options)
- else
- return @newHotWindow(options)
-
- # This sets up some windows in the background with the requested
- # packages already pre-loaded into it.
- #
- # REQUIRED options:
- # - windowType: registers a new hot window of the given type. This is
- # the key we use to find what packages to load and what kind of window
- # to open
- #
- # Optional options:
- # - replenishNum - (defaults 1) The number of hot windows to keep
- # loaded at any given time. If your package is expected to use a large
- # number of windows, it may be advisable to make this number more than
- # 1. Beware that each load is very resource intensive.
- #
- # - windowPackages - A list of additional packages to load into a
- # window in addition to those declared in various `package.json`s
- #
- registerHotWindow: ({windowType, replenishNum, windowPackages, windowOptions}={}) ->
- if not windowType
- throw new Error("registerHotWindow: please provide a windowType")
-
- @_hotWindows ?= {}
- @_hotWindows[windowType] ?= {}
- @_hotWindows[windowType].replenishNum ?= (replenishNum ? 1)
- @_hotWindows[windowType].loadedWindows ?= []
- @_hotWindows[windowType].windowPackages ?= (windowPackages ? [])
- @_hotWindows[windowType].windowOptions ?= (windowOptions ? {})
-
- @_replenishHotWindows()
-
- unregisterHotWindow: (windowType) ->
- return unless @_hotWindows[windowType]
-
- # Remove entries from the replentishQueue
- @_replenishQueue = _.reject @_replenishQueue, (item) => item.windowType is windowType
-
- # Destroy any hot windows already loaded
- destroyedLoadingWindow = false
- {loadedWindows} = @_hotWindows[windowType]
- for win in loadedWindows
- destroyedLoadingWindow = true if not win.isLoaded()
- win.browserWindow.destroy()
-
- # Delete the hot window configuration
- delete @_hotWindows[windowType]
-
- # If we destroyed a window that was currently loading,
- # the queue will stop processing forever.
- if destroyedLoadingWindow
- @_processingQueue = false
- @_processReplenishQueue()
-
- # Immediately close all of the hot windows and reset the replentish queue
- # to prevent more from being opened without additional calls to registerHotWindow.
- #
- # Note: This method calls `browserWindow.destroy()` which closes windows without
- # waiting for them to load or firing window lifecycle events. This is necessary
- # for the app to quit promptly on Linux. https://phab.nylas.com/T1282
- #
- unregisterAllHotWindows: ->
- for type, {loadedWindows} of @_hotWindows
- for win in loadedWindows
- win.browserWindow.destroy()
- @_replenishQueue = []
- @_hotWindows = {}
-
- defaultWindowOptions: ->
- #TODO: Defaults are also applied in NylasWindow.constructor.
- devMode: @devMode
- safeMode: @safeMode
- windowType: 'popout'
- resourcePath: @resourcePath
- configDirPath: @configDirPath
- bootstrapScript: require.resolve("../window-secondary-bootstrap")
-
- newColdWindow: (options={}) ->
- options = _.extend(@defaultWindowOptions(), options)
- win = new NylasWindow(options)
- newLoadSettings = _.extend(win.loadSettings(), options)
- win.setLoadSettings(newLoadSettings)
- win.showWhenLoaded() unless options.hidden
- return win
-
- # Tries to create a new hot window. Since we're updating an existing
- # window instead of creatinga new one, there are limitations in the
- # options you can provide.
- #
- # Returns a new NylasWindow
- #
- newHotWindow: (options={}) ->
- hotWindowParams = @_hotWindows[options.windowType]
- win = null
-
- if not hotWindowParams?
- console.log "WindowManager: Warning! The requested windowType
- '#{options.windowType}' has not been registered. Be sure to call
- `registerWindowType` first in your packages setup."
- return @newColdWindow(options)
-
- supportedHotWindowKeys = [
- "x"
- "y"
- "title"
- "width"
- "height"
- "bounds"
- "windowType"
- "windowProps"
- ]
-
- unsupported = _.difference(Object.keys(options), supportedHotWindowKeys)
-
- if unsupported.length > 0
- console.log "WindowManager: For the winodw of type
- #{options.windowType}, you are passing options that can't be
- applied to the preloaded window (#{JSON.stringify(unsupported)}).
- Please change the options or pass the `coldStart:true` option to use
- a new window instead of a hot window. If it's just data for the
- window, please put them in the `windowProps` param."
-
- if hotWindowParams.loadedWindows.length is 0
- # No windows ready
- console.log "No windows ready. Loading a new coldWindow"
- options.windowPackages = hotWindowParams.windowPackages
- win = @newColdWindow(options)
- else
- [win] = hotWindowParams.loadedWindows.splice(0,1)
-
- newLoadSettings = _.extend(win.loadSettings(), options)
- win.setLoadSettings(newLoadSettings)
-
- win.browserWindow.setTitle options.title ? ""
-
- if options.x and options.y
- win.browserWindow.setPosition options.x, options.w
-
- if options.width or options.height
- [w,h] = win.browserWindow.getSize()
- w = options.width ? w
- h = options.height ? h
- win.browserWindow.setSize(w,h)
-
- if options.bounds
- win.browserWindow.setBounds options.bounds
-
- @_replenishHotWindows()
-
- return win
-
- # There may be many windowTypes, each that request many windows of that
- # type (the `replenishNum`).
- #
- # Loading windows is very resource intensive, so we want to do them
- # sequentially.
- #
- # We also want to round-robin load across the breadth of window types
- # instead of loading all of the windows of a single type then moving on
- # to the next.
- #
- # We first need to cycle through the registered `hotWindows` and create
- # a breadth-first queue of window loads that we'll store in
- # `@_replenishQueue`.
- #
- # Next we need to start processing the `@_replenishQueue`
- __replenishHotWindows: =>
- @_replenishQueue = []
- queues = {}
- maxWin = 0
- for windowType, data of @_hotWindows
- numOfType = data.replenishNum - data.loadedWindows.length
- maxWin = Math.max(numOfType, maxWin)
- if numOfType > 0
- options = _.extend {}, @defaultWindowOptions(), data.windowOptions
- options.windowType = windowType
- options.windowPackages = data.windowPackages
- queues[windowType] ?= []
- queues[windowType].push(options) for [0...numOfType]
-
- for [0...maxWin]
- for windowType, optionsArray of queues
- if optionsArray.length > 0
- @_replenishQueue.push(optionsArray.shift())
-
- @_processReplenishQueue()
-
- _replenishHotWindows: _.debounce(WindowManager::__replenishHotWindows, 100)
-
- _processReplenishQueue: ->
- return if @_processingQueue
- @_processingQueue = true
- if @_replenishQueue.length > 0
- options = @_replenishQueue.shift()
- console.log "WindowManager: Preparing a new '#{options.windowType}' window"
- newWindow = new NylasWindow(options)
- @_hotWindows[options.windowType].loadedWindows.push(newWindow)
- newWindow.once 'window:loaded', =>
- @_processingQueue = false
- @_processReplenishQueue()
- else
- @_processingQueue = false
-
-
- ###
- Methods called from NylasWindow
- ###
-
- # Public: Removes the {NylasWindow} from the global window list.
- removeWindow: (window) ->
- @_windows.splice @_windows.indexOf(window), 1
- if window is @_mainWindow
- @_mainWindow = null
- if window is @_workWindow
- @_workWindow = null
- @applicationMenu?.enableWindowSpecificItems(false) if @_windows.length == 0
- @windowClosedOrHidden()
-
- # Public: Adds the {NylasWindow} to the global window list.
- # IMPORTANT: NylasWindows add themselves - you don't need to manually add them
- addWindow: (window) ->
- @_windows.push window
- global.application.applicationMenu?.addWindow(window.browserWindow)
- window.once 'window:loaded', =>
- global.application.autoUpdateManager.emitUpdateAvailableEvent(window)
-
- unless window.isSpec
- closePreventedHandler = => @windowClosedOrHidden()
- window.on 'window:close-prevented', closePreventedHandler
- window.browserWindow.once 'closed', =>
- window.removeListener('window:close-prevented', closePreventedHandler)
-
- windowClosedOrHidden: ->
+ quitWinLinuxIfNoWindows: ->
# Typically, N1 stays running in the background on all platforms, since it
# has a status icon you can use to quit it.
#
@@ -457,12 +80,58 @@ class WindowManager
#
if process.platform in ['win32', 'linux']
@quitCheck ?= _.debounce =>
- noVisibleWindows = @visibleWindows().length is 0
- noMainWindowLoaded = not @mainWindow()?.isLoaded()
- if noVisibleWindows and noMainWindowLoaded
+ visibleWindows = _.filter(@_windows, (win) -> win.isVisible())
+ noMainWindowLoaded = not @get(WindowManager.MAIN_WINDOW)?.isLoaded()
+ if visibleWindows.length is 0 and noMainWindowLoaded
app.quit()
, 10000
@quitCheck()
+ focusedWindow: -> _.find(@_windows, (win) -> win.isFocused())
+
+ _coreWindowOpts: (winId, extraOpts={}) ->
+ coreWinOpts = {}
+ coreWinOpts[WindowManager.MAIN_WINDOW] =
+ windowKey: WindowManager.MAIN_WINDOW
+ windowType: WindowManager.MAIN_WINDOW
+ title: "Nylas N1"
+ neverClose: true
+ bootstrapScript: require.resolve("../window-bootstrap")
+ mainWindow: true
+ width: 640
+ height: 396
+ center: true
+ resizable: false
+ initializeInBackground: @initializeInBackground
+
+ coreWinOpts[WindowManager.WORK_WINDOW] =
+ windowKey: WindowManager.WORK_WINDOW
+ windowType: WindowManager.WORK_WINDOW
+ coldStartOnly: true # It's a secondary window, but not a hot window
+ title: "Activity"
+ toolbar: true
+ hidden: true
+ neverClose: true
+ width: 800
+ height: 400
+
+ coreWinOpts[WindowManager.ONBOARDING_WINDOW] =
+ windowKey: WindowManager.ONBOARDING_WINDOW
+ windowType: WindowManager.ONBOARDING_WINDOW
+ resizable: false
+ toolbar: false
+ hidden: true # Displayed by PageRouter::_initializeWindowSize
+
+ # The SPEC_WINDOW gets passed its own bootstrapScript
+ coreWinOpts[WindowManager.SPEC_WINDOW] =
+ windowKey: WindowManager.SPEC_WINDOW
+ windowType: WindowManager.SPEC_WINDOW
+ hidden: true,
+ isSpec: true,
+ devMode: true,
+
+ defaultOptions = coreWinOpts[winId] ? {}
+
+ return Object.assign({}, defaultOptions, extraOpts)
module.exports = WindowManager
diff --git a/src/components/popover.cjsx b/src/components/popover.cjsx
index 6410f351c..7fa3865d6 100644
--- a/src/components/popover.cjsx
+++ b/src/components/popover.cjsx
@@ -1,4 +1,5 @@
React = require 'react'
+ReactDOM = require 'react-dom'
_ = require 'underscore'
{CompositeDisposable} = require 'event-kit'
diff --git a/src/database-object-registry.coffee b/src/database-object-registry.coffee
deleted file mode 100644
index 0647483ea..000000000
--- a/src/database-object-registry.coffee
+++ /dev/null
@@ -1,16 +0,0 @@
-_ = require 'underscore'
-Model = null
-SerializableRegistry = require './serializable-registry'
-
-class DatabaseObjectRegistry extends SerializableRegistry
-
- classMap: -> return @_constructors
-
- register: (constructor) ->
- Model ?= require './flux/models/model'
- if constructor?.prototype instanceof Model
- super
- else
- throw new Error("You must register a Database Object class with this registry", constructor)
-
-module.exports = new DatabaseObjectRegistry()
diff --git a/src/database-object-registry.es6 b/src/database-object-registry.es6
new file mode 100644
index 000000000..766de3fdd
--- /dev/null
+++ b/src/database-object-registry.es6
@@ -0,0 +1,6 @@
+import SerializableRegistry from './serializable-registry'
+
+class DatabaseObjectRegistry extends SerializableRegistry { }
+
+const registry = new DatabaseObjectRegistry()
+export default registry
diff --git a/src/deprecate-utils.coffee b/src/deprecate-utils.coffee
index 165209a0e..4c642c40d 100644
--- a/src/deprecate-utils.coffee
+++ b/src/deprecate-utils.coffee
@@ -1,5 +1,3 @@
-_ = require 'underscore'
-
class DeprecateUtils
# See
# http://www.codeovertones.com/2011/08/how-to-print-stack-trace-anywhere-in.html
@@ -26,7 +24,7 @@ class DeprecateUtils
)
warn = false
return fn.apply(ctx, arguments)
- return _.extend(newFn, fn)
+ return Object.assign(newFn, fn)
return fn
module.exports = DeprecateUtils
diff --git a/src/flux/models/utils.coffee b/src/flux/models/utils.coffee
index 238d3bf4b..47bca868d 100644
--- a/src/flux/models/utils.coffee
+++ b/src/flux/models/utils.coffee
@@ -4,9 +4,11 @@ path = require('path')
moment = require('moment-timezone')
tz = Intl.DateTimeFormat().resolvedOptions().timeZone
-TaskRegistry = null
-DatabaseObjectRegistry = null
DefaultResourcePath = null
+TaskRegistry = require '../../task-registry'
+DatabaseObjectRegistry = require '../../database-object-registry'
+
+imageData = null
module.exports =
Utils =
@@ -36,9 +38,6 @@ Utils =
type = v?.__constructorName
return v unless type
- TaskRegistry ?= require '../../task-registry'
- DatabaseObjectRegistry ?= require '../../database-object-registry'
-
if DatabaseObjectRegistry.isInRegistry(type)
return DatabaseObjectRegistry.deserialize(type, v)
@@ -48,9 +47,6 @@ Utils =
return v
registeredObjectReplacer: (k, v) ->
- TaskRegistry ?= require '../../task-registry'
- DatabaseObjectRegistry ?= require '../../database-object-registry'
-
if _.isObject(v)
type = this[k].constructor.name
if DatabaseObjectRegistry.isInRegistry(type) or TaskRegistry.isInRegistry(type)
@@ -189,17 +185,23 @@ Utils =
DefaultResourcePath ?= NylasEnv.getLoadSettings().resourcePath
resourcePath ?= DefaultResourcePath
- Utils.images ?= {}
- if not Utils.images[resourcePath]?
+ if not imageData
+ imageData = NylasEnv.fileListCache().imageData ? "{}"
+ Utils.images = JSON.parse(imageData) ? {}
+
+ if not Utils?.images?[resourcePath]
+ Utils.images ?= {}
+ Utils.images[resourcePath] ?= {}
imagesPath = path.join(resourcePath, 'static', 'images')
files = fs.listTreeSync(imagesPath)
-
- Utils.images[resourcePath] ?= {}
for file in files
- # On Windows, we get paths like C:\images\compose.png, but Chromium doesn't
- # accept the backward slashes. Convert to C:/images/compose.png
+ # On Windows, we get paths like C:\images\compose.png, but
+ # Chromium doesn't accept the backward slashes. Convert to
+ # C:/images/compose.png
file = file.replace(/\\/g, '/')
+ basename = path.basename(file)
Utils.images[resourcePath][path.basename(file)] = file
+ NylasEnv.fileListCache().imageData = JSON.stringify(Utils.images)
plat = process.platform ? ""
ratio = window.devicePixelRatio ? 1
diff --git a/src/flux/stores/database-setup-query-builder.coffee b/src/flux/stores/database-setup-query-builder.coffee
index 9b8a837be..1b2484716 100644
--- a/src/flux/stores/database-setup-query-builder.coffee
+++ b/src/flux/stores/database-setup-query-builder.coffee
@@ -12,13 +12,13 @@ class DatabaseSetupQueryBuilder
setupQueries: ->
queries = []
- for key, klass of DatabaseObjectRegistry.classMap()
+ for klass in DatabaseObjectRegistry.getAllConstructors()
queries = queries.concat @setupQueriesForTable(klass)
return queries
analyzeQueries: ->
queries = []
- for key, klass of DatabaseObjectRegistry.classMap()
+ for klass in DatabaseObjectRegistry.getAllConstructors()
attributes = _.values(klass.attributes)
collectionAttributes = _.filter attributes, (attr) ->
attr.queryable && attr instanceof AttributeCollection
diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee
index 9884c388a..8df2ba7df 100644
--- a/src/flux/stores/draft-store.coffee
+++ b/src/flux/stores/draft-store.coffee
@@ -279,16 +279,15 @@ class DraftStore
title = if options.newDraft then "New Message" else "Message"
save.then =>
- app = require('electron').remote.getGlobal('application')
- existing = app.windowManager.windowWithPropsMatching({draftClientId})
- if existing
- existing.restore() if existing.isMinimized()
- existing.focus()
- else
- NylasEnv.newWindow
- title: title
- windowType: "composer"
- windowProps: _.extend(options, {draftClientId, draftJSON})
+ # Since we pass a windowKey, if the popout composer draft already
+ # exists we'll simply show that one instead of spawning a whole new
+ # window.
+ NylasEnv.newWindow
+ title: title
+ hidden: true # We manually show in ComposerWithWindowProps::onDraftReady
+ windowKey: "composer-#{draftClientId}"
+ windowType: "composer-preload"
+ windowProps: _.extend(options, {draftClientId, draftJSON})
_onHandleMailtoLink: (event, urlString) =>
DraftFactory.createDraftForMailto(urlString).then (draft) =>
diff --git a/src/flux/tasks/syncback-metadata-task.es6 b/src/flux/tasks/syncback-metadata-task.es6
index 733b6d902..8309ac18f 100644
--- a/src/flux/tasks/syncback-metadata-task.es6
+++ b/src/flux/tasks/syncback-metadata-task.es6
@@ -10,7 +10,7 @@ export default class SyncbackMetadataTask extends SyncbackModelTask {
}
getModelConstructor() {
- return DatabaseObjectRegistry.classMap()[this.modelClassName];
+ return DatabaseObjectRegistry.get(this.modelClassName);
}
getRequestData = (model) => {
diff --git a/src/global/nylas-exports.coffee b/src/global/nylas-exports.coffee
index 3b35e161c..0bc64e76f 100644
--- a/src/global/nylas-exports.coffee
+++ b/src/global/nylas-exports.coffee
@@ -1,211 +1,187 @@
-Task = null
-Model = null
-TaskRegistry = null
-DatabaseObjectRegistry = null
+TaskRegistry = require '../task-registry'
+StoreRegistry = require '../store-registry'
+DatabaseObjectRegistry = require '../database-object-registry'
class NylasExports
- @registerSerializable = (exported) ->
- if exported.prototype
- Task ?= require '../flux/tasks/task'
- Model ?= require '../flux/models/model'
- if exported.prototype instanceof Model
- DatabaseObjectRegistry ?= require '../database-object-registry'
- DatabaseObjectRegistry.register(exported)
- else if exported.prototype instanceof Task
- TaskRegistry ?= require '../task-registry'
- TaskRegistry.register(exported)
+ # Will lazy load when requested
+ @lazyLoad = (prop, path) ->
+ Object.defineProperty @, prop,
+ get: -> require("../#{path}")
+ enumerable: true
- @get = (prop, get) ->
+ @lazyLoadCustomGetter = (prop, get) ->
Object.defineProperty @, prop, {get, enumerable: true}
- # Will lazy load when requested
- @load = (prop, path) ->
- Object.defineProperty @, prop,
- get: ->
- exported = require "../#{path}"
- NylasExports.registerSerializable(exported)
- return exported
- enumerable: true
+ @lazyLoadAndRegisterStore = (klassName, path) ->
+ constructorFactory = -> require("../flux/stores/#{path}")
+ StoreRegistry.register(klassName, constructorFactory)
+ @lazyLoad(klassName, "flux/stores/#{path}")
- # Will require immediately
- @require = (prop, path) ->
- exported = require "../#{path}"
- NylasExports.registerSerializable(exported)
- @[prop] = exported
+ @lazyLoadAndRegisterModel = (klassName, path) ->
+ constructorFactory = -> require("../flux/models/#{path}")
+ DatabaseObjectRegistry.register(klassName, constructorFactory)
+ @lazyLoad(klassName, "flux/models/#{path}")
- @requireDeprecated = (prop, path, {instead} = {}) ->
+ @lazyLoadAndRegisterTask = (klassName, path) ->
+ constructorFactory = -> require("../flux/tasks/#{path}")
+ TaskRegistry.register(klassName, constructorFactory)
+ @lazyLoad(klassName, "flux/tasks/#{path}")
+
+ @lazyLoadDeprecated = (prop, path, {instead} = {}) ->
{deprecate} = require '../deprecate-utils'
Object.defineProperty @, prop,
- get: deprecate prop, instead, @, ->
- exported = require "../#{path}"
- NylasExports.registerSerializable(exported)
- return exported
+ get: deprecate prop, instead, @, -> require("../#{path}")
enumerable: true
- # Make sure our custom observable helpers are defined immediately
- # (fromStore, fromQuery, etc...)
- require 'nylas-observables'
-
# Actions
- @load "Actions", 'flux/actions'
+ @lazyLoad "Actions", 'flux/actions'
# API Endpoints
- @load "NylasAPI", 'flux/nylas-api'
- @load "NylasSyncStatusStore", 'flux/stores/nylas-sync-status-store'
- @load "EdgehillAPI", 'flux/edgehill-api'
+ @lazyLoad "NylasAPI", 'flux/nylas-api'
+ @lazyLoad "EdgehillAPI", 'flux/edgehill-api'
+ @lazyLoad "NylasSyncStatusStore", 'flux/stores/nylas-sync-status-store'
# The Database
- @load "Matcher", 'flux/attributes/matcher'
- @load "DatabaseStore", 'flux/stores/database-store'
- @load "DatabaseTransaction", 'flux/stores/database-transaction'
- @load "QueryResultSet", 'flux/models/query-result-set'
- @load "MutableQueryResultSet", 'flux/models/mutable-query-result-set'
- @load "ObservableListDataSource", 'flux/stores/observable-list-data-source'
- @load "CalendarDataSource", 'components/nylas-calendar/calendar-data-source'
- @load "QuerySubscription", 'flux/models/query-subscription'
- @load "MutableQuerySubscription", 'flux/models/mutable-query-subscription'
- @load "QuerySubscriptionPool", 'flux/models/query-subscription-pool'
+ @lazyLoad "Matcher", 'flux/attributes/matcher'
+ @lazyLoad "DatabaseStore", 'flux/stores/database-store'
+ @lazyLoad "QueryResultSet", 'flux/models/query-result-set'
+ @lazyLoad "QuerySubscription", 'flux/models/query-subscription'
+ @lazyLoad "CalendarDataSource", 'components/nylas-calendar/calendar-data-source'
+ @lazyLoad "DatabaseTransaction", 'flux/stores/database-transaction'
+ @lazyLoad "MutableQueryResultSet", 'flux/models/mutable-query-result-set'
+ @lazyLoad "QuerySubscriptionPool", 'flux/models/query-subscription-pool'
+ @lazyLoad "ObservableListDataSource", 'flux/stores/observable-list-data-source'
+ @lazyLoad "MutableQuerySubscription", 'flux/models/mutable-query-subscription'
# Database Objects
- # These need to be required immeidatley to populated the
- # DatabaseObjectRegistry so we know what Database Tables to construct
- @require "File", 'flux/models/file'
- @require "Event", 'flux/models/event'
- @require "Label", 'flux/models/label'
- @require "Folder", 'flux/models/folder'
- @require "Thread", 'flux/models/thread'
- @require "Account", 'flux/models/account'
- @require "Message", 'flux/models/message'
- @require "Contact", 'flux/models/contact'
- @require "Category", 'flux/models/category'
- @require "Calendar", 'flux/models/calendar'
- @require "JSONBlob", 'flux/models/json-blob'
- @require "DatabaseObjectRegistry", "database-object-registry"
- @require "MailboxPerspective", 'mailbox-perspective'
-
- # Exported so 3rd party packages can subclass Model
- @load "Model", 'flux/models/model'
- @load "Attributes", 'flux/attributes'
-
- # The Task Queue
- @require "Task", 'flux/tasks/task'
- @require "TaskRegistry", "task-registry"
- @require "TaskQueue", 'flux/stores/task-queue'
- @require "TaskFactory", 'flux/tasks/task-factory'
- @load "TaskQueueStatusStore", 'flux/stores/task-queue-status-store'
- @require "UndoRedoStore", 'flux/stores/undo-redo-store'
+ @DatabaseObjectRegistry = DatabaseObjectRegistry
+ @lazyLoad "Model", 'flux/models/model'
+ @lazyLoad "Attributes", 'flux/attributes'
+ @lazyLoadAndRegisterModel "File", 'file'
+ @lazyLoadAndRegisterModel "Event", 'event'
+ @lazyLoadAndRegisterModel "Label", 'label'
+ @lazyLoadAndRegisterModel "Folder", 'folder'
+ @lazyLoadAndRegisterModel "Thread", 'thread'
+ @lazyLoadAndRegisterModel "Account", 'account'
+ @lazyLoadAndRegisterModel "Message", 'message'
+ @lazyLoadAndRegisterModel "Contact", 'contact'
+ @lazyLoadAndRegisterModel "Category", 'category'
+ @lazyLoadAndRegisterModel "Calendar", 'calendar'
+ @lazyLoadAndRegisterModel "JSONBlob", 'json-blob'
# Tasks
- # These need to be required immediately to populate the TaskRegistry so
- # we know how to deserialized saved or IPC-sent tasks.
- @require "EventRSVPTask", 'flux/tasks/event-rsvp-task'
- @require "SendDraftTask", 'flux/tasks/send-draft-task'
- @require "DestroyDraftTask", 'flux/tasks/destroy-draft-task'
- @require "ChangeMailTask", 'flux/tasks/change-mail-task'
- @require "ChangeLabelsTask", 'flux/tasks/change-labels-task'
- @require "ChangeFolderTask", 'flux/tasks/change-folder-task'
- @require "SyncbackCategoryTask", 'flux/tasks/syncback-category-task'
- @require "DestroyCategoryTask", 'flux/tasks/destroy-category-task'
- @require "ChangeUnreadTask", 'flux/tasks/change-unread-task'
- @require "SyncbackDraftFilesTask", 'flux/tasks/syncback-draft-files-task'
- @require "SyncbackDraftTask", 'flux/tasks/syncback-draft-task'
- @require "ChangeStarredTask", 'flux/tasks/change-starred-task'
- @require "DestroyModelTask", 'flux/tasks/destroy-model-task'
- @require "SyncbackModelTask", 'flux/tasks/syncback-model-task'
- @require "SyncbackMetadataTask", 'flux/tasks/syncback-metadata-task'
- @require "ReprocessMailRulesTask", 'flux/tasks/reprocess-mail-rules-task'
+ @TaskRegistry = TaskRegistry
+ @lazyLoad "Task", 'flux/tasks/task'
+ @lazyLoad "TaskFactory", 'flux/tasks/task-factory'
+ @lazyLoadAndRegisterTask "EventRSVPTask", 'event-rsvp-task'
+ @lazyLoadAndRegisterTask "SendDraftTask", 'send-draft-task'
+ @lazyLoadAndRegisterTask "ChangeMailTask", 'change-mail-task'
+ @lazyLoadAndRegisterTask "DestroyDraftTask", 'destroy-draft-task'
+ @lazyLoadAndRegisterTask "ChangeLabelsTask", 'change-labels-task'
+ @lazyLoadAndRegisterTask "ChangeFolderTask", 'change-folder-task'
+ @lazyLoadAndRegisterTask "ChangeUnreadTask", 'change-unread-task'
+ @lazyLoadAndRegisterTask "DestroyModelTask", 'destroy-model-task'
+ @lazyLoadAndRegisterTask "SyncbackDraftTask", 'syncback-draft-task'
+ @lazyLoadAndRegisterTask "ChangeStarredTask", 'change-starred-task'
+ @lazyLoadAndRegisterTask "SyncbackModelTask", 'syncback-model-task'
+ @lazyLoadAndRegisterTask "DestroyCategoryTask", 'destroy-category-task'
+ @lazyLoadAndRegisterTask "SyncbackCategoryTask", 'syncback-category-task'
+ @lazyLoadAndRegisterTask "SyncbackMetadataTask", 'syncback-metadata-task'
+ @lazyLoadAndRegisterTask "SyncbackDraftFilesTask", 'syncback-draft-files-task'
+ @lazyLoadAndRegisterTask "ReprocessMailRulesTask", 'reprocess-mail-rules-task'
# Stores
# These need to be required immediately since some Stores are
# listen-only and not explicitly required from anywhere. Stores
# currently set themselves up on require.
- @require "DraftStore", 'flux/stores/draft-store'
- @require "OutboxStore", 'flux/stores/outbox-store'
- @require "AccountStore", 'flux/stores/account-store'
- @require "MessageStore", 'flux/stores/message-store'
- @require "MetadataStore", 'flux/stores/metadata-store'
- @require "ContactStore", 'flux/stores/contact-store'
- @require "CategoryStore", 'flux/stores/category-store'
- @require "WorkspaceStore", 'flux/stores/workspace-store'
- @require "FileUploadStore", 'flux/stores/file-upload-store'
- @require "MailRulesStore", 'flux/stores/mail-rules-store'
- @require "ThreadCountsStore", 'flux/stores/thread-counts-store'
- @require "BadgeStore", 'flux/stores/badge-store'
- @require "FileDownloadStore", 'flux/stores/file-download-store'
- @require "FocusedContentStore", 'flux/stores/focused-content-store'
- @require "FocusedPerspectiveStore", 'flux/stores/focused-perspective-store'
- @require "FocusedContactsStore", 'flux/stores/focused-contacts-store'
- @require "PreferencesUIStore", 'flux/stores/preferences-ui-store'
- @require "PopoverStore", 'flux/stores/popover-store'
- @require "ModalStore", 'flux/stores/modal-store'
- @require "SearchableComponentStore", 'flux/stores/searchable-component-store'
- @require "MessageBodyProcessor", 'flux/stores/message-body-processor'
- @require "MailRulesTemplates", 'mail-rules-templates'
- @require "MailRulesProcessor", 'mail-rules-processor'
-
- # Deprecated
- @requireDeprecated "DraftStoreExtension", 'flux/stores/draft-store-extension',
- instead: 'ComposerExtension'
- @requireDeprecated "MessageStoreExtension", 'flux/stores/message-store-extension',
- instead: 'MessageViewExtension'
+ @lazyLoadAndRegisterStore "TaskQueue", 'task-queue'
+ @lazyLoadAndRegisterStore "BadgeStore", 'badge-store'
+ @lazyLoadAndRegisterStore "DraftStore", 'draft-store'
+ @lazyLoadAndRegisterStore "ModalStore", 'modal-store'
+ @lazyLoadAndRegisterStore "OutboxStore", 'outbox-store'
+ @lazyLoadAndRegisterStore "PopoverStore", 'popover-store'
+ @lazyLoadAndRegisterStore "AccountStore", 'account-store'
+ @lazyLoadAndRegisterStore "MessageStore", 'message-store'
+ @lazyLoadAndRegisterStore "ContactStore", 'contact-store'
+ @lazyLoadAndRegisterStore "MetadataStore", 'metadata-store'
+ @lazyLoadAndRegisterStore "CategoryStore", 'category-store'
+ @lazyLoadAndRegisterStore "UndoRedoStore", 'undo-redo-store'
+ @lazyLoadAndRegisterStore "WorkspaceStore", 'workspace-store'
+ @lazyLoadAndRegisterStore "MailRulesStore", 'mail-rules-store'
+ @lazyLoadAndRegisterStore "FileUploadStore", 'file-upload-store'
+ @lazyLoadAndRegisterStore "ThreadCountsStore", 'thread-counts-store'
+ @lazyLoadAndRegisterStore "FileDownloadStore", 'file-download-store'
+ @lazyLoadAndRegisterStore "PreferencesUIStore", 'preferences-ui-store'
+ @lazyLoadAndRegisterStore "FocusedContentStore", 'focused-content-store'
+ @lazyLoadAndRegisterStore "MessageBodyProcessor", 'message-body-processor'
+ @lazyLoadAndRegisterStore "FocusedContactsStore", 'focused-contacts-store'
+ @lazyLoadAndRegisterStore "TaskQueueStatusStore", 'task-queue-status-store'
+ @lazyLoadAndRegisterStore "FocusedPerspectiveStore", 'focused-perspective-store'
+ @lazyLoadAndRegisterStore "SearchableComponentStore", 'searchable-component-store'
# Extensions
- @require "ExtensionRegistry", 'extension-registry'
- @require "ContenteditableExtension", 'extensions/contenteditable-extension'
- @require "ComposerExtension", 'extensions/composer-extension'
- @require "MessageViewExtension", 'extensions/message-view-extension'
+ @lazyLoad "ExtensionRegistry", 'extension-registry'
+ @lazyLoad "ComposerExtension", 'extensions/composer-extension'
+ @lazyLoad "MessageViewExtension", 'extensions/message-view-extension'
+ @lazyLoad "ContenteditableExtension", 'extensions/contenteditable-extension'
- # Libraries
- @get "React", -> require 'react' # Our version of React for 3rd party use
- @get "ReactDOM", -> require 'react-dom'
- @get "ReactTestUtils", -> require 'react-addons-test-utils'
- @get "Reflux", -> require 'reflux'
- @get "Rx", -> require 'rx-lite'
- @get "Keytar", -> require 'keytar' # atom-keytar access through native module
+ # 3rd party libraries
+ @lazyLoadCustomGetter "Rx", -> require 'rx-lite'
+ @lazyLoadCustomGetter "React", -> require 'react'
+ @lazyLoadCustomGetter "Reflux", -> require 'reflux'
+ @lazyLoadCustomGetter "ReactDOM", -> require 'react-dom'
+ @lazyLoadCustomGetter "ReactTestUtils", -> require 'react-addons-test-utils'
+ @lazyLoadCustomGetter "Keytar", -> require 'keytar' # atom-keytar access through native module
# React Components
- @load "ReactRemote", 'react-remote/react-remote-parent'
- @load "ComponentRegistry", 'component-registry'
- @load "PriorityUICoordinator", 'priority-ui-coordinator'
+ @lazyLoad "ComponentRegistry", 'component-registry'
+ @lazyLoad "PriorityUICoordinator", 'priority-ui-coordinator'
# Utils
- @load "DeprecateUtils", 'deprecate-utils'
- @load "Utils", 'flux/models/utils'
- @load "DOMUtils", 'dom-utils'
- @load "VirtualDOMUtils", 'virtual-dom-utils'
- @load "CanvasUtils", 'canvas-utils'
- @load "RegExpUtils", 'regexp-utils'
- @load "DateUtils", 'date-utils'
- @load "MenuHelpers", 'menu-helpers'
- @load "MessageUtils", 'flux/models/message-utils'
- @load "NylasSpellchecker", 'nylas-spellchecker'
+ @lazyLoad "Utils", 'flux/models/utils'
+ @lazyLoad "DOMUtils", 'dom-utils'
+ @lazyLoad "DateUtils", 'date-utils'
+ @lazyLoad "CanvasUtils", 'canvas-utils'
+ @lazyLoad "RegExpUtils", 'regexp-utils'
+ @lazyLoad "MenuHelpers", 'menu-helpers'
+ @lazyLoad "MessageUtils", 'flux/models/message-utils'
+ @lazyLoad "DeprecateUtils", 'deprecate-utils'
+ @lazyLoad "VirtualDOMUtils", 'virtual-dom-utils'
+ @lazyLoad "NylasSpellchecker", 'nylas-spellchecker'
# Services
- @load "UndoManager", 'undo-manager'
- @load "SoundRegistry", 'sound-registry'
- @load "NativeNotifications", 'native-notifications'
-
- @load "SearchableComponentMaker", 'searchable-components/searchable-component-maker'
-
- @load "QuotedHTMLTransformer", 'services/quoted-html-transformer'
- @load "QuotedPlainTextTransformer", 'services/quoted-plain-text-transformer'
- @load "SanitizeTransformer", 'services/sanitize-transformer'
- @load "InlineStyleTransformer", 'services/inline-style-transformer'
- @requireDeprecated "QuotedHTMLParser", 'services/quoted-html-transformer',
- instead: 'QuotedHTMLTransformer'
+ @lazyLoad "UndoManager", 'undo-manager'
+ @lazyLoad "SoundRegistry", 'sound-registry'
+ @lazyLoad "MailRulesTemplates", 'mail-rules-templates'
+ @lazyLoad "MailRulesProcessor", 'mail-rules-processor'
+ @lazyLoad "MailboxPerspective", 'mailbox-perspective'
+ @lazyLoad "NativeNotifications", 'native-notifications'
+ @lazyLoad "SanitizeTransformer", 'services/sanitize-transformer'
+ @lazyLoad "QuotedHTMLTransformer", 'services/quoted-html-transformer'
+ @lazyLoad "InlineStyleTransformer", 'services/inline-style-transformer'
+ @lazyLoad "SearchableComponentMaker", 'searchable-components/searchable-component-maker'
+ @lazyLoad "QuotedPlainTextTransformer", 'services/quoted-plain-text-transformer'
# Errors
- @get "APIError", -> require('../flux/errors').APIError
- @get "TimeoutError", -> require('../flux/errors').TimeoutError
+ @lazyLoadCustomGetter "APIError", -> require('../flux/errors').APIError
+ @lazyLoadCustomGetter "TimeoutError", -> require('../flux/errors').TimeoutError
# Process Internals
- @load "LaunchServices", 'launch-services'
- @load "SystemStartService", 'system-start-service'
- @load "BufferedProcess", 'buffered-process'
- @get "APMWrapper", -> require('../apm-wrapper')
+ @lazyLoad "LaunchServices", 'launch-services'
+ @lazyLoad "BufferedProcess", 'buffered-process'
+ @lazyLoad "SystemStartService", 'system-start-service'
+ @lazyLoadCustomGetter "APMWrapper", -> require('../apm-wrapper')
# Testing
- @get "NylasTestUtils", -> require '../../spec/nylas-test-utils'
+ @lazyLoadCustomGetter "NylasTestUtils", -> require '../../spec/nylas-test-utils'
+
+ # Deprecated
+ @lazyLoadDeprecated "QuotedHTMLParser", 'services/quoted-html-transformer',
+ instead: 'QuotedHTMLTransformer'
+ @lazyLoadDeprecated "DraftStoreExtension", 'flux/stores/draft-store-extension',
+ instead: 'ComposerExtension'
+ @lazyLoadDeprecated "MessageStoreExtension", 'flux/stores/message-store-extension',
+ instead: 'MessageViewExtension'
window.$n = NylasExports
module.exports = NylasExports
diff --git a/src/less-compile-cache.coffee b/src/less-compile-cache.coffee
index bf7d7d913..16b3d5894 100644
--- a/src/less-compile-cache.coffee
+++ b/src/less-compile-cache.coffee
@@ -1,6 +1,9 @@
+_ = require 'underscore'
path = require 'path'
LessCache = require 'less-cache'
+fileCacheImportPaths = null
+
# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
module.exports =
class LessCompileCache
@@ -21,8 +24,18 @@ class LessCompileCache
resourcePath: resourcePath
fallbackDir: path.join(resourcePath, 'less-compile-cache')
+ # Setting the import paths is a VERY expensive operation (200ms +)
+ # because it walks the entire file tree and does a file state for each
+ # and every importPath. If we already have the imports, then load it
+ # from our backend FileListCache.
setImportPaths: (importPaths=[]) ->
- @cache.setImportPaths(importPaths.concat(@lessSearchPaths))
+ fileCache = NylasEnv.fileListCache()
+ fileCacheImportPaths ?= fileCache.lessCacheImportPaths ? []
+ fullImportPaths = importPaths.concat(@lessSearchPaths)
+ pathDiff = _.difference(fullImportPaths, fileCacheImportPaths)
+ if pathDiff.length isnt 0
+ @cache.setImportPaths(fullImportPaths)
+ fileCache.lessCacheImportPaths = fullImportPaths
read: (stylesheetPath) ->
@cache.readFileSync(stylesheetPath)
diff --git a/src/mail-rules-processor.coffee b/src/mail-rules-processor.coffee
index 6231f959a..447f5a975 100644
--- a/src/mail-rules-processor.coffee
+++ b/src/mail-rules-processor.coffee
@@ -9,13 +9,13 @@ AccountStore = require './flux/stores/account-store'
DatabaseStore = require './flux/stores/database-store'
TaskQueueStatusStore = require './flux/stores/task-queue-status-store'
-MailRulesStore = require './flux/stores/mail-rules-store'
{ConditionMode, ConditionTemplates} = require './mail-rules-templates'
ChangeUnreadTask = require './flux/tasks/change-unread-task'
ChangeFolderTask = require './flux/tasks/change-folder-task'
ChangeStarredTask = require './flux/tasks/change-starred-task'
ChangeLabelsTask = require './flux/tasks/change-labels-task'
+MailRulesStore = null
###
Note: At first glance, it seems like these task factory methods should use the
@@ -78,6 +78,7 @@ class MailRulesProcessor
constructor: ->
processMessages: (messages) =>
+ MailRulesStore ?= require './flux/stores/mail-rules-store'
return Promise.resolve() unless messages.length > 0
enabledRules = MailRulesStore.rules().filter (r) -> not r.disabled
diff --git a/src/nylas-env.coffee b/src/nylas-env.coffee
index 9a6fb94f9..adb79417e 100644
--- a/src/nylas-env.coffee
+++ b/src/nylas-env.coffee
@@ -14,6 +14,7 @@ fs = require 'fs-plus'
WindowEventHandler = require './window-event-handler'
StylesElement = require './styles-element'
+StoreRegistry = require './store-registry'
Utils = require './flux/models/utils'
{APIError} = require './flux/errors'
@@ -211,6 +212,15 @@ class NylasEnvConstructor extends Model
unless @inSpecMode()
@actionBridge = new ActionBridge(ipcRenderer)
+ # Nylas exports is designed to provide a lazy-loaded set of globally
+ # accessible objects to all packages. Upon require, nylas-exports will
+ # fill the TaskRegistry, StoreRegistry, and DatabaseObjectRegistries
+ # with various constructors.
+ #
+ # We initialize all of the stores loaded into the StoreRegistry once
+ # the window starts loading.
+ require('nylas-exports')
+
# This ties window.onerror and Promise.onPossiblyUnhandledRejection to
# the publically callable `reportError` method. This will take care of
# reporting errors if necessary and hooking into error handling
@@ -535,19 +545,6 @@ class NylasEnvConstructor extends Model
reload: ->
ipcRenderer.send('call-webcontents-method', 'reload')
- # Updates the window load settings - called when the app is ready to display
- # a hot-loaded window. Causes listeners registered with `onWindowPropsReceived`
- # to receive new window props.
- loadSettingsChanged: (event, loadSettings) =>
- @loadSettings = loadSettings
- @constructor.loadSettings = loadSettings
- {width, height, windowProps} = loadSettings
-
- @emitter.emit('window-props-received', windowProps ? {})
-
- if width and height
- @setWindowDimensions({width, height})
-
# Public: The windowProps passed when creating the window via `newWindow`.
#
getWindowProps: ->
@@ -663,9 +660,23 @@ class NylasEnvConstructor extends Model
@savedState.columnWidths ?= {}
@savedState.columnWidths[id]
+ startWindow: ->
+ {packageLoadingDeferred, windowType} = @getLoadSettings()
+ @extendRxObservables()
+ StoreRegistry.activateAllStores()
+ @loadConfig()
+ @keymaps.loadBundledKeymaps()
+ @themes.loadBaseStylesheets()
+ @packages.loadPackages(windowType) unless packageLoadingDeferred
+ @deserializePackageStates() unless packageLoadingDeferred
+ @initializeReactRoot()
+ @packages.activate() unless packageLoadingDeferred
+ @keymaps.loadUserKeymap()
+ @menu.update()
+
# Call this method when establishing a real application window.
startRootWindow: ->
- {safeMode, windowType, initializeInBackground} = @getLoadSettings()
+ {safeMode, initializeInBackground} = @getLoadSettings()
# Temporary. It takes five paint cycles for all the CSS in index.html to
# be applied. Remove if https://github.com/atom/brightray/issues/196 fixed!
@@ -675,73 +686,66 @@ class NylasEnvConstructor extends Model
window.requestAnimationFrame =>
window.requestAnimationFrame =>
@displayWindow() unless initializeInBackground
-
- @loadConfig()
- @keymaps.loadBundledKeymaps()
- @themes.loadBaseStylesheets()
- @packages.loadPackages(windowType)
- @deserializePackageStates()
- @deserializeSheetContainer()
- @packages.activate()
- @keymaps.loadUserKeymap()
+ @startWindow()
@requireUserInitScript() unless safeMode
- @menu.update()
-
- @showRootWindow()
-
+ @showMainWindow()
ipcRenderer.send('window-command', 'window:loaded')
- showRootWindow: ->
+ # Initializes a secondary window.
+ # NOTE: If the `packageLoadingDeferred` option is set (which is true for
+ # hot windows), the packages won't be loaded until `populateHotWindow`
+ # gets fired.
+ startSecondaryWindow: ->
+ document.getElementById("application-loading-cover")?.remove()
+ @startWindow()
+ ipcRenderer.on("load-settings-changed", @populateHotWindow)
+ ipcRenderer.send('window-command', 'window:loaded')
+
+ showMainWindow: ->
document.getElementById("application-loading-cover").remove()
document.body.classList.add("window-loaded")
@restoreWindowDimensions()
@getCurrentWindow().setMinimumSize(875, 250)
- # Call this method when establishing a secondary application window
- # displaying a specific set of packages.
+ # Updates the window load settings - called when the app is ready to
+ # display a hot-loaded window. Causes listeners registered with
+ # `onWindowPropsReceived` to receive new window props.
#
- startSecondaryWindow: ->
- {width,
- height,
- windowType,
- windowPackages} = @getLoadSettings()
+ # This also means that the windowType has changed and a different set of
+ # plugins needs to be loaded.
+ populateHotWindow: (event, loadSettings) =>
+ @loadSettings = loadSettings
+ @constructor.loadSettings = loadSettings
- cover = document.getElementById("application-loading-cover")
- cover.remove() if cover
-
- @loadConfig()
-
- @keymaps.loadBundledKeymaps()
- @themes.loadBaseStylesheets()
+ {width, height, windowProps, windowType, hidden} = loadSettings
@packages.loadPackages(windowType)
- @packages.loadPackage(pack) for pack in (windowPackages ? [])
- @deserializeSheetContainer()
+ @deserializePackageStates()
@packages.activate()
- @keymaps.loadUserKeymap()
- ipcRenderer.on("load-settings-changed", @loadSettingsChanged)
+ @emitter.emit('window-props-received', windowProps ? {})
- @setWindowDimensions({width, height}) if width and height
+ if width and height
+ @setWindowDimensions({width, height})
- @menu.update()
+ @displayWindow() unless hidden
- ipcRenderer.send('window-command', 'window:loaded')
+ # We extend nylas observables with our own methods. This happens on
+ # require of nylas-observables
+ extendRxObservables: ->
+ require('nylas-observables')
- # Requests that the backend browser bootup a new window with the given
- # options.
- # See the valid option types in Application::newWindow in
- # src/browser/application.coffee
+ # Launches a new window via the browser/WindowLauncher.
+ #
+ # If you pass a `windowKey` in the options, and that windowKey already
+ # exists, it'll show that window instead of spawing a new one. This is
+ # useful for places like popout composer windows where you want to
+ # simply display the draft instead of spawning a whole new window for
+ # the same draft.
+ #
+ # `options` are documented in browser/WindowLauncher
newWindow: (options={}) -> ipcRenderer.send('new-window', options)
- # Registers a hot window for certain packages
- # See the valid option types in Application::registerHotWindow in
- # src/browser/application.coffee
- registerHotWindow: (options={}) -> ipcRenderer.send('register-hot-window', options)
-
- # Unregisters a hot window with the given windowType
- unregisterHotWindow: (windowType) -> ipcRenderer.send('unregister-hot-window', windowType)
-
saveStateAndUnloadWindow: ->
@packages.deactivatePackages()
@savedState.packageStates = @packages.packageStates
@@ -753,6 +757,7 @@ class NylasEnvConstructor extends Model
###
displayWindow: ({maximize} = {}) ->
+ return if @inSpecMode()
@show()
@focus()
@maximize() if maximize
@@ -821,7 +826,7 @@ class NylasEnvConstructor extends Model
Section: Private
###
- deserializeSheetContainer: ->
+ initializeReactRoot: ->
startTime = Date.now()
# Put state back into sheet-container? Restore app state here
@item = document.createElement("nylas-workspace")
@@ -886,6 +891,10 @@ class NylasEnvConstructor extends Model
detail: message
}
+ # Delegate to the browser's process fileListCache
+ fileListCache: ->
+ return remote.getGlobal('application').fileListCache
+
saveSync: ->
stateString = JSON.stringify(@savedState)
if statePath = @constructor.getStatePath()
@@ -944,6 +953,8 @@ class NylasEnvConstructor extends Model
# work and then call finishUnload. We do not support cancelling quit!
# https://phab.nylas.com/D1932#inline-11722
#
+ # Also see logic in browser/NylasWindow::handleEvents where we listen
+ # to the browserWindow.on 'close' event to catch "unclosable" windows.
onBeforeUnload: (callback) ->
@windowEventHandler.addUnloadCallback(callback)
diff --git a/src/package-manager.coffee b/src/package-manager.coffee
index 194d88521..0a5bbfe92 100644
--- a/src/package-manager.coffee
+++ b/src/package-manager.coffee
@@ -15,6 +15,8 @@ ThemePackage = require './theme-package'
DatabaseStore = require './flux/stores/database-store'
APMWrapper = require './apm-wrapper'
+basePackagePaths = null
+
# Extended: Package manager for coordinating the lifecycle of N1 packages.
#
# An instance of this class is always available as the `NylasEnv.packages` global.
@@ -296,13 +298,20 @@ class PackageManager
loadPackagesWhenNoTypesSpecified = windowType is 'default'
- for packageDirPath in @packageDirPaths
- for packagePath in fs.listSync(packageDirPath)
- # Ignore files in package directory
- continue unless fs.isDirectorySync(packagePath)
- # Ignore .git in package directory
- continue if path.basename(packagePath)[0] is '.'
- packagePaths.push(packagePath)
+ basePackagePaths ?= NylasEnv.fileListCache().basePackagePaths ? []
+ if basePackagePaths.length is 0
+ for packageDirPath in @packageDirPaths
+ for packagePath in fs.listSync(packageDirPath)
+ # Ignore files in package directory
+ continue unless fs.isDirectorySync(packagePath)
+ # Ignore .git in package directory
+ continue if path.basename(packagePath)[0] is '.'
+ packagePaths.push(packagePath)
+ basePackagePaths = packagePaths
+ cache = NylasEnv.fileListCache()
+ cache.basePackagePaths = basePackagePaths
+ else
+ packagePaths = basePackagePaths
if windowType
packagePaths = _.filter packagePaths, (packagePath) ->
diff --git a/src/package.coffee b/src/package.coffee
index 19acaa200..1f51e7286 100644
--- a/src/package.coffee
+++ b/src/package.coffee
@@ -157,12 +157,15 @@ class Package
registerModelConstructors: (constructors=[]) ->
if constructors.length > 0
@declaresNewDatabaseObjects = true
- for constructor in constructors
- DatabaseObjectRegistry.register(constructor)
+
+ _.each constructors, (constructor) ->
+ constructorFactory = -> constructor
+ DatabaseObjectRegistry.register(constructor.name, constructorFactory)
registerTaskConstructors: (constructors=[]) ->
- for constructor in constructors
- TaskRegistry.register(constructor)
+ _.each constructors, (constructor) ->
+ constructorFactory = -> constructor
+ TaskRegistry.register(constructor.name, constructorFactory)
reset: ->
@stylesheets = []
diff --git a/src/react-remote/react-remote-child.js b/src/react-remote/react-remote-child.js
deleted file mode 100644
index 4082ee9af..000000000
--- a/src/react-remote/react-remote-child.js
+++ /dev/null
@@ -1,128 +0,0 @@
-var _ = require('underscore')
-var container = document.getElementById("container");
-var ipc = require('electron').ipcRenderer;
-var lastSelectionData = {}
-
-document.body.classList.add("platform-"+process.platform);
-document.body.classList.add("window-type-react-remote");
-
-exp = require('./selection-listeners.js');
-restoreSelection = exp.restoreSelection;
-getSelectionData = exp.getSelectionData;
-
-var receiveEvent = function (json) {
- var remote = require('electron').remote;
-
- if (json.selectionData) {
- document.removeEventListener("selectionchange", selectionChange);
- restoreSelection(json.selectionData)
- document.addEventListener("selectionchange", selectionChange);
- }
-
- if (json.html) {
- var browserWindow = remote.getCurrentWindow();
- browserWindow.on('focus', function() {
- document.body.classList.remove('is-blurred')
- });
- browserWindow.on('blur', function() {
- document.body.classList.add('is-blurred')
- });
-
- container.innerHTML = json.html;
- var style = document.createElement('style');
- style.onload = function() {
- for (var ii = 0; ii < json.waiting.length; ii ++) {
- receiveEvent(json.waiting[ii]);
- }
- window.requestAnimationFrame(function() {
- browserWindow.show();
- });
- };
- style.textContent = json.style;
- document.body.appendChild(style);
- }
-
- if (json.sel) {
- var React = require('react');
- var ReactMount = require('react/lib/ReactMount');
-
- ReactMount.getNode = function(id) {
- return document.querySelector("[data-reactid='"+id+"']");
- };
-
- var sources = {
- CSSPropertyOperations: require('react/lib/CSSPropertyOperations'),
- DOMPropertyOperations: require('react/lib/DOMPropertyOperations'),
- DOMChildrenOperations: require('react/lib/DOMChildrenOperations'),
- ReactDOMIDOperations: require('react/lib/ReactDOMIDOperations'),
- Custom: {
- setSelectCurrentValue: function(reactid, value) {
- var children = ReactMount.getNode(reactid).childNodes;
- for (var ii = 0; ii < children.length; ii ++) {
- children[ii].selected = (children[ii].value == value);
- }
- }
- }
- };
-
- if (json.firstArgType == 'node') {
- json.arguments[0] = ReactMount.getNode(json.arguments[0]);
- } else if (json.firstArgType == 'array') {
- for (var ii = 0; ii < json.arguments[0].length; ii ++) {
- json.arguments[0][ii].parentNode = ReactMount.getNode(json.arguments[0][ii].parentNode)
- }
- }
- sources[json.parent][json.sel].apply(sources[json.parent], json.arguments);
- }
-};
-
-ipc.on("to-react-remote-window", receiveEvent);
-
-var events = ['keypress', 'keydown', 'keyup', 'change', 'submit', 'click', 'focus', 'blur', 'input', 'select'];
-events.forEach(function(type) {
- container.addEventListener(type, function(event) {
- var representation = {
- eventType: event.type,
- eventClass: event.constructor.name,
- pageX: event.pageX,
- pageY: event.pageY,
- bubbles: event.bubbles,
- cancelable: event.cancelable,
- clientX: event.clientX,
- clientY: event.clientY,
- charCode: event.charCode,
- keyCode: event.keyCode,
- detail: event.detail,
- eventPhase: event.eventPhase
- }
- if (event.target) {
- representation.targetReactId = event.target.dataset.reactid;
- }
- if (event.target.contentEditable=="true") {
- representation.targetValue = event.target.innerHTML;
- }
- else if (event.target.value !== undefined) {
- representation.targetValue = event.target.value;
- }
- if (event.target.checked !== undefined) {
- representation.targetChecked = event.target.checked;
- }
-
- var remote = require('electron').remote;
- ipc.send("from-react-remote-window", {windowId: remote.getCurrentWindow().id, event: representation});
- if ((event.type != 'keydown') && (event.type != 'keypress') && (event.type != 'keyup')) {
- event.preventDefault();
- }
- }, true);
-});
-
-
-selectionChange = function() {
- selectionData = getSelectionData()
- if (_.isEqual(selectionData, lastSelectionData)) { return; }
- lastSelectionData = _.clone(selectionData)
- var remote = require('electron').remote;
- remote.getCurrentWindow().id
- ipc.send("from-react-remote-window-selection", selectionData);
-}
-// document.addEventListener("selectionchange", selectionChange);
diff --git a/src/react-remote/react-remote-parent.js b/src/react-remote/react-remote-parent.js
deleted file mode 100644
index 5a27009f6..000000000
--- a/src/react-remote/react-remote-parent.js
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
-
-This code stopped working when we moved from React `0.13.2` to `0.14.7`.
-It would most likely still work, but some of the internal modules have moved.
-
-var ipcRenderer = require("electron").ipcRenderer;
-var React = require('react');
-var ReactDOM = require('react-dom');
-var _ = require('underscore');
-var LinkedValueUtils = require('react/lib/LinkedValueUtils');
-var ReactDOMComponent = require('react/lib/ReactDOMComponent');
-var methods = Object.keys(ReactDOMComponent.BackendIDOperations);
-var invocationTargets = [];
-var lastSelectionData = {}
-
-var sources = {
- CSSPropertyOperations: require('react/lib/CSSPropertyOperations'),
- DOMPropertyOperations: require('react/lib/DOMPropertyOperations'),
- DOMChildrenOperations: require('react/lib/DOMChildrenOperations'),
- ReactDOMIDOperations: require('react/lib/ReactDOMIDOperations'),
- ReactDOMSelect: require('react/lib/ReactDOMSelect')
-}
-
-var Custom = {
- sendSelectCurrentValue: function() {
- var reactid = this.getDOMNode().dataset.reactid;
- var target = invocationTargetForReactId(reactid);
- if (target) {
- var value = LinkedValueUtils.getValue(this);
- target.send({
- parent: 'Custom',
- sel: 'setSelectCurrentValue',
- arguments: [reactid, LinkedValueUtils.getValue(this)]
- });
- }
- }
-};
-
-var invocationTargetForReactId = function(id) {
- for (var ii = 0; ii < invocationTargets.length; ii++) {
- var target = invocationTargets[ii];
- if (id.substr(0, target.reactid.length) == target.reactid) {
- return target;
- }
- if (target.reactid == 'not-yet-rendered') {
- var node = document.querySelector("[data-reactid='"+id+"']");
- while (node = node.parentNode) {
- if (node == target.container) {
- return target;
- }
- }
- }
- }
- return null;
-};
-
-var observeMethod = function(parent, sel, callback) {
- var owner = sources[parent];
- if (!owner[sel]) {
- owner = owner.prototype;
- }
-
- var oldImpl = owner[sel];
- owner[sel] = function() {
- oldImpl.apply(this, arguments);
-
- if (invocationTargets.length == 0)
- return;
-
- callback.apply(this, arguments);
- }
-};
-
-var observeMethodAndBroadcast = function(parent, sel) {
- observeMethod(parent, sel, function() {
- var id = null;
- var target = null;
- var firstArgType = null;
-
- var args = [];
- for (var ii = 0; ii < arguments.length; ii ++) {
- args.push(arguments[ii]);
- }
-
- if (arguments[0] instanceof Node) {
- args[0] = args[0].dataset.reactid;
- target = invocationTargetForReactId(args[0]);
- firstArgType = "node";
-
- } else if (typeof(args[0]) === 'string') {
- target = invocationTargetForReactId(args[0]);
- firstArgType = "id";
-
- } else if (args[0] instanceof Array) {
- for (var ii = 0; ii < args[0].length; ii ++) {
- args[0][ii].parentNode = args[0][ii].parentNode.dataset.reactid;
- }
- target = invocationTargetForReactId(args[0][0].parentNode);
- firstArgType = "array";
- }
-
- if (target) {
- target.send({
- parent: parent,
- sel: sel,
- arguments: args,
- firstArgType: firstArgType
- });
- target.sendSizingInformation();
- }
- });
-};
-
-setTimeout(function(){
- observeMethodAndBroadcast('CSSPropertyOperations', 'setValueForStyles');
- observeMethodAndBroadcast('DOMChildrenOperations', 'updateTextContent');
- observeMethodAndBroadcast('DOMChildrenOperations', 'dangerouslyReplaceNodeWithMarkup');
- observeMethodAndBroadcast('DOMPropertyOperations', 'deleteValueForProperty');
- observeMethodAndBroadcast('DOMPropertyOperations', 'setValueForProperty');
- observeMethodAndBroadcast('ReactDOMIDOperations', 'updateInnerHTMLByID');
- observeMethodAndBroadcast('DOMChildrenOperations', 'processUpdates');
- observeMethod('ReactDOMSelect', 'componentDidUpdate', Custom.sendSelectCurrentValue);
- observeMethod('ReactDOMSelect', 'componentDidMount', Custom.sendSelectCurrentValue);
-}, 10);
-
-ipcRenderer.on('from-react-remote-window', function(event, json) {
- var container = null;
- for (var ii = 0; ii < invocationTargets.length; ii ++) {
- if (invocationTargets[ii].windowId == json.windowId) {
- container = invocationTargets[ii].container;
- }
- }
- if (!container) {
- console.error("Received message from child window "+json.windowId+" which is not recognized.");
- return;
- }
-
- if (json.event) {
- var rep = json.event;
- if (rep.targetReactId) {
- rep.target = document.querySelector(["[data-reactid='"+rep.targetReactId+"']"]);
- }
- if (rep.target && rep.target.contentEditable=="true") {
- rep.target.innerHTML = rep.targetValue;
- }
- else if (rep.target && (rep.targetValue !== undefined)) {
- rep.target.value = rep.targetValue;
- }
- if (rep.target && (rep.targetChecked !== undefined)) {
- rep.target.checked = rep.targetChecked;
- }
-
- var EventClass = {
- "MouseEvent": MouseEvent,
- "KeyboardEvent": KeyboardEvent,
- "FocusEvent": FocusEvent
- }[rep.eventClass] || Event;
-
- var e = new EventClass(rep.eventType, rep);
-
- if (rep.target) {
- rep.target.dispatchEvent(e);
- } else {
- container.dispatchEvent(e);
- }
- }
-});
-
-exp = require('./selection-listeners.js');
-restoreSelection = exp.restoreSelection;
-getSelectionData = exp.getSelectionData;
-
-selectionChange = function() {
- selectionData = getSelectionData();
- if (_.isEqual(selectionData, lastSelectionData)) { return; }
- lastSelectionData = _.clone(selectionData)
- for (var i = 0; i < invocationTargets.length; i++) {
- var target = invocationTargets[i];
- target.send({selectionData: selectionData})
- }
-}
-// document.addEventListener("selectionchange", selectionChange);
-
-ipcRenderer.on('from-react-remote-window-selection', function(event, selectionData){
- document.removeEventListener("selectionchange", selectionChange)
- restoreSelection(selectionData)
- document.addEventListener("selectionchange", selectionChange);
-});
-
-
-var parentListenersAttached = false;
-var reactRemoteContainer = document.createElement('div');
-reactRemoteContainer.style.left = '-10000px';
-reactRemoteContainer.style.top = '40px';
-reactRemoteContainer.style.backgroundColor = 'white';
-reactRemoteContainer.style.position = 'absolute';
-reactRemoteContainer.style.zIndex = 10000;
-reactRemoteContainer.style.border = '5px solid orange';
-document.body.appendChild(reactRemoteContainer);
-
-var reactRemoteContainerTitle = document.createElement('div');
-reactRemoteContainerTitle.style.color = 'white';
-reactRemoteContainerTitle.style.backgroundColor = 'orange';
-reactRemoteContainerTitle.innerText = 'React Remote Container';
-reactRemoteContainer.appendChild(reactRemoteContainerTitle);
-
-var toggleContainerVisible = function() {
- if (reactRemoteContainer.style.left === '-10000px') {
- reactRemoteContainer.style.left = 0;
- } else {
- reactRemoteContainer.style.left = '-10000px';
- }
-};
-
-var openWindowForComponent = function(Component, options) {
- // If a tag is specified, see if we can find an existing window to bring to foreground
- if (options.tag) {
- for (var ii = 0; ii < invocationTargets.length; ii++) {
- if (invocationTargets[ii].tag === options.tag) {
- invocationTargets[ii].window.focus();
- return;
- }
- }
- }
-
- var remote = require('electron').remote;
- var url = require('url');
- var BrowserWindow = remote.require('browser-window');
-
- // Read rendered styles out of the page
- var styles = document.querySelectorAll("style");
- var thinStyles = "";
- for (var ii = 0; ii < styles.length; ii++) {
- var styleNode = styles[ii];
- if (!styleNode.sourcePath) {
- continue;
- }
- if ((styleNode.sourcePath.indexOf('index') > 0) || (options.stylesheetRegex && options.stylesheetRegex.test(styleNode.sourcePath))) {
- thinStyles = thinStyles + styleNode.innerText;
- }
- }
-
- // Create a browser window
- var thinWindowUrl = url.format({
- protocol: 'file',
- pathname: NylasEnv.getLoadSettings().resourcePath+"/static/react-remote-child.html",
- slashes: true
- });
- var thinWindow = new BrowserWindow({
- title: options.title || "",
- frame: process.platform !== 'darwin',
- width: options.width || 800,
- height: options.height || 600,
- resizable: options.resizable,
- show: false
- });
- thinWindow.loadURL(thinWindowUrl);
- if (process.platform !== 'darwin') {
- thinWindow.setMenu(null);
- }
-
- // Add a container to our local document to hold the root component of the window
- var container = document.createElement('div');
- container.id = 'react-remote-window-container-'+thinWindow.id;
- if (options.width) {
- container.style.width = options.width+'px';
- } else {
- container.style.height = 'auto';
- }
- if (options.height) {
- container.style.height = options.height+'px';
- } else {
- container.style.height = 'auto';
- }
- reactRemoteContainer.appendChild(container);
-
- var cleanup = function() {
- if (container == null) {
- return;
- }
- for (var ii = 0; ii < invocationTargets.length; ii++) {
- if (invocationTargets[ii].container === container) {
- invocationTargets[ii].windowReady = false
- invocationTargets[ii].window = null
- invocationTargets.splice(ii, 1);
- break;
- }
- }
-
- ReactDOM.render(React.createElement('div'), container, function() {
- reactRemoteContainer.removeChild(container);
- });
- container = null;
- thinWindow = null;
- };
-
- var sendWaiting = [];
-
- var sendSizingInformation = function() {
- if (!options.autosize) {
- return;
- }
- if (!thinWindow) {
- return;
- }
- // Weirdly, this returns an array of [width, height] and not a hash
- var size = thinWindow.getContentSize();
- var changed = false;
-
- var containerSize = container.getBoundingClientRect();
- var containerWidth = Math.ceil(containerSize.width);
- var containerHeight = Math.ceil(containerSize.height);
-
- if (containerHeight == 0) {
- containerHeight = 100;
- debugger;
- }
- if (containerWidth == 0) {
- containerWidth = 400;
- debugger;
- }
-
- if ((!options.height) && (size[1] != containerHeight)) {
- size[1] = containerHeight;
- changed = true;
- }
- if ((!options.width) && (size[0] != containerWidth)) {
- size[0] = containerWidth;
- changed = true;
- }
- if (changed) {
- thinWindow.setContentSize(size[0], size[1]);
- }
- };
-
- // Create a "Target" object that we'll use to store information about the
- // remote window, it's reactId, etc.
- var target = {
- container: container,
- containerReady: false,
- window: thinWindow,
- windowReady: false,
- windowId: thinWindow.id,
- tag: options.tag,
- reactid: 'not-yet-rendered',
- send: function(args) {
- if (target.containerReady && target.windowReady) {
- thinWindow.webContents.send('to-react-remote-window', args);
- } else {
- sendWaiting.push(args);
- }
- },
- sendSizingInformation: _.debounce(function() {
- if (target.containerReady && target.windowReady) {
- sendSizingInformation();
- }
- }, 20),
- sendHTMLIfReady: function() {
- if (target.containerReady && target.windowReady) {
- sendSizingInformation();
- thinWindow.webContents.send('to-react-remote-window', {
- html: container.innerHTML,
- style: thinStyles,
- waiting: sendWaiting
- });
- }
- }
- };
- invocationTargets.push(target);
-
- // Finally, render the react component into our local container and open
- // the browser window. When both of these things finish, we send the html
- // css, and any observed method invocations that occurred during the first
- // React cycle (componentDidMount).
- ReactDOM.render(React.createElement(Component, options.props), container, function() {
- target.reactid = container.firstChild.dataset.reactid,
- target.containerReady = true;
- target.sendHTMLIfReady();
- });
-
- thinWindow.on('closed', cleanup);
- thinWindow.webContents.on('crashed', cleanup);
- thinWindow.webContents.on('did-finish-load', function () {
- target.windowReady = true;
- target.sendHTMLIfReady();
- });
-
- // The first time a remote window is opened, add event listeners to our
- // own window so that we close dependent windows when we're closed.
- if (parentListenersAttached == false) {
- remote.getCurrentWindow().on('close', function() {
- for (var ii = 0; ii < invocationTargets.length; ii++) {
- invocationTargets[ii].window.close();
- }
- invocationTargets = [];
- })
- parentListenersAttached = true;
- }
-};
-
-module.exports = {
- openWindowForComponent: openWindowForComponent,
- toggleContainerVisible: toggleContainerVisible
-};
-*/
diff --git a/src/window-secondary-bootstrap.coffee b/src/secondary-window-bootstrap.coffee
similarity index 54%
rename from src/window-secondary-bootstrap.coffee
rename to src/secondary-window-bootstrap.coffee
index 7df9801f0..768e7b30b 100644
--- a/src/window-secondary-bootstrap.coffee
+++ b/src/secondary-window-bootstrap.coffee
@@ -1,18 +1,26 @@
+# Effectively all secondary windows are empty hot windows. We spawn the
+# window and pre-load all of the basic javascript libraries (which takes a
+# full second or so).
+#
+# Eventually when `WindowManager::newWindow` gets called, instead of
+# actually spawning a new window, we'll call
+# `NylasWindow::setLoadSettings` on the window instead. This will replace
+# the window options, adjust params as necessary, and then re-load the
+# plugins. Once `NylasWindow::setLoadSettings` fires, the main NylasEnv in
+# the window will be notified via the `load-settings-changed` config
+
# Swap out Node's native Promise for Bluebird, which allows us to
# do fancy things like handle exceptions inside promise blocks
global.Promise = require 'bluebird'
Promise.setScheduler(global.setImmediate)
-# Like sands through the hourglass, so are the days of our lives.
require './window'
-# Skip "?loadSettings=".
-# loadSettings = JSON.parse(decodeURIComponent(location.search.substr(14)))
-# {windowType} = loadSettings
-
NylasEnvConstructor = require './nylas-env'
window.NylasEnv = window.atom = NylasEnvConstructor.loadOrCreate()
+
global.Promise.longStackTraces() if NylasEnv.inDevMode()
+
NylasEnv.initialize()
NylasEnv.startSecondaryWindow()
diff --git a/src/serializable-registry.coffee b/src/serializable-registry.coffee
deleted file mode 100644
index 85307a91e..000000000
--- a/src/serializable-registry.coffee
+++ /dev/null
@@ -1,38 +0,0 @@
-_ = require 'underscore'
-
-# Public: This keeps track of constructors so we know how to inflate
-# serialized objects.
-#
-# If 3rd party packages want to register new inflatable models, they can
-# use `register` and pass the constructor along with the name.
-#
-# Note that there is one registry per window.
-class SerializableRegistry
- constructor: ->
- # A mapping of the string name and the constructor class.
- @_constructors = {}
-
- get: (name) -> @_constructors[name]
-
- isInRegistry: (name) -> @_constructors[name]?
-
- deserialize: (name, data) ->
- if _.isString(data)
- data = JSON.parse(data)
-
- constructor = @get(name)
-
- if not _.isFunction(constructor)
- throw new Error "Unsure of how to inflate #{JSON.stringify(data)}"
-
- object = new constructor()
- object.fromJSON(data)
-
- return object
-
- register: (constructor) ->
- @_constructors[constructor.name] = constructor
-
- unregister: (name) -> delete @_constructors[name]
-
-module.exports = SerializableRegistry
diff --git a/src/serializable-registry.es6 b/src/serializable-registry.es6
new file mode 100644
index 000000000..ccb304f13
--- /dev/null
+++ b/src/serializable-registry.es6
@@ -0,0 +1,67 @@
+/**
+ * Public: This keeps track of constructors so we know how to inflate
+ * serialized objects.
+ *
+ * We map constructor string names with factory functions that will return
+ * the actual constructor itself.
+ *
+ * The reason we have an extra function call to return a constructor is so
+ * we don't need to `require` all constructors at once on load. We are
+ * wasting a very large amount of time on bootup requiring files that may
+ * never be used or only used way down the line.
+ *
+ * If 3rd party packages want to register new inflatable models, they can
+ * use `register` and pass the constructor generator along with the name.
+ *
+ * Note that there is one registry per window.
+ */
+export default class SerializableRegistry {
+ constructor() {
+ this._constructorFactories = {}
+ }
+
+ get(name) {
+ return this._constructorFactories[name].call(null)
+ }
+
+ getAllConstructors() {
+ const constructors = []
+ for (const name in this._constructorFactories) {
+ if (this._constructorFactories.hasOwnProperty(name)) {
+ constructors.push(this.get(name))
+ }
+ }
+ return constructors
+ }
+
+ isInRegistry(name) {
+ return !!this._constructorFactories[name]
+ }
+
+ deserialize(name, dataJSON) {
+ let data = dataJSON;
+ if (typeof data === "string") {
+ data = JSON.parse(dataJSON)
+ }
+
+ const constructor = this.get(name)
+
+ if (typeof constructor !== "function") {
+ throw new Error(`Unsure of how to inflate ${JSON.stringify(data)}. \
+Your constructor factory must return a class constructor.`);
+ }
+
+ const object = new constructor()
+ object.fromJSON(data)
+
+ return object
+ }
+
+ register(name, constructorFactory) {
+ this._constructorFactories[name] = constructorFactory
+ }
+
+ unregister(name) {
+ delete this._constructorFactories[name]
+ }
+}
diff --git a/src/store-registry.es6 b/src/store-registry.es6
new file mode 100644
index 000000000..35f002fed
--- /dev/null
+++ b/src/store-registry.es6
@@ -0,0 +1,26 @@
+import SerializableRegistry from './serializable-registry'
+
+class StoreRegistry extends SerializableRegistry {
+ /**
+ * Most of the core Flux stores construct themselves on require. That
+ * construction initialize the stores, sets up listeners, and may access
+ * the database.
+ *
+ * It also kicks off a fairly large tree of require statements that
+ * takes considerable time to process.
+ */
+ activateAllStores() {
+ for (const name in this._constructorFactories) {
+ if (this._constructorFactories.hasOwnProperty(name)) {
+ // All we need to do is hit `require` on the store. This will
+ // construct the object an initialize the require cache. The
+ // stores are now available in nylas-exports or from the node
+ // require cache.
+ this.get(name)
+ }
+ }
+ }
+}
+
+const registry = new StoreRegistry()
+export default registry
diff --git a/src/task-registry.coffee b/src/task-registry.coffee
deleted file mode 100644
index c37098733..000000000
--- a/src/task-registry.coffee
+++ /dev/null
@@ -1,5 +0,0 @@
-SerializableRegistry = require './serializable-registry'
-
-class TaskRegistry extends SerializableRegistry
-
-module.exports = new TaskRegistry()
diff --git a/src/task-registry.es6 b/src/task-registry.es6
new file mode 100644
index 000000000..a20131529
--- /dev/null
+++ b/src/task-registry.es6
@@ -0,0 +1,6 @@
+import SerializableRegistry from './serializable-registry'
+
+class TaskRegistry extends SerializableRegistry { }
+
+const registry = new TaskRegistry()
+export default registry
diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee
index 364123c4d..870782493 100644
--- a/src/theme-manager.coffee
+++ b/src/theme-manager.coffee
@@ -368,7 +368,9 @@ class ThemeManager
NylasEnv.config.observe 'core.themes', =>
@deactivateThemes()
- @refreshLessCache() # Update cache for packages in core.themes config
+ # Refreshing the less cache is very expensive (hundreds of ms). It
+ # will be refreshed once the promise resolves after packages are
+ # activated.
promises = []
for themeName in @getEnabledThemeNames()
diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee
index 604013580..8bedf4419 100644
--- a/src/window-event-handler.coffee
+++ b/src/window-event-handler.coffee
@@ -82,10 +82,6 @@ class WindowEventHandler
ComponentRegistry = require './component-registry'
ComponentRegistry.toggleComponentRegions()
- @subscribeToCommand $(window), 'window:toggle-react-remote', ->
- ReactRemote = require './react-remote/react-remote-parent'
- ReactRemote.toggleContainerVisible()
-
document.addEventListener 'keydown', @onKeydown
# "Pinch to zoom" on the Mac gets translated by the system into a
diff --git a/src/window-thin-bootstrap.coffee b/src/window-thin-bootstrap.coffee
deleted file mode 100644
index 736b9689d..000000000
--- a/src/window-thin-bootstrap.coffee
+++ /dev/null
@@ -1,37 +0,0 @@
-path = require('path')
-fs = require('fs-plus')
-ipc = require('electron').ipcRenderer
-
-require('module').globalPaths.push(path.resolve('exports'))
-
-# Swap out Node's native Promise for Bluebird, which allows us to
-# do fancy things like handle exceptions inside promise blocks
-global.Promise = require 'bluebird'
-global.NylasEnv =
- commands:
- add: ->
- remove: ->
- config:
- get: -> null
- set: ->
- onDidChange: ->
- onBeforeUnload: ->
- getWindowLoadTime: -> 0
- getConfigDirPath: ->
- @configDirPath ?= JSON.parse(decodeURIComponent(location.search.substr(14))).configDirPath
- getLoadSettings: ->
- @loadSettings ?= JSON.parse(decodeURIComponent(location.search.substr(14)))
- inSpecMode: ->
- false
-
- isMainWindow: ->
- false
-
-# Like sands through the hourglass, so are the days of our lives.
-require './window'
-prefs = require '../internal_packages/preferences/lib/main'
-prefs.activate()
-
-ipc.on 'command', (command, args) ->
- if command is 'window:toggle-dev-tools'
- ipc.send('call-webcontents-method', 'toggleDevTools')
diff --git a/static/react-remote-child.html b/static/react-remote-child.html
deleted file mode 100644
index 6196b2545..000000000
--- a/static/react-remote-child.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-