diff --git a/internal_packages/account-sidebar/lib/account-sidebar.cjsx b/internal_packages/account-sidebar/lib/account-sidebar.cjsx index 5e86502a6..13425d5bc 100644 --- a/internal_packages/account-sidebar/lib/account-sidebar.cjsx +++ b/internal_packages/account-sidebar/lib/account-sidebar.cjsx @@ -1,10 +1,13 @@ React = require 'react' -{Actions, MailViewFilter} = require("nylas-exports") +{Actions, MailViewFilter, AccountStore} = require("nylas-exports") {ScrollRegion} = require("nylas-component-kit") SidebarDividerItem = require("./account-sidebar-divider-item") SidebarSheetItem = require("./account-sidebar-sheet-item") AccountSidebarStore = require ("./account-sidebar-store") AccountSidebarMailViewItem = require("./account-sidebar-mail-view-item") +crypto = require 'crypto' +{RetinaImg} = require 'nylas-component-kit' +classNames = require 'classnames' class AccountSidebar extends React.Component @displayName: 'AccountSidebar' @@ -16,10 +19,12 @@ class AccountSidebar extends React.Component constructor: (@props) -> @state = @_getStateFromStores() + @state.showing = false componentDidMount: => @unsubscribers = [] @unsubscribers.push AccountSidebarStore.listen @_onStoreChange + @unsubscribers.push AccountStore.listen @_onStoreChange # It's important that every React class explicitly stops listening to # atom events before it unmounts. Thank you event-kit @@ -29,11 +34,105 @@ class AccountSidebar extends React.Component render: => + {@_accountSwitcher()}
{@_sections()}
+ _accountSwitcher: => + return undefined if @state.accounts.length < 1 + +
+ {@_renderAccount @state.account, true} + {@_renderDropdown()} +
+ + _renderAccount: (account, isPrimaryItem) => + classes = classNames + "account": true + "item": true + "dropdown-item-padding": not isPrimaryItem + "active": account is @state.account + "bg-color-hover": not isPrimaryItem + "primary-item": isPrimaryItem + "account-option": not isPrimaryItem + + email = account.emailAddress.trim().toLowerCase() + + if isPrimaryItem + dropdownClasses = classNames + "account-switcher-dropdown": true, + "account-switcher-dropdown-hidden": @state.showing + + dropdownArrow =
+ +
+ + onClick = @_toggleDropdown + + else + onClick = => + @_onSwitchAccount account + +
+
+
+ +
+ {dropdownArrow} +
+ {email} +
+
+
+
+ + _renderNewAccountOption: => +
+
+ +
+
+ Add account… +
+
+
+
+ + _renderDropdown: => + display = if @state.showing then "block" else "none" + # display = "block" + + accounts = @state.accounts.map (a) => + @_renderAccount(a) + +
+ {accounts} + {@_renderNewAccountOption()} +
+ + _toggleDropdown: => + @setState showing: !@state.showing + + _gravatarUrl: (email) => + hash = crypto.createHash('md5').update(email, 'utf8').digest('hex') + + "url(http://www.gravatar.com/avatar/#{hash}?d=blank&s=56)" + _sections: => return @state.sections.map (section) =>
@@ -64,12 +163,25 @@ class AccountSidebar extends React.Component _onStoreChange: => @setState @_getStateFromStores() + _onBlur: (e) => + target = e.nativeEvent.relatedTarget + if target? and React.findDOMNode(@refs.button).contains(target) + return + @setState(showing: false) + _onSwitchAccount: (account) => Actions.selectAccountId(account.id) + @setState(showing: false) + + _onAddAccount: => + require('remote').getGlobal('application').windowManager.newOnboardingWindow() + @setState showing: false _getStateFromStores: => sections: AccountSidebarStore.sections() selected: AccountSidebarStore.selected() + accounts: AccountStore.items() + account: AccountStore.current() module.exports = AccountSidebar diff --git a/internal_packages/account-sidebar/lib/account-switcher.cjsx b/internal_packages/account-sidebar/lib/account-switcher.cjsx deleted file mode 100644 index e8bcfde40..000000000 --- a/internal_packages/account-sidebar/lib/account-switcher.cjsx +++ /dev/null @@ -1,65 +0,0 @@ -React = require 'react' -{Actions, AccountStore} = require("nylas-exports") -{RetinaImg} = require('nylas-component-kit') -crypto = require 'crypto' -classNames = require 'classnames' - -class AccountSwitcher extends React.Component - @displayName: 'AccountSwitcher' - - @containerRequired: false - @containerStyles: - minWidth: 64 - maxWidth: 64 - - constructor: (@props) -> - @state = @_getStateFromStores() - - componentDidMount: => - @unsubscribers = [] - @unsubscribers.push AccountStore.listen @_onStoreChange - - # 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() for unsubscribe in @unsubscribers - - render: => - if @state.accounts.length is 0 - return - -
- {@_accounts()} -
- - _accounts: => - return @state.accounts.map (account) => - hash = account.emailAddress.trim().toLowerCase() - hash = crypto.createHash('md5').update(hash, 'utf8').digest('hex') - - classnames = classNames - 'account': true - 'active': account is @state.account - - gravatarUrl = "http://www.gravatar.com/avatar/#{hash}?d=blank&s=44" - -
@_onSwitchAccount(account) }> -
- -
- - _onStoreChange: => - @setState @_getStateFromStores() - - _onSwitchAccount: (account) => - Actions.selectAccountId(account.id) - - _getStateFromStores: => - accounts: AccountStore.items() - account: AccountStore.current() - -module.exports = AccountSwitcher diff --git a/internal_packages/account-sidebar/lib/main.cjsx b/internal_packages/account-sidebar/lib/main.cjsx index 0d57b5528..48bc24ef7 100644 --- a/internal_packages/account-sidebar/lib/main.cjsx +++ b/internal_packages/account-sidebar/lib/main.cjsx @@ -1,15 +1,11 @@ React = require "react" AccountSidebar = require "./account-sidebar" -AccountSwitcher = require "./account-switcher" {ComponentRegistry, WorkspaceStore} = require "nylas-exports" module.exports = item: null # The DOM item the main React component renders into activate: (@state) -> - ComponentRegistry.register AccountSwitcher, - location: WorkspaceStore.Location.RootSwitcher - ComponentRegistry.register AccountSidebar, location: WorkspaceStore.Location.RootSidebar diff --git a/internal_packages/account-sidebar/spec/account-sidebar-spec.cjsx b/internal_packages/account-sidebar/spec/account-sidebar-spec.cjsx new file mode 100644 index 000000000..5ae935903 --- /dev/null +++ b/internal_packages/account-sidebar/spec/account-sidebar-spec.cjsx @@ -0,0 +1,60 @@ +React = require 'react/addons' +TestUtils = React.addons.TestUtils +AccountSidebar = require './../lib/account-sidebar' +{AccountStore} = require 'nylas-exports' + +describe "AccountSidebar", -> + describe "account switcher", -> + sidebar = null + + beforeEach -> + spyOn(AccountStore, "items").andCallFake -> + [ + AccountStore.current(), + { + emailAddress: "dillon@nylas.com", + provider: "exchange" + } + ] + + sidebar = TestUtils.renderIntoDocument( + + ) + + it "doesn't render the dropdown if nothing clicked", -> + dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown" + dropdownNode = React.findDOMNode dropdown, "account-switcher-dropdown" + + expect(dropdownNode.style.display).toBe "none" + + it "renders the dropdown if clicking the arrow btn", -> + toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item' + TestUtils.Simulate.click toggler + dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown" + dropdownNode = React.findDOMNode dropdown, "account-switcher-dropdown" + + expect(dropdownNode.style.display).toBe "block" + + it "removes the dropdown when clicking elsewhere", -> + toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item' + TestUtils.Simulate.blur toggler + dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown" + dropdownNode = React.findDOMNode dropdown, "account-switcher-dropdown" + + expect(dropdownNode.style.display).toBe "none" + + it "shows all the accounts in the dropdown", -> + toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item' + TestUtils.Simulate.click toggler + dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown" + accounts = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "account-option" + + expect(accounts.length).toBe 2 + + it "shows the 'Add Account' button too", -> + toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item' + TestUtils.Simulate.click toggler + dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown" + accounts = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "new-account-option" + + expect(accounts.length).toBe 1 diff --git a/internal_packages/account-sidebar/stylesheets/account-sidebar.less b/internal_packages/account-sidebar/stylesheets/account-sidebar.less index 1598e346c..8f0b03abf 100644 --- a/internal_packages/account-sidebar/stylesheets/account-sidebar.less +++ b/internal_packages/account-sidebar/stylesheets/account-sidebar.less @@ -1,45 +1,7 @@ @import "ui-variables"; @import "ui-mixins"; -#account-switcher { - background-color: #212831; - flex: 1; - .account { - position: relative; - margin:15px 10px; - margin-bottom: 0; - display:block; - border-radius: 8px; - opacity: 0.7; - img { - -webkit-filter: ~"saturate(0%)"; - } - .gravatar { - width: 44px; - height: 44px; - position: absolute; - z-index: 2; - border-radius: 7px; - } - } - .account.active { - opacity: 1.0; - img { - -webkit-filter: ~"saturate(100%)"; - } - } - .account.active::after { - box-shadow: inset 0 0 1px 2px @source-list-active-color; - border-radius: 7px; - width:100%; - height:100%; - content: ' '; - left: 0; - position:absolute; - z-index: 3; - } -} - +#account-switcher, #account-sidebar { order: 1; height: 100%; @@ -79,7 +41,6 @@ float: left; } .name { - text-transform: capitalize; padding-left: @padding-small-horizontal; position:relative; top:1px; @@ -110,3 +71,85 @@ padding-bottom: 0.25em; } } + +#account-sidebar { + .name { + text-transform: capitalize; + } +} + +#account-switcher { + padding-top: @padding-large-vertical; + padding-bottom: @padding-base-vertical; + border-bottom: 1px solid @border-color-divider; + + .account { + position: relative; + margin-bottom: 0; + display:block; + .gravatar { + background-size: 28px 28px; + width: 28px; + height: 28px; + position: absolute; + z-index: 2; + border-radius: 7px; + top: -2px; + } + } + .name { + text-transform: lowercase; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .account-switcher-dropdown { + margin-top: -7px; + transform: scale(1, -1); + + &.account-switcher-dropdown-hidden { + transform: scale(1, 1); + } + } + + .dropdown-positioning { + display: block; + position: absolute; + top: 45px; + width: 100%; + z-index: 999; + + .account .gravatar { + top: 6px; + } + } + + .dropdown-colors { + background: lighten(@source-list-bg, 5%); + border: 1px solid @border-color-divider; + border-radius: @border-radius-base; + box-shadow: @standard-shadow; + } + + .dropdown-item-padding { + padding: 6px 5px 0 14px; + + &:first-child { + padding-top: 8px; + border-top-left-radius: @list-border-radius; + border-top-right-radius: @list-border-radius; + } + &:last-child { + padding-bottom: 2px; + border-bottom-left-radius: @list-border-radius; + border-bottom-right-radius: @list-border-radius; + } + } + + .bg-color-hover { + + &:hover { + background: @list-hover-bg; + } + } +} diff --git a/internal_packages/settings/lib/main.cjsx b/internal_packages/settings/lib/main.cjsx index 3b8fe4e26..b6937cf5b 100644 --- a/internal_packages/settings/lib/main.cjsx +++ b/internal_packages/settings/lib/main.cjsx @@ -8,7 +8,7 @@ module.exports = activate: (@state={}) -> WorkspaceStore.defineSheet 'Settings', {root: true, supportedModes: ['list'], name: 'Plugins'}, - list: ['RootSwitcher', 'RootSidebar', 'SettingsSidebar', 'Settings'] + list: ['RootSidebar', 'SettingsSidebar', 'Settings'] ComponentRegistry.register SettingsTabsView, location: WorkspaceStore.Location.SettingsSidebar diff --git a/internal_packages/thread-list/lib/main.cjsx b/internal_packages/thread-list/lib/main.cjsx index 2d6e1cd89..d86953a4c 100644 --- a/internal_packages/thread-list/lib/main.cjsx +++ b/internal_packages/thread-list/lib/main.cjsx @@ -14,7 +14,7 @@ DraftList = require './draft-list' module.exports = activate: (@state={}) -> WorkspaceStore.defineSheet 'Drafts', {root: true, name: 'Drafts', sidebarComponent: DraftListSidebarItem}, - list: ['RootSwitcher', 'RootSidebar', 'DraftList'] + list: ['RootSidebar', 'DraftList'] ComponentRegistry.register ThreadList, location: WorkspaceStore.Location.ThreadList diff --git a/src/flux/stores/workspace-store.coffee b/src/flux/stores/workspace-store.coffee index 3c61b8c32..f7ced57c7 100644 --- a/src/flux/stores/workspace-store.coffee +++ b/src/flux/stores/workspace-store.coffee @@ -46,8 +46,8 @@ class WorkspaceStore extends NylasStore if atom.isMainWindow() @defineSheet 'Global' @defineSheet 'Threads', {root: true}, - list: ['RootSwitcher', 'RootSidebar', 'ThreadList'] - split: ['RootSwitcher', 'RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar'] + list: ['RootSidebar', 'ThreadList'] + split: ['RootSidebar', 'ThreadList', 'MessageList', 'MessageListSidebar'] @defineSheet 'Thread', {}, list: ['MessageList', 'MessageListSidebar'] else diff --git a/static/images/sidebar/account-switcher-dropdown@2x.png b/static/images/sidebar/account-switcher-dropdown@2x.png new file mode 100644 index 000000000..3424240a3 Binary files /dev/null and b/static/images/sidebar/account-switcher-dropdown@2x.png differ diff --git a/static/images/source-list/icon-accounts-addnew@2x.png b/static/images/source-list/icon-accounts-addnew@2x.png new file mode 100644 index 000000000..e2afaa2cc Binary files /dev/null and b/static/images/source-list/icon-accounts-addnew@2x.png differ