feat(onboarding): refactor onboarding flow

Summary:
Add spinner and refactor container view to be router

add `NylasStore` as a global importable. specs for APIEnv

login page fixes

add old fixes to container view

finish extracting pages

fix onboarding flow

Test Plan: edgehill --test

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://phab.nylas.com/D1652
This commit is contained in:
Evan Morikawa 2015-06-17 15:58:58 -07:00
parent 4638c5bc5b
commit fe1e18740c
21 changed files with 530 additions and 277 deletions

View file

@ -0,0 +1,11 @@
{Listener, Publisher} = require '../src/flux/modules/reflux-coffee'
CoffeeHelpers = require '../src/flux/coffee-helpers'
# A simple Flux implementation
class NylasStore
@include: CoffeeHelpers.includeModule
@include Publisher
@include Listener
module.exports = NylasStore

View file

@ -0,0 +1,31 @@
React = require 'react'
Page = require './page'
{RetinaImg} = require 'nylas-component-kit'
{EdgehillAPI} = require 'nylas-exports'
OnboardingActions = require './onboarding-actions'
class ConnectAccountPage extends Page
@displayName: "ConnectAccountPage"
render: =>
<div className="page">
{@_renderClose("close")}
<RetinaImg name="onboarding-logo.png" mode={RetinaImg.Mode.ContentPreserve} className="logo"/>
<h2>Connect an Account</h2>
<RetinaImg name="onboarding-divider.png" mode={RetinaImg.Mode.ContentPreserve} />
<div className="thin-container">
<div className="prompt">Link accounts from other services to supercharge your email.</div>
<button className="btn btn-larger btn-gradient" onClick={=> @_fireAuthAccount('salesforce')}>Salesforce</button>
</div>
</div>
_fireAuthAccount: (service) =>
url = EdgehillAPI.urlForConnecting(service)
OnboardingActions.moveToPage "add-account-auth", {url}
module.exports = ConnectAccountPage

View file

@ -1,181 +0,0 @@
React = require 'react/addons'
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
OnboardingActions = require './onboarding-actions'
OnboardingStore = require './onboarding-store'
querystring = require 'querystring'
{EdgehillAPI} = require 'nylas-exports'
{RetinaImg} = require 'nylas-component-kit'
class ContainerView extends React.Component
@displayName: 'ContainerView'
@containerRequired: false
constructor: (@props) ->
@state = @getStateFromStore()
getStateFromStore: =>
page: OnboardingStore.page()
error: OnboardingStore.error()
environment: OnboardingStore.environment()
connectType: OnboardingStore.connectType()
componentDidMount: =>
@unsubscribe = OnboardingStore.listen(@_onStateChanged, @)
# It's important that every React class explicitly stops listening to
# atom events before it unmounts. Thank you event-kit
# This can be fixed via a Reflux mixin
componentWillUnmount: =>
@unsubscribe() if @unsubscribe
componentDidUpdate: =>
webview = @refs['connect-iframe']
if webview
node = React.findDOMNode(webview)
if node.hasListeners is undefined
# Remove as soon as possible. Initial src is not correctly loaded
# on webview, and this fixes it. Electron 0.26.0. (Still in 0.28.1)
setTimeout ->
node.src = node.src
,10
node.addEventListener 'new-window', (e) ->
require('shell').openExternal(e.url)
node.addEventListener 'did-start-loading', (e) ->
if node.hasMobileUserAgent is undefined
node.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 7_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D167 Safari/9537.53")
node.hasMobileUserAgent = true
node.reload()
node.addEventListener 'did-finish-load', (e) ->
if node.getUrl().indexOf('/connect/complete') != -1
query = node.getUrl().split('?')[1]
query = query[0..-2] if query[query.length - 1] is '#'
token = querystring.decode(query)
OnboardingActions.finishedConnect(token)
if node.getUrl().indexOf('cancelled') != -1
OnboardingActions.moveToPreviousPage()
render: =>
<div className="onboarding-container">
<ReactCSSTransitionGroup transitionName="page">
{@_pageComponent()}
<div className="dragRegion" style={"WebkitAppRegion": "drag", position: 'absolute', top:0, left:40, right:0, height: 20, zIndex:100}></div>
</ReactCSSTransitionGroup>
</div>
_pageComponent: =>
if @state.error
alert = <div className="alert alert-danger" role="alert">{@state.error}</div>
else
alert = <div></div>
if @state.page is 'welcome'
<div className="page" key={@state.page}>
<div className="quit" onClick={@_fireQuit}>
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve} />
</div>
<RetinaImg name="onboarding-logo.png" className="logo"/>
<h2>Welcome to Nylas</h2>
<RetinaImg name="onboarding-divider.png" mode={RetinaImg.Mode.ContentPreserve} />
<form role="form" className="thin-container">
<div className="prompt">Enter your email address:</div>
<input className="input-bordered"
type="email"
placeholder="you@gmail.com"
tabIndex="1"
value={@state.email}
onChange={@_onValueChange}
id="email"
spellCheck="false"/>
<button className="btn btn-larger btn-gradient" style={width:215} onClick={@_fireStart}>Start using Nylas</button>
{@_environmentComponent()}
</form>
</div>
else if @state.page == 'add-account'
<div className="page" key={@state.page}>
<div className="quit" onClick={@_fireDismiss}>
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve} />
</div>
<RetinaImg name="onboarding-logo.png" className="logo" mode={RetinaImg.Mode.ContentPreserve} />
<h2>Connect an Account</h2>
<RetinaImg name="onboarding-divider.png" mode={RetinaImg.Mode.ContentPreserve} />
<form role="form" className="thin-container">
<div className="prompt">Link accounts from other services to supercharge your email.</div>
<button className="btn btn-larger btn-gradient" onClick={=> @_fireAuthAccount('salesforce')}>Salesforce</button>
<button className="btn btn-larger btn-gradient" onClick={=> @_fireAuthAccount('linkedin')}>LinkedIn</button>
</form>
</div>
else if @state.page == 'add-account-auth'
<div>
{
React.createElement('webview',{
"ref": "connect-iframe",
"key": @state.page,
"src": @_connectWebViewURL()
})
}
<div className="back" onClick={@_fireMoveToPrevPage}>
<RetinaImg name="onboarding-back.png" mode={RetinaImg.Mode.ContentPreserve} />
</div>
</div>
else if @state.page == 'add-account-success'
# http://codepen.io/stevenfabre/pen/NPWeVb
<div className="page" key={@state.page}>
<div className="check">
<svg preserveAspectRatio="xMidYMid" width="61" height="52" viewBox="0 0 61 52" className="check-icon">
<path d="M56.560,-0.010 C37.498,10.892 26.831,26.198 20.617,33.101 C20.617,33.101 5.398,23.373 5.398,23.373 C5.398,23.373 0.010,29.051 0.010,29.051 C0.010,29.051 24.973,51.981 24.973,51.981 C29.501,41.166 42.502,21.583 60.003,6.565 C60.003,6.565 56.560,-0.010 56.560,-0.010 Z" id="path-1" className="cls-2" fill-rule="evenodd"/>
</svg>
</div>
</div>
_environmentComponent: =>
return [] unless atom.inDevMode()
<div className="environment-selector">
<select value={@state.environment} onChange={@_fireSetEnvironment}>
<option value="development">Development (edgehill-dev, api-staging)</option>
<option value="staging">Staging (edgehill-staging, api-staging)</option>
<option value="production">Production (edgehill, api)</option>
</select>
</div>
_connectWebViewURL: =>
EdgehillAPI.urlForConnecting(@state.connectType, @state.email)
_onStateChanged: =>
@setState(@getStateFromStore())
_onValueChange: (event) =>
changes = {}
changes[event.target.id] = event.target.value
@setState(changes)
_fireDismiss: =>
atom.close()
_fireQuit: =>
require('ipc').send('command', 'application:quit')
_fireSetEnvironment: (event) =>
OnboardingActions.setEnvironment(event.target.value)
_fireStart: (e) =>
OnboardingActions.startConnect('inbox')
_fireAuthAccount: (service) =>
OnboardingActions.startConnect(service)
_fireMoveToPage: (page) =>
OnboardingActions.moveToPage(page)
_fireMoveToPrevPage: =>
OnboardingActions.moveToPreviousPage()
module.exports = ContainerView

