Mailspring/app/internal_packages/preferences/lib/tabs/preferences-mail-rules.jsx
2017-09-26 11:33:08 -07:00

322 lines
9 KiB
JavaScript

import React from 'react';
import _ from 'underscore';
import {
Actions,
AccountStore,
MailRulesStore,
MailRulesTemplates,
TaskQueue,
ReprocessMailRulesTask,
} from 'nylas-exports';
import {
Flexbox,
EditableList,
RetinaImg,
ScrollRegion,
ScenarioEditor,
} from 'nylas-component-kit';
const { ActionTemplatesForAccount, ConditionTemplatesForAccount } = MailRulesTemplates;
class PreferencesMailRules extends React.Component {
static displayName = 'PreferencesMailRules';
constructor() {
super();
this.state = this._getStateFromStores();
}
componentDidMount() {
this._unsubscribers = [];
this._unsubscribers.push(MailRulesStore.listen(this._onRulesChanged));
this._unsubscribers.push(TaskQueue.listen(this._onTasksChanged));
}
componentWillUnmount() {
this._unsubscribers.forEach(unsubscribe => unsubscribe());
}
_getStateFromStores() {
const accounts = AccountStore.accounts();
const state = this.state || {};
let { currentAccount } = state;
if (!accounts.find(acct => acct === currentAccount)) {
currentAccount = accounts[0];
}
const rules = MailRulesStore.rulesForAccountId(currentAccount.id);
const selectedRule =
this.state && this.state.selectedRule
? rules.find(r => r.id === this.state.selectedRule.id)
: rules[0];
return {
accounts: accounts,
currentAccount: currentAccount,
rules: rules,
selectedRule: selectedRule,
tasks: TaskQueue.findTasks(ReprocessMailRulesTask, {}),
actionTemplates: ActionTemplatesForAccount(currentAccount),
conditionTemplates: ConditionTemplatesForAccount(currentAccount),
};
}
_onSelectAccount = event => {
const accountId = event.target.value;
const currentAccount = this.state.accounts.find(acct => acct.id === accountId);
this.setState({ currentAccount: currentAccount }, () => {
this.setState(this._getStateFromStores());
});
};
_onReprocessRules = () => {
const needsMessageBodies = () => {
for (const rule of this.state.rules) {
for (const condition of rule.conditions) {
if (condition.templateKey === 'body') {
return true;
}
}
}
return false;
};
if (needsMessageBodies()) {
NylasEnv.showErrorDialog(
"One or more of your mail rules requires the bodies of messages being processed. These rules can't be run on your entire mailbox."
);
}
const task = new ReprocessMailRulesTask(this.state.currentAccount.id);
Actions.queueTask(task);
};
_onAddRule = () => {
Actions.addMailRule({ accountId: this.state.currentAccount.id });
};
_onSelectRule = rule => {
this.setState({ selectedRule: rule });
};
_onReorderRule = (rule, startIdx, endIdx) => {
Actions.reorderMailRule(rule.id, endIdx);
};
_onDeleteRule = rule => {
Actions.deleteMailRule(rule.id);
};
_onRuleNameEdited = (newName, rule) => {
Actions.updateMailRule(rule.id, { name: newName });
};
_onRuleConditionModeEdited = event => {
Actions.updateMailRule(this.state.selectedRule.id, { conditionMode: event.target.value });
};
_onRuleEnabled = () => {
Actions.updateMailRule(this.state.selectedRule.id, { disabled: false, disabledReason: null });
};
_onRulesChanged = () => {
const next = this._getStateFromStores();
const nextRules = next.rules;
const prevRules = this.state.rules ? this.state.rules : [];
const added = _.difference(nextRules, prevRules);
if (added.length === 1) {
next.selectedRule = added[0];
}
this.setState(next);
};
_onTasksChanged = () => {
this.setState({ tasks: TaskQueue.findTasks(ReprocessMailRulesTask, {}) });
};
_renderAccountPicker() {
const options = this.state.accounts.map(account => (
<option value={account.id} key={account.id}>
{account.label}
</option>
));
return (
<select
value={this.state.currentAccount.id}
onChange={this._onSelectAccount}
style={{ margin: 0, minWidth: 200 }}
>
{options}
</select>
);
}
_renderMailRules() {
if (this.state.rules.length === 0) {
return (
<div className="empty-list">
<RetinaImg
className="icon-mail-rules"
name="rules-big.png"
mode={RetinaImg.Mode.ContentDark}
/>
<h2>No rules</h2>
<button className="btn btn-small" onClick={this._onAddRule}>
Create a new rule
</button>
</div>
);
}
return (
<Flexbox>
<EditableList
showEditIcon
className="rule-list"
items={this.state.rules}
itemContent={this._renderListItemContent}
onCreateItem={this._onAddRule}
onReorderItem={this._onReorderRule}
onDeleteItem={this._onDeleteRule}
onItemEdited={this._onRuleNameEdited}
selected={this.state.selectedRule}
onSelectItem={this._onSelectRule}
/>
{this._renderDetail()}
</Flexbox>
);
}
_renderListItemContent(rule) {
if (rule.disabled) {
return <div className="item-rule-disabled">{rule.name}</div>;
}
return rule.name;
}
_renderDetail() {
const rule = this.state.selectedRule;
if (rule) {
return (
<ScrollRegion className="rule-detail">
{this._renderDetailDisabledNotice()}
<div className="inner">
<span>If </span>
<select value={rule.conditionMode} onChange={this._onRuleConditionModeEdited}>
<option value="any">Any</option>
<option value="all">All</option>
</select>
<span> of the following conditions are met:</span>
<ScenarioEditor
instances={rule.conditions}
templates={this.state.conditionTemplates}
onChange={conditions => Actions.updateMailRule(rule.id, { conditions })}
className="well well-matchers"
/>
<span>Perform the following actions:</span>
<ScenarioEditor
instances={rule.actions}
templates={this.state.actionTemplates}
onChange={actions => Actions.updateMailRule(rule.id, { actions })}
className="well well-actions"
/>
</div>
</ScrollRegion>
);
}
return (
<div className="rule-detail">
<div className="no-selection">Create a rule or select one to get started</div>
</div>
);
}
_renderDetailDisabledNotice() {
if (!this.state.selectedRule.disabled) return false;
return (
<div className="disabled-reason">
<button className="btn" onClick={this._onRuleEnabled}>
Enable
</button>
This rule has been disabled. Make sure the actions below are valid and re-enable the rule.
<div>({this.state.selectedRule.disabledReason})</div>
</div>
);
}
_renderTasks() {
if (this.state.tasks.length === 0) return false;
return (
<div style={{ flex: 1, paddingLeft: 20 }}>
{this.state.tasks.map(task => {
return (
<Flexbox style={{ alignItems: 'baseline' }}>
<div style={{ paddingRight: '12px' }}>
<RetinaImg
name="sending-spinner.gif"
width={18}
mode={RetinaImg.Mode.ContentPreserve}
/>
</div>
<div>
<strong>{AccountStore.accountForId(task.accountId).emailAddress}</strong>
{`${Number(task.numberOfImpactedItems()).toLocaleString()} processed...`}
</div>
<div style={{ flex: 1 }} />
<button className="btn btn-sm" onClick={() => Actions.cancelTask(task)}>
Cancel
</button>
</Flexbox>
);
})}
</div>
);
}
render() {
const processDisabled = this.state.tasks.some(
task => task.accountId === this.state.currentAccount.id
);
return (
<div className="container-mail-rules">
<section>
<Flexbox className="container-dropdown">
<div>Account:</div>
<div className="dropdown">{this._renderAccountPicker()}</div>
</Flexbox>
<p>Rules only apply to the selected account.</p>
{this._renderMailRules()}
<Flexbox style={{ marginTop: 40, maxWidth: 600 }}>
<div>
<button
disabled={processDisabled}
className="btn"
style={{ float: 'right' }}
onClick={this._onReprocessRules}
>
Process entire inbox
</button>
</div>
{this._renderTasks()}
</Flexbox>
<p style={{ marginTop: 10 }}>
By default, mail rules are only applied to new mail as it arrives. Applying rules to
your entire inbox may take a long time and degrade performance.
</p>
</section>
</div>
);
}
}
export default PreferencesMailRules;