mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-13 08:04:40 +08:00
UndoRedoButton now shows
Summary: A notification now appears at the bottom of the window when the user performs an action that can be undone Test Plan: Tested manually Reviewers: bengotow Reviewed By: bengotow Subscribers: mg Differential Revision: https://phab.nylas.com/D1820
This commit is contained in:
parent
73ae6ea6f3
commit
17bcb88ae3
11 changed files with 236 additions and 24 deletions
|
@ -57,6 +57,7 @@ class ThreadList extends React.Component
|
||||||
constructor: (@props) ->
|
constructor: (@props) ->
|
||||||
@state =
|
@state =
|
||||||
style: 'unknown'
|
style: 'unknown'
|
||||||
|
task: null
|
||||||
|
|
||||||
componentWillMount: =>
|
componentWillMount: =>
|
||||||
c1 = new ListTabular.Column
|
c1 = new ListTabular.Column
|
||||||
|
@ -169,33 +170,42 @@ class ThreadList extends React.Component
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
if @state.style is 'wide'
|
if @state.style is 'wide'
|
||||||
<MultiselectList
|
<div>
|
||||||
dataStore={ThreadListStore}
|
<MultiselectList
|
||||||
columns={@wideColumns}
|
dataStore={ThreadListStore}
|
||||||
commands={@commands}
|
columns={@wideColumns}
|
||||||
itemPropsProvider={@itemPropsProvider}
|
commands={@commands}
|
||||||
itemHeight={39}
|
itemPropsProvider={@itemPropsProvider}
|
||||||
className="thread-list"
|
itemHeight={39}
|
||||||
scrollTooltipComponent={ThreadListScrollTooltip}
|
className="thread-list"
|
||||||
emptyComponent={EmptyState}
|
scrollTooltipComponent={ThreadListScrollTooltip}
|
||||||
onDragStart={@_onDragStart}
|
emptyComponent={EmptyState}
|
||||||
onDragEnd={@_onDragEnd}
|
onDragStart={@_onDragStart}
|
||||||
draggable="true"
|
onDragEnd={@_onDragEnd}
|
||||||
collection="thread" />
|
draggable="true"
|
||||||
|
collection="thread" />
|
||||||
|
</div>
|
||||||
else if @state.style is 'narrow'
|
else if @state.style is 'narrow'
|
||||||
<MultiselectList
|
<div>
|
||||||
dataStore={ThreadListStore}
|
<MultiselectList
|
||||||
columns={@narrowColumns}
|
dataStore={ThreadListStore}
|
||||||
commands={@commands}
|
columns={@narrowColumns}
|
||||||
itemPropsProvider={@itemPropsProvider}
|
commands={@commands}
|
||||||
itemHeight={90}
|
itemPropsProvider={@itemPropsProvider}
|
||||||
className="thread-list thread-list-narrow"
|
itemHeight={90}
|
||||||
scrollTooltipComponent={ThreadListScrollTooltip}
|
className="thread-list thread-list-narrow"
|
||||||
emptyComponent={EmptyState}
|
scrollTooltipComponent={ThreadListScrollTooltip}
|
||||||
collection="thread" />
|
emptyComponent={EmptyState}
|
||||||
|
collection="thread" />
|
||||||
|
</div>
|
||||||
else
|
else
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
||||||
|
# _renderUndoRedo: =>
|
||||||
|
# if @state.task
|
||||||
|
# <UndoRedoComponent task:{null}
|
||||||
|
# show:{false} />
|
||||||
|
|
||||||
_threadIdAtPoint: (x, y) ->
|
_threadIdAtPoint: (x, y) ->
|
||||||
item = document.elementFromPoint(event.clientX, event.clientY).closest('.list-item')
|
item = document.elementFromPoint(event.clientX, event.clientY).closest('.list-item')
|
||||||
return null unless item
|
return null unless item
|
||||||
|
|
13
internal_packages/undo-redo/lib/main.cjsx
Normal file
13
internal_packages/undo-redo/lib/main.cjsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ComponentRegistry, WorkspaceStore} = require 'nylas-exports'
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
activate: (@state={}) ->
|
||||||
|
UndoRedoComponent = require "./undo-redo-component"
|
||||||
|
|
||||||
|
ComponentRegistry.register UndoRedoComponent,
|
||||||
|
location: WorkspaceStore.Location.ThreadList
|
||||||
|
|
||||||
|
deactivate: ->
|
||||||
|
ComponentRegistry.unregister UndoRedoComponent
|
||||||
|
|
||||||
|
serialize: -> @state
|
90
internal_packages/undo-redo/lib/undo-redo-component.cjsx
Normal file
90
internal_packages/undo-redo/lib/undo-redo-component.cjsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
_ = require 'underscore'
|
||||||
|
path = require 'path'
|
||||||
|
React = require 'react'
|
||||||
|
classNames = require 'classnames'
|
||||||
|
{RetinaImg, TimeoutTransitionGroup} = require 'nylas-component-kit'
|
||||||
|
{Actions,
|
||||||
|
Utils,
|
||||||
|
ComponentRegistry,
|
||||||
|
UndoRedoStore,
|
||||||
|
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
|
||||||
|
|
||||||
|
_onChange: =>
|
||||||
|
@setState(@_getStateFromStores(), =>
|
||||||
|
@_setNewTimeout())
|
||||||
|
|
||||||
|
_clearTimeout: =>
|
||||||
|
clearTimeout(@_timeout)
|
||||||
|
|
||||||
|
_setNewTimeout: =>
|
||||||
|
clearTimeout(@_timeout)
|
||||||
|
@_timeout = setTimeout (=>
|
||||||
|
@_hide()
|
||||||
|
return
|
||||||
|
), 3000
|
||||||
|
|
||||||
|
_getStateFromStores: ->
|
||||||
|
t = UndoRedoStore.getMostRecentTask()
|
||||||
|
s = false
|
||||||
|
if t
|
||||||
|
s = true
|
||||||
|
|
||||||
|
return {show: s, task: t}
|
||||||
|
|
||||||
|
componentWillMount: ->
|
||||||
|
@unsub = UndoRedoStore.listen(@_onChange)
|
||||||
|
|
||||||
|
componentWillUnmount: ->
|
||||||
|
@unsub()
|
||||||
|
|
||||||
|
render: =>
|
||||||
|
items = [].concat(@_renderUndoRedoManager())
|
||||||
|
|
||||||
|
names = classNames
|
||||||
|
"undo-redo-manager": true
|
||||||
|
|
||||||
|
<TimeoutTransitionGroup
|
||||||
|
className={names}
|
||||||
|
leaveTimeout={450}
|
||||||
|
enterTimeout={250}
|
||||||
|
transitionName="undo-redo-item">
|
||||||
|
{items}
|
||||||
|
</TimeoutTransitionGroup>
|
||||||
|
|
||||||
|
_renderUndoRedoManager: =>
|
||||||
|
if @state.show
|
||||||
|
<div className="undo-redo" onMouseEnter={@_clearTimeout} onMouseLeave={@_setNewTimeout}>
|
||||||
|
<div className="undo-redo-message-wrapper">
|
||||||
|
<p className="undo-redo-message">{@_formatMessage()}</p>
|
||||||
|
</div>
|
||||||
|
<div className="undo-redo-action-wrapper" onClick={@_undoTask}>
|
||||||
|
<RetinaImg name="undo-icon@2x.png"
|
||||||
|
mode={RetinaImg.Mode.ContentPreserve}/>
|
||||||
|
<p className="undo-redo-action-text">Undo</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
|
||||||
|
_undoTask: =>
|
||||||
|
atom.commands.dispatch(document.querySelector('body'), 'core:undo')
|
||||||
|
@_hide()
|
||||||
|
|
||||||
|
_formatMessage: =>
|
||||||
|
return @state.task.description()
|
||||||
|
|
||||||
|
_hide: =>
|
||||||
|
@setState({show: false, task: null})
|
||||||
|
|
||||||
|
module.exports = UndoRedoComponent
|
13
internal_packages/undo-redo/package.json
Normal file
13
internal_packages/undo-redo/package.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "undo-redo",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "./lib/main",
|
||||||
|
"description": "Undo modal button",
|
||||||
|
"license": "Proprietary",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"atom": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
}
|
||||||
|
}
|
53
internal_packages/undo-redo/stylesheets/undo-redo.less
Normal file
53
internal_packages/undo-redo/stylesheets/undo-redo.less
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
@import "ui-variables";
|
||||||
|
@import "ui-mixins";
|
||||||
|
|
||||||
|
.undo-redo{
|
||||||
|
position: absolute;
|
||||||
|
height: 45px;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 39%;
|
||||||
|
padding-top: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
background: @gray-darker;
|
||||||
|
|
||||||
|
.undo-redo-message-wrapper{
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-right: 30px;
|
||||||
|
|
||||||
|
.undo-redo-message{
|
||||||
|
color: lighten(@gray-base, 70%);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-redo-action-wrapper{
|
||||||
|
float: right;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
|
.undo-redo-action-text{
|
||||||
|
margin-left: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
color: @white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-redo-item-enter {
|
||||||
|
opacity: 0.01;
|
||||||
|
transition: all .3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-redo-item-enter.undo-redo-item-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-redo-item-leave {
|
||||||
|
opacity: 1;
|
||||||
|
transition: all .3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-redo-item-leave.undo-redo-item-leave-active {
|
||||||
|
opacity: 0.01;
|
||||||
|
}
|
|
@ -25,11 +25,12 @@ class UndoRedoStore
|
||||||
if task.canBeUndone() and not task.isUndo()
|
if task.canBeUndone() and not task.isUndo()
|
||||||
@_redo = []
|
@_redo = []
|
||||||
@_undo.push(task)
|
@_undo.push(task)
|
||||||
|
@trigger() unless task._isReverting
|
||||||
|
|
||||||
undo: =>
|
undo: =>
|
||||||
topTask = @_undo.pop()
|
topTask = @_undo.pop()
|
||||||
return unless topTask
|
return unless topTask
|
||||||
|
@trigger()
|
||||||
Actions.queueTask(topTask.createUndoTask())
|
Actions.queueTask(topTask.createUndoTask())
|
||||||
@_redo.push(topTask.createIdenticalTask())
|
@_redo.push(topTask.createIdenticalTask())
|
||||||
|
|
||||||
|
@ -38,6 +39,10 @@ class UndoRedoStore
|
||||||
return unless redoTask
|
return unless redoTask
|
||||||
Actions.queueTask(redoTask)
|
Actions.queueTask(redoTask)
|
||||||
|
|
||||||
|
getMostRecentTask: =>
|
||||||
|
for idx in [@_undo.length-1...-1]
|
||||||
|
return @_undo[idx] unless @_undo[idx]._isReverting
|
||||||
|
|
||||||
print: ->
|
print: ->
|
||||||
console.log("Undo Stack")
|
console.log("Undo Stack")
|
||||||
console.log(@_undo)
|
console.log(@_undo)
|
||||||
|
|
|
@ -23,6 +23,19 @@ class ChangeFolderTask extends ChangeCategoryTask
|
||||||
|
|
||||||
label: -> "Moving to folder…"
|
label: -> "Moving to folder…"
|
||||||
|
|
||||||
|
description: ->
|
||||||
|
folderName = @folderOrId.displayName
|
||||||
|
if @threadIds.length > 0
|
||||||
|
if @threadIds.length > 1
|
||||||
|
return "Moving " + @threadIds.length + " threads to \"" + folderName + "\""
|
||||||
|
return "Moving 1 thread to \"" + folderName + "\""
|
||||||
|
else if @messageIds.length > 0
|
||||||
|
if @messageIds.length > 1
|
||||||
|
return "Moving " + @messageIds.length + "messages to \"" + folderName + "\""
|
||||||
|
return "Moving 1 message to \"" + folderName + "\""
|
||||||
|
else
|
||||||
|
return "Moving objects to \"" + folderName + "\""
|
||||||
|
|
||||||
collectCategories: ->
|
collectCategories: ->
|
||||||
if @folderOrId instanceof Folder
|
if @folderOrId instanceof Folder
|
||||||
return Promise.props
|
return Promise.props
|
||||||
|
|
|
@ -23,6 +23,12 @@ class ChangeLabelsTask extends ChangeCategoryTask
|
||||||
|
|
||||||
label: -> "Applying labels…"
|
label: -> "Applying labels…"
|
||||||
|
|
||||||
|
description: ->
|
||||||
|
addingMessage = "Adding " + @labelsToAdd.length + " labels"
|
||||||
|
removingMessage = "Removing " + @labelsToRemove.length + " labels"
|
||||||
|
|
||||||
|
return addingMessage + " " + removingMessage
|
||||||
|
|
||||||
collectCategories: ->
|
collectCategories: ->
|
||||||
labelOrIdPromiseMapper = (labelOrId) ->
|
labelOrIdPromiseMapper = (labelOrId) ->
|
||||||
if labelOrId instanceof Label
|
if labelOrId instanceof Label
|
||||||
|
|
|
@ -20,6 +20,10 @@ class UpdateNylasObjectsTask extends Task
|
||||||
endpoint: (obj) ->
|
endpoint: (obj) ->
|
||||||
inflection.pluralize(obj.constructor.name.toLowerCase())
|
inflection.pluralize(obj.constructor.name.toLowerCase())
|
||||||
|
|
||||||
|
# OVERRIDE ME
|
||||||
|
description: ->
|
||||||
|
'Updating Nylas object'
|
||||||
|
|
||||||
performLocal: ({reverting}={}) ->
|
performLocal: ({reverting}={}) ->
|
||||||
if reverting or @isUndo()
|
if reverting or @isUndo()
|
||||||
Promise.map @objects, (obj) =>
|
Promise.map @objects, (obj) =>
|
||||||
|
|
|
@ -134,6 +134,11 @@ class Task
|
||||||
throw new Error('Cannot send message to terminated process')
|
throw new Error('Cannot send message to terminated process')
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
|
# Public: Describe the function of the task. Each task should override this
|
||||||
|
# to explain its individual function
|
||||||
|
description: ->
|
||||||
|
''
|
||||||
|
|
||||||
# Public: Call a function when an event is emitted by the child process
|
# Public: Call a function when an event is emitted by the child process
|
||||||
#
|
#
|
||||||
# * `eventName` The {String} name of the event to handle.
|
# * `eventName` The {String} name of the event to handle.
|
||||||
|
|
BIN
static/images/thread-list/undo-icon@2x.png
Normal file
BIN
static/images/thread-list/undo-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 796 B |
Loading…
Add table
Reference in a new issue