From 2aa24bcd66947d4a98be3858f69e696f439f2977 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Thu, 11 Jun 2015 18:38:57 -0700 Subject: [PATCH] feat(thread-actions): Hover actions on thread list, improved drawing performance Summary: Adds hover actions to threads in the thread list, uses overflow:hidden to improve thread list render times Test Plan: Run tests Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1621 --- .../lib/thread-list-quick-actions.cjsx | 45 +++++++++++++++++++ .../thread-list/lib/thread-list.cjsx | 8 +++- .../thread-list/stylesheets/thread-list.less | 42 +++++++++++++++++ src/components/list-tabular.cjsx | 2 +- src/components/scroll-region.cjsx | 20 +++++---- src/flux/stores/draft-store.coffee | 8 ++-- static/components/scroll-region.less | 6 ++- static/variables/ui-variables.less | 6 +-- 8 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 internal_packages/thread-list/lib/thread-list-quick-actions.cjsx diff --git a/internal_packages/thread-list/lib/thread-list-quick-actions.cjsx b/internal_packages/thread-list/lib/thread-list-quick-actions.cjsx new file mode 100644 index 000000000..8466bb575 --- /dev/null +++ b/internal_packages/thread-list/lib/thread-list-quick-actions.cjsx @@ -0,0 +1,45 @@ +_ = require 'underscore' +React = require 'react' +{Actions, + Utils, + Thread, + AddRemoveTagsTask, + NamespaceStore} = require 'nylas-exports' +{RetinaImg} = require 'nylas-component-kit' + +class ThreadListQuickActions extends React.Component + @displayName: 'ThreadListQuickActions' + @propTypes: + thread: React.PropTypes.object + + render: => + actions = [] + actions.push
+ actions.push
+ if not @props.thread.hasTagId('archive') + actions.push
+ +
+ {actions} +
+ + shouldComponentUpdate: (newProps, newState) -> + newProps.thread.id isnt @props?.thread.id + + _onForward: (event) => + Actions.composeForward({thread: @props.thread, popout: true}) + # Don't trigger the thread row click + event.stopPropagation() + + _onReply: (event) => + Actions.composeReply({thread: @props.thread, popout: true}) + # Don't trigger the thread row click + event.stopPropagation() + + _onArchive: (event) => + Actions.queueTask(new AddRemoveTagsTask(@props.thread, ['archive'], ['inbox'])) + + # Don't trigger the thread row click + event.stopPropagation() + +module.exports = ThreadListQuickActions diff --git a/internal_packages/thread-list/lib/thread-list.cjsx b/internal_packages/thread-list/lib/thread-list.cjsx index b43250980..8b5f1fc30 100644 --- a/internal_packages/thread-list/lib/thread-list.cjsx +++ b/internal_packages/thread-list/lib/thread-list.cjsx @@ -10,6 +10,7 @@ classNames = require 'classnames' NamespaceStore} = require 'nylas-exports' ThreadListParticipants = require './thread-list-participants' +ThreadListQuickActions = require './thread-list-quick-actions' ThreadListStore = require './thread-list-store' ThreadListIcon = require './thread-list-icon' @@ -92,7 +93,12 @@ class ThreadList extends React.Component resolver: (thread) => {timestamp(thread.lastMessageTimestamp)} - @wideColumns = [c1, c2, c3, c4] + c5 = new ListTabular.Column + name: "HoverActions" + resolver: (thread) => + + + @wideColumns = [c1, c2, c3, c4, c5] cNarrow = new ListTabular.Column name: "Item" diff --git a/internal_packages/thread-list/stylesheets/thread-list.less b/internal_packages/thread-list/stylesheets/thread-list.less index dae3dcb7d..181831565 100644 --- a/internal_packages/thread-list/stylesheets/thread-list.less +++ b/internal_packages/thread-list/stylesheets/thread-list.less @@ -171,6 +171,48 @@ } +// quick actions + +.thread-list .list-item .list-column:last-child { + display:none; + .action { + margin:8px; + display:inline-block; + } + .action:last-child { + margin-right:20px; + } +} +.thread-list .list-item:hover .list-column:last-child { + width: 0; + padding: 0; + display:block; + overflow: visible; + height:100%; + + .inner { + position:relative; + width:300px; + height:100%; + left: -300px; + } +} + +.thread-list .list-item:hover .list-column:last-child .inner { + background-image: -webkit-linear-gradient(left, fade(darken(@list-bg, 5%), 0%) 0%, darken(@list-bg, 5%) 50%, darken(@list-bg, 5%) 100%); +} + +.thread-list .list-item.selected:hover .list-column:last-child .inner { + background-image: -webkit-linear-gradient(left, fade(@list-selected-bg, 0%) 0%, @list-selected-bg 50%, @list-selected-bg 100%); +} + +.thread-list .list-item.focused:hover .list-column:last-child .inner { + background-image: -webkit-linear-gradient(left, fade(@list-focused-bg, 0%) 0%, @list-focused-bg 50%, @list-focused-bg 100%); +} + + +// stars + .thread-icon-star-on-hover:hover, .thread-list .list-item:hover .thread-icon-star-on-hover:hover { background-image:url(../static/images/thread-list/icon-star-@2x.png); diff --git a/src/components/list-tabular.cjsx b/src/components/list-tabular.cjsx index 6f0c5da32..e8c7c74f3 100644 --- a/src/components/list-tabular.cjsx +++ b/src/components/list-tabular.cjsx @@ -32,7 +32,7 @@ class ListTabularItem extends React.Component className = "list-item list-tabular-item #{@props.itemProps?.className}" props = _.omit(@props.itemProps ? {}, 'className') -
+
{@_columns()}
diff --git a/src/components/scroll-region.cjsx b/src/components/scroll-region.cjsx index d55e19a18..ab0bb24dc 100644 --- a/src/components/scroll-region.cjsx +++ b/src/components/scroll-region.cjsx @@ -136,16 +136,20 @@ class ScrollRegion extends React.Component @_onHandleMove(event) _onScroll: (event) => - @_recomputeDimensions() - @props.onScroll?(event) + if not @_requestedAnimationFrame + @_requestedAnimationFrame = true + window.requestAnimationFrame => + @_requestedAnimationFrame = false + @_recomputeDimensions() + @props.onScroll?(event) - if not @state.scrolling - @setState(scrolling: true) + if not @state.scrolling + @setState(scrolling: true) - @_onStoppedScroll ?= _.debounce => - @setState(scrolling: false) - , 250 - @_onStoppedScroll() + @_onStoppedScroll ?= _.debounce => + @setState(scrolling: false) + , 250 + @_onStoppedScroll() module.exports = ScrollRegion diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee index 0951ae8fc..28e3c8575 100644 --- a/src/flux/stores/draft-store.coffee +++ b/src/flux/stores/draft-store.coffee @@ -205,7 +205,7 @@ class DraftStore @_newMessageWithContext context, (thread, message) -> forwardMessage: message - _newMessageWithContext: ({thread, threadId, message, messageId}, attributesCallback) => + _newMessageWithContext: ({thread, threadId, message, messageId, popout}, attributesCallback) => return unless NamespaceStore.current() # We accept all kinds of context. You can pass actual thread and message objects, @@ -227,7 +227,7 @@ class DraftStore queries.message = DatabaseStore.find(Message, messageId) queries.message.include(Message.attributes.body) else - queries.message = DatabaseStore.findBy(Message, {threadId: threadId}).order(Message.attributes.date.descending()).limit(1) + queries.message = DatabaseStore.findBy(Message, {threadId: threadId ? thread.id}).order(Message.attributes.date.descending()).limit(1) queries.message.include(Message.attributes.body) # Waits for the query promises to resolve and then resolve with a hash @@ -304,7 +304,9 @@ class DraftStore @_draftSessions[draftLocalId] = new DraftStoreProxy(draftLocalId, draft) DatabaseStore.bindToLocalId(draft, draftLocalId) - DatabaseStore.persistModel(draft) + DatabaseStore.persistModel(draft).then => + Actions.composePopoutDraft(draftLocalId) if popout + # Eventually we'll want a nicer solution for inline attachments _formatBodyForQuoting: (body="") => diff --git a/static/components/scroll-region.less b/static/components/scroll-region.less index be5f6c8bd..04a3715c0 100644 --- a/static/components/scroll-region.less +++ b/static/components/scroll-region.less @@ -13,6 +13,7 @@ transform: translate(-15px, 0); position: relative; white-space:nowrap; + pointer-events: none; } .scroll-tooltip:after, .scroll-tooltip:before { left: 100%; @@ -75,10 +76,12 @@ border-radius:8px; .tooltip { opacity: 0; + display:none; transition: opacity 0.3s; top: 50%; transform: translate(-100%, -50%); position: absolute; + pointer-events: none; } } } @@ -98,7 +101,8 @@ border:1px solid lighten(@gray, 20%); .tooltip { opacity: 1; + display:block; } } } -} \ No newline at end of file +} diff --git a/static/variables/ui-variables.less b/static/variables/ui-variables.less index 3960699d0..793a5ceb5 100644 --- a/static/variables/ui-variables.less +++ b/static/variables/ui-variables.less @@ -334,11 +334,11 @@ //** Text color of active list items @list-selected-color: inherit; //** Background color of active list items -@list-selected-bg: fade(@component-active-color, 17%); +@list-selected-bg: mix(@component-active-color, @list-bg, 17%); //** Border color of active list elements -@list-selected-border: fade(@component-active-color, 50%); +@list-selected-border: mix(@component-active-color, @list-bg, 50%); //** Text color for content within active list items -@list-selected-color-muted: lighten(@list-selected-bg, 40%); +@list-selected-color-muted: lighten(@list-selected-bg, 40%); //** Text color of disabled list items @list-disabled-color: @gray-light;