From 182f6abb2565e8544e922059b0e5b4e8945a3d45 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Thu, 4 Feb 2016 14:48:15 -0800 Subject: [PATCH] update(sidebar): Update sidebar design + context menus Summary: - Removes account switcher almost entirely - Update context menu to edit and delete sidebar items - Gross hardcoded position and size for the switcher icon -- will likely update with later redesign Test Plan: - Visual Reviewers: evan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2527 --- .../lib/components/account-sidebar.cjsx | 2 +- .../lib/components/account-switcher.cjsx | 140 +++++----------- .../account-sidebar/lib/sidebar-item.coffee | 5 + .../lib/sidebar-section.coffee | 10 +- .../spec/account-switcher-spec.cjsx | 52 ------ .../stylesheets/account-sidebar.less | 151 ++---------------- src/components/outline-view-item.jsx | 7 +- 7 files changed, 70 insertions(+), 297 deletions(-) delete mode 100644 internal_packages/account-sidebar/spec/account-switcher-spec.cjsx diff --git a/internal_packages/account-sidebar/lib/components/account-sidebar.cjsx b/internal_packages/account-sidebar/lib/components/account-sidebar.cjsx index b17a0c2fb..eddb35821 100644 --- a/internal_packages/account-sidebar/lib/components/account-sidebar.cjsx +++ b/internal_packages/account-sidebar/lib/components/account-sidebar.cjsx @@ -40,8 +40,8 @@ class AccountSidebar extends React.Component {accounts, focusedAccounts, userSections, standardSection} = @state - +
{@_renderUserSections(userSections)} diff --git a/internal_packages/account-sidebar/lib/components/account-switcher.cjsx b/internal_packages/account-sidebar/lib/components/account-switcher.cjsx index d55385c7a..ed561482f 100644 --- a/internal_packages/account-sidebar/lib/components/account-switcher.cjsx +++ b/internal_packages/account-sidebar/lib/components/account-switcher.cjsx @@ -13,19 +13,10 @@ ItemTypes = { class AccountSwitcher extends React.Component @displayName: 'AccountSwitcher' - @containerRequired: false - @containerStyles: - minWidth: 165 - maxWidth: 210 - @propTypes: accounts: React.PropTypes.array.isRequired focusedAccounts: React.PropTypes.array.isRequired - constructor: (@props) -> - @state = - showing: false - # Helpers _makeAccountItem: (account) => @@ -53,107 +44,60 @@ class AccountSwitcher extends React.Component _toggleDropdown: => @setState showing: !@state.showing + _makeMenuItem: (item, idx = 0) => + menuItem = { + label: item.label, + click: @_onSwitchAccount.bind(@, item) + accelerator: "CmdOrCtrl+#{idx}" + } + + if @_selectedItem().id is item.id + menuItem.type = 'checkbox' + menuItem.checked = true + + return menuItem + + _makeMenuTemplate: => + template = [] + items = @props.accounts.map(@_makeAccountItem) + + if @props.accounts.length > 1 + unifiedItem = @_makeUnifiedItem() + template = [ + @_makeMenuItem(unifiedItem) + {type: 'separator'} + ] + + items.forEach (item, idx) => template.push(@_makeMenuItem(item, idx + 1)) + + template = template.concat [ + {type: 'separator'} + {label: 'Manage Accounts...', click: @_onManageAccounts} + ] + return template + # Handlers - _onBlur: (e) => - target = e.nativeEvent.relatedTarget - if target? and React.findDOMNode(@refs.button).contains(target) - return - @setState(showing: false) - _onSwitchAccount: (item) => SidebarActions.focusAccounts(item.accounts) - @setState(showing: false) _onManageAccounts: => Actions.switchPreferencesTab('Accounts') Actions.openPreferences() - @setState(showing: false) - - _renderItem: (item) => - classes = classNames - "active": item.id is @_selectedItem().id - "item": true - "secondary-item": true - -
- {@_renderGravatar(item)} -
{item.label}
-
-
- - _renderManageAccountsItem: => -
-
- -
-
- Manage accounts… -
-
-
- - _renderDropdown: (items) => -
-
- {items.map(@_renderItem)} - {@_renderManageAccountsItem()} -
-
- - _renderGravatar: ({email, iconName}) => - if email - hash = crypto.createHash('md5').update(email, 'utf8').digest('hex') - url = "url(http://www.gravatar.com/avatar/#{hash}?d=blank&s=56)" - else - url = '' - -
-
- -
- - _renderPrimaryItem: (item) => -
- {@_renderGravatar(item)} -
- -
-
- {item.label} -
-
-
+ _onShowMenu: => + remote = require('electron').remote + Menu = remote.Menu + menu = Menu.buildFromTemplate(@_makeMenuTemplate()) + menu.popup() render: => - return unless @props.focusedAccounts - classnames = "account-switcher" - classnames += " open" if @state.showing - selected = @_selectedItem() - if @props.accounts.length is 1 - items = @props.accounts.map(@_makeAccountItem) - else - items = [@_makeUnifiedItem()].concat @props.accounts.map(@_makeAccountItem) - -
- {@_renderPrimaryItem(selected)} - {@_renderDropdown(items)} +
+
diff --git a/internal_packages/account-sidebar/lib/sidebar-item.coffee b/internal_packages/account-sidebar/lib/sidebar-item.coffee index fa9e56146..5793f8a75 100644 --- a/internal_packages/account-sidebar/lib/sidebar-item.coffee +++ b/internal_packages/account-sidebar/lib/sidebar-item.coffee @@ -1,4 +1,5 @@ _ = require 'underscore' +_str = require 'underscore.string' {WorkspaceStore, MailboxPerspective, FocusedPerspectiveStore, @@ -58,6 +59,7 @@ class SidebarItem return _.extend({ id: id name: perspective.name + contextMenuLabel: perspective.name count: countForItem(perspective) iconName: perspective.iconName children: [] @@ -95,9 +97,12 @@ class SidebarItem @forCategories: (categories = [], opts = {}) -> id = idForCategories(categories) + contextMenuLabel = _str.capitalize(categories[0]?.displayType()) perspective = MailboxPerspective.forCategories(categories) + opts.deletable ?= true opts.editable ?= true + opts.contextMenuLabel = contextMenuLabel @forPerspective(id, perspective, opts) @forStarred: (accountIds, opts = {}) -> diff --git a/internal_packages/account-sidebar/lib/sidebar-section.coffee b/internal_packages/account-sidebar/lib/sidebar-section.coffee index 4eaab5e53..2205b7da2 100644 --- a/internal_packages/account-sidebar/lib/sidebar-section.coffee +++ b/internal_packages/account-sidebar/lib/sidebar-section.coffee @@ -31,7 +31,7 @@ class SidebarSection throw new Error("standardSectionForAccount: You must pass an account.") cats = CategoryStore.standardCategories(account) - return @empty('Mailboxes') if cats.length is 0 + return @empty(account.label) if cats.length is 0 items = _ .reject(cats, (cat) -> cat.name is 'drafts') @@ -45,13 +45,13 @@ class SidebarSection items.push(draftsItem) return { - title: 'Mailboxes' + title: account.label items: items } @standardSectionForAccounts: (accounts) -> - return @empty('Mailboxes') if not accounts or accounts.length is 0 - return @empty('Mailboxes') if CategoryStore.categories().length is 0 + return @empty('All Accounts') if not accounts or accounts.length is 0 + return @empty('All Accounts') if CategoryStore.categories().length is 0 return @standardSectionForAccount(accounts[0]) if accounts.length is 1 standardNames = [ @@ -89,7 +89,7 @@ class SidebarSection items.push(draftsItem) return { - title: 'Mailboxes' + title: 'All Accounts' items: items } diff --git a/internal_packages/account-sidebar/spec/account-switcher-spec.cjsx b/internal_packages/account-sidebar/spec/account-switcher-spec.cjsx deleted file mode 100644 index c80a32dbb..000000000 --- a/internal_packages/account-sidebar/spec/account-switcher-spec.cjsx +++ /dev/null @@ -1,52 +0,0 @@ -React = require 'react/addons' -TestUtils = React.addons.TestUtils -AccountSwitcher = require './../lib/components/account-switcher' -SidebarStore = require './../lib/sidebar-store' -{AccountStore} = require 'nylas-exports' - -describe "AccountSwitcher", -> - switcher = null - - beforeEach -> - account = AccountStore.accounts()[0] - accounts = [ - account, - { - emailAddress: "dillon@nylas.com", - provider: "exchange" - label: "work" - } - ] - switcher = TestUtils.renderIntoDocument( - - ) - - it "doesn't render the dropdown if nothing clicked", -> - openDropdown = TestUtils.scryRenderedDOMComponentsWithClass switcher, 'open' - expect(openDropdown.length).toBe 0 - - it "shows the dropdown on click", -> - toggler = TestUtils.findRenderedDOMComponentWithClass switcher, 'primary-item' - TestUtils.Simulate.click toggler - openDropdown = TestUtils.scryRenderedDOMComponentsWithClass switcher, 'open' - expect(openDropdown.length).toBe 1 - - it "hides the dropdown on blur", -> - toggler = TestUtils.findRenderedDOMComponentWithClass switcher, 'primary-item' - TestUtils.Simulate.click toggler - toggler = TestUtils.findRenderedDOMComponentWithClass switcher, 'primary-item' - TestUtils.Simulate.blur toggler - openDropdown = TestUtils.scryRenderedDOMComponentsWithClass switcher, 'open' - expect(openDropdown.length).toBe 0 - - it "shows other accounts and the 'Add Account' button", -> - toggler = TestUtils.findRenderedDOMComponentWithClass switcher, 'primary-item' - TestUtils.Simulate.click toggler - - dropdown = TestUtils.findRenderedDOMComponentWithClass switcher, "dropdown" - items = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "secondary-item" - newAccountButton = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "new-account-option" - - # The unified Inbox item, then both accounts, then the manage item - expect(items.length).toBe 4 - expect(newAccountButton.length).toBe 1 diff --git a/internal_packages/account-sidebar/stylesheets/account-sidebar.less b/internal_packages/account-sidebar/stylesheets/account-sidebar.less index 52b3b2263..63540b063 100644 --- a/internal_packages/account-sidebar/stylesheets/account-sidebar.less +++ b/internal_packages/account-sidebar/stylesheets/account-sidebar.less @@ -9,147 +9,22 @@ .item.deleted { opacity: 0.5; } + + .nylas-outline-view:first-child { + .heading span { + margin-right: 25px; + } + } } .account-switcher { - order: 1; - height: auto; - background-color: @source-list-bg; - border-bottom: 1px solid @border-color-divider; - - &.open { - .dropdown { - opacity: 1; - pointer-events: initial; - transform:scale(1, 1); - transform-origin: top; - .inner { - opacity: 1; - transition: opacity 50ms linear; - transition-delay: 50ms; - } - } - .toggle { - transform: rotateX(0deg); - } - } - - .primary-item { - padding-top: @padding-large-vertical; - padding-bottom: @padding-base-vertical; - padding-left: 10px; - .name { - padding-left: 7px; - } - } - - .secondary-item { - &:hover { - background: @list-hover-bg; - } - - 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; - } - } - - .toggle { + position: absolute; + top: 7px; + right: 17px; + z-index: 3; + img { transform: rotateX(180deg); } - - .dropdown { - opacity: 0; - pointer-events: none; - transform:scale(1, 0.2); - transform-origin: top; - transition: transform 125ms cubic-bezier(0.18, 0.89, 0.32, 1.12), opacity 100ms linear; - .inner { - opacity: 0; - transition: opacity 25ms linear; - transition-delay: 0; - } - margin-top: -7px; - background: lighten(@source-list-bg, 5%); - border: 1px solid @border-color-divider; - border-radius: @border-radius-base; - border-top-left-radius: 0; - border-top-right-radius: 0; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.21); - - position: absolute; - top: 54px; - width: 100%; - z-index: 999; - - .account .gravatar { - top: 6px; - } - } - - .item { - color: @text-color-subtle; - display: block; - img.content-mask { - background-color: @text-color-subtle; - vertical-align: text-bottom; - } - font-size: @font-size-small; - font-weight: 400; - padding-right: @spacing-standard; - line-height: @line-height-large * 1.1; - clear: both; - position: relative; - margin-bottom: 0; - - .gravatar { - background-size: 28px 28px; - width: 28px; - height: 28px; - position: absolute; - z-index: 2; - border-radius: 4px; - top: -2px; - background-repeat: no-repeat; - } - - .name { - order: 2; - padding-left: @padding-small-horizontal * 0.85; - position:relative; - top:1px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding-top: @padding-small-vertical; - padding-bottom:@padding-small-vertical; - line-height: @line-height-small; - } - &:hover { - cursor: default; - } - } -} -body.platform-win32 { - .account-switcher { - .dropdown { - border-radius: 0; - } - .item { - border-radius: 0; - } - .secondary-item { - &:first-child, &:last-child { - border-radius: 0; - } - } - } } + + diff --git a/src/components/outline-view-item.jsx b/src/components/outline-view-item.jsx index bbc206e9d..7a49f887f 100644 --- a/src/components/outline-view-item.jsx +++ b/src/components/outline-view-item.jsx @@ -36,6 +36,7 @@ const CounterStyles = { * @param {object} props.item - props for OutlineViewItem * @param {string} props.item.id - Unique id for the item. * @param {string} props.item.name - Name to display + * @param {string} props.item.contextMenuLabel - Label to be displayed in context menu * @param {string} props.item.className - Extra classes to add to the item * @param {string} props.item.iconName - Icon name for icon. See {@link RetinaImg} for further reference. * @param {array} props.item.children - Array of children of the same type to be @@ -245,21 +246,21 @@ class OutlineViewItem extends Component { _onShowContextMenu = (event)=> { event.stopPropagation() const item = this.props.item; - const name = item.name; + const contextMenuLabel = item.contextMenuLabel || item.name const {remote} = require('electron'); const {Menu, MenuItem} = remote; const menu = new Menu(); if (this.props.item.onEdited) { menu.append(new MenuItem({ - label: `Edit ${name}`, + label: `Rename ${contextMenuLabel}`, click: this._onEdit, })); } if (this.props.item.onDelete) { menu.append(new MenuItem({ - label: `Delete ${name}`, + label: `Delete ${contextMenuLabel}`, click: this._onDelete, })); }