feat(post-auth): Initial prefs + packages screens, welcome copy changes

Summary:
Package names must match directory names

Not going to use new Swithc component, but might as well be part of component kit

Move APMWrapper into core so it can be used from anywhere

Move manual package install coe to package-manager

Gray out window titles when in the background

Do not allow multiple onboarding windows at the same time

Finalize styling f initial-prefs and initial-packages, make it work (only github package atm)

Other nits

Change the welcome copy:

- Call it easy to extend vs easy to use
- Remove the subtitle from the first screen which doesn't really fit
- Make the second page emphasize that its created /for/ developers and easy to extend with Javascript.
- Explain what the sync engine is rather than saying it's "faster and more extensible" (??)

Test Plan: Run tests

Reviewers: evan, dillon

Reviewed By: evan

Maniphest Tasks: T3346

Differential Revision: https://phab.nylas.com/D2079
This commit is contained in:
Ben Gotow 2015-09-29 23:58:30 -07:00
parent e9879a5495
commit 76cb33b3f6
25 changed files with 300 additions and 152 deletions

View file

@ -1,5 +1,5 @@
{
"name": "translate",
"name": "N1-Composer-Translate",
"version": "0.2.0",
"main": "./lib/main",
"description": "An example package for N1 that translates drafts into other languages using the Yandex API.",

View file

@ -33,7 +33,7 @@ class GithubProfile extends React.Component
# Coffeescript at transpile-time. We're actually creating a nested tree of Javascript
# objects here that *represent* the DOM we want.
<div className="profile">
<img className="logo" src="nylas://sidebar-github-profile/assets/github.png"/>
<img className="logo" src="nylas://N1-Github-Contact-Card-Section/assets/github.png"/>
<a href={@props.profile.html_url}>{@props.profile.login}</a>
<div>{repoElements}</div>
</div>

View file

@ -1,5 +1,5 @@
{
"name": "sidebar-github-profile",
"name": "N1-Github-Contact-Card-Section",
"version": "0.1.0",
"main": "./lib/main",
"description": "View github user data in the sidebar!",

View file

@ -80,7 +80,11 @@ class ViewOnGithubButton extends React.Component
return null unless @state.link
<button className="btn btn-toolbar"
onClick={@_openLink}
data-tooltip={"Visit Thread on GitHub"}><RetinaImg mode={RetinaImg.Mode.ContentIsMask} url="nylas://github/assets/github@2x.png" /></button>
data-tooltip={"Visit Thread on GitHub"}>
<RetinaImg
mode={RetinaImg.Mode.ContentIsMask}
url="nylas://N1-Message-View-on-Github/assets/github@2x.png" />
</button>
#### Super common N1 Component private methods ####

View file

@ -1,5 +1,5 @@
{
"name": "github",
"name": "N1-Message-View-on-Github",
"version": "0.1.0",
"main": "./lib/main",
"description": "Github integration in Nylas",

View file

@ -13,6 +13,7 @@ class NylasComponentKit
@load "Menu", 'menu'
@load "DropZone", 'drop-zone'
@load "Spinner", 'spinner'
@load "Switch", 'switch'
@load "Popover", 'popover'
@load "Flexbox", 'flexbox'
@load "RetinaImg", 'retina-img'

View file

@ -144,6 +144,7 @@ class NylasExports
@load "LaunchServices", 'launch-services'
@load "BufferedProcess", 'buffered-process'
@load "BufferedNodeProcess", 'buffered-node-process'
@get "APMWrapper", -> require('../src/apm-wrapper')
# Testing
@get "NylasTestUtils", -> require '../spec-nylas/test_utils'

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -10,7 +10,7 @@ Providers = [
settings: []
}, {
name: 'exchange'
displayName: 'Microsoft Exchange / Live'
displayName: 'Microsoft Exchange'
icon: 'ic-settings-account-eas.png'
header_icon: 'setup-icon-provider-exchange.png'
color: '#1ea2a3'
@ -92,7 +92,7 @@ Providers = [
}]
}, {
name: 'imap'
displayName: 'Other / Manual setup'
displayName: 'IMAP / SMTP Setup'
icon: 'ic-settings-account-imap.png'
header_icon: 'setup-icon-provider-imap.png'
pages: ['Set up your email account','Configure incoming mail','Configure outgoing mail']

View file

@ -1,85 +1,81 @@
React = require 'react'
path = require 'path'
{RetinaImg, ConfigPropContainer} = require 'nylas-component-kit'
{EdgehillAPI} = require 'nylas-exports'
OnboardingActions = require './onboarding-actions'
InitialPackages = [{
'name': 'Templates',
'label': 'Templates',
'packageName': 'templates',
'description': 'Templates let you fill an email with a pre-set body of text and a snumber of fields you can fill quickly to save time.'
'icon': 'setup-icon-templates.png'
}, {
'name': 'Signatures',
'label': 'Signatures',
'packageName': 'signatures',
'description': 'Select from and edit mutiple signatures that N1 will automatically append to your sent messages.'
'icon': 'setup-icon-signatures.png'
},{
'name': 'Github',
'label': 'Github',
'packageName': 'N1-Github-Contact-Card-Section'
'description': 'Adds Github quick actions to many emails, and allows you to see the Github profiles of the people you email.'
'icon': 'setup-icon-github.png'
}]
class SlideSwitch extends React.Component
@propTypes:
active: React.PropTypes.bool.isRequired
class InstallButton extends React.Component
constructor: (@props) ->
@state =
installed: atom.packages.resolvePackagePath(@props.packageName)?
installing: false
render: =>
classnames = "slide-switch"
if @props.active
classnames += " active"
classname = "btn btn-install"
classname += " installing" if @state.installing
classname += " installed" if @state.installed
<div className={classnames} onClick={@props.onChange}>
<div className="handle"></div>
</div>
<div className={classname} onClick={@_onInstall}></div>
_onInstall: =>
return false unless @props.packageName
{resourcePath} = atom.getLoadSettings()
packagePath = path.join(resourcePath, "examples", @props.packageName)
@setState(installing: true)
atom.packages.installPackageFromPath packagePath, (err) =>
@setState({
installing: false
installed: atom.packages.resolvePackagePath(@props.packageName)?
})
class InitialPackagesList extends React.Component
@displayName: "InitialPackagesList"
render: =>
<div>
{InitialPackages.map (item) =>
<div className="initial-package" key={item.name}>
<RetinaImg name={item.icon} mode={RetinaImg.Mode.ContentPreserve} />
<div className="name">{item.name}</div>
<div className="description">{item.description}</div>
<SlideSwitch active={@_isPackageEnabled(item.packageName)} onChange={ => @_togglePackageEnabled(item.packageName)}/>
</div>
}
</div>
_isPackageEnabled: (packageName) =>
!atom.packages.isPackageDisabled(packageName)
_togglePackageEnabled: (packageName) =>
if atom.packages.isPackageDisabled(packageName)
atom.packages.enablePackage(packageName)
else
atom.packages.disablePackage(packageName)
componentWillUnmount: =>
@listener?.dispose()
class InitialPackagesPage extends React.Component
@displayName: "InitialPackagesPage"
render: =>
<div className="page opaque" style={width:900, height:650}>
<div className="quit" onClick={ -> OnboardingActions.closeWindow() }>
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve}/>
</div>
<div className="back" onClick={@_onPrevPage}>
<RetinaImg name="onboarding-back.png" mode={RetinaImg.Mode.ContentPreserve}/>
</div>
<h1 style={paddingTop: 20}>Welcome to N1</h1>
<h4 style={marginBottom: 50}>Explore packages</h4>
<p>
Packages lie at the heart of N1—you can enable community packages or build<br/>
your own to create the perfect workflow. Want to enable a few packages now?
<h1 style={paddingTop: 60, marginBottom: 20}>Explore packages</h1>
<p style={paddingBottom: 20}>
Packages lie at the heart of N1 and give it it's powerful features.<br/>
Want to enable a few example packages now? They'll be installed to <code>~/.nylas</code>
</p>
<ConfigPropContainer>
<InitialPackagesList />
</ConfigPropContainer>
<button className="btn btn-large" onClick={@_onGetStarted}>Start Using N1</button>
<div>
{InitialPackages.map (item) =>
<div className="initial-package" key={item.label}>
<RetinaImg name={item.icon} mode={RetinaImg.Mode.ContentPreserve} />
<div className="install-container">
<InstallButton packageName={item.packageName} />
</div>
<div className="name">{item.label}</div>
<div className="description">{item.description}</div>
</div>
}
</div>
<button className="btn btn-large btn-emphasis" style={marginTop: 15} onClick={@_onGetStarted}>Start Using N1</button>
</div>
_onPrevPage: =>

View file

@ -1,6 +1,7 @@
React = require 'react'
path = require 'path'
fs = require 'fs'
_ = require 'underscore'
{RetinaImg, Flexbox, ConfigPropContainer} = require 'nylas-component-kit'
{EdgehillAPI} = require 'nylas-exports'
OnboardingActions = require './onboarding-actions'
@ -34,10 +35,30 @@ class InitialPreferencesOptions extends React.Component
fs.readdir templatesDir, (err, files) =>
return unless files and files instanceof Array
templates = files.filter (filename) =>
path.extname(filename) is '.cson' or path.extname(filename) is '.json'
path.extname(filename) is '.cson' or path.extname(filename) is '.json'
templates = templates.map (filename) =>
path.parse(filename).name
@setState(templates: templates)
@_setConfigDefaultsForAccount(templates)
_setConfigDefaultsForAccount: (templates) =>
return unless @props.account
templateWithBasename = (name) =>
_.find templates, (t) -> t.indexOf(name) is 0
if @props.account.provider is 'gmail'
@props.config.set('core.workspace.mode', 'list')
@props.config.set('core.keymapTemplate', templateWithBasename('Gmail'))
else if @props.account.provider is 'eas'
@props.config.set('core.workspace.mode', 'split')
@props.config.set('core.keymapTemplate', templateWithBasename('Outlook'))
else
@props.config.set('core.workspace.mode', 'split')
if process.platform is 'darwin'
@props.config.set('core.keymapTemplate', templateWithBasename('Apple Mail'))
else
@props.config.set('core.keymapTemplate', templateWithBasename('Outlook'))
render: =>
return false unless @props.config
@ -60,8 +81,8 @@ class InitialPreferencesOptions extends React.Component
<div key="divider" style={marginLeft:20, marginRight:20, borderLeft:'1px solid #ccc'}></div>
<div style={flex:1}>
<p>
We see you're a Gmail user, so N1 is set up to use
Gmail keyboard shortcuts. You can also pick another set:
We've picked a set of keyboard shortcuts based on your email
account and platform. You can also pick another set:
</p>
<select
style={margin:0}
@ -81,13 +102,10 @@ class InitialPreferencesPage extends React.Component
render: =>
<div className="page opaque" style={width:900, height:620}>
<div className="quit" onClick={ -> OnboardingActions.closeWindow() }>
<RetinaImg name="onboarding-close.png" mode={RetinaImg.Mode.ContentPreserve}/>
</div>
<h1 style={paddingTop: 100}>Welcome to N1</h1>
<h4 style={marginBottom: 70}>Let's set things up to your liking.</h4>
<ConfigPropContainer>
<InitialPreferencesOptions />
<InitialPreferencesOptions account={@props.pageData.account} />
</ConfigPropContainer>
<button className="btn btn-large" style={marginBottom:60} onClick={@_onNextPage}>Looks Good!</button>
</div>

View file

@ -45,17 +45,18 @@ class WelcomePage extends React.Component
_renderStep0: ->
<div className={@_stepClass(0)} key="step-0">
<p className="hero-text" style={marginTop: 25}>N1 is a new email app that is fast,<br/>friendly, and easy to use.</p>
<RetinaImg className="logo" style={zoom: 0.35, marginTop: 13} url="nylas://onboarding/assets/nylas-pictograph@2x.png" mode={RetinaImg.Mode.ContentIsMask} />
<RetinaImg className="logo" style={zoom: 0.20, marginTop: 60} url="nylas://onboarding/assets/nylas-pictograph@2x.png" mode={RetinaImg.Mode.ContentIsMask} />
<p className="hero-text" style={marginTop: 30, fontSize: 44}>Say hello to N1.</p>
<p className="sub-text" style={marginTop: 0, fontSize: 24}>The next-generation email platform.</p>
<div style={fontSize:17, marginTop: 45}>Built with ❤︎ by Nylas</div>
<RetinaImg className="icons" style={position: "absolute", left: -45, top: 130} url="nylas://onboarding/assets/shapes-left@2x.png" mode={RetinaImg.Mode.ContentIsMask} />
<RetinaImg className="icons" style={position: "absolute", right: -40, top: 130} url="nylas://onboarding/assets/shapes-right@2x.png" mode={RetinaImg.Mode.ContentIsMask} />
<p className="sub-text" style={marginTop: 17}>It is the foundation for a highly extensible email experience</p>
{@_renderNavBubble(0)}
</div>
_renderStep1: ->
<div className={@_stepClass(1)} key="step-1">
<p className="hero-text" style={marginTop: 40}>Under the hood, N1 is built for developers.</p>
<p className="hero-text" style={marginTop: 40}>Developers welcome.</p>
<div className="gear-outer-container"><div className="gear-container">
{@_gears()}
</div></div>
@ -64,7 +65,7 @@ class WelcomePage extends React.Component
<RetinaImg className="wrench" mode={RetinaImg.Mode.ContentPreserve}
url="nylas://onboarding/assets/wrench@2x.png" />
<p className="sub-text">You can extend it using packages and build your own components.</p>
<p className="sub-text">N1 is built with modern web technologies and easy to extend with JavaScript.</p>
{@_renderNavBubble(1)}
</div>
@ -78,21 +79,21 @@ class WelcomePage extends React.Component
_renderStep2: ->
<div className={@_stepClass(2)} key="step-2">
<p className="hero-text" style={marginTop: 40}>N1 is secured and enhanced by the Nylas Sync Engine</p>
<p className="hero-text" style={marginTop: 40}>N1 is made possible by the Nylas Sync Engine</p>
<div className="cell-wrap">
<div className="cell" style={float: "left"}>
<RetinaImg mode={RetinaImg.Mode.ContentPreserve}
style={paddingTop: 4, paddingBottom: 4}
url="nylas://onboarding/assets/cloud@2x.png" />
<p>A modern API layer for<br/>email, contacts &amp; calendar</p>
<a onClick={=> @_open("https://github.com/nylas/sync-engine")}>more info</a>
</div>
<div className="cell" style={float: "right"}>
<RetinaImg mode={RetinaImg.Mode.ContentPreserve}
url="nylas://onboarding/assets/lock@2x.png" />
<p>Secured using<br/>bank-grade encryption</p>
<a onClick={=> @_open("https://nylas.com/security/")}>more info</a>
</div>
<div className="cell" style={float: "right"}>
<RetinaImg mode={RetinaImg.Mode.ContentPreserve}
style={paddingTop: 4, paddingBottom: 4}
url="nylas://onboarding/assets/cloud@2x.png" />
<p>Synced by Nylas to be<br/>faster and more extensible</p>
<a onClick={=> @_open("https://github.com/nylas/sync-engine")}>more info</a>
</div>
</div>
{@_renderNavBubble(2)}
</div>

View file

@ -28,6 +28,7 @@
h1 {
font-weight: 100;
font-size: 40pt;
margin:0;
}
h2 {
@ -325,15 +326,15 @@
text-align: left;
border-top: 1px solid rgba(0,0,0,0.05);
cursor: default;
line-height: 70px;
img.icon {
}
.icon-container {
width: 50px;
height: 50px;
display: inline-block;
box-sizing: content-box;
padding: 10px 25px 12px 25px;
padding: 0 25px 0 25px;
vertical-align: top;
}
}
.provider:hover{
@ -348,13 +349,14 @@
margin-bottom:10px;
width:550px;
padding:15px;
border:1px solid #eee;
border:1px solid #e1e1e1;
text-align:left;
cursor: default;
img {
float:left;
margin-right:20px;
margin-top:30px;
margin-bottom:30px;
margin: 20px;
margin-left: 0;
margin-right: 30px;
}
.name {
font-size:20px;
@ -363,6 +365,45 @@
font-size:14px;
max-width:500px;
}
.install-container {
width: 90px;
float: right;
text-align: right;
}
.btn-install {
display:inline-block;
width: 70px;
margin: 24px;
margin-right: 7px;
margin-left: 14px;
height: 24px;
color: @text-color;
transition: width 150ms ease-in-out, color 150ms ease-in-out;
}
.btn-install:after {
content: "Install";
}
.btn-install.installing {
width: 32px;
color: transparent;
background: url('nylas://onboarding/assets/installing-spinner.gif') center no-repeat;
background-size: 18px;
box-shadow: none;
}
.btn-install.installed {
width: 32px;
height: 32px;
margin-top: 20px;
color: transparent;
background: url('nylas://onboarding/assets/green_check@2x.png') center no-repeat;
background-size: 27px;
box-shadow: none;
}
.btn-install.installed:after,
.btn-install.installing:after {
content: "";
}
}
.welcome-page {
@ -479,7 +520,7 @@
.nylas-static-wash-bg;
.hero-text {
font-size: 36px;
font-size: 34px;
line-height: 41px;
}

View file

@ -5,7 +5,7 @@ path = require 'path'
fs = require 'fs-plus'
shell = require 'shell'
SettingsActions = require './settings-actions'
APMWrapper = require './apm-wrapper'
{APMWrapper} = require 'nylas-exports'
dialog = require('remote').require('dialog')
module.exports =
@ -46,7 +46,7 @@ SettingsPackagesStore = Reflux.createStore
@_apm.install pkg, (err) =>
if err
delete @_installing[pkg.name]
@_displayError(err)
@_displayMessage("Sorry, an error occurred", err.toString())
else
if atom.packages.isPackageDisabled(pkg.name)
atom.packages.enablePackage(pkg.name)
@ -57,7 +57,7 @@ SettingsPackagesStore = Reflux.createStore
atom.packages.disablePackage(pkg.name)
atom.packages.unloadPackage(pkg.name)
@_apm.uninstall pkg, (err) =>
@_displayError(err) if err
@_displayMessage("Sorry, an error occurred", err.toString()) if err
@_onPackagesChanged()
@listenTo SettingsActions.enablePackage, (pkg) ->
@ -167,34 +167,13 @@ SettingsPackagesStore = Reflux.createStore
properties: ['openDirectory']
, (filenames) =>
return if not filenames or filenames.length is 0
packagesDir = path.join(atom.getConfigDirPath(), 'packages')
fs.makeTreeSync(packagesDir)
packageSourceDir = filenames[0]
packageName = path.basename(packageSourceDir)
packageTargetDir = path.join(packagesDir, packageName)
if fs.existsSync(packageTargetDir)
msg = "A package named '#{packageName}' is already installed in \
~/.nylas/packages. Remove it before trying to install another \
package of the same name."
dialog.showErrorBox('Package already installed', msg)
return
fs.copySync(packageSourceDir, packageTargetDir)
@_apm.installDependenciesInPackageDirectory packageTargetDir, (err) =>
shell.showItemInFolder(packageTargetDir)
if err
dialog.showErrorBox('Package installation failed', err.toString())
return
else
atom.packages.enablePackage(packageTargetDir)
atom.packages.activatePackage(packageName)
msg = "#{packageName} has been installed and enabled. No need to \
restart! If you don't see the package loaded, check the \
console for errors."
dialog.showErrorBox('Package installed', msg)
atom.packages.installPackageFromPath filenames[0], (err) =>
return if err
packageName = path.basename(filenames[0])
msg = "#{packageName} has been installed and enabled. No need to \
restart! If you don't see the package loaded, check the \
console for errors."
@_displayMessage("Package installed", msg)
_onCreatePackage: ->
packagesDir = path.join(atom.getConfigDirPath(), 'packages')
@ -210,15 +189,19 @@ SettingsPackagesStore = Reflux.createStore
packageName = path.basename(packageDir)
if not packageDir.startsWith(packagesDir)
return dialog.showErrorBox('Invalid package location', 'Sorry, you must \
create packages in the dev packages folder.')
return @_displayMessage('Invalid package location', 'Sorry, you must
create packages in the packages folder.')
if atom.packages.resolvePackagePath(packageName)
return dialog.showErrorBox('Invalid package name', 'Sorry, you must \
return @_displayMessage('Invalid package name', 'Sorry, you must
give your package a unqiue name.')
if packageName.indexOf(' ') isnt -1
return @_displayMessage('Invalid package name', 'Sorry, package names
cannot contain spaces.')
fs.mkdir packageDir, (err) =>
return dialog.showErrorBox('Could not create package', err.toString()) if err
return @_displayMessage('Could not create package', err.toString()) if err
{resourcePath} = atom.getLoadSettings()
packageTemplatePath = path.join(resourcePath, 'static', 'package-template')
@ -264,10 +247,9 @@ SettingsPackagesStore = Reflux.createStore
pkgs
_displayError: (err) ->
console.error(err)
_displayMessage: (title, message) ->
chosen = dialog.showMessageBox
type: 'warning'
message: "Sorry, an error occurred."
detail: err.toString()
message: title
detail: message
buttons: ["OK"]

View file

@ -1,8 +1,9 @@
_ = require 'underscore'
{BufferedProcess} = require 'nylas-exports'
Q = require 'q'
semver = require 'semver'
BufferedProcess = require './buffered-process'
module.exports =
class APMWrapper

View file

@ -371,9 +371,9 @@ class Atom extends Model
isReleasedVersion: ->
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
# Public: Get the directory path to Atom's configuration area.
# Public: Get the directory path to N1's configuration area.
#
# Returns the absolute path to `~/.atom`.
# Returns the absolute path to `~/.nylas`.
getConfigDirPath: ->
@constructor.getConfigDirPath()

View file

@ -159,7 +159,7 @@ class Application
@windowManager.showMainWindow(loadingMessage)
@windowManager.ensureWorkWindow()
else
@windowManager.newOnboardingWindow()
@windowManager.ensureOnboardingWindow(welcome: true)
# The onboarding window automatically shows when it's ready
_resetConfigAndRelaunch: =>
@ -169,19 +169,13 @@ class Application
@config.set('nylas', null)
@config.set('edgehill', null)
@setDatabasePhase('setup')
@windowManager.newOnboardingWindow()
@windowManager.ensureOnboardingWindow(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')
_accountSetupSuccessful: =>
@windowManager.showMainWindow()
@windowManager.ensureWorkWindow()
@windowManager.mainWindow().waitForLoad =>
@windowManager.onboardingWindow()?.close()
databasePhase: ->
@_databasePhase
@ -247,7 +241,7 @@ class Application
atomWindow ?= @windowManager.focusedWindow()
atomWindow?.browserWindow.inspectElement(x, y)
@on 'application:add-account', => @windowManager.newOnboardingWindow()
@on 'application:add-account', => @windowManager.ensureOnboardingWindow()
@on 'application:new-message', => @windowManager.sendToMainWindow('new-message')
@on 'application:send-feedback', => @windowManager.sendToMainWindow('send-feedback')
@on 'application:open-preferences', => @windowManager.sendToMainWindow('open-preferences')
@ -378,7 +372,9 @@ class Application
clipboard.writeText(selectedText, 'selection')
ipc.on 'account-setup-successful', (event) =>
@_accountSetupSuccessful()
@windowManager.showMainWindow()
@windowManager.ensureWorkWindow()
@windowManager.onboardingWindow()?.close()
ipc.on 'run-in-window', (event, params) =>
@_sourceWindows ?= {}

View file

@ -132,22 +132,26 @@ class WindowManager
# Returns a new onboarding window
#
newOnboardingWindow: ({welcome}={}) ->
options =
title: "Add an Account"
toolbar: false
resizable: false
hidden: true # The `PageRouter` will center and show on load
windowType: 'onboarding'
windowProps:
page: 'account-choose'
uniqueId: 'onboarding'
ensureOnboardingWindow: ({welcome}={}) ->
existing = @onboardingWindow()
if existing
existing.focus()
else
options =
title: "Add an Account"
toolbar: false
resizable: false
hidden: true # The `PageRouter` will center and show on load
windowType: 'onboarding'
windowProps:
page: 'account-choose'
uniqueId: 'onboarding'
if welcome
options.title = "Welcome to N1"
options.page = 'welcome'
if welcome
options.title = "Welcome to N1"
options.windowProps.page = 'welcome'
win = @newWindow(options)
@newWindow(options)
# Makes a new window appear of a certain `windowType`.
#

View file

@ -0,0 +1,26 @@
React = require 'react'
# Public: A small React component which renders as a horizontal on/off switch.
# Provide it with `onChange` and `checked` props just like a checkbox:
#
# ```
# <Switch onChange={@_onToggleChecked} checked={@state.form.isChecked} />
# ```
#
class Switch extends React.Component
@propTypes:
checked: React.PropTypes.bool.isRequired
onChange: React.PropTypes.func.isRequired
constructor: (@props) ->
render: =>
classnames = "slide-switch"
if @props.checked
classnames += " active"
<div className={classnames} onClick={@props.onChange}>
<div className="handle"></div>
</div>
module.exports = Switch

View file

@ -11,6 +11,7 @@ ServiceHub = require 'service-hub'
Package = require './package'
ThemePackage = require './theme-package'
DatabaseStore = require './flux/stores/database-store'
APMWrapper = require './apm-wrapper'
# Extended: Package manager for coordinating the lifecycle of Atom packages.
#
@ -148,7 +149,6 @@ class PackageManager
@apmPath = path.join(@resourcePath, 'apm', 'bin', commandName)
if not fs.isFileSync(@apmPath)
@apmPath = path.join(@resourcePath, 'apm', 'node_modules', 'atom-package-manager', 'bin', commandName)
console.log(@apmPath)
@apmPath
# Public: Get the paths being used to look for packages.
@ -318,6 +318,49 @@ class PackageManager
packages.push(metadata)
packages
installPackageFromPath: (packageSourceDir, callback) ->
dialog = require('remote').require('dialog')
shell = require('shell')
packagesDir = path.join(atom.getConfigDirPath(), 'packages')
packageName = path.basename(packageSourceDir)
packageTargetDir = path.join(packagesDir, packageName)
fs.makeTree packagesDir, (err) =>
return callback(err) if err
fs.exists packageTargetDir, (packageAlreadyExists) =>
if packageAlreadyExists
message = "A package named '#{packageName}' is already installed
in ~/.nylas/packages."
dialog.showMessageBox({
type: 'warning'
buttons: ['OK']
title: 'Package already installed'
detail: 'Remove it before trying to install another package of the same name.'
message: message
})
callback(new Error(message))
return
fs.copySync(packageSourceDir, packageTargetDir)
apm = new APMWrapper()
apm.installDependenciesInPackageDirectory packageTargetDir, (err) =>
shell.showItemInFolder(packageTargetDir)
if err
dialog.showMessageBox({
type: 'warning'
buttons: ['OK']
title: 'Package installation failed'
message: err.toString()
})
callback(err)
else
@enablePackage(packageTargetDir)
@activatePackage(packageName)
callback(null)
###
Section: Private
###

View file

@ -297,7 +297,6 @@ var openWindowForComponent = function(Component, options) {
changed = true;
}
if (changed) {
console.log(size[0], size[1]);
thinWindow.setContentSize(size[0], size[1]);
}
};

View file

@ -0,0 +1,29 @@
@import "ui-variables";
.slide-switch {
border-radius: 12px;
box-shadow: inset 0 1px 1.5px rgba(0,0,0,0.3);
background-color: @gray-light;
position: relative;
display:inline-block;
height:21px;
width:40px;
.handle {
border-radius:14px;
width:25px;
height: 25px;
position: absolute;
top: -2px;
left: -3px;
background-color: @white;
box-shadow: 0 0.5px 3px rgba(0,0,0,0.4);
transition: all 150ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
&.active {
background-color: @component-active-color;
.handle {
left: 18px;
}
}
}

View file

@ -15,6 +15,7 @@
@import "components/popover";
@import "components/menu";
@import "components/switch";
@import "components/tokenizing-text-field";
@import "components/extra";
@import "components/list-tabular";

View file

@ -138,6 +138,11 @@ body.is-blurred {
background: none;
img { opacity:0.5; }
}
.item-container {
.window-title {
opacity: 0.5;
}
}
}
}