mirror of
https://github.com/zoriya/flood.git
synced 2026-06-01 18:47:44 +00:00
Improve notifications
This commit is contained in:
@@ -13,7 +13,8 @@ const TorrentActions = {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_ADD_TORRENT_SUCCESS,
|
||||
data: {
|
||||
request: options,
|
||||
count: options.urls.length,
|
||||
destination: options.destination,
|
||||
response
|
||||
}
|
||||
});
|
||||
@@ -37,6 +38,8 @@ const TorrentActions = {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_ADD_TORRENT_SUCCESS,
|
||||
data: {
|
||||
count: filesData.getAll('torrents').length,
|
||||
destination,
|
||||
response
|
||||
}
|
||||
});
|
||||
@@ -59,13 +62,19 @@ const TorrentActions = {
|
||||
.then((data) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_REMOVE_TORRENT_SUCCESS,
|
||||
data
|
||||
data: {
|
||||
data,
|
||||
count: hash.length
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_REMOVE_TORRENT_ERROR,
|
||||
error
|
||||
error: {
|
||||
error,
|
||||
count: hash.length
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -169,7 +178,10 @@ const TorrentActions = {
|
||||
.then((data) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS,
|
||||
data
|
||||
data: {
|
||||
data,
|
||||
count: hashes.length
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import BaseIcon from './BaseIcon';
|
||||
|
||||
export default class CircleCheckmarkIcon extends BaseIcon {
|
||||
render() {
|
||||
return (
|
||||
<svg className={`icon icon--circle-checkmark ${this.props.className}`}
|
||||
xmlns={this.getXmlns()} viewBox={this.getViewBox()}>
|
||||
<path fillOpacity="0.05" d="M30,0A30,30,0,1,1,0,30,30,30,0,0,1,30,0Z"/>
|
||||
<path fillOpacity="0.2" d="M30,0A30,30,0,1,0,60,30,30,30,0,0,0,30,0Zm0,56.47A26.47,26.47,0,1,1,56.47,30,26.47,26.47,0,0,1,30,56.47Z"/>
|
||||
<polygon points="43.93 19.51 27.64 35.46 19.07 27.07 16.5 29.58 27.64 40.5 46.5 22.03 43.93 19.51"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import BaseIcon from './BaseIcon';
|
||||
|
||||
export default class CircleExclamationIcon extends BaseIcon {
|
||||
render() {
|
||||
return (
|
||||
<svg className={`icon icon--circle-checkmark ${this.props.className}`}
|
||||
xmlns={this.getXmlns()} viewBox={this.getViewBox()}>
|
||||
<path fillOpacity="0.05" d="M30,0A30,30,0,1,1,0,30,30,30,0,0,1,30,0Z"/>
|
||||
<path fillOpacity="0.2" d="M30,0A30,30,0,1,0,60,30,30,30,0,0,0,30,0Zm0,56.47A26.47,26.47,0,1,1,56.47,30,26.47,26.47,0,0,1,30,56.47Z"/>
|
||||
<path d="M30,39.18a3.12,3.12,0,0,1,2.26.83,3,3,0,0,1,0,4.21,3.48,3.48,0,0,1-4.5,0,2.79,2.79,0,0,1-.86-2.1A2.82,2.82,0,0,1,27.75,40,3.07,3.07,0,0,1,30,39.18Zm2.31-3H27.68L27,16.72H33Z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import Close from '../icons/Close';
|
||||
import File from '../icons/File';
|
||||
import Files from '../icons/Files';
|
||||
import ModalActions from './ModalActions';
|
||||
import SettingsStore from '../../stores/SettingsStore';
|
||||
import TorrentActions from '../../actions/TorrentActions';
|
||||
|
||||
const METHODS_TO_BIND = [
|
||||
@@ -75,7 +74,7 @@ export default class AddTorrentsByFile extends React.Component {
|
||||
<File />
|
||||
</span>
|
||||
<span className="dropzone__file__item dropzone__file__item--file-name">
|
||||
{file.name}{file.name}
|
||||
{file.name}
|
||||
</span>
|
||||
<span className="dropzone__file__item dropzone__file__item--icon dropzone__file__item--remove-icon" onClick={this.handleFileRemove.bind(this, index)}>
|
||||
<Close />
|
||||
@@ -106,8 +105,6 @@ export default class AddTorrentsByFile extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsStore.saveSettings({id: 'torrentDestination', data: this.state.destination});
|
||||
|
||||
this.setState({isAddingTorrents: true});
|
||||
|
||||
let fileData = new FormData();
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
|
||||
import AddTorrentsActions from './AddTorrentsActions';
|
||||
import AddTorrentsDestination from './AddTorrentsDestination';
|
||||
import SettingsStore from '../../stores/SettingsStore';
|
||||
import TextboxRepeater from '../forms/TextboxRepeater';
|
||||
import TorrentActions from '../../actions/TorrentActions';
|
||||
|
||||
@@ -21,7 +20,7 @@ export default class AddTorrentsByURL extends React.Component {
|
||||
|
||||
this.state = {
|
||||
addTorrentsError: null,
|
||||
destination: null,
|
||||
destination: '',
|
||||
isAddingTorrents: false,
|
||||
urlTextboxes: [{value: ''}],
|
||||
startTorrents: true
|
||||
@@ -40,7 +39,6 @@ export default class AddTorrentsByURL extends React.Component {
|
||||
destination: this.state.destination,
|
||||
start: this.state.startTorrents
|
||||
});
|
||||
SettingsStore.saveSettings({id: 'torrentDestination', data: this.state.destination});
|
||||
}
|
||||
|
||||
handleDestinationChange(destination) {
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class AddTorrentsDestination extends React.Component {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
destination: null
|
||||
destination: ''
|
||||
};
|
||||
|
||||
METHODS_TO_BIND.forEach((method) => {
|
||||
@@ -20,7 +20,7 @@ export default class AddTorrentsDestination extends React.Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let destination = SettingsStore.getSettings('torrentDestination');
|
||||
let destination = SettingsStore.getSettings('torrentDestination') || '';
|
||||
if (this.props.suggested) {
|
||||
destination = this.props.suggested;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import CircleCheckmarkIcon from '../icons/CircleCheckmarkIcon';
|
||||
import CircleExclamationIcon from '../icons/CircleExclamationIcon';
|
||||
import stringUtil from '../../../../../shared/util/stringUtil';
|
||||
|
||||
export default class Notification extends React.Component {
|
||||
render() {
|
||||
let icon = <CircleCheckmarkIcon />;
|
||||
let countText = null;
|
||||
let itemText = this.props.subject;
|
||||
let notificationClasses = classnames('notification', {
|
||||
'is-success': this.props.type === 'success',
|
||||
'is-error': this.props.type === 'error'
|
||||
});
|
||||
|
||||
if (this.props.type === 'error') {
|
||||
icon = <CircleExclamationIcon />;
|
||||
}
|
||||
|
||||
if (this.props.count !== 1) {
|
||||
countText = (
|
||||
<span className="notification__count">
|
||||
{this.props.count}
|
||||
</span>
|
||||
);
|
||||
|
||||
itemText = stringUtil.pluralize(itemText, this.props.count);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={notificationClasses}>
|
||||
{icon}
|
||||
<span className="notification__content">
|
||||
{this.props.adverb} {this.props.action} {countText} {itemText}.
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Notification.defaultProps = {
|
||||
count: 0,
|
||||
type: 'success'
|
||||
};
|
||||
|
||||
Notification.propTypes = {
|
||||
count: React.PropTypes.number,
|
||||
action: React.PropTypes.string.isRequired,
|
||||
adverb: React.PropTypes.string.isRequired,
|
||||
subject: React.PropTypes.string.isRequired,
|
||||
subject: React.PropTypes.string
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import EventTypes from '../../constants/EventTypes';
|
||||
import Notification from './Notification';
|
||||
import NotificationStore from '../../stores/NotificationStore';
|
||||
|
||||
const METHODS_TO_BIND = ['handleNotificationChange'];
|
||||
@@ -31,11 +32,7 @@ export default class NotificationList extends React.Component {
|
||||
|
||||
getNotifications() {
|
||||
return this.state.notifications.map((notification, index) => {
|
||||
return (
|
||||
<li className="notifications__list__item" key={index}>
|
||||
{notification.content}
|
||||
</li>
|
||||
);
|
||||
return <Notification {...notification} key={index} />;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,31 +27,17 @@ class NotificationStoreClass extends BaseStore {
|
||||
notification.duration = this.getDuration(notification);
|
||||
notification.id = this.getID(notification);
|
||||
|
||||
if (notification.content == null) {
|
||||
throw new Error('Notification content cannot be empty.');
|
||||
}
|
||||
|
||||
if (!!notification.accumulation) {
|
||||
this.accumulate(notification);
|
||||
}
|
||||
|
||||
this.scheduleCleanse(notification);
|
||||
|
||||
this.notifications[notification.id] = {
|
||||
content: this.getContent(notification)
|
||||
};
|
||||
this.notifications[notification.id] = notification;
|
||||
|
||||
this.emit(EventTypes.NOTIFICATIONS_CHANGE);
|
||||
}
|
||||
|
||||
getContent(notification) {
|
||||
if (!!notification.accumulation) {
|
||||
return notification.content(this.accumulation[notification.accumulation.id]);
|
||||
}
|
||||
|
||||
return notification.content;
|
||||
}
|
||||
|
||||
getDuration(notification) {
|
||||
return notification.duration || DEFAULT_DURATION;
|
||||
}
|
||||
@@ -60,7 +46,13 @@ class NotificationStoreClass extends BaseStore {
|
||||
let notificationIDs = Object.keys(this.notifications).sort();
|
||||
|
||||
return notificationIDs.map((id) => {
|
||||
return this.notifications[id];
|
||||
let notification = this.notifications[id];
|
||||
|
||||
if (!!notification.accumulation) {
|
||||
notification.count = this.accumulation[notification.accumulation.id];
|
||||
}
|
||||
|
||||
return notification;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {filterTorrents} from '../util/filterTorrents';
|
||||
import NotificationStore from './NotificationStore';
|
||||
import {searchTorrents} from '../util/searchTorrents';
|
||||
import {selectTorrents} from '../util/selectTorrents';
|
||||
import SettingsStore from './SettingsStore';
|
||||
import {sortTorrents} from '../util/sortTorrents';
|
||||
import TorrentActions from '../actions/TorrentActions';
|
||||
import TorrentFilterStore from './TorrentFilterStore';
|
||||
@@ -100,22 +101,20 @@ class TorrentStoreClass extends BaseStore {
|
||||
this.emit(EventTypes.CLIENT_ADD_TORRENT_ERROR);
|
||||
}
|
||||
|
||||
handleAddTorrentSuccess(responseData) {
|
||||
handleAddTorrentSuccess(response) {
|
||||
this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS);
|
||||
|
||||
NotificationStore.add({
|
||||
content: function (count = 0) {
|
||||
if (count === 1) {
|
||||
return 'Successfully added torrent.';
|
||||
}
|
||||
SettingsStore.saveSettings({id: 'torrentDestination', data: response.destination});
|
||||
|
||||
return `Successfully added ${count} torrents.`;
|
||||
},
|
||||
NotificationStore.add({
|
||||
adverb: 'Successfully',
|
||||
action: 'added',
|
||||
subject: 'torrent',
|
||||
accumulation: {
|
||||
id: 'add-torrents',
|
||||
value: responseData.request.urls.length || 1
|
||||
id: 'add-torrents-success',
|
||||
value: response.count || 1
|
||||
},
|
||||
id: 'add-torrents'
|
||||
id: 'add-torrents-success'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -135,12 +134,34 @@ class TorrentStoreClass extends BaseStore {
|
||||
return this.sortedTorrents;
|
||||
}
|
||||
|
||||
handleMoveTorrentsSuccess(data) {
|
||||
handleMoveTorrentsSuccess(response) {
|
||||
this.emit(EventTypes.CLIENT_MOVE_TORRENTS_SUCCESS);
|
||||
|
||||
NotificationStore.add({
|
||||
adverb: 'Successfully',
|
||||
action: 'moved',
|
||||
accumulation: {
|
||||
id: 'move-torrents-success',
|
||||
value: response.count
|
||||
},
|
||||
id: 'move-torrents-success',
|
||||
subject: 'torrent'
|
||||
});
|
||||
}
|
||||
|
||||
handleMoveTorrentsError(error) {
|
||||
this.emit(EventTypes.CLIENT_MOVE_TORRENTS_REQUEST_ERROR);
|
||||
|
||||
NotificationStore.add({
|
||||
adverb: 'Failed to',
|
||||
action: 'move',
|
||||
subject: 'torrent',
|
||||
accumulation: {
|
||||
id: 'move-torrents-error',
|
||||
value: error.count
|
||||
},
|
||||
id: 'move-torrents-error'
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedTorrents(event, hash) {
|
||||
@@ -153,11 +174,23 @@ class TorrentStoreClass extends BaseStore {
|
||||
this.emit(EventTypes.UI_TORRENT_SELECTION_CHANGE);
|
||||
}
|
||||
|
||||
handleFetchTorrentsError(action) {
|
||||
console.log(action);
|
||||
handleFetchTorrentsError(error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
handleFetchTorrentsSuccess(torrents) {
|
||||
NotificationStore.add({
|
||||
adverb: 'Successfully',
|
||||
action: 'fetched',
|
||||
duration: 20000,
|
||||
subject: 'torrent',
|
||||
accumulation: {
|
||||
id: 'remove-torrents-error',
|
||||
value: 1
|
||||
},
|
||||
id: 'remove-torrents-error'
|
||||
});
|
||||
|
||||
this.sortTorrents(torrents);
|
||||
this.filterTorrents();
|
||||
|
||||
@@ -165,6 +198,32 @@ class TorrentStoreClass extends BaseStore {
|
||||
this.resolveRequest('fetch-torrents');
|
||||
}
|
||||
|
||||
handleRemoveTorrentsSuccess(response) {
|
||||
NotificationStore.add({
|
||||
adverb: 'Successfully',
|
||||
action: 'removed',
|
||||
subject: 'torrent',
|
||||
accumulation: {
|
||||
id: 'remove-torrents-error',
|
||||
value: response.count
|
||||
},
|
||||
id: 'remove-torrents-error'
|
||||
});
|
||||
}
|
||||
|
||||
handleRemoveTorrentsError(error) {
|
||||
NotificationStore.add({
|
||||
adverb: 'Failed to',
|
||||
action: 'remove',
|
||||
subject: 'torrent',
|
||||
accumulation: {
|
||||
id: 'remove-torrents-error',
|
||||
value: error.count
|
||||
},
|
||||
id: 'remove-torrents-error'
|
||||
});
|
||||
}
|
||||
|
||||
setTorrentDetails(hash, torrentDetails) {
|
||||
this.torrents[hash].details = torrentDetails;
|
||||
this.resolveRequest('fetch-torrent-details');
|
||||
@@ -231,14 +290,20 @@ TorrentStore.dispatcherID = AppDispatcher.register((payload) => {
|
||||
case ActionTypes.CLIENT_FETCH_TORRENTS_SUCCESS:
|
||||
TorrentStore.handleFetchTorrentsSuccess(action.data.torrents);
|
||||
break;
|
||||
case ActionTypes.CLIENT_FETCH_TORRENTS_ERROR:
|
||||
TorrentStore.handleFetchTorrentsError(action.error);
|
||||
break;
|
||||
case ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS:
|
||||
TorrentStore.handleMoveTorrentsSuccess(action.data);
|
||||
break;
|
||||
case ActionTypes.CLIENT_MOVE_TORRENTS_ERROR:
|
||||
TorrentStore.handleMoveTorrentsError(action.error);
|
||||
break;
|
||||
case ActionTypes.CLIENT_FETCH_TORRENTS_ERROR:
|
||||
TorrentStore.handleFetchTorrentsError();
|
||||
case ActionTypes.CLIENT_REMOVE_TORRENT_SUCCESS:
|
||||
TorrentStore.handleRemoveTorrentsSuccess(action.data);
|
||||
break;
|
||||
case ActionTypes.CLIENT_REMOVE_TORRENT_ERROR:
|
||||
TorrentStore.handleRemoveTorrentsError(action.error);
|
||||
break;
|
||||
case ActionTypes.UI_CLICK_TORRENT:
|
||||
TorrentStore.setSelectedTorrents(action.data.event, action.data.hash);
|
||||
|
||||
Reference in New Issue
Block a user