mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 05:06:53 +08:00
Summary: This implements EditableList re-ordering via a new prop callback. You can drag and drop items in the mail rules list and the accounts list. Note that you can't drag between lists - right now this is just to enable re-ordering. Test Plan: No new specs yet Reviewers: evan, juan Reviewed By: juan Differential Revision: https://phab.nylas.com/D2495
This commit is contained in:
parent
7612ecc8cd
commit
0fb109aeee
8 changed files with 155 additions and 3 deletions
|
@ -7,6 +7,7 @@ class PreferencesAccountList extends Component {
|
|||
accounts: PropTypes.array,
|
||||
selected: PropTypes.object,
|
||||
onAddAccount: PropTypes.func.isRequired,
|
||||
onReorderAccount: PropTypes.func.isRequired,
|
||||
onSelectAccount: PropTypes.func.isRequired,
|
||||
onRemoveAccount: PropTypes.func.isRequired,
|
||||
}
|
||||
|
@ -45,6 +46,7 @@ class PreferencesAccountList extends Component {
|
|||
items={this.props.accounts}
|
||||
itemContent={this._renderAccount}
|
||||
selected={this.props.selected}
|
||||
onReorderItem={this.props.onReorderAccount}
|
||||
onCreateItem={this.props.onAddAccount}
|
||||
onSelectItem={this.props.onSelectAccount}
|
||||
onDeleteItem={this.props.onRemoveAccount} />
|
||||
|
|
|
@ -30,6 +30,9 @@ class PreferencesAccounts extends React.Component
|
|||
ipc = require('electron').ipcRenderer
|
||||
ipc.send('command', 'application:add-account')
|
||||
|
||||
_onReorderAccount: (account, oldIdx, newIdx) =>
|
||||
Actions.reorderAccount(account.id, newIdx)
|
||||
|
||||
_onSelectAccount: (account) =>
|
||||
@setState(selected: account)
|
||||
|
||||
|
@ -49,6 +52,7 @@ class PreferencesAccounts extends React.Component
|
|||
accounts={@state.accounts}
|
||||
selected={@state.selected}
|
||||
onAddAccount={@_onAddAccount}
|
||||
onReorderAccount={@_onReorderAccount}
|
||||
onSelectAccount={@_onSelectAccount}
|
||||
onRemoveAccount={@_onRemoveAccount} />
|
||||
<PreferencesAccountDetails
|
||||
|
|
|
@ -86,6 +86,7 @@ class PreferencesMailRules extends React.Component
|
|||
items={@state.rules}
|
||||
itemContent={@_renderListItemContent}
|
||||
onCreateItem={@_onAddRule}
|
||||
onReorderItem={@_onReorderRule}
|
||||
onDeleteItem={@_onDeleteRule}
|
||||
onItemEdited={@_onRuleNameEdited}
|
||||
selected={@state.selectedRule}
|
||||
|
@ -96,7 +97,7 @@ class PreferencesMailRules extends React.Component
|
|||
return <div className="item-rule-disabled">{rule.name}</div>
|
||||
else
|
||||
return rule.name
|
||||
|
||||
|
||||
_renderDetail: =>
|
||||
rule = @state.selectedRule
|
||||
|
||||
|
@ -177,6 +178,9 @@ class PreferencesMailRules extends React.Component
|
|||
_onSelectRule: (rule, idx) =>
|
||||
@setState(selectedRule: rule)
|
||||
|
||||
_onReorderRule: (rule, idx, newIdx) =>
|
||||
Actions.reorderMailRule(rule.id, newIdx)
|
||||
|
||||
_onDeleteRule: (rule, idx) =>
|
||||
Actions.deleteMailRule(rule.id)
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import React, {Component, PropTypes} from 'react';
|
|||
* @param {props.onCreateItem} props.onCreateItem
|
||||
* @param {props.onDeleteItem} props.onDeleteItem
|
||||
* @param {props.onSelectItem} props.onSelectItem
|
||||
* @param {props.onReorderItem} props.onReorderItem
|
||||
* @param {props.onItemEdited} props.onItemEdited
|
||||
* @param {props.onItemCreated} props.onItemCreated
|
||||
* @class EditableList
|
||||
|
@ -76,6 +77,15 @@ class EditableList extends Component {
|
|||
* @callback props.onItemCreated
|
||||
* @param {string} value - The value for the new item
|
||||
*/
|
||||
/**
|
||||
* If provided, the user will be able to drag and drop items to re-arrange them
|
||||
* within the list. Note that dragging between lists is not supported.
|
||||
* @callback props.onReorderItem
|
||||
* @param {Object} item - The item that was dragged
|
||||
* @param {number} startIdx - The index the item was dragged from
|
||||
* @param {number} endIdx - The new index of the item, assuming it was
|
||||
already removed from startIdx.
|
||||
*/
|
||||
static propTypes = {
|
||||
items: PropTypes.array.isRequired,
|
||||
itemContent: PropTypes.func,
|
||||
|
@ -84,6 +94,7 @@ class EditableList extends Component {
|
|||
createInputProps: PropTypes.object,
|
||||
onCreateItem: PropTypes.func,
|
||||
onDeleteItem: PropTypes.func,
|
||||
onReorderItem: PropTypes.func,
|
||||
onItemEdited: PropTypes.func,
|
||||
onItemCreated: PropTypes.func,
|
||||
initialState: PropTypes.object,
|
||||
|
@ -107,6 +118,7 @@ class EditableList extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = props.initialState || {
|
||||
dropInsertionIndex: -1,
|
||||
editingIndex: null,
|
||||
creatingItem: false,
|
||||
};
|
||||
|
@ -263,6 +275,70 @@ class EditableList extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
_onItemDragStart = (event)=> {
|
||||
if (!this.props.onReorderItem) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const row = event.target.closest('[data-item-idx]') || event.target;
|
||||
const wrapperId = React.findDOMNode(this.refs.itemsWrapper).dataset.reactid;
|
||||
|
||||
if (!row.dataset.itemIdx) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('editablelist-index', row.dataset.itemIdx);
|
||||
event.dataTransfer.setData('editablelist-reactid', wrapperId);
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
}
|
||||
|
||||
_onDragOver = (event)=> {
|
||||
const wrapperNode = React.findDOMNode(this.refs.itemsWrapper);
|
||||
const originWrapperId = event.dataTransfer.getData('editablelist-reactid')
|
||||
const originSameList = (originWrapperId === wrapperNode.dataset.reactid);
|
||||
let dropInsertionIndex = 0;
|
||||
|
||||
if ((event.currentTarget === wrapperNode) && originSameList) {
|
||||
const itemNodes = wrapperNode.querySelectorAll('[data-item-idx]')
|
||||
for (let i = 0; i < itemNodes.length; i ++) {
|
||||
const itemNode = itemNodes[i];
|
||||
const rect = itemNode.getBoundingClientRect();
|
||||
if (event.clientY > rect.top + (rect.height / 2)) {
|
||||
dropInsertionIndex = itemNode.dataset.itemIdx / 1 + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dropInsertionIndex = -1;
|
||||
}
|
||||
|
||||
if (this.state.dropInsertionIndex !== dropInsertionIndex) {
|
||||
this.setState({dropInsertionIndex: dropInsertionIndex});
|
||||
}
|
||||
}
|
||||
|
||||
_onDragLeave = ()=> {
|
||||
this.setState({dropInsertionIndex: -1});
|
||||
}
|
||||
|
||||
_onDrop = (event)=> {
|
||||
if (this.state.dropInsertionIndex !== -1) {
|
||||
const startIdx = event.dataTransfer.getData('editablelist-index');
|
||||
if (startIdx && (this.state.dropInsertionIndex !== startIdx)) {
|
||||
const item = this.props.items[startIdx];
|
||||
|
||||
let endIdx = this.state.dropInsertionIndex;
|
||||
if (endIdx > startIdx) {
|
||||
endIdx -= 1
|
||||
}
|
||||
|
||||
this.props.onReorderItem(item, startIdx, endIdx);
|
||||
this.setState({dropInsertionIndex: -1});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renderers
|
||||
|
||||
|
@ -327,6 +403,9 @@ class EditableList extends Component {
|
|||
<div
|
||||
className={classes}
|
||||
key={idx}
|
||||
data-item-idx={idx}
|
||||
draggable
|
||||
onDragStart={this._onItemDragStart}
|
||||
onClick={_.partial(onClick, _, item, idx)}
|
||||
onDoubleClick={_.partial(onEdit, _, item, idx)}>
|
||||
{itemContent}
|
||||
|
@ -357,12 +436,22 @@ class EditableList extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
_renderDropInsertion = ()=> {
|
||||
return (
|
||||
<div className="insertion-point"><div></div></div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
let items = this.props.items.map( (item, idx)=> this._renderItem(item, idx));
|
||||
if (this.state.creatingItem === true) {
|
||||
items = items.concat(this._renderCreateInput());
|
||||
}
|
||||
|
||||
if (this.state.dropInsertionIndex !== -1) {
|
||||
items.splice(this.state.dropInsertionIndex, 0, this._renderDropInsertion());
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyCommandsRegion
|
||||
tabIndex="1"
|
||||
|
@ -370,7 +459,10 @@ class EditableList extends Component {
|
|||
className={`nylas-editable-list ${this.props.className}`}>
|
||||
<ScrollRegion
|
||||
className="items-wrapper"
|
||||
ref="itemsWrapper" >
|
||||
ref="itemsWrapper"
|
||||
onDragOver={this._onDragOver}
|
||||
onDragLeave={this._onDragLeave}
|
||||
onDrop={this._onDrop}>
|
||||
{items}
|
||||
</ScrollRegion>
|
||||
{this._renderButtons()}
|
||||
|
|
|
@ -167,6 +167,17 @@ class Actions
|
|||
###
|
||||
@updateAccount: ActionScopeWindow
|
||||
|
||||
###
|
||||
Public: Re-order the provided account in the account list.
|
||||
|
||||
*Scope: Window*
|
||||
|
||||
```
|
||||
Actions.reorderAccount(account.id, newIndex)
|
||||
```
|
||||
###
|
||||
@reorderAccount: ActionScopeWindow
|
||||
|
||||
###
|
||||
Public: Select the provided sheet in the current window. This action changes
|
||||
the top level sheet.
|
||||
|
@ -487,6 +498,7 @@ class Actions
|
|||
@recordUserEvent: ActionScopeWindow
|
||||
|
||||
@addMailRule: ActionScopeWindow
|
||||
@reorderMailRule: ActionScopeWindow
|
||||
@updateMailRule: ActionScopeWindow
|
||||
@deleteMailRule: ActionScopeWindow
|
||||
@disableMailRule: ActionScopeWindow
|
||||
|
|
|
@ -26,6 +26,7 @@ class AccountStore
|
|||
@_load()
|
||||
@listenTo Actions.removeAccount, @_onRemoveAccount
|
||||
@listenTo Actions.updateAccount, @_onUpdateAccount
|
||||
@listenTo Actions.reorderAccount, @_onReorderAccount
|
||||
|
||||
@_caches = {}
|
||||
|
||||
|
@ -81,6 +82,16 @@ class AccountStore
|
|||
else
|
||||
@trigger()
|
||||
|
||||
_onReorderAccount: (id, newIdx) =>
|
||||
existingIdx = _.findIndex @_accounts, (a) -> a.id is id
|
||||
return if existingIdx is -1
|
||||
account = @_accounts[existingIdx]
|
||||
@_caches = {}
|
||||
@_accounts.splice(existingIdx, 1)
|
||||
@_accounts.splice(newIdx, 0, account)
|
||||
@_save()
|
||||
@trigger()
|
||||
|
||||
addAccountFromJSON: (json) =>
|
||||
if not json.email_address or not json.provider
|
||||
console.error("Returned account data is invalid", json)
|
||||
|
|
|
@ -14,7 +14,7 @@ RulesJSONBlobKey = "MailRules-V2"
|
|||
class MailRulesStore extends NylasStore
|
||||
constructor: ->
|
||||
@_rules = []
|
||||
|
||||
|
||||
query = DatabaseStore.findJSONBlob(RulesJSONBlobKey)
|
||||
@_subscription = Rx.Observable.fromQuery(query).subscribe (rules) =>
|
||||
@_rules = rules ? []
|
||||
|
@ -22,6 +22,7 @@ class MailRulesStore extends NylasStore
|
|||
|
||||
@listenTo Actions.addMailRule, @_onAddMailRule
|
||||
@listenTo Actions.deleteMailRule, @_onDeleteMailRule
|
||||
@listenTo Actions.reorderMailRule, @_onReorderMailRule
|
||||
@listenTo Actions.updateMailRule, @_onUpdateMailRule
|
||||
@listenTo Actions.disableMailRule, @_onDisableMailRule
|
||||
@listenTo Actions.notificationActionTaken, @_onNotificationActionTaken
|
||||
|
@ -37,6 +38,15 @@ class MailRulesStore extends NylasStore
|
|||
@_saveMailRules()
|
||||
@trigger()
|
||||
|
||||
_onReorderMailRule: (id, newIdx) =>
|
||||
currentIdx = _.findIndex(@_rules, _.matcher({id}))
|
||||
return if currentIdx is -1
|
||||
rule = @_rules[currentIdx]
|
||||
@_rules.splice(currentIdx, 1)
|
||||
@_rules.splice(newIdx, 0, rule)
|
||||
@_saveMailRules()
|
||||
@trigger()
|
||||
|
||||
_onAddMailRule: (properties) =>
|
||||
defaults =
|
||||
id: Utils.generateTempId()
|
||||
|
|
|
@ -21,6 +21,23 @@
|
|||
color: @component-active-bg;
|
||||
}
|
||||
|
||||
.insertion-point {
|
||||
display:block;
|
||||
width:100%;
|
||||
height: 0;
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
|
||||
div {
|
||||
height:2px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
background-color: @component-active-color;
|
||||
box-shadow: 0 0 1px fade(@component-active-color, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
|
|
Loading…
Reference in a new issue