diff --git a/internal_packages/account-sidebar/lib/account-switcher.cjsx b/internal_packages/account-sidebar/lib/account-switcher.cjsx index c9634399d..fe2ae0b03 100644 --- a/internal_packages/account-sidebar/lib/account-switcher.cjsx +++ b/internal_packages/account-sidebar/lib/account-switcher.cjsx @@ -20,8 +20,7 @@ class AccountSwitcher extends React.Component unsubscribe() for unsubscribe in @unsubscribers render: => - return undefined if @state.accounts.length < 1 - + return false unless @state.account
{@_renderAccount(@state.account, true)} {@_renderDropdown()} @@ -125,8 +124,9 @@ class AccountSwitcher extends React.Component @setState(showing: false) _onAddAccount: => - require('remote').getGlobal('application').windowManager.newOnboardingWindow() - @setState showing: false + ipc = require('ipc') + ipc.send('command', 'application:add-account') + @setState(showing: false) _getStateFromStores: => accounts: AccountStore.items() diff --git a/internal_packages/account-sidebar/stylesheets/account-sidebar.less b/internal_packages/account-sidebar/stylesheets/account-sidebar.less index 8f0b03abf..c6b9f80f2 100644 --- a/internal_packages/account-sidebar/stylesheets/account-sidebar.less +++ b/internal_packages/account-sidebar/stylesheets/account-sidebar.less @@ -79,10 +79,13 @@ } #account-switcher { - padding-top: @padding-large-vertical; - padding-bottom: @padding-base-vertical; border-bottom: 1px solid @border-color-divider; + .primary-item { + padding-top: @padding-large-vertical; + padding-bottom: @padding-base-vertical; + } + .account { position: relative; margin-bottom: 0; diff --git a/internal_packages/onboarding/lib/account-choose-page.cjsx b/internal_packages/onboarding/lib/account-choose-page.cjsx index aa4163f8b..a2592223c 100644 --- a/internal_packages/onboarding/lib/account-choose-page.cjsx +++ b/internal_packages/onboarding/lib/account-choose-page.cjsx @@ -3,13 +3,12 @@ _ = require 'underscore' {RetinaImg} = require 'nylas-component-kit' {EdgehillAPI, Utils} = require 'nylas-exports' -Page = require './page' OnboardingActions = require './onboarding-actions' NylasApiEnvironmentStore = require './nylas-api-environment-store' Providers = require './account-types' url = require 'url' -class AccountChoosePage extends Page +class AccountChoosePage extends React.Component @displayName: "AccountChoosePage" constructor: (@props) -> @@ -27,7 +26,9 @@ class AccountChoosePage extends Page render: =>
- {@_renderClose("quit")} +
atom.close() }> + +
@@ -88,14 +89,6 @@ class AccountChoosePage extends Page }) shell.openExternal(googleUrl) - _onSubmit: (e) => - valid = React.findDOMNode(@refs.form).reportValidity() - if valid - url = EdgehillAPI.urlForConnecting("inbox", @state.email) - OnboardingActions.moveToPage("add-account-auth", {url}) - else - e.preventDefault() - _environmentComponent: => return
unless atom.inDevMode()
diff --git a/internal_packages/onboarding/lib/account-settings-page.cjsx b/internal_packages/onboarding/lib/account-settings-page.cjsx index 8eba4d0da..434682a4a 100644 --- a/internal_packages/onboarding/lib/account-settings-page.cjsx +++ b/internal_packages/onboarding/lib/account-settings-page.cjsx @@ -4,12 +4,11 @@ ipc = require 'ipc' {RetinaImg} = require 'nylas-component-kit' {EdgehillAPI, NylasAPI, APIError} = require 'nylas-exports' -Page = require './page' OnboardingActions = require './onboarding-actions' NylasApiEnvironmentStore = require './nylas-api-environment-store' Providers = require './account-types' -class AccountSettingsPage extends Page +class AccountSettingsPage extends React.Component @displayName: "AccountSettingsPage" constructor: (@props) -> @@ -18,6 +17,8 @@ class AccountSettingsPage extends Page settings: {} fields: {} pageNumber: 0 + errorFieldNames: [] + errorMessage: null show_advanced: false @props.pageData.provider.settings.forEach (field) => @@ -36,7 +37,7 @@ class AccountSettingsPage extends Page @_pollForGmailAccount((account) -> if account? done = true - OnboardingActions.nylasAccountReceived(account) + OnboardingActions.accountJSONReceived(account) else if tries < 10 and id is poll_attempt_id setTimeout(_retry, delay) delay *= 1.5 # exponential backoff @@ -106,8 +107,8 @@ class AccountSettingsPage extends Page _renderErrorMessage: => - if @state.error -
{@state.error.message ? ""}
+ if @state.errorMessage +
{@state.errorMessage ? ""}
_fieldOnCurrentPage: (field) => !@state.provider.pages || field.page is @state.pageNumber @@ -115,7 +116,7 @@ class AccountSettingsPage extends Page _renderFields: => @state.provider.fields?.filter(@_fieldOnCurrentPage) .map (field, idx) => - errclass = if field.name in (@state.error?.invalid_fields ? []) then "error " else "" + errclass = if field.name in @state.errorFieldNames then "error " else ""
-class InitialPreferencesPage extends Page +class InitialPreferencesPage extends React.Component @displayName: "InitialPreferencesPage" render: => diff --git a/internal_packages/onboarding/lib/onboarding-actions.coffee b/internal_packages/onboarding/lib/onboarding-actions.coffee index 4959df2bb..daa7b25f7 100644 --- a/internal_packages/onboarding/lib/onboarding-actions.coffee +++ b/internal_packages/onboarding/lib/onboarding-actions.coffee @@ -6,7 +6,7 @@ OnboardingActions = Reflux.createActions [ "moveToPreviousPage" "moveToPage" - "nylasAccountReceived" + "accountJSONReceived" ] for key, action of OnboardingActions diff --git a/internal_packages/onboarding/lib/page-router-store.coffee b/internal_packages/onboarding/lib/page-router-store.coffee index 92a3bb8da..7ddd26b35 100644 --- a/internal_packages/onboarding/lib/page-router-store.coffee +++ b/internal_packages/onboarding/lib/page-router-store.coffee @@ -1,5 +1,6 @@ Reflux = require 'reflux' OnboardingActions = require './onboarding-actions' +{AccountStore} = require 'nylas-exports' NylasStore = require 'nylas-store' ipc = require 'ipc' url = require 'url' @@ -8,7 +9,7 @@ return unless atom.getWindowType() is "onboarding" class PageRouterStore extends NylasStore constructor: -> - atom.onWindowPropsReceived @_onWindowPropsChagned + atom.onWindowPropsReceived @_onWindowPropsChanged @_page = atom.getWindowProps().page ? '' @_pageData = atom.getWindowProps().pageData ? {} @@ -17,20 +18,18 @@ class PageRouterStore extends NylasStore @listenTo OnboardingActions.moveToPreviousPage, @_onMoveToPreviousPage @listenTo OnboardingActions.moveToPage, @_onMoveToPage - @listenTo OnboardingActions.nylasAccountReceived, @_onNylasAccountReceived + @listenTo OnboardingActions.accountJSONReceived, @_onAccountJSONReceived - _onNylasAccountReceived: (account) => - tokens = atom.config.get('tokens') || [] - tokens.push({ - provider: 'nylas' - identifier: account.email_address - access_token: account.auth_token - }) - atom.config.set('tokens', tokens) - atom.config.save() - @_onMoveToPage('initial-preferences', {account}) + _onAccountJSONReceived: (json) => + isFirstAccount = AccountStore.items().length is 0 + AccountStore.addAccountFromJSON(json) + atom.displayWindow() + if isFirstAccount + @_onMoveToPage('initial-preferences', {account: json}) + else + ipc.send('account-setup-successful') - _onWindowPropsChagned: ({page, pageData}={}) => + _onWindowPropsChanged: ({page, pageData}={}) => @_onMoveToPage(page, pageData) page: -> @_page diff --git a/internal_packages/onboarding/lib/page-router.cjsx b/internal_packages/onboarding/lib/page-router.cjsx index 47143cef7..0840f9e87 100644 --- a/internal_packages/onboarding/lib/page-router.cjsx +++ b/internal_packages/onboarding/lib/page-router.cjsx @@ -23,21 +23,29 @@ class PageRouter extends React.Component componentDidMount: => @unsubscribe = PageRouterStore.listen(@_onStateChanged, @) + setTimeout(@_initializeWindowSize, 10) + + componentDidUpdate: => + setTimeout(@_updateWindowSize, 10) + + _initializeWindowSize: => + return if @_unmounted {width, height} = React.findDOMNode(@refs.activePage).getBoundingClientRect() atom.center() atom.setSizeAnimated(width, height, 0) atom.show() - componentDidUpdate: => - setTimeout(@_resizePage, 10) - - _resizePage: => + _updateWindowSize: => + return if @_unmounted {width, height} = React.findDOMNode(@refs.activePage).getBoundingClientRect() atom.setSizeAnimated(width, height) - _onStateChanged: => @setState(@_getStateFromStore()) + _onStateChanged: => + @setState(@_getStateFromStore()) - componentWillUnmount: => @unsubscribe?() + componentWillUnmount: => + @_unmounted = true + @unsubscribe?() render: =>
@@ -72,7 +80,7 @@ class PageRouter extends React.Component }[@state.page]
- +
_renderDragRegion: -> diff --git a/internal_packages/onboarding/lib/page.cjsx b/internal_packages/onboarding/lib/page.cjsx deleted file mode 100644 index e3b2343fe..000000000 --- a/internal_packages/onboarding/lib/page.cjsx +++ /dev/null @@ -1,34 +0,0 @@ -React = require 'react' -{RetinaImg} = require 'nylas-component-kit' - -class Page extends React.Component - @displayName: "Page" - - constructor: (@props) -> - - _renderClose: (action="close") -> - if action is "close" - onClick = -> atom.close() - else if action is "quit" - onClick = -> - require('ipc').send('command', 'application:quit') - else onClick = -> - -
- -
- - _renderSpinner: -> - styles = - position: "absolute" - zIndex: 10 - top: "50%" - left: "50%" - transform: 'translate(-50%, -50%)' - - - -module.exports = Page diff --git a/internal_packages/onboarding/lib/welcome-page.cjsx b/internal_packages/onboarding/lib/welcome-page.cjsx index a87e0a692..32b091ccb 100644 --- a/internal_packages/onboarding/lib/welcome-page.cjsx +++ b/internal_packages/onboarding/lib/welcome-page.cjsx @@ -1,9 +1,8 @@ React = require 'react' -Page = require './page' {RetinaImg, TimeoutTransitionGroup} = require 'nylas-component-kit' OnboardingActions = require './onboarding-actions' -class WelcomePage extends Page +class WelcomePage extends React.Component @displayName: "WelcomePage" constructor: (@props) -> @@ -17,7 +16,10 @@ class WelcomePage extends Page buttons.push
- {@_renderClose("close")} +
atom.close() }> + +
+ @state = @getStateFromStores() @@ -31,8 +28,6 @@ class PreferencesAccounts extends React.Component _renderAccounts: => return false unless @state.accounts - allowUnlinking = @state.accounts.length > 1 -
Accounts: @@ -49,7 +44,7 @@ class PreferencesAccounts extends React.Component
{account.emailAddress}
{account.name || "No name provided."} ({account.displayProvider()})
-
+
@@ -94,29 +89,15 @@ class PreferencesAccounts extends React.Component tokens _onAddAccount: => - require('remote').getGlobal('application').windowManager.newOnboardingWindow() + ipc = require('ipc') + ipc.send('command', 'application:add-account') _onAccountChange: => @setState(@getStateFromStores()) _onUnlinkAccount: (account) => - return [] unless @props.config - - tokens = @props.config.get('tokens') || [] - token = _.find tokens, (t) -> - t.provider is 'nylas' and t.identifier is account.emailAddress - tokens = _.without(tokens, token) - - if not token - console.warn("Could not find nylas token for email address #{account.emailAddress}") - return - - DatabaseStore.unpersistModel(account).then => - # TODO: Delete other mail data - EdgehillAPI.unlinkToken(token) + AccountStore.removeAccountId(account.id) _onUnlinkToken: (token) => - EdgehillAPI.unlinkToken(token) - return module.exports = PreferencesAccounts diff --git a/menus/darwin.cson b/menus/darwin.cson index 47d9923f3..a113f5d0f 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -6,7 +6,7 @@ { type: 'separator' } { label: 'Preferences', command: 'application:open-preferences' } { type: 'separator' } - { label: 'Add Account...', command: 'atom-workspace:add-account' } + { label: 'Add Account...', command: 'application:add-account' } { label: 'VERSION', enabled: false } { label: 'Restart and Install Update', command: 'application:install-update', visible: false} { label: 'Check for Update', command: 'application:check-for-update', visible: false} diff --git a/menus/linux.cson b/menus/linux.cson index c66355f1b..3df7dc11d 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -5,7 +5,7 @@ { label: '&New Message', command: 'application:new-message' } { type: 'separator' } { label: 'Preferences', command: 'application:open-preferences' } - { label: 'Add Account...', command: 'atom-workspace:add-account' } + { label: 'Add Account...', command: 'application:add-account' } { label: 'Clos&e Window', command: 'window:close' } { label: 'Quit', command: 'application:quit' } ] diff --git a/menus/win32.cson b/menus/win32.cson index 1506c7138..76b5362aa 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -1,5 +1,5 @@ 'menu': [ - { label: 'Add Account...', command: 'atom-workspace:add-account' } + { label: 'Add Account...', command: 'application:add-account' } { label: '&Edit' submenu: [ diff --git a/spec-nylas/stores/account-store-spec.coffee b/spec-nylas/stores/account-store-spec.coffee index 32e31ce21..8fbfb9780 100644 --- a/spec-nylas/stores/account-store-spec.coffee +++ b/spec-nylas/stores/account-store-spec.coffee @@ -1,5 +1,6 @@ _ = require 'underscore' AccountStore = require '../../src/flux/stores/account-store' +Account = require '../../src/flux/models/account' describe "AccountStore", -> beforeEach -> @@ -9,17 +10,38 @@ describe "AccountStore", -> afterEach -> @instance.stopListeningToAll() - it "should initialize current() using data saved in config", -> - state = - "id": "123", - "email_address":"bengotow@gmail.com", - "object":"account" - "organization_unit": "label" + it "should initialize using data saved in config", -> + accounts = + [{ + "id": "123", + "client_id" : 'local-4f9d476a-c173', + "server_id" : '123', + "email_address":"bengotow@gmail.com", + "object":"account" + "organization_unit": "label" + },{ + "id": "1234", + "client_id" : 'local-4f9d476a-c175', + "server_id" : '1234', + "email_address":"ben@nylas.com", + "object":"account" + "organization_unit": "label" + }] - spyOn(atom.config, 'get').andCallFake -> state + spyOn(atom.config, 'get').andCallFake (key) -> + if key is 'nylas.accounts' + return accounts + else if key is 'nylas.currentAccountIndex' + return 1 @instance = new @constructor - expect(@instance.current().id).toEqual(state['id']) - expect(@instance.current().emailAddress).toEqual(state['email_address']) + + expect(@instance.items()).toEqual([ + (new Account).fromJSON(accounts[0]), + (new Account).fromJSON(accounts[1]) + ]) + expect(@instance.current() instanceof Account).toBe(true) + expect(@instance.current().id).toEqual(accounts[1]['id']) + expect(@instance.current().emailAddress).toEqual(accounts[1]['email_address']) it "should initialize current() to null if data is not present", -> spyOn(atom.config, 'get').andCallFake -> null diff --git a/src/atom.coffee b/src/atom.coffee index a13419a96..6ac872048 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -631,18 +631,7 @@ class Atom extends Model CommandInstaller.installApmCommand resourcePath, false, (error) -> console.warn error.message if error? @commands.add 'atom-workspace', - 'atom-workspace:add-account': @onAddAccount - - onAddAccount: => - @newWindow - title: 'Add an Account' - width: 340 - height: 550 - toolbar: false - resizable: false - windowType: 'onboarding' - windowProps: - page: 'add-account' + 'atom-workspace:add-account': @addAccount # Call this method when establishing a secondary application window # displaying a specific set of packages. @@ -777,6 +766,17 @@ class Atom extends Model executeJavaScriptInDevTools: (code) -> ipc.send('call-window-method', 'executeJavaScriptInDevTools', code) + addAccount: => + @newWindow + title: 'Add an Account' + width: 340 + height: 550 + toolbar: false + resizable: false + windowType: 'onboarding' + windowProps: + page: 'add-account' + ### Section: Private ### diff --git a/src/browser/application.coffee b/src/browser/application.coffee index dd9fa5dc2..b1244fa0f 100644 --- a/src/browser/application.coffee +++ b/src/browser/application.coffee @@ -154,31 +154,31 @@ class Application app.commandLine.appendSwitch 'js-flags', '--harmony' openWindowsForTokenState: (loadingMessage) => - hasToken = @config.get('tokens')?.length > 0 - if hasToken + hasAccount = @config.get('nylas.accounts')?.length > 0 + if hasAccount @windowManager.showMainWindow(loadingMessage) @windowManager.ensureWorkWindow() else - @windowManager.newOnboardingWindow() + @windowManager.newOnboardingWindow({welcome: true}) # The onboarding window automatically shows when it's ready _resetConfigAndRelaunch: => @setDatabasePhase('close') @windowManager.closeAllWindows() @_deleteDatabase => - @config.set('tokens', null) @config.set('nylas', null) @config.set('edgehill', null) @setDatabasePhase('setup') - @openWindowsForTokenState() + @windowManager.newOnboardingWindow({welcome: true}) _deleteDatabase: (callback) -> @deleteFileWithRetry path.join(configDirPath,'edgehill.db'), callback @deleteFileWithRetry path.join(configDirPath,'edgehill.db-wal') @deleteFileWithRetry path.join(configDirPath,'edgehill.db-shm') - _loginSuccessful: => - @openWindowsForTokenState() + _accountSetupSuccessful: => + @windowManager.showMainWindow() + @windowManager.ensureWorkWindow() @windowManager.mainWindow().waitForLoad => @windowManager.onboardingWindow()?.close() @@ -247,6 +247,7 @@ class Application atomWindow ?= @windowManager.focusedWindow() atomWindow?.browserWindow.inspectElement(x, y) + @on 'application:add-account', => @windowManager.newOnboardingWindow() @on 'application:new-message', => @windowManager.sendToMainWindow('new-message') @on 'application:send-feedback', => @windowManager.sendToMainWindow('send-feedback') @on 'application:open-preferences', => @windowManager.sendToMainWindow('open-preferences') @@ -257,6 +258,7 @@ class Application @quitting = true @windowManager.unregisterAllHotWindows() @autoUpdateManager.install() + @on 'application:open-dev', => @devMode = true @windowManager.closeAllWindows() @@ -375,8 +377,8 @@ class Application clipboard ?= require 'clipboard' clipboard.writeText(selectedText, 'selection') - ipc.on 'login-successful', (event) => - @_loginSuccessful() + ipc.on 'account-setup-successful', (event) => + @_accountSetupSuccessful() ipc.on 'run-in-window', (event, params) => @_sourceWindows ?= {} diff --git a/src/browser/window-manager.coffee b/src/browser/window-manager.coffee index b3e18420f..097b6e585 100644 --- a/src/browser/window-manager.coffee +++ b/src/browser/window-manager.coffee @@ -132,17 +132,23 @@ class WindowManager # Returns a new onboarding window # - newOnboardingWindow: -> - @newWindow - title: 'Welcome to Nylas' + newOnboardingWindow: ({welcome} = {}) -> + options = toolbar: false resizable: false hidden: true + title: 'Add an Account' windowType: 'onboarding' windowProps: - page: "welcome" + page: 'account-choose' uniqueId: 'onboarding' + 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, diff --git a/src/flux/edgehill-api.coffee b/src/flux/edgehill-api.coffee index fb40ce5f6..f767e9436 100644 --- a/src/flux/edgehill-api.coffee +++ b/src/flux/edgehill-api.coffee @@ -13,14 +13,6 @@ class EdgehillAPI constructor: -> atom.config.onDidChange('env', @_onConfigChanged) @_onConfigChanged() - - # Always ask Edgehill Server for our tokens at launch. This way accounts - # added elsewhere will appear, and we'll also handle the 0.2.5=>0.3.0 upgrade. - if atom.isWorkWindow() - existing = @_getCredentials() - if existing and existing.username - @setUserIdentifierAndRetrieveTokens(existing.username) - @ _onConfigChanged: => @@ -72,48 +64,6 @@ class EdgehillAPI else options.success(body) if options.success - urlForConnecting: (provider, email_address = '') -> - auth = @_getCredentials() - root = @APIRoot - token = auth?.username - "#{root}/connect/#{provider}?login_hint=#{email_address}&token=#{token}" - - setUserIdentifierAndRetrieveTokens: (user_identifier) -> - @_setCredentials(username: user_identifier, password: '') - @request - path: "/users/me" - success: (userData={}) => - @setTokens(userData.tokens) - if atom.getWindowType() is 'onboarding' - ipc = require 'ipc' - ipc.send('login-successful') - error: (apiError) => - console.error apiError - - setTokens: (incoming) -> - # todo: remove once the edgehill-server inbox service is called `nylas` - for token in incoming - if token.provider is 'inbox' - token.provider = 'nylas' - atom.config.set('tokens', incoming) - atom.config.save() - - unlinkToken: (token) -> - @request - path: "/users/token/#{token.id}" - method: 'DELETE' - success: => - tokens = atom.config.get('tokens') || [] - tokens = _.reject tokens, (t) -> t.id is token.id - atom.config.set('tokens', tokens) - - accessTokenForProvider: (provider) -> - tokens = atom.config.get('tokens') || [] - for token in tokens - if token.provider is provider - return token.access_token - return null - _getCredentials: -> atom.config.get('edgehill.credentials') diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee index 4cb568806..ff03807d5 100644 --- a/src/flux/nylas-api.coffee +++ b/src/flux/nylas-api.coffee @@ -123,16 +123,11 @@ class NylasAPI @_optimisticChangeTracker = new NylasAPIOptimisticChangeTracker() atom.config.onDidChange('env', @_onConfigChanged) - atom.config.onDidChange('tokens', @_onConfigChanged) @_onConfigChanged() _onConfigChanged: => prev = {@AppID, @APIRoot, @APITokens} - tokens = atom.config.get('tokens') || [] - tokens = tokens.filter (t) -> t.provider is 'nylas' - @APITokens = tokens.map (t) -> t.access_token - env = atom.config.get('env') if not env env = 'production' @@ -154,13 +149,6 @@ class NylasAPI current = {@AppID, @APIRoot, @APITokens} - if atom.isWorkWindow() and not _.isEqual(prev, current) - @APITokens.forEach (token) => - @makeRequest - path: "/account" - auth: {'user': token, 'pass': '', sendImmediately: true} - returnsModel: true - # Delegates to node's request object. # On success, it will call the passed in success callback with options. # On error it will create a new APIError object that wraps the error, @@ -356,17 +344,8 @@ class NylasAPI decrementOptimisticChangeCount: (klass, id) -> @_optimisticChangeTracker.decrement(klass, id) - tokenObjectForAccountId: (aid) -> - AccountStore = require './stores/account-store' - accounts = AccountStore.items() || [] - account = _.find accounts, (acct) -> acct.id is aid - return null unless account - - tokens = atom.config.get('tokens') || [] - token = _.find tokens, (t) -> t.provider is 'nylas' and t.identifier is account.emailAddress - return token - accessTokenForAccountId: (aid) -> - @tokenObjectForAccountId(aid)?.access_token + AccountStore = require './stores/account-store' + AccountStore.tokenForAccountId(aid) module.exports = new NylasAPI() diff --git a/src/flux/stores/account-store.coffee b/src/flux/stores/account-store.coffee index 94b66c3d5..b266eff2c 100644 --- a/src/flux/stores/account-store.coffee +++ b/src/flux/stores/account-store.coffee @@ -6,7 +6,9 @@ _ = require 'underscore' {Listener, Publisher} = require '../modules/reflux-coffee' CoffeeHelpers = require '../coffee-helpers' -saveStateKey = "nylas.current_account" +saveObjectsKey = "nylas.accounts" +saveTokensKey = "nylas.accountTokens" +saveIndexKey = "nylas.currentAccountIndex" ### Public: The AccountStore listens to changes to the available accounts in @@ -21,49 +23,63 @@ class AccountStore @include Listener constructor: -> - @_items = [] - @_current = null - @_accounts = [] - - saveState = atom.config.get(saveStateKey) - if saveState and _.isObject(saveState) - savedAccount = (new Account).fromJSON(saveState) - if savedAccount.usesLabels() or savedAccount.usesFolders() - @_setCurrent(savedAccount) - @_accounts = [@_current] - + @_load() @listenTo Actions.selectAccountId, @onSelectAccountId - @listenTo DatabaseStore, @onDataChanged + atom.config.observe saveTokensKey, (updatedTokens) => + return if _.isEqual(updatedTokens, @_tokens) + newAccountIds = _.keys(_.omit(updatedTokens, _.keys(@_tokens))) + if newAccountIds.length > 0 + Actions.selectAccountId(newAccountIds[0]) + @_load() - @populateItems() + _load: => + @_accounts = [] + for json in atom.config.get(saveObjectsKey) || [] + @_accounts.push((new Account).fromJSON(json)) - populateItems: => - DatabaseStore.findAll(Account).order(Account.attributes.emailAddress.descending()).then (accounts) => - current = _.find accounts, (a) -> a.id is @_current?.id - current = accounts?[0] unless current + index = atom.config.get(saveIndexKey) || 0 + @_index = Math.min(@_accounts.length - 1, Math.max(0, index)) - if not _.isEqual(current, @_current) or not _.isEqual(accounts, @_accounts) - @_setCurrent(current) - @_accounts = accounts - @trigger() + @_tokens = atom.config.get(saveTokensKey) || {} + @trigger() - .catch (err) => - console.warn("Request for accounts failed. #{err}", err.stack) - - _setCurrent: (current) => - atom.config.set(saveStateKey, current) - @_current = current + _save: => + atom.config.set(saveObjectsKey, @_accounts) + atom.config.set(saveIndexKey, @_index) + atom.config.set(saveTokensKey, @_tokens) + atom.config.save() # Inbound Events - onDataChanged: (change) => - return unless change && change.objectClass is Account.name - @populateItems() - onSelectAccountId: (id) => - return if @_current?.id is id - @_current = _.find @_accounts, (a) -> a.id is id - @trigger(@) + idx = _.findIndex @_accounts, (a) -> a.id is id + return if idx is -1 + atom.config.set(saveIndexKey, idx) + @_index = idx + @trigger() + + removeAccountId: (id) => + idx = _.findIndex @_accounts, (a) -> a.id is id + return if idx is -1 + + delete @_tokens[id] + @_accounts.splice(idx, 1) + @_save() + + if @_accounts.length is 0 + ipc = require('ipc') + ipc.send('command', 'application:reset-config-and-relaunch') + else + if @_index is idx + Actions.selectAccountId(@_accounts[0].id) + @trigger() + + addAccountFromJSON: (json) => + return if @_tokens[json.id] + @_tokens[json.id] = json.auth_token + @_accounts.push((new Account).fromJSON(json)) + @_save() + @trigger() # Exposed Data @@ -73,6 +89,11 @@ class AccountStore # Public: Returns the currently active {Account}. current: => - @_current + @_accounts[@_index] || null + + # Private: This method is going away soon, do not rely on it. + # + tokenForAccountId: (id) => + @_tokens[id] module.exports = new AccountStore() diff --git a/static/images/onboarding/sending-spinner.gif b/static/images/onboarding/sending-spinner.gif new file mode 100644 index 000000000..1c72ebb55 Binary files /dev/null and b/static/images/onboarding/sending-spinner.gif differ