Mailspring/packages/client-app/internal_packages/thread-list/lib/thread-toolbar-buttons.jsx
Juan Tejada a8fbcb0c93 [client-app] Measure and report archiving times
Summary:
This commit makes so it we report perf metrics for archive actions.
To achieve this, I added a new `ThreadListActionsStore` which serves as
a proxy for thread actions, which allow us to time them.

The new store is in charge of listening to thread list actions, creating and
queueing  the appropriate tasks for any given action, and timing and
reporting action times to our MetricsReporter.

This commit only times archiving actions, and subsequent diffs will time
other relevant thread list actions.

Test Plan: manual

Reviewers: halla, spang, evan

Reviewed By: spang, evan

Differential Revision: https://phab.nylas.com/D3983
2017-02-21 11:50:55 -08:00

324 lines
8.2 KiB
JavaScript

import React from "react";
import classNames from 'classnames';
import {RetinaImg} from 'nylas-component-kit';
import {
Actions,
TaskFactory,
AccountStore,
CategoryStore,
FocusedContentStore,
FocusedPerspectiveStore,
} from "nylas-exports";
import ThreadListStore from './thread-list-store';
export class ArchiveButton extends React.Component {
static displayName = 'ArchiveButton';
static containerRequired = false;
static propTypes = {
items: React.PropTypes.array.isRequired,
}
_onArchive = (event) => {
Actions.archiveThreads({
threads: this.props.items,
source: "Toolbar Button: Thread List",
})
Actions.popSheet();
event.stopPropagation();
return;
}
render() {
const allowed = FocusedPerspectiveStore.current().canArchiveThreads(this.props.items);
if (!allowed) {
return <span />;
}
return (
<button
tabIndex={-1}
style={{order: -107}}
className="btn btn-toolbar"
title="Archive"
onClick={this._onArchive}
>
<RetinaImg name="toolbar-archive.png" mode={RetinaImg.Mode.ContentIsMask} />
</button>
)
}
}
export class TrashButton extends React.Component {
static displayName = 'TrashButton'
static containerRequired = false;
static propTypes = {
items: React.PropTypes.array.isRequired,
}
_onRemove = (event) => {
const tasks = TaskFactory.tasksForMovingToTrash({threads: this.props.items, source: "Toolbar Button: Thread List"});
Actions.queueTasks(tasks);
Actions.popSheet();
event.stopPropagation();
return;
}
render() {
const allowed = FocusedPerspectiveStore.current().canMoveThreadsTo(this.props.items, 'trash')
if (!allowed) {
return <span />;
}
return (
<button
tabIndex={-1}
style={{order: -106}}
className="btn btn-toolbar"
title="Move to Trash"
onClick={this._onRemove}
>
<RetinaImg name="toolbar-trash.png" mode={RetinaImg.Mode.ContentIsMask} />
</button>
);
}
}
export class MarkAsSpamButton extends React.Component {
static displayName = 'MarkAsSpamButton';
static containerRequired = false;
static propTypes = {
items: React.PropTypes.array.isRequired,
}
_allInSpam() {
return this.props.items.every(item => item.categories.map(c => c.name).includes('spam'));
}
_onNotSpam = (event) => {
const tasks = TaskFactory.tasksForApplyingCategories({
source: "Toolbar Button: Thread List",
threads: this.props.items,
categoriesToAdd: (accountId) => {
const account = AccountStore.accountForId(accountId)
return account.usesFolders() ? [CategoryStore.getInboxCategory(accountId)] : [];
},
categoriesToRemove: (accountId) => {
return [CategoryStore.getSpamCategory(accountId)];
},
})
Actions.queueTasks(tasks);
Actions.popSheet();
event.stopPropagation();
return;
}
_onMarkAsSpam = (event) => {
const tasks = TaskFactory.tasksForMarkingAsSpam({threads: this.props.items, source: "Toolbar Button: Thread List"});
Actions.queueTasks(tasks);
Actions.popSheet();
event.stopPropagation();
return;
}
render() {
if (this._allInSpam()) {
return (
<button
tabIndex={-1}
style={{order: -105}}
className="btn btn-toolbar"
title="Not Spam"
onClick={this._onNotSpam}
>
<RetinaImg name="toolbar-not-spam.png" mode={RetinaImg.Mode.ContentIsMask} />
</button>
)
}
const allowed = FocusedPerspectiveStore.current().canMoveThreadsTo(this.props.items, 'spam');
if (!allowed) {
return <span />;
}
return (
<button
tabIndex={-1}
style={{order: -105}}
className="btn btn-toolbar"
title="Mark as Spam"
onClick={this._onMarkAsSpam}
>
<RetinaImg name="toolbar-spam.png" mode={RetinaImg.Mode.ContentIsMask} />
</button>
);
}
}
export class ToggleStarredButton extends React.Component {
static displayName = 'ToggleStarredButton';
static containerRequired = false;
static propTypes = {
items: React.PropTypes.array.isRequired,
};
_onStar = (event) => {
const task = TaskFactory.taskForInvertingStarred({threads: this.props.items, source: "Toolbar Button: Thread List"});
Actions.queueTask(task);
event.stopPropagation();
return;
}
render() {
const postClickStarredState = this.props.items.every((t) => t.starred === false);
const title = postClickStarredState ? "Star" : "Unstar";
const imageName = postClickStarredState ? "toolbar-star.png" : "toolbar-star-selected.png"
return (
<button
tabIndex={-1}
style={{order: -103}}
className="btn btn-toolbar"
title={title}
onClick={this._onStar}
>
<RetinaImg name={imageName} mode={RetinaImg.Mode.ContentIsMask} />
</button>
);
}
}
export class ToggleUnreadButton extends React.Component {
static displayName = 'ToggleUnreadButton';
static containerRequired = false;
static propTypes = {
items: React.PropTypes.array.isRequired,
}
_onClick = (event) => {
const task = TaskFactory.taskForInvertingUnread({threads: this.props.items, source: "Toolbar Button: Thread List"});
Actions.queueTask(task);
Actions.popSheet();
event.stopPropagation();
return;
}
render() {
const postClickUnreadState = this.props.items.every(t => t.unread === false);
const fragment = postClickUnreadState ? "unread" : "read";
return (
<button
tabIndex={-1}
style={{order: -104}}
className="btn btn-toolbar"
title={`Mark as ${fragment}`}
onClick={this._onClick}
>
<RetinaImg
name={`toolbar-markas${fragment}.png`}
mode={RetinaImg.Mode.ContentIsMask}
/>
</button>
);
}
}
class ThreadArrowButton extends React.Component {
static propTypes = {
getStateFromStores: React.PropTypes.func,
direction: React.PropTypes.string,
command: React.PropTypes.string,
title: React.PropTypes.string,
}
constructor(props) {
super(props);
this.state = this.props.getStateFromStores();
}
componentDidMount() {
this._unsubscribe = ThreadListStore.listen(this._onStoreChange);
this._unsubscribe_focus = FocusedContentStore.listen(this._onStoreChange);
}
componentWillUnmount() {
this._unsubscribe();
this._unsubscribe_focus();
}
_onClick = () => {
if (this.state.disabled) {
return;
}
NylasEnv.commands.dispatch(this.props.command);
return;
}
_onStoreChange = () => {
this.setState(this.props.getStateFromStores());
}
render() {
const {direction, title} = this.props;
const classes = classNames({
"btn-icon": true,
"message-toolbar-arrow": true,
"disabled": this.state.disabled,
});
return (
<div className={`${classes} ${direction}`} onClick={this._onClick} title={title}>
<RetinaImg name={`toolbar-${direction}-arrow.png`} mode={RetinaImg.Mode.ContentIsMask} />
</div>
);
}
}
export const DownButton = () => {
const getStateFromStores = () => {
const selectedId = FocusedContentStore.focusedId('thread');
const lastIndex = ThreadListStore.dataSource().count() - 1
const lastItem = ThreadListStore.dataSource().get(lastIndex);
return {
disabled: (lastItem && lastItem.id === selectedId),
};
}
return (
<ThreadArrowButton
getStateFromStores={getStateFromStores}
direction={"down"}
title={"Next thread"}
command={'core:next-item'}
/>
);
}
DownButton.displayName = 'DownButton';
DownButton.containerRequired = false;
export const UpButton = () => {
const getStateFromStores = () => {
const selectedId = FocusedContentStore.focusedId('thread');
const item = ThreadListStore.dataSource().get(0)
return {
disabled: (item && item.id === selectedId),
};
}
return (
<ThreadArrowButton
getStateFromStores={getStateFromStores}
direction={"up"}
title={"Previous thread"}
command={'core:previous-item'}
/>
);
}
UpButton.displayName = 'UpButton';
UpButton.containerRequired = false;