mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-04 19:54:32 +08:00
fix(*): Minor performance tweaks and fixes to category picker
Summary: fix(undo-redo): UndoRedoComponent does not take props fix(category-picker): - Use Actions.queueTask like the rest of the app so UndoRedoStore can see it. Can change this in the future but it's currently the only place in the app we directly queue tasks. - Stop subscribing to the FocusedContentStore / FocusedCategoryStore (which are not used in setState?) since we receive threads as props - Rename categoryDatum to item because it's not a category. (Was super confused that categories were becoming JSON in `_extendCategoryWithDisplayData`) Give item a category property so that tasks can specify items and not IDs (allows for better descriptions like "Moved one thread to Archive" Add simple shouldComponentUpdate to retina-img Test Plan: Run tests Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1832
This commit is contained in:
parent
8884435da0
commit
9b2f830348
8 changed files with 56 additions and 61 deletions
|
@ -10,7 +10,6 @@ React = require 'react'
|
|||
WorkspaceStore,
|
||||
ChangeLabelsTask,
|
||||
ChangeFolderTask,
|
||||
FocusedContentStore,
|
||||
FocusedCategoryStore} = require 'nylas-exports'
|
||||
|
||||
{Menu,
|
||||
|
@ -33,8 +32,6 @@ class CategoryPicker extends React.Component
|
|||
@unsubscribers = []
|
||||
@unsubscribers.push CategoryStore.listen @_onStoreChanged
|
||||
@unsubscribers.push NamespaceStore.listen @_onStoreChanged
|
||||
@unsubscribers.push FocusedContentStore.listen @_onStoreChanged
|
||||
@unsubscribers.push FocusedCategoryStore.listen @_onStoreChanged
|
||||
|
||||
@_commandUnsubscriber = atom.commands.add 'body',
|
||||
"application:change-category": @_onOpenCategoryPopover
|
||||
|
@ -94,7 +91,7 @@ class CategoryPicker extends React.Component
|
|||
headerComponents={headerComponents}
|
||||
footerComponents={[]}
|
||||
items={@state.categoryData}
|
||||
itemKey={ (categoryDatum) -> categoryDatum.id }
|
||||
itemKey={ (item) -> item.id }
|
||||
itemContent={@_renderItemContent}
|
||||
onSelect={@_onSelectCategory}
|
||||
defaultSelectedIndex={if @state.searchValue is "" then -1 else 0}
|
||||
|
@ -107,55 +104,55 @@ class CategoryPicker extends React.Component
|
|||
@refs.popover.open()
|
||||
return
|
||||
|
||||
_renderItemContent: (categoryDatum) =>
|
||||
if categoryDatum.divider
|
||||
return <Menu.Item divider={categoryDatum.divider} />
|
||||
_renderItemContent: (item) =>
|
||||
if item.divider
|
||||
return <Menu.Item divider={item.divider} />
|
||||
if @_namespace?.usesLabels()
|
||||
icon = @_renderCheckbox(categoryDatum)
|
||||
icon = @_renderCheckbox(item)
|
||||
else if @_namespace?.usesFolders()
|
||||
icon = @_renderFolderIcon(categoryDatum)
|
||||
icon = @_renderFolderIcon(item)
|
||||
else return <span></span>
|
||||
|
||||
<div className="category-item">
|
||||
{icon}
|
||||
<div className="category-display-name">
|
||||
{@_renderBoldedSearchResults(categoryDatum)}
|
||||
{@_renderBoldedSearchResults(item)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
_renderCheckbox: (categoryDatum) ->
|
||||
_renderCheckbox: (item) ->
|
||||
styles = {}
|
||||
styles.backgroundColor = categoryDatum.backgroundColor
|
||||
styles.backgroundColor = item.backgroundColor
|
||||
|
||||
if categoryDatum.usage is 0
|
||||
if item.usage is 0
|
||||
checkStatus = <span></span>
|
||||
else if categoryDatum.usage < categoryDatum.numThreads
|
||||
else if item.usage < item.numThreads
|
||||
checkStatus = <RetinaImg
|
||||
className="check-img dash"
|
||||
name="tagging-conflicted.png"
|
||||
mode={RetinaImg.Mode.ContentPreserve}
|
||||
onClick={=> @_onSelectCategory(categoryDatum)}/>
|
||||
onClick={=> @_onSelectCategory(item)}/>
|
||||
else
|
||||
checkStatus = <RetinaImg
|
||||
className="check-img check"
|
||||
name="tagging-checkmark.png"
|
||||
mode={RetinaImg.Mode.ContentPreserve}
|
||||
onClick={=> @_onSelectCategory(categoryDatum)}/>
|
||||
onClick={=> @_onSelectCategory(item)}/>
|
||||
|
||||
<div className="check-wrap" style={styles}>
|
||||
<RetinaImg
|
||||
className="check-img check"
|
||||
name="tagging-checkbox.png"
|
||||
mode={RetinaImg.Mode.ContentPreserve}
|
||||
onClick={=> @_onSelectCategory(categoryDatum)}/>
|
||||
onClick={=> @_onSelectCategory(item)}/>
|
||||
{checkStatus}
|
||||
</div>
|
||||
|
||||
_renderFolderIcon: (categoryDatum) ->
|
||||
<RetinaImg name={"#{categoryDatum.name}.png"} fallback={'folder.png'} mode={RetinaImg.Mode.ContentIsMask} />
|
||||
_renderFolderIcon: (item) ->
|
||||
<RetinaImg name={"#{item.name}.png"} fallback={'folder.png'} mode={RetinaImg.Mode.ContentIsMask} />
|
||||
|
||||
_renderBoldedSearchResults: (categoryDatum) ->
|
||||
name = categoryDatum.display_name
|
||||
_renderBoldedSearchResults: (item) ->
|
||||
name = item.display_name
|
||||
searchTerm = (@state.searchValue ? "").trim()
|
||||
|
||||
return name if searchTerm.length is 0
|
||||
|
@ -173,33 +170,35 @@ class CategoryPicker extends React.Component
|
|||
else return part
|
||||
return <span>{parts}</span>
|
||||
|
||||
_onSelectCategory: (categoryDatum) =>
|
||||
_onSelectCategory: (item) =>
|
||||
return unless @_threads().length > 0
|
||||
return unless @_namespace
|
||||
@refs.menu.setSelectedItem(null)
|
||||
|
||||
if @_namespace.usesLabels()
|
||||
if categoryDatum.usage > 0
|
||||
if item.usage > 0
|
||||
task = new ChangeLabelsTask
|
||||
labelsToRemove: [categoryDatum.id]
|
||||
labelsToRemove: [item.category]
|
||||
threadIds: @_threadIds()
|
||||
else
|
||||
task = new ChangeLabelsTask
|
||||
labelsToAdd: [categoryDatum.id]
|
||||
labelsToAdd: [item.category]
|
||||
threadIds: @_threadIds()
|
||||
Actions.queueTask(task)
|
||||
|
||||
else if @_namespace.usesFolders()
|
||||
task = new ChangeFolderTask
|
||||
folderOrId: categoryDatum.id
|
||||
folderOrId: item.category
|
||||
threadIds: @_threadIds()
|
||||
if @props.thread
|
||||
Actions.moveThread(@props.thread, task)
|
||||
else if @props.items
|
||||
Actions.moveThreads(@_threads(), task)
|
||||
|
||||
else throw new Error("Invalid organizationUnit")
|
||||
else
|
||||
throw new Error("Invalid organizationUnit")
|
||||
|
||||
@refs.popover.close()
|
||||
TaskQueue.enqueue(task)
|
||||
|
||||
_onStoreChanged: =>
|
||||
@setState @_recalculateState(@props)
|
||||
|
@ -219,6 +218,7 @@ class CategoryPicker extends React.Component
|
|||
numThreads = @_threads(props).length
|
||||
if numThreads is 0
|
||||
return {categoryData: [], searchValue}
|
||||
|
||||
@_namespace = NamespaceStore.current()
|
||||
return unless @_namespace
|
||||
|
||||
|
@ -235,7 +235,7 @@ class CategoryPicker extends React.Component
|
|||
categoryData = _.chain(categories)
|
||||
.filter(_.partial(@_isUserFacing, allInInbox))
|
||||
.filter(_.partial(@_isInSearch, searchValue))
|
||||
.map(_.partial(@_extendCategoryWithDisplayData, displayData))
|
||||
.map(_.partial(@_itemForCategory, displayData))
|
||||
.value()
|
||||
|
||||
return {categoryData, searchValue}
|
||||
|
@ -267,14 +267,15 @@ class CategoryPicker extends React.Component
|
|||
inbox = CategoryStore.getStandardCategory("inbox")
|
||||
return usageCount[inbox.id] is numThreads
|
||||
|
||||
_extendCategoryWithDisplayData: ({usageCount, numThreads}, category) ->
|
||||
_itemForCategory: ({usageCount, numThreads}, category) ->
|
||||
return category if category.divider
|
||||
cat = category.toJSON()
|
||||
usage = usageCount[cat.id] ? 0
|
||||
cat.backgroundColor = LabelColorizer.backgroundColorDark(category)
|
||||
cat.usage = usage
|
||||
cat.numThreads = numThreads
|
||||
return cat
|
||||
|
||||
item = category.toJSON()
|
||||
item.category = category
|
||||
item.backgroundColor = LabelColorizer.backgroundColorDark(category)
|
||||
item.usage = usageCount[category.id] ? 0
|
||||
item.numThreads = numThreads
|
||||
item
|
||||
|
||||
_threadCategories: (thread) =>
|
||||
if @_namespace.usesLabels()
|
||||
|
@ -283,7 +284,7 @@ class CategoryPicker extends React.Component
|
|||
return (thread.folders ? [])
|
||||
else throw new Error("Invalid organizationUnit")
|
||||
|
||||
_threads: (props=@props) =>
|
||||
_threads: (props = @props) =>
|
||||
if props.items then return (props.items ? [])
|
||||
else if props.thread then return [props.thread]
|
||||
else return []
|
||||
|
|
|
@ -261,7 +261,6 @@ class ComposerView extends React.Component
|
|||
tabIndex="108"
|
||||
placeholder="Subject"
|
||||
disabled={not @state.showsubject}
|
||||
className="compose-field compose-subject"
|
||||
value={@state.subject}
|
||||
onChange={@_onChangeSubject}/>
|
||||
</div>
|
||||
|
|
|
@ -134,7 +134,6 @@
|
|||
.compose-subject-wrap {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 11px @spacing-standard 11px 0;
|
||||
margin: 0 23px;
|
||||
border-bottom: 1px solid @border-color-divider;
|
||||
flex-shrink:0;
|
||||
|
@ -146,16 +145,15 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
input.compose-field {
|
||||
input {
|
||||
display: inline-block;
|
||||
width: calc(~"100% - 61px");
|
||||
padding: 0;
|
||||
padding: 13px 0 9px 0;
|
||||
margin: 0 0 0 5px;
|
||||
min-width: 5em;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
input.compose-field.compose-subject {
|
||||
input {
|
||||
&::-webkit-input-placeholder {
|
||||
color: @text-color-very-subtle;
|
||||
}
|
||||
|
|
|
@ -245,15 +245,12 @@ class MessageList extends React.Component
|
|||
<MailLabel label={label} key={label.id} onRemove={ => @_onRemoveLabel(label) }/>
|
||||
|
||||
_renderReplyArea: =>
|
||||
if @_hasReplyArea()
|
||||
<div className="footer-reply-area-wrap" onClick={@_onClickReplyArea} key={Utils.generateTempId()}>
|
||||
<div className="footer-reply-area">
|
||||
<RetinaImg name="#{@_replyType()}-footer.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
<span className="reply-text">Write a reply…</span>
|
||||
</div>
|
||||
<div className="footer-reply-area-wrap" onClick={@_onClickReplyArea} key='reply-area'>
|
||||
<div className="footer-reply-area">
|
||||
<RetinaImg name="#{@_replyType()}-footer.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
<span className="reply-text">Write a reply…</span>
|
||||
</div>
|
||||
else
|
||||
<div key={Utils.generateTempId()}></div>
|
||||
</div>
|
||||
|
||||
_hasReplyArea: =>
|
||||
not _.last(@state.messages)?.draft
|
||||
|
@ -308,7 +305,8 @@ class MessageList extends React.Component
|
|||
collapsed={collapsed}
|
||||
isLastMsg={(messages.length - 1 is idx)} />
|
||||
|
||||
components.push @_renderReplyArea()
|
||||
if @_hasReplyArea()
|
||||
components.push @_renderReplyArea()
|
||||
|
||||
return components
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
margin: 0 auto;
|
||||
padding: @message-spacing 0;
|
||||
&:last-child {
|
||||
padding-bottom: @message-spacing * 2;
|
||||
padding-bottom: @spacing-double;
|
||||
}
|
||||
.message-item-white-wrap {
|
||||
background: @background-primary;
|
||||
|
|
|
@ -12,10 +12,6 @@ NamespaceStore} = require 'nylas-exports'
|
|||
class UndoRedoComponent extends React.Component
|
||||
@displayName: 'UndoRedoComponent'
|
||||
|
||||
@propTypes:
|
||||
task: React.PropTypes.object.isRequired
|
||||
show: React.PropTypes.bool
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = @_getStateFromStores()
|
||||
@_timeout = null
|
||||
|
|
|
@ -98,7 +98,10 @@ class RetinaImg extends React.Component
|
|||
active: React.PropTypes.bool
|
||||
resourcePath: React.PropTypes.string
|
||||
|
||||
render: ->
|
||||
shouldComponentUpdate: (nextProps) =>
|
||||
not _.isEqual(@props, nextProps)
|
||||
|
||||
render: =>
|
||||
path = @props.url ? @_pathFor(@props.name) ? @_pathFor(@props.fallback) ? ''
|
||||
pathIsRetina = path.indexOf('@2x') > 0
|
||||
className = @props.className ? ''
|
||||
|
@ -125,7 +128,7 @@ class RetinaImg extends React.Component
|
|||
otherProps = _.omit(@props, _.keys(@constructor.propTypes))
|
||||
<img className={className} src={path} style={style} {...otherProps} />
|
||||
|
||||
_pathFor: (name) ->
|
||||
_pathFor: (name) =>
|
||||
return null unless name and _.isString(name)
|
||||
|
||||
[basename, ext] = name.split('.')
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid @border-color-divider;
|
||||
min-height: 30px;
|
||||
min-height: 42px;
|
||||
position: relative;
|
||||
|
||||
.content-container {
|
||||
|
@ -134,7 +134,7 @@
|
|||
|
||||
.tokenizing-field-input {
|
||||
position: relative;
|
||||
padding-left: 2.1em;
|
||||
padding-left: 2.8em;
|
||||
|
||||
&:hover {
|
||||
cursor: text;
|
||||
|
|
Loading…
Add table
Reference in a new issue