View file

@ -0,0 +1,95 @@
React = require 'react'
Page = require './page'
querystring = require 'querystring'
{RetinaImg} = require 'nylas-component-kit'
{EdgehillAPI} = require 'nylas-exports'
OnboardingActions = require './onboarding-actions'
class ExternalAuthWebviewPage extends Page
@displayName: "ExternalAuthWebviewPage"
render: =>
<div className="page no-top">
{
React.createElement('webview',{
"ref": "connect-iframe",
"src": @props.pageData.url
"style": {position: "relative", zIndex: 1}
})
}
{@_renderSpinner()}
{@_renderAction()}
</div>
componentDidMount: =>
@_listeners = {}
webview = @refs['connect-iframe']
return unless webview
webview = React.findDOMNode(webview)
@_setupWebviewListeners(webview)
componentWillUnmount: ->
webview = @refs['connect-iframe']
webview = React.findDOMNode(webview)
@_teardownWebviewListeners(webview)
_fireMoveToPrevPage: =>
OnboardingActions.moveToPreviousPage()
_teardownWebviewListeners: (webview) ->
for event, listener of @_listeners
webview.removeEventListener event, listener
_renderAction: ->
if @props.pageData.noPreviousPage
@_renderClose()
else
<div className="back" onClick={@_fireMoveToPrevPage}>
<RetinaImg name="onboarding-back.png"
mode={RetinaImg.Mode.ContentPreserve}/>
</div>
_setupWebviewListeners: (webview) ->
# Remove as soon as possible. Initial src is not correctly loaded
# on webview, and this fixes it. Electron 0.26.0. (Still in 0.28.1)
setTimeout ->
webview.src = webview.src
, 20
@_listeners =
"new-window": (e) ->
require('shell').openExternal(e.url)
"did-start-loading": (e) =>
@_setUserAgent(e, webview)
"did-finish-load": (e) =>
@_onDidFinishLoad(e, webview)
for event, listener of @_listeners
webview.addEventListener event, listener
_setUserAgent: (e, webview) ->
if webview.hasMobileUserAgent is undefined
webview.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 7_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D167 Safari/9537.53")
webview.hasMobileUserAgent = true
webview.reload()
_onDidFinishLoad: (e, webview) =>
return unless webview
# We can't use `setState` because that'll blow away the webview :(
React.findDOMNode(@refs.spinner).style.visibility = "hidden"
url = webview.getUrl()
if url.indexOf('/connect/complete') != -1
query = url.split('?')[1]
query = query[0..-2] if query[query.length - 1] is '#'
token = querystring.decode(query)
EdgehillAPI.addTokens([token])
OnboardingActions.moveToPage('add-account-success')
else if url.indexOf('cancelled') != -1
OnboardingActions.moveToPreviousPage()
module.exports = ExternalAuthWebviewPage

View file

@ -0,0 +1,87 @@
React = require 'react'
{RetinaImg} = require 'nylas-component-kit'
{EdgehillAPI} = require 'nylas-exports'
Page = require './page'
OnboardingActions = require './onboarding-actions'
NylasApiEnvironmentStore = require './nylas-api-environment-store'
class LoginPage extends Page
@displayName: "LoginPage"
constructor: (@props) ->
@state =
email: ""
environment: NylasApiEnvironmentStore.getEnvironment()
componentDidMount: ->
@_usub = NylasApiEnvironmentStore.listen =>
@setState environment: NylasApiEnvironmentStore.getEnvironment()
componentWillUnmount: ->
@_usub?()
render: =>
<div className="page">
{@_renderClose("quit")}
<RetinaImg name="onboarding-logo.png" mode={RetinaImg.Mode.ContentPreserve} className="logo"/>
<h2>Welcome to Nylas</h2>
<RetinaImg name="onboarding-divider.png" mode={RetinaImg.Mode.ContentPreserve} />
<form role="form" ref="form" onSubmit={@_onSubmit} className="email-form thin-container">
<div className="prompt">Enter your email address:</div>
<input type="email"
required={true}
ref="email"
className="input-email input-bordered"
placeholder="you@gmail.com"
tabIndex="1"
value={@state.email}
onChange={@_onEmailChange}
id="email"
spellCheck="false"/>
<button className="btn btn-larger btn-gradient"
style={width:215}>Start using Nylas</button>
{@_environmentComponent()}
</form>
</div>
_renderError: ->
if @state.error
<div className="alert alert-danger" role="alert">
{@state.error}
</div>
else <div></div>
_onEmailChange: (event) =>
@setState email: event.target.value
_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 <div></div> unless atom.inDevMode()
<div className="environment-selector">
<select value={@state.environment} onChange={@_onEnvChange}>
<option value="development">Development (edgehill-dev, api-staging)</option>
<option value="staging">Staging (edgehill-staging, api-staging)</option>
<option value="production">Production (edgehill, api)</option>
</select>
</div>
_onEnvChange: (event) =>
OnboardingActions.changeAPIEnvironment event.target.value
module.exports = LoginPage

View file

@ -1,11 +1,14 @@
ContainerView = require './container-view'
PageRouter = require "./page-router"
{WorkspaceStore, ComponentRegistry} = require 'nylas-exports'
module.exports =
item: null
activate: (@state) ->
# This package does nothing in other windows
return unless atom.getWindowType() is 'onboarding'
WorkspaceStore.defineSheet 'Main', {root: true},
list: ['Center']
ComponentRegistry.register ContainerView,
ComponentRegistry.register PageRouter,
location: WorkspaceStore.Location.Center

View file

@ -0,0 +1,18 @@
Actions = require './onboarding-actions'
NylasStore = require 'nylas-store'
class NylasApiEnvironmentStore extends NylasStore
constructor: ->
@listenTo Actions.changeAPIEnvironment, @_setEnvironment
defaultEnv = if atom.inDevMode() then 'staging' else 'staging'
@_setEnvironment(defaultEnv) unless atom.config.get('env')
getEnvironment: -> atom.config.get('env')
_setEnvironment: (env) ->
throw new Error("Environment #{env} is not allowed") unless env in ['development', 'staging', 'production']
atom.config.set('env', env)
@trigger()
module.exports = new NylasApiEnvironmentStore()

View file

@ -1,13 +1,14 @@
Reflux = require 'reflux'
actions = [
"setEnvironment",
"authErrorOccurred",
"startConnect",
"finishedConnect",
"moveToPreviousPage",
OnboardingActions = Reflux.createActions [
"changeAPIEnvironment"
"loadExternalAuthPage"
"moveToPreviousPage"
"moveToPage"
]
module.exports =
Actions = Reflux.createActions(actions)
for key, action of OnboardingActions
action.sync = true
module.exports = OnboardingActions

View file

@ -1,64 +0,0 @@
Reflux = require 'reflux'
Actions = require './onboarding-actions'
{EdgehillAPI} = require 'nylas-exports'
ipc = require 'ipc'
return unless atom.getWindowType() is "onboarding"
module.exports =
OnboardingStore = Reflux.createStore
init: ->
@_error = ''
@_page = atom.getLoadSettings().page || 'welcome'
@_pageStack = [@_page]
# For the time being, always use staging
defaultEnv = if atom.inDevMode() then 'staging' else 'staging'
atom.config.set('env', defaultEnv) unless atom.config.get('env')
@listenTo Actions.setEnvironment, @_onSetEnvironment
@listenTo Actions.moveToPreviousPage, @_onMoveToPreviousPage
@listenTo Actions.moveToPage, @_onMoveToPage
@listenTo Actions.startConnect, @_onStartConnect
@listenTo Actions.finishedConnect, @_onFinishedConnect
page: ->
@_page
error: ->
@_error
environment: ->
atom.config.get('env')
connectType: ->
@_connectType
_onMoveToPreviousPage: ->
current = @_pageStack.pop()
prev = @_pageStack.pop()
@_onMoveToPage(prev)
_onMoveToPage: (page) ->
@_error = null
@_pageStack.push(page)
@_page = page
@trigger()
_onStartConnect: (service) ->
@_connectType = service
@_onMoveToPage('add-account-auth')
_onFinishedConnect: (token) ->
EdgehillAPI.addTokens([token])
@_onMoveToPage('add-account-success')
setTimeout ->
atom.close()
, 2500
_onSetEnvironment: (env) ->
throw new Error("Environment #{env} is not allowed") unless env in ['development', 'staging', 'production']
atom.config.set('env', env)
@trigger()

View file

@ -0,0 +1,41 @@
Reflux = require 'reflux'
OnboardingActions = require './onboarding-actions'
NylasStore = require 'nylas-store'
ipc = require 'ipc'
return unless atom.getWindowType() is "onboarding"
class PageRouterStore extends NylasStore
constructor: ->
atom.onWindowPropsReceived @_onWindowPropsChagned
@_page = atom.getWindowProps().page ? ''
@_pageData = atom.getWindowProps().pageData ? {}
@_pageStack = [{page: @_page, pageData: @_pageData}]
@listenTo OnboardingActions.moveToPreviousPage, @_onMoveToPreviousPage
@listenTo OnboardingActions.moveToPage, @_onMoveToPage
_onWindowPropsChagned: ({page, pageData}={}) =>
@_onMoveToPage(page, pageData)
page: -> @_page
pageData: -> @_pageData
connectType: ->
@_connectType
_onMoveToPreviousPage: ->
current = @_pageStack.pop()
prev = @_pageStack.pop()
@_onMoveToPage(prev.page, prev.pageData)
_onMoveToPage: (page, pageData={}) ->
@_pageStack.push({page, pageData})
@_page = page
@_pageData = pageData
@trigger()
module.exports = new PageRouterStore()

View file

@ -0,0 +1,62 @@
React = require 'react/addons'
OnboardingActions = require './onboarding-actions'
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
PageRouterStore = require './page-router-store'
LoginPage = require './login-page'
ConnectAccountPage = require './connect-account-page'
ExternalAuthWebviewPage = require './external-auth-webview-page'
SuccessPage = require './success-page'
class PageRouter extends React.Component
@displayName: 'PageRouter'
@containerRequired: false
constructor: (@props) ->
@state = @_getStateFromStore()
window.OnboardingActions = OnboardingActions
_getStateFromStore: =>
page: PageRouterStore.page()
pageData: PageRouterStore.pageData()
componentDidMount: =>
@unsubscribe = PageRouterStore.listen(@_onStateChanged, @)
_onStateChanged: => @setState(@_getStateFromStore())
componentWillUnmount: => @unsubscribe?()
render: =>
<div className="page-frame">
<ReactCSSTransitionGroup transitionName="page">
{@_renderCurrentPage()}
{@_renderDragRegion()}
</ReactCSSTransitionGroup>
</div>
_renderCurrentPage: =>
switch @state.page
when "welcome"
<LoginPage pageData={@state.pageData} />
when "add-account"
<ConnectAccountPage pageData={@state.pageData} />
when "add-account-auth"
<ExternalAuthWebviewPage pageData={@state.pageData} />
when "add-account-success"
<SuccessPage pageData={@state.pageData} />
else
<div></div>
_renderDragRegion: ->
styles =
top:0
left:40
right:0
height: 20
zIndex:100
position: 'absolute'
"WebkitAppRegion": "drag"
<div className="dragRegion" style={styles}></div>
module.exports = PageRouter

View file

@ -0,0 +1,36 @@
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 = ->
<div className="quit" onClick={onClick}>
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve}/>
</div>
_renderSpinner: ->
styles =
position: "absolute"
zIndex: 10
top: "50%"
left: "50%"
width: "256px"
marginLeft: "-128px"
marginTop: "-128px"
<RetinaImg ref="spinner"
style={styles}
name="setup-spinner.gif"
mode={RetinaImg.Mode.ContentPreserve}/>
module.exports = Page

View file

@ -0,0 +1,23 @@
React = require 'react'
Page = require './page'
{RetinaImg} = require 'nylas-component-kit'
class SuccessPage extends Page
@displayName: "SuccessPage"
componentDidMount: ->
setTimeout ->
atom.close()
, 2500
render: =>
# http://codepen.io/stevenfabre/pen/NPWeVb
<div className="page">
<div className="check">
<svg preserveAspectRatio="xMidYMid" width="61" height="52" viewBox="0 0 61 52" className="check-icon">
<path d="M56.560,-0.010 C37.498,10.892 26.831,26.198 20.617,33.101 C20.617,33.101 5.398,23.373 5.398,23.373 C5.398,23.373 0.010,29.051 0.010,29.051 C0.010,29.051 24.973,51.981 24.973,51.981 C29.501,41.166 42.502,21.583 60.003,6.565 C60.003,6.565 56.560,-0.010 56.560,-0.010 Z" id="path-1" className="cls-2" fill-rule="evenodd"/>
</svg>
</div>
</div>
module.exports = SuccessPage

View file

@ -9,5 +9,8 @@
"atom": "*"
},
"dependencies": {
},
"windowTypes": {
"onboarding": true
}
}

View file

@ -0,0 +1,45 @@
_ = require "underscore"
React = require "react/addons"
ReactTestUtils = React.addons.TestUtils
LoginPage = require '../lib/login-page'
OnboardingActions = require '../lib/onboarding-actions'
describe "LoginPage", ->
it "shows env picker in Dev Mode", ->
spyOn(atom, "inDevMode").andReturn true
@loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
picker = ReactTestUtils.findRenderedDOMComponentWithClass(@loginPage, "environment-selector")
expect(picker).toBeDefined()
it "hides env picker in other modes", ->
spyOn(atom, "inDevMode").andReturn false
@loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
expect(-> ReactTestUtils.findRenderedDOMComponentWithClass(@loginPage, "environment-selector")).toThrow()
it 'can change the environment', ->
spyOn(atom, "inDevMode").andReturn true
spyOn(OnboardingActions, "changeAPIEnvironment")
@loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
sel = ReactTestUtils.findRenderedDOMComponentWithTag(@loginPage, "select")
ReactTestUtils.Simulate.change(sel, {target: {value: 'staging'}})
expect(OnboardingActions.changeAPIEnvironment).toHaveBeenCalledWith("staging")
describe "logging in", ->
beforeEach ->
@connectURL = "foo"
spyOn(OnboardingActions, "moveToPage")
@loginPage = ReactTestUtils.renderIntoDocument(<LoginPage />)
hasEmail = (email) ->
page = OnboardingActions.moveToPage.calls[0].args[0]
data = OnboardingActions.moveToPage.calls[0].args[1]
expect(page).toBe "add-account-auth"
expect(data.url.length).toBeGreaterThan 0
it "submits information when the form submits", ->
@loginPage.setState email: "test@nylas.com"
form = ReactTestUtils.findRenderedDOMComponentWithClass(@loginPage, 'email-form')
ReactTestUtils.Simulate.submit(form)
hasEmail("test@nylas.com")

View file

@ -0,0 +1,35 @@
Actions = require '../lib/onboarding-actions'
NylasApiEnvironmentStore = require '../lib/nylas-api-environment-store'
storeConstructor = NylasApiEnvironmentStore.constructor
describe "NylasApiEnvironmentStore", ->
beforeEach ->
spyOn(atom.config, "set")
it "doesn't set if it alreayd exists", ->
spyOn(atom.config, "get").andReturn "staging"
store = new storeConstructor()
expect(atom.config.set).not.toHaveBeenCalled()
it "initializes with the correct default in dev mode", ->
spyOn(atom, "inDevMode").andReturn true
spyOn(atom.config, "get").andReturn undefined
store = new storeConstructor()
expect(atom.config.set).toHaveBeenCalledWith("env", "staging")
it "initializes with the correct default in production", ->
spyOn(atom, "inDevMode").andReturn false
spyOn(atom.config, "get").andReturn undefined
store = new storeConstructor()
expect(atom.config.set).toHaveBeenCalledWith("env", "staging")
describe "when setting the environment", ->
it "sets from the desired action", ->
Actions.changeAPIEnvironment("production")
expect(atom.config.set).toHaveBeenCalledWith("env", "production")
it "throws if the env is invalid", ->
expect( -> Actions.changeAPIEnvironment("bad")).toThrow()
it "throws if the env is blank", ->
expect( -> Actions.changeAPIEnvironment()).toThrow()

View file

@ -19,7 +19,7 @@
100% { opacity: 1; border-width: 0; }
}
.onboarding-container {
.page-frame {
width:100%;
height:100%;
background-color: @gray-lighter;
@ -82,6 +82,9 @@
width:100%;
height:100%;
padding-top:15%;
&.no-top {
padding-top: 0;
}
}
.quit {

View file

@ -607,16 +607,15 @@ class Atom extends Model
@commands.add 'atom-workspace',
'atom-workspace:add-account': =>
options =
@newWindow
title: 'Add an Account'
page: 'add-account'
width: 340
height: 550
toolbar: false
resizable: false
windowType: 'onboarding'
windowPackages: ['onboarding']
ipc.send('new-window', options)
windowProps:
page: 'add-account'
# Make sure we can't be made so small that the interface looks like crap
@getCurrentWindow().setMinimumSize(875, 500)

View file

@ -108,9 +108,9 @@ class WindowManager
height: 550
resizable: false
windowType: 'onboarding'
windowPackages: ['onboarding']
windowProps:
'uniqueId': 'onboarding'
page: "welcome"
uniqueId: 'onboarding'
# Makes a new window appear of a certain `windowType`.
#
@ -188,7 +188,7 @@ class WindowManager
# - 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}={}) ->
registerHotWindow: ({windowType, replenishNum, windowPackages, windowOptions}={}) ->
if not windowType
throw new Error("registerHotWindow: please provide a windowType")
@ -197,6 +197,7 @@ class WindowManager
@_hotWindows[windowType].replenishNum ?= (replenishNum ? 1)
@_hotWindows[windowType].loadedWindows ?= []
@_hotWindows[windowType].windowPackages ?= (windowPackages ? [])
@_hotWindows[windowType].windowOptions ?= (windowOptions ? {})
@_replenishHotWindows()
@ -248,6 +249,8 @@ class WindowManager
newColdWindow: (options={}) ->
options = _.extend(@defaultWindowOptions(), options)
win = new AtomWindow(options)
newLoadSettings = _.extend(win.loadSettings(), options)
win.setLoadSettings(newLoadSettings)
win.showWhenLoaded()
return win
@ -262,9 +265,9 @@ class WindowManager
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."
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 = [
@ -279,22 +282,25 @@ class WindowManager
]
unsupported = _.difference(Object.keys(options), supportedHotWindowKeys)
if unsupported.length > 0
console.log "WindowManager: Nylas will open a new hot window of type #{options.windowType},
but 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."
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.showWhenLoaded()
win.browserWindow.setTitle options.title ? ""
@ -307,12 +313,11 @@ class WindowManager
h = options.height ? h
win.browserWindow.setSize(w,h)
console.log JSON.stringify(options)
if options.bounds
console.log "------------- SETTING BOUNDS"
console.log JSON.stringify(options.bounds)
win.browserWindow.setBounds options.bounds
win.showWhenLoaded()
@_replenishHotWindows()
return win
@ -340,7 +345,7 @@ class WindowManager
numOfType = data.replenishNum - data.loadedWindows.length
maxWin = Math.max(numOfType, maxWin)
if numOfType > 0
options = @defaultWindowOptions()
options = _.extend {}, @defaultWindowOptions(), data.windowOptions
options.windowType = windowType
options.windowPackages = data.windowPackages
queues[windowType] ?= []

View file

@ -1,6 +1,6 @@
_ = require 'underscore'
module.exports =
module.exports = CoffeeHelpers =
# This copied out CoffeeScript
includeModule: (mixin) ->
if not mixin

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB