From 213bbde692a19ff5993685823b1034e9a5715b9c Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Wed, 23 Sep 2015 09:59:34 -0700 Subject: [PATCH] fix(onboarding): Tweaks, styles, etc. for new onboarding experience Summary: Remove logout menu item and buttons, turn Link External Account to Add Account Onboarding window starts hidden, is shown when react component is mounted and sized Use get/setBounds to animate position and size at the same time smoothly Fix specs, change 401 notice Delay bouncing to Gmail to show users the Gmail screen momentarily Make the animated resizing code defer so it doesn't run in a hard loop, and other animations can run at the same time Bring back crossfade between screens, remove left/right shift on welcome screens Test Plan: Run tests Reviewers: drew, evan Reviewed By: evan Maniphest Tasks: T3529 Differential Revision: https://phab.nylas.com/D2054 --- .../onboarding/lib/account-choose-page.cjsx | 44 +++++++++------- .../onboarding/lib/page-router.cjsx | 52 +++++++++---------- .../onboarding/stylesheets/onboarding.less | 20 +++---- .../lib/tabs/preferences-accounts.cjsx | 8 --- menus/darwin.cson | 3 +- menus/linux.cson | 3 +- menus/win32.cson | 3 +- spec-nylas/action-bridge-spec.coffee | 16 +++--- spec-nylas/nylas-api-spec.coffee | 2 +- src/atom.coffee | 26 ++++++---- src/browser/application.coffee | 9 ++-- src/browser/window-manager.coffee | 3 +- src/flux/actions.coffee | 16 ++---- src/flux/nylas-api.coffee | 18 ++++--- 14 files changed, 105 insertions(+), 118 deletions(-) diff --git a/internal_packages/onboarding/lib/account-choose-page.cjsx b/internal_packages/onboarding/lib/account-choose-page.cjsx index 224ce3cec..aa4163f8b 100644 --- a/internal_packages/onboarding/lib/account-choose-page.cjsx +++ b/internal_packages/onboarding/lib/account-choose-page.cjsx @@ -1,5 +1,5 @@ React = require 'react' - +_ = require 'underscore' {RetinaImg} = require 'nylas-component-kit' {EdgehillAPI, Utils} = require 'nylas-exports' @@ -61,26 +61,32 @@ class AccountChoosePage extends Page _onChooseProvider: (provider) => if provider.name is 'gmail' - provider.clientKey = Utils.generateTempId()[6..]+'-'+Utils.generateTempId()[6..] - shell = require 'shell' - googleUrl = url.format({ - protocol: 'https' - host: 'accounts.google.com/o/oauth2/auth' - query: - response_type: 'code' - state: provider.clientKey - client_id: '372024217839-cdsnrrqfr4d6b4gmlqepd7v0n0l0ip9q.apps.googleusercontent.com' - redirect_uri: 'http://localhost:5009/oauth/google/callback' - access_type: 'offline' - scope: 'https://www.googleapis.com/auth/userinfo.email \ - https://www.googleapis.com/auth/userinfo.profile \ - https://mail.google.com/ \ - https://www.google.com/m8/feeds \ - https://www.googleapis.com/auth/calendar' - }) - shell.openExternal(googleUrl) + # Show the "Sign in to Gmail" prompt for a moment before actually bouncing + # to Gmail. (400msec animation + 200msec to read) + _.delay => + @_onBounceToGmail(provider) + , 600 OnboardingActions.moveToPage("account-settings", {provider}) + _onBounceToGmail: (provider) => + provider.clientKey = Utils.generateTempId()[6..]+'-'+Utils.generateTempId()[6..] + shell = require 'shell' + googleUrl = url.format({ + protocol: 'https' + host: 'accounts.google.com/o/oauth2/auth' + query: + response_type: 'code' + state: provider.clientKey + client_id: '372024217839-cdsnrrqfr4d6b4gmlqepd7v0n0l0ip9q.apps.googleusercontent.com' + redirect_uri: "#{EdgehillAPI.APIRoot}/oauth/google/callback" + access_type: 'offline' + scope: 'https://www.googleapis.com/auth/userinfo.email \ + https://www.googleapis.com/auth/userinfo.profile \ + https://mail.google.com/ \ + https://www.google.com/m8/feeds \ + https://www.googleapis.com/auth/calendar' + }) + shell.openExternal(googleUrl) _onSubmit: (e) => valid = React.findDOMNode(@refs.form).reportValidity() diff --git a/internal_packages/onboarding/lib/page-router.cjsx b/internal_packages/onboarding/lib/page-router.cjsx index ec8a82dce..47143cef7 100644 --- a/internal_packages/onboarding/lib/page-router.cjsx +++ b/internal_packages/onboarding/lib/page-router.cjsx @@ -22,17 +22,18 @@ class PageRouter extends React.Component pageData: PageRouterStore.pageData() componentDidMount: => - atom.setSize(667,482) @unsubscribe = PageRouterStore.listen(@_onStateChanged, @) + {width, height} = React.findDOMNode(@refs.activePage).getBoundingClientRect() + atom.center() + atom.setSizeAnimated(width, height, 0) + atom.show() componentDidUpdate: => - setTimeout( => - @_resizePage() - ,10) + setTimeout(@_resizePage, 10) _resizePage: => - {width,height} = React.findDOMNode(@refs.container).getBoundingClientRect() - atom.setSizeAnimated(width,height) + {width, height} = React.findDOMNode(@refs.activePage).getBoundingClientRect() + atom.setSizeAnimated(width, height) _onStateChanged: => @setState(@_getStateFromStore()) @@ -41,20 +42,17 @@ class PageRouter extends React.Component render: =>
{@_renderDragRegion()} -
{@_renderCurrentPage()} -
- {@_renderGradients()} - -
+ {@_renderCurrentPageGradient()} + +
- _renderGradients: => + _renderCurrentPageGradient: => gradient = @state.pageData?.provider?.color if gradient background = "linear-gradient(to top, #f6f7f8, #{gradient})" @@ -62,22 +60,20 @@ class PageRouter extends React.Component background = "linear-gradient(to top, #f6f7f8 0%, rgba(255,255,255,0) 100%), linear-gradient(to right, #e1e58f 0%, #a8d29e 50%, #8bc9c9 100%)" -
+
_renderCurrentPage: => - switch @state.page - when "welcome" - - when "account-choose" - - when "account-settings" - - when "initial-preferences" - - when "initial-packages" - - else -
+ Component = { + "welcome": WelcomePage + "account-choose": AccountChoosePage + "account-settings": AccountSettingsPage + "initial-preferences": InitialPreferencesPage + "initial-packages": InitialPackagesPage + }[@state.page] + +
+ +
_renderDragRegion: -> styles = diff --git a/internal_packages/onboarding/stylesheets/onboarding.less b/internal_packages/onboarding/stylesheets/onboarding.less index 67170ae04..8bae8bb4f 100644 --- a/internal_packages/onboarding/stylesheets/onboarding.less +++ b/internal_packages/onboarding/stylesheets/onboarding.less @@ -86,17 +86,15 @@ .page-container { z-index: 10; - display: inline-block; - position: relative; + position: absolute; + top: 0; + left: 0; + width: 100%; } .page { - //position: absolute; - //top: 0; - //width: 100%; - //height: 100%; + margin:auto; padding-top: 10%; - z-index: 10; &.no-top { padding-top: 0; } @@ -169,24 +167,20 @@ position:absolute; } .welcome-image-enter { - transform: translate(10%, 0); opacity: 0; - transition: all .3s linear; + transition: opacity .3s linear; } .welcome-image-enter.welcome-image-enter-active { opacity: 1; - transform: translate(0%, 0); } .welcome-image-leave { opacity: 1; - transform: translate(0%, 0); - transition: all .3s linear; + transition: opacity .3s linear; } .welcome-image-leave.welcome-image-leave-active { opacity: 0; - transform: translate(-10%, 0); } .check { diff --git a/internal_packages/preferences/lib/tabs/preferences-accounts.cjsx b/internal_packages/preferences/lib/tabs/preferences-accounts.cjsx index 8a688b5f7..2ec109b37 100644 --- a/internal_packages/preferences/lib/tabs/preferences-accounts.cjsx +++ b/internal_packages/preferences/lib/tabs/preferences-accounts.cjsx @@ -26,10 +26,6 @@ class PreferencesAccounts extends React.Component
{@_renderLinkedAccounts()} - -
- -
_renderAccounts: => @@ -123,8 +119,4 @@ class PreferencesAccounts extends React.Component EdgehillAPI.unlinkToken(token) return - _onLogout: => - atom.logout() - - module.exports = PreferencesAccounts diff --git a/menus/darwin.cson b/menus/darwin.cson index 12dd00f1b..47d9923f3 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -6,8 +6,7 @@ { type: 'separator' } { label: 'Preferences', command: 'application:open-preferences' } { type: 'separator' } - { label: 'Link External Account', command: 'atom-workspace:add-account' } - { label: 'Log Out', command: 'application:logout' } + { label: 'Add Account...', command: 'atom-workspace: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 2f260f8d8..c66355f1b 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -5,8 +5,7 @@ { label: '&New Message', command: 'application:new-message' } { type: 'separator' } { label: 'Preferences', command: 'application:open-preferences' } - { label: 'Link External Account', command: 'atom-workspace:add-account' } - { label: 'Log Out', command: 'application:logout' } + { label: 'Add Account...', command: 'atom-workspace: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 161d25e98..1506c7138 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -1,5 +1,5 @@ 'menu': [ - { label: 'Link External Account', command: 'atom-workspace:add-account' } + { label: 'Add Account...', command: 'atom-workspace:add-account' } { label: '&Edit' submenu: [ @@ -55,7 +55,6 @@ } { type: 'separator' } { label: 'Preferences', command: 'application:open-preferences' } - { label: 'Log Out', command: 'application:logout' } { type: 'separator' } { label: 'E&xit', command: 'application:quit' } ] diff --git a/spec-nylas/action-bridge-spec.coffee b/spec-nylas/action-bridge-spec.coffee index 186f3e967..5c88cb669 100644 --- a/spec-nylas/action-bridge-spec.coffee +++ b/spec-nylas/action-bridge-spec.coffee @@ -83,19 +83,19 @@ describe "ActionBridge", -> describe "when called with TargetWindows.ALL", -> it "should broadcast the action over IPC to all windows", -> spyOn(ipc, 'send') - Actions.logout.firing = false - @bridge.onRebroadcast(ActionBridge.TargetWindows.ALL, 'logout', [{oldModel: '1', newModel: 2}]) - expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-all', 'popout', 'logout', '[{"oldModel":"1","newModel":2}]') + Actions.didPassivelyReceiveNewModels.firing = false + @bridge.onRebroadcast(ActionBridge.TargetWindows.ALL, 'didPassivelyReceiveNewModels', [{oldModel: '1', newModel: 2}]) + expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-all', 'popout', 'didPassivelyReceiveNewModels', '[{"oldModel":"1","newModel":2}]') describe "when called with TargetWindows.WORK", -> it "should broadcast the action over IPC to the main window only", -> spyOn(ipc, 'send') - Actions.logout.firing = false - @bridge.onRebroadcast(ActionBridge.TargetWindows.WORK, 'logout', [{oldModel: '1', newModel: 2}]) - expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-work', 'popout', 'logout', '[{"oldModel":"1","newModel":2}]') + Actions.didPassivelyReceiveNewModels.firing = false + @bridge.onRebroadcast(ActionBridge.TargetWindows.WORK, 'didPassivelyReceiveNewModels', [{oldModel: '1', newModel: 2}]) + expect(ipc.send).toHaveBeenCalledWith('action-bridge-rebroadcast-to-work', 'popout', 'didPassivelyReceiveNewModels', '[{"oldModel":"1","newModel":2}]') it "should not do anything if the current invocation of the Action was triggered by itself", -> spyOn(ipc, 'send') - Actions.logout.firing = true - @bridge.onRebroadcast(ActionBridge.TargetWindows.ALL, 'logout', [{oldModel: '1', newModel: 2}]) + Actions.didPassivelyReceiveNewModels.firing = true + @bridge.onRebroadcast(ActionBridge.TargetWindows.ALL, 'didPassivelyReceiveNewModels', [{oldModel: '1', newModel: 2}]) expect(ipc.send).not.toHaveBeenCalled() diff --git a/spec-nylas/nylas-api-spec.coffee b/spec-nylas/nylas-api-spec.coffee index 372b154e1..8a5f76c52 100644 --- a/spec-nylas/nylas-api-spec.coffee +++ b/spec-nylas/nylas-api-spec.coffee @@ -52,7 +52,7 @@ describe "NylasAPI", -> spyOn(Actions, 'postNotification') NylasAPI._handle401('/threads/1234') expect(Actions.postNotification).toHaveBeenCalled() - expect(Actions.postNotification.mostRecentCall.args[0].message).toEqual("Nylas can no longer authenticate with your mail provider. You will not be able to send or receive mail. Please log out and sign in again.") + expect(Actions.postNotification.mostRecentCall.args[0].message).toEqual("Nylas can no longer authenticate with your mail provider. You will not be able to send or receive mail. Please unlink your account and sign in again.") describe "handleModelResponse", -> diff --git a/src/atom.coffee b/src/atom.coffee index 875315227..4cbf17d2d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -389,9 +389,6 @@ class Atom extends Model isReleasedVersion: -> not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix - logout: -> - ipc.send('command', 'application:logout') - # Public: Get the directory path to Atom's configuration area. # # Returns the absolute path to `~/.atom`. @@ -443,16 +440,23 @@ class Atom extends Model # * `duration` The {Number} of pixels. setSizeAnimated: (width, height, duration=400) -> cubicInOut = (t) -> if t<.5 then 4*t**3 else (t-1)*(2*t-2)**2+1 - {width:startWidth,height:startHeight} = @getSize() + win = @getCurrentWindow() + startBounds = win.getBounds() + startTime = Date.now() - while (t = (Date.now() - startTime) / (duration)) < 1 + boundsForI = (i) -> + x: Math.round(startBounds.x + (width-startBounds.width) * -0.5 * i) + y: Math.round(startBounds.y + (height-startBounds.height) * -0.5 * i) + width: Math.round(startBounds.width + (width-startBounds.width) * i) + height: Math.round(startBounds.height + (height-startBounds.height) * i) + + tick = -> + t = Math.min(1, (Date.now() - startTime) / (duration)) i = cubicInOut(t) - @setSize( - Math.round(startWidth + (width-startWidth) * i), - Math.round(startHeight + (height-startHeight) * i)) - @setSize(width, height) - - + win.setBounds(boundsForI(i)) + unless t is 1 + _.defer(tick) + tick() setMinimumWidth: (minWidth) -> win = @getCurrentWindow() diff --git a/src/browser/application.coffee b/src/browser/application.coffee index ea8bbce0a..dc61b95cf 100644 --- a/src/browser/application.coffee +++ b/src/browser/application.coffee @@ -159,9 +159,10 @@ class Application @windowManager.showMainWindow() @windowManager.ensureWorkWindow() else - @windowManager.newOnboardingWindow().showWhenLoaded() - - _logout: => + @windowManager.newOnboardingWindow() + # The onboarding window automatically shows when it's ready + + _resetConfigAndRelaunch: => @setDatabasePhase('close') @windowManager.closeAllWindows() @_deleteDatabase => @@ -247,7 +248,7 @@ class Application @on 'application:run-benchmarks', -> @runBenchmarks() - @on 'application:logout', @_logout + @on 'application:reset-config-and-relaunch', @_resetConfigAndRelaunch @on 'application:quit', => app.quit() @on 'application:inspect', ({x,y, atomWindow}) -> diff --git a/src/browser/window-manager.coffee b/src/browser/window-manager.coffee index 217baa25a..c9601226d 100644 --- a/src/browser/window-manager.coffee +++ b/src/browser/window-manager.coffee @@ -128,9 +128,8 @@ class WindowManager @newWindow title: 'Welcome to Nylas' toolbar: false - width: 340 - height: 550 resizable: false + hidden: true windowType: 'onboarding' windowProps: page: "welcome" diff --git a/src/flux/actions.coffee b/src/flux/actions.coffee index e5716d349..96282044d 100644 --- a/src/flux/actions.coffee +++ b/src/flux/actions.coffee @@ -74,14 +74,6 @@ class Actions ### @didPassivelyReceiveNewModels: ActionScopeGlobal - ### - Public: Log out the current user. Closes the main application window and takes - the user back to the sign-in window. - - *Scope: Global* - ### - @logout: ActionScopeGlobal - @uploadStateChanged: ActionScopeGlobal @fileAborted: ActionScopeGlobal @downloadStateChanged: ActionScopeGlobal @@ -371,7 +363,7 @@ class Actions Public: Fire to display an in-window notification to the user in the app's standard notification interface. - *Scope: Window* + *Scope: Global* ``` # A simple notification @@ -396,13 +388,13 @@ class Actions ``` ### - @postNotification: ActionScopeWindow + @postNotification: ActionScopeGlobal ### Public: Listen to this action to handle user interaction with notifications you published via `postNotification`. - *Scope: Window* + *Scope: Global* ``` @_unlisten = Actions.notificationActionTaken.listen(@_onActionTaken, @) @@ -412,7 +404,7 @@ class Actions # perform action ``` ### - @notificationActionTaken: ActionScopeWindow + @notificationActionTaken: ActionScopeGlobal # FullContact Sidebar @getFullContactDetails: ActionScopeWindow diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee index 43e711509..4cb568806 100644 --- a/src/flux/nylas-api.coffee +++ b/src/flux/nylas-api.coffee @@ -134,8 +134,13 @@ class NylasAPI @APITokens = tokens.map (t) -> t.access_token env = atom.config.get('env') + if not env + env = 'production' + console.error("NylasAPI: config.cson does not contain an environment \ + value. Defaulting to `production`.") + if env in ['production'] - @AppID = 'c96gge1jo29pl2rebcb7utsbp' + @AppID = 'eco3rpsghu81xdc48t5qugwq7' @APIRoot = 'https://api.nylas.com' else if env in ['staging', 'development'] @AppID = '54miogmnotxuo5st254trcmb9' @@ -229,17 +234,18 @@ class NylasAPI type: 'error' tag: '401' sticky: true - message: "Nylas can no longer authenticate with your mail provider. You will not be able to send or receive mail. Please log out and sign in again.", + message: "Nylas can no longer authenticate with your mail provider. You will not be able to send or receive mail. Please unlink your account and sign in again.", icon: 'fa-sign-out' actions: [{ - label: 'Log Out' - id: '401:logout' + label: 'Unlink' + id: '401:unlink' }] unless @_notificationUnlisten handler = ({notification, action}) -> - if action.id is '401:logout' - atom.logout() + if action.id is '401:unlink' + ipc = require 'ipc' + ipc.send('command', 'application:reset-config-and-relaunch') @_notificationUnlisten = Actions.notificationActionTaken.listen(handler, @) return Promise.resolve()