mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-05 07:35:34 +08:00
fix(*): Throttle so that DatabaseView won't pile on queries during scroll, better small attachment styles
Summary: fix(attachment): Bad filenames breaking icons fix developer bar colors fix critical bug with files Render small attachments inline-block, without hover effect, and with nice dotted transparency background Test Plan: No new tests Reviewers: evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D1661
This commit is contained in:
parent
0e96dd3372
commit
19d97e5393
10 changed files with 96 additions and 43 deletions
|
@ -14,12 +14,13 @@ class ImageAttachmentComponent extends AttachmentComponent
|
|||
</span>
|
||||
|
||||
<div className="attachment-file-actions">
|
||||
{@_fileActions()}
|
||||
{@_renderFileActions()}
|
||||
</div>
|
||||
|
||||
<div className="attachment-preview" onClick={@_onClickView}>
|
||||
<div className="attachment-name-bg"></div>
|
||||
<div className="attachment-name">{@props.file.filename}</div>
|
||||
<div className="attachment-name-container">
|
||||
<div className="attachment-name">{@props.file.filename}</div>
|
||||
</div>
|
||||
{@_imgOrLoader()}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -79,8 +79,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.image-file-upload, .image-attachment-file-wrap, .attachment-file-wrap,
|
||||
.image-file-upload,
|
||||
.image-attachment-file-wrap,
|
||||
.attachment-file-wrap,
|
||||
.attachment-inner-wrap {
|
||||
|
||||
.attachment-download-bar-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
@ -128,10 +131,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.image-attachment-file-wrap, .image-file-upload {
|
||||
.image-attachment-file-wrap,
|
||||
.image-file-upload {
|
||||
|
||||
position: relative;
|
||||
margin: 0 0 8px 0;
|
||||
text-align: center;
|
||||
display:inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: @spacing-standard;
|
||||
margin-right: @spacing-standard;
|
||||
|
||||
.attachment-download-progress,
|
||||
.attachment-upload-progress {
|
||||
|
@ -147,11 +155,11 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
.attachment-file-actions, .attachment-name-bg, .attachment-name {
|
||||
.attachment-file-actions, .attachment-name-container, .attachment-name {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.attachment-file-actions, .attachment-name-bg, .attachment-name {
|
||||
.attachment-file-actions, .attachment-name-container, .attachment-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -167,30 +175,35 @@
|
|||
.attachment-preview {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.attachment-name-bg {
|
||||
.attachment-name-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.75) 0%,rgba(0,0,0,0) 23%)
|
||||
}
|
||||
.attachment-name {
|
||||
color: @white;
|
||||
left: 15px;
|
||||
bottom: 13px;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
height:100%;
|
||||
min-height:300px;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.75) 0%,rgba(0,0,0,0) 23%);
|
||||
vertical-align:bottom;
|
||||
|
||||
.attachment-name {
|
||||
color: @white;
|
||||
left: @spacing-standard;
|
||||
bottom: @spacing-standard;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 100%;
|
||||
background: @background-secondary;
|
||||
background: url(../static/images/attachments/transparency-background.png) top left repeat;
|
||||
background-size:8px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@ class ImageFileUpload extends FileUpload
|
|||
</div>
|
||||
|
||||
<div className="attachment-preview" >
|
||||
<div className="attachment-name-bg"></div>
|
||||
<div className="attachment-name">{@props.uploadData.fileName}</div>
|
||||
<div className="attachment-name-container">
|
||||
<div className="attachment-name">{@props.uploadData.fileName}</div>
|
||||
</div>
|
||||
<DraggableImg src={@props.uploadData.filePath} />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
order:1000;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
|
||||
|
||||
.resizable {
|
||||
display: flex;
|
||||
width:100%;
|
||||
|
@ -49,12 +49,12 @@
|
|||
font-size: 13px;
|
||||
line-height: 15px;
|
||||
height: 25px;
|
||||
background-color: #999;
|
||||
background: rgba(60,60,60,1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #AAA;
|
||||
background: rgba(40,40,40,1);
|
||||
}
|
||||
|
||||
.fa-caret-square-o-down,
|
||||
|
@ -146,7 +146,7 @@
|
|||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&.long-polling {
|
||||
.item {
|
||||
|
@ -214,5 +214,3 @@
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ class ThreadListIcon extends React.Component
|
|||
thread: React.PropTypes.object
|
||||
|
||||
_iconType: =>
|
||||
myEmail = NamespaceStore.current()?.emailAddress
|
||||
if !@props.thread
|
||||
return 'thread-icon-star-on-hover'
|
||||
|
||||
if @props.thread.hasTagId('starred')
|
||||
return 'thread-icon-star'
|
||||
|
@ -23,6 +24,7 @@ class ThreadListIcon extends React.Component
|
|||
msgs = @_nonDraftMessages()
|
||||
last = msgs[msgs.length - 1]
|
||||
|
||||
myEmail = NamespaceStore.current()?.emailAddress
|
||||
if msgs.length > 1 and last.from[0]?.email is myEmail
|
||||
if Utils.isForwardedMessage(last)
|
||||
return 'thread-icon-forwarded thread-icon-star-on-hover'
|
||||
|
|
|
@ -159,13 +159,19 @@ class ModelQuery
|
|||
Query Execution
|
||||
###
|
||||
|
||||
# Public: Starts query execution and returns a Promise.
|
||||
# Public: Short-hand syntax that calls run().then(fn) with the provided function.
|
||||
#
|
||||
# Returns a {Promise} that resolves with the Models returned by the
|
||||
# query, or rejects with an error from the Database layer.
|
||||
#
|
||||
then: (next) ->
|
||||
@_database.run(@).then(next)
|
||||
@run(@).then(next)
|
||||
|
||||
# Public: Returns a {Promise} that resolves with the Models returned by the
|
||||
# query, or rejects with an error from the Database layer.
|
||||
#
|
||||
run: ->
|
||||
@_database.run(@)
|
||||
|
||||
formatResult: (result) ->
|
||||
return null unless result
|
||||
|
|
|
@ -6,6 +6,36 @@ EventEmitter = require('events').EventEmitter
|
|||
|
||||
verbose = true
|
||||
|
||||
# A small helper class that prevents the DatabaseView from making too many
|
||||
# queries. It tracks the number of jobs in flight via `increment` and allows
|
||||
# a callback to run "when there are fewer then N ongoing queries".
|
||||
# Sort of like _.throttle, but with a work threshold rather than a time threshold.
|
||||
class TaskThrottler
|
||||
constructor: (@_maxConcurrent) ->
|
||||
@_inflight = 0
|
||||
@_whenReady = null
|
||||
|
||||
whenReady: (fn) ->
|
||||
if @_inflight < @_maxConcurrent
|
||||
fn()
|
||||
else
|
||||
@_whenReady = fn
|
||||
|
||||
increment: ->
|
||||
decremented = false
|
||||
@_inflight += 1
|
||||
|
||||
# Returns a function that can be called once and only once to
|
||||
# decrement the counter.
|
||||
return =>
|
||||
if not decremented
|
||||
@_inflight -= 1
|
||||
if @_whenReady and @_inflight < @_maxConcurrent
|
||||
@_whenReady()
|
||||
@_whenReady = null
|
||||
decremented = true
|
||||
|
||||
|
||||
# Public: DatabaseView abstracts away the process of paginating a query
|
||||
# and loading ranges of data. It's very smart about deciding when
|
||||
# results need to be refreshed. There are a few core concepts that
|
||||
|
@ -34,13 +64,15 @@ class DatabaseView extends ModelView
|
|||
constructor: (@klass, config = {}, @_metadataProvider) ->
|
||||
super
|
||||
@_pageSize = 100
|
||||
@_throttler = new TaskThrottler(2)
|
||||
|
||||
@_matchers = config.matchers ? []
|
||||
@_includes = config.includes ? []
|
||||
@_orders = config.orders ? []
|
||||
|
||||
@_count = -1
|
||||
@invalidateCount()
|
||||
@invalidateRetainedRangeImmediate()
|
||||
@invalidateRetainedRange()
|
||||
@
|
||||
|
||||
log: ->
|
||||
|
@ -251,18 +283,16 @@ class DatabaseView extends ModelView
|
|||
@_count = count
|
||||
@_emitter.emit('trigger')
|
||||
|
||||
invalidateRetainedRange: _.debounce ->
|
||||
@invalidateRetainedRangeImmediate()
|
||||
,10
|
||||
|
||||
invalidateRetainedRangeImmediate: ->
|
||||
for idx in @pagesRetained()
|
||||
@retrievePage(idx)
|
||||
invalidateRetainedRange: ->
|
||||
@_throttler.whenReady =>
|
||||
for idx in @pagesRetained()
|
||||
@retrievePage(idx)
|
||||
|
||||
retrieveDirtyInRetainedRange: ->
|
||||
for idx in @pagesRetained()
|
||||
if not @_pages[idx] or @_pages[idx].lastTouchTime > @_pages[idx].lastLoadTime
|
||||
@retrievePage(idx)
|
||||
@_throttler.whenReady =>
|
||||
for idx in @pagesRetained()
|
||||
if not @_pages[idx] or @_pages[idx].lastTouchTime > @_pages[idx].lastLoadTime
|
||||
@retrievePage(idx)
|
||||
|
||||
retrievePage: (idx) ->
|
||||
page = @_pages[idx] ? {
|
||||
|
@ -284,7 +314,8 @@ class DatabaseView extends ModelView
|
|||
query.include(attr) for attr in @_includes
|
||||
query.order(@_orders) if @_orders.length > 0
|
||||
|
||||
query.then (items) =>
|
||||
decrement = @_throttler.increment()
|
||||
query.run().finally(decrement).then (items) =>
|
||||
# If the page is no longer in the cache at all, it may have fallen out of the
|
||||
# retained range and been cleaned up.
|
||||
return unless @_pages[idx]
|
||||
|
@ -323,7 +354,8 @@ class DatabaseView extends ModelView
|
|||
if idsMissingMetadata.length > 0 and @_metadataProvider
|
||||
metadataPromise = @_metadataProvider(idsMissingMetadata)
|
||||
|
||||
metadataPromise.then (results) =>
|
||||
decrement = @_throttler.increment()
|
||||
metadataPromise.finally(decrement).then (results) =>
|
||||
# If we've started reloading since we made our query, don't do any more work
|
||||
if page.lastTouchTime >= touchTime
|
||||
@log("Metadata version #{touchTime} fetched, but out of date (current is #{page.lastTouchTime})")
|
||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
static/images/attachments/transparency-background.png
Normal file
BIN
static/images/attachments/transparency-background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
Loading…
Reference in a new issue