mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-11 01:54:40 +08:00
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
This commit is contained in:
parent
aa10ddfd1c
commit
f7f1ab3605
8 changed files with 120 additions and 17 deletions
|
@ -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 <div className="action" onClick={@_onReply}><RetinaImg name="toolbar-reply.png" mode={RetinaImg.Mode.ContentPreserve} /></div>
|
||||
actions.push <div className="action" onClick={@_onForward}><RetinaImg name="toolbar-forward.png" mode={RetinaImg.Mode.ContentPreserve} /></div>
|
||||
if not @props.thread.hasTagId('archive')
|
||||
actions.push <div className="action" onClick={@_onArchive}><RetinaImg name="toolbar-archive.png" mode={RetinaImg.Mode.ContentPreserve} /></div>
|
||||
|
||||
<div className="inner">
|
||||
{actions}
|
||||
</div>
|
||||
|
||||
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
|
|
@ -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) =>
|
||||
<span className="timestamp">{timestamp(thread.lastMessageTimestamp)}</span>
|
||||
|
||||
@wideColumns = [c1, c2, c3, c4]
|
||||
c5 = new ListTabular.Column
|
||||
name: "HoverActions"
|
||||
resolver: (thread) =>
|
||||
<ThreadListQuickActions thread={thread}/>
|
||||
|
||||
@wideColumns = [c1, c2, c3, c4, c5]
|
||||
|
||||
cNarrow = new ListTabular.Column
|
||||
name: "Item"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -32,7 +32,7 @@ class ListTabularItem extends React.Component
|
|||
className = "list-item list-tabular-item #{@props.itemProps?.className}"
|
||||
props = _.omit(@props.itemProps ? {}, 'className')
|
||||
|
||||
<div {...props} className={className} onClick={@_onClick} style={position:'absolute', top: @props.metrics.top, width:'100%', height:@props.metrics.height}>
|
||||
<div {...props} className={className} onClick={@_onClick} style={position:'absolute', top: @props.metrics.top, width:'100%', height:@props.metrics.height, overflow: 'hidden'}>
|
||||
{@_columns()}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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="") =>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue