From 297320df94cf887c860622c83e66fb4a5558e012 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Thu, 24 Sep 2015 14:51:15 -0700 Subject: [PATCH] fix(onboarding): Lots of changes to account management, dead code cleanup Summary: Better error handling in the account settings page and a loading spinner Add Account... replaces "Link External Account", and it works Clean dead code from onboarding pages, remove base class component Always show the account switcher rm dead EdgehillAPI code, AccountStore now manages accounts and credentials in config, not in database Fix specs Test Plan: Run tests Reviewers: dillon, evan Reviewed By: evan Projects: #edgehill Differential Revision: https://phab.nylas.com/D2059 --- .../account-sidebar/lib/account-switcher.cjsx | 8 +- .../stylesheets/account-sidebar.less | 7 +- .../onboarding/lib/account-choose-page.cjsx | 15 +-- .../onboarding/lib/account-settings-page.cjsx | 100 ++++++++++-------- .../onboarding/lib/connect-account-page.cjsx | 31 ------ .../onboarding/lib/initial-packages-page.cjsx | 5 +- .../lib/initial-preferences-page.cjsx | 3 +- .../onboarding/lib/onboarding-actions.coffee | 2 +- .../onboarding/lib/page-router-store.coffee | 25 +++-- .../onboarding/lib/page-router.cjsx | 22 ++-- internal_packages/onboarding/lib/page.cjsx | 34 ------ .../onboarding/lib/welcome-page.cjsx | 8 +- .../onboarding/stylesheets/onboarding.less | 19 +++- .../lib/tabs/preferences-accounts.cjsx | 27 +---- menus/darwin.cson | 2 +- menus/linux.cson | 2 +- menus/win32.cson | 2 +- spec-nylas/stores/account-store-spec.coffee | 40 +++++-- src/atom.coffee | 24 ++--- src/browser/application.coffee | 20 ++-- src/browser/window-manager.coffee | 14 ++- src/flux/edgehill-api.coffee | 50 --------- src/flux/nylas-api.coffee | 25 +---- src/flux/stores/account-store.coffee | 93 +++++++++------- static/images/onboarding/sending-spinner.gif | Bin 0 -> 2545 bytes 25 files changed, 249 insertions(+), 329 deletions(-) delete mode 100644 internal_packages/onboarding/lib/connect-account-page.cjsx delete mode 100644 internal_packages/onboarding/lib/page.cjsx create mode 100644 static/images/onboarding/sending-spinner.gif 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 0000000000000000000000000000000000000000..1c72ebb554be018511ae972c3f2361dff02dce02 GIT binary patch literal 2545 zcma*pX;2es8VB%~zPr=ibVMCx-JQ^BhLDAsK)^**h(ZDp9YGuzZ%~j!}+w%FI;|aC7){7CdVvG)P{bng1y9Te*f}~*`1kQl$jwb z$tlW~rRS!X?#xfm_&6tTdp_`cjgYwbRFLNdoJCN$S-yhg`ZnC-yvedRSmOh%;Y`Gl6bY$Z-}#C=#F4%9!I1b zWQ~f+9P?;vhCxWwlwl=lrWG|7IYo;{jjmzJ5R9?f>n%-d@>kLINUc z4wM5dAO;kq<$}Dk{2-u0$I6@2N}&cUx9nmV1dYc8jfC}%=F9WCg^OQK9C6poh#2!A z3^EU*UFZvS^)?bu3T?J;@Ahb~%I?+@4!l5!*TjC}GIslNan-RCrrd~PdHYnNLJk+m&`$Y+NV(e>CCu%R#_8GqY4cv#j`#uRWdsg9DxWy(?oOvgCU}&@jy%c!H&-Q zqXJxajAtmQRoRa9V-RFXXh-bK*;Fum{BjpkYQGX~i@OZ^Dx0n&H}kvGKqQ?w(6iGXu_g08T|_hp#ZvFzIwKF*a=oMJ~3UGAjZ?g}GOxm44td zXoyYrU*I=y*vHv89hkYH(v5R#wc)BC3dZJKb3K)f>zaM3%JP(mpecViP0eKKYf3zy z->jx_mc?mCtPEvCQ?uppk?eLJt}_IR7giW%Jr)RyI!+E-voIs*lXI*z`GQc_&D#X( z{6G};HPYj6O|$lXxBJeDaweqa{4L=tOZCjTI^&UOxXg})LRG_cr^B9Rqt(i5ORbQX zq`_xCRsH>xEYY%&*Nyi#{S_JZNlTm#K56`RI%7^amom;*h90Si&g1CfaFV3D|a!`3Y-GKKbL*KSbl z>I96`TR@CqPJl(>QqB~RvK~-U)`e`l4LIqj+IU^~yyIe*|BRVB>4Bup%j{tLdKz4j zY^<8P8m~GRGz*yv0&-RJE+-keJ+%m3wNeopzsltWd->eWmBVwUr)pX` zK~CD<;~Z*Uy3W`3+MrEYxm5qYQ!z%YI;y7DTG`UVH0;@{M{!B&id_}3DBQ?zsotuR zEGLdRx25nLm%-wjlnEi;-aN_1S7???rO~WgA67jjr&(vRa3y$u#kqJbeKnw z{!T!1li9>M+sJ6AUe+*9d}2uGjhzd z|L1Rtp8uTGYyZoQ*`DS^m2dw-X{a)l+3m?ncvn^+O>)hdd3(hMtlhkRGns{<8c0I! zDDjpmwtj?@!6kA|iu3q+Ai;@JR+ zfk+ln&YFC{4bhK6IxVgLs4W%^8Lk`qzWU*L>yq0A3;l}{!wKZ!ue)C)SKI)9dl1hl zhIRLV@8E}rwvE{gX(}$f6x*k)_`*Ijt1=EU-Ls6-(phomeQBgtUs z5Xz~Cd*nE)Ac!0i4ep}Z1AugMB(&F?)#CU{Qc{Sp^vKsdL}vRB30H+Bbzrn`M##H3 z{W8dc_mDroEE+p8_}mnJtzZ4!RNe)zhB)Ds;S57nYSJxtek>^~&(7B+N5MPf2+2xx z5Dl&4X|c@f{Kd|z1r+N|$DmsoVp*3yOdxT^J^-VAk)Z@$4^XrPrFP-Co+MXZ+KJ(W z{JNYvraLLWA;&tRhIKOvhW|HC|L-dLvAUF(MG0(Nl?4tB{RzN7I(}Cb%hwN{crFC8 zji#aJElKvDFV+&VI1V?oUMA>*kto0^;3W8FQBSZ|{ z$v~TqE=(8DZa^i$^oht&h};P1N&wMXorKh*Z68gPV&ouy>%f36Oqkwemyeas$Qbz# zV?7Jy%o7KY6^I=P@eCji%W`o5sf(5hySYo9$l4e2`(hIV_?=H-#R6}0$WVA|*(K@3 z=5?@RlcLh(meW%A4)hGzcvEpm(_w?>zhL*i&s9$2>r zAtk{8Cia|+Y+V!uX9BtpXoF%lswuRKsM!pSs!?yhlCy!269K0|b M?FSZn2B>%I-}ej|s{jB1 literal 0 HcmV?d00001