feat(notifications): implement a react/redux notification system (#1786)
* feat(notifications): implement a react/redux notification system * squash into impl explicit timeout, style * ref(notifications): convert toastr notifications to use react * ref(toastr): remove library * squash into conversion: pass timeout * squash into clean remove from debian patch
This commit is contained in:
parent
e818fa1e9e
commit
da1c760abf
2
app.js
2
app.js
|
@ -13,8 +13,6 @@ import 'aui-experimental';
|
|||
import 'aui-css';
|
||||
import 'aui-experimental-css';
|
||||
|
||||
window.toastr = require('toastr');
|
||||
|
||||
import conference from './conference';
|
||||
import API from './modules/API';
|
||||
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
|
||||
|
|
107
css/_toastr.scss
107
css/_toastr.scss
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Toastr
|
||||
* Copyright 2012-2014 John Papa and Hans Fjällemark.
|
||||
* All Rights Reserved.
|
||||
* Use, reproduction, distribution, and modification of this code is subject to the terms and
|
||||
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Author: John Papa and Hans Fjällemark
|
||||
* Project: https://github.com/CodeSeven/toastr
|
||||
*
|
||||
* Last updated: October 13, 2016
|
||||
*/
|
||||
|
||||
.toast-title,
|
||||
.toast-message .title {
|
||||
font-weight: bold;
|
||||
margin: 0 0 3px;
|
||||
@include text-truncate;
|
||||
}
|
||||
|
||||
.toast-message {
|
||||
-ms-word-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.toast-message a,
|
||||
.toast-message label,
|
||||
.toast-message .connected,
|
||||
.toast-message .disconnected {
|
||||
color: $notificationLinkColor;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toast-message a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.toast-message br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// close button
|
||||
.toast-close-button {
|
||||
color: $notificationColor;
|
||||
background: transparent;
|
||||
|
||||
font-size: 15px;
|
||||
line-height: 1.2;
|
||||
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: -6px -10px 0 0;
|
||||
float: right;
|
||||
|
||||
cursor: pointer;
|
||||
@include opacity(0.4);
|
||||
/* Additional properties for button version
|
||||
iOS requires the button element instead of an anchor tag.
|
||||
If you want the anchor version, it requires `href="#"`. */
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.toast-close-button:hover,
|
||||
.toast-close-button:focus {
|
||||
@include opacity(1);
|
||||
}
|
||||
|
||||
|
||||
.toast {
|
||||
color: $notificationColor;
|
||||
background-color: $notificationBackground;
|
||||
|
||||
font-size: $notificationFontSize;
|
||||
|
||||
padding: $notificationPadding;
|
||||
border: 1px solid lighten($notificationBackground, 10%);
|
||||
|
||||
@include border-radius($notificationBorderRadius);
|
||||
@include box-shadow(1px, 1px, 2px, rgba(0,0,0,0.3));
|
||||
@include opacity($notificationOpacity);
|
||||
}
|
||||
|
||||
.toast:hover {
|
||||
@include opacity(1);
|
||||
}
|
||||
|
||||
#toast-container {
|
||||
position: fixed;
|
||||
z-index: $notificationZ;
|
||||
}
|
||||
|
||||
#toast-container.notification-bottom-right {
|
||||
$videoOffset: 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
|
||||
bottom: 135px;
|
||||
right: $filmstripToggleButtonWidth + $videoOffset;
|
||||
}
|
||||
|
||||
#toast-container * {
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
#toast-container .toast {
|
||||
width: $notificationWidth;
|
||||
margin: 0 0 8px;
|
||||
}
|
|
@ -85,20 +85,6 @@ $modalMockAKInputBackground: #fafbfc;
|
|||
$modalMockAKInputBorder: 1px solid #f4f5f7;
|
||||
$modalTextColor: #333;
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*/
|
||||
$notificationFontSize: 13px;
|
||||
$notificationColor: #FFFFFF;
|
||||
$notificationBackground: $tooltipBg;
|
||||
$notificationTitleColor: $notificationColor;
|
||||
$notificationMessageColor: $notificationColor;
|
||||
$notificationLinkColor: $notificationColor;
|
||||
$notificationOpacity: 0.9;
|
||||
$notificationPadding: 15px 20px;
|
||||
$notificationBorderRadius: 4px;
|
||||
$notificationWidth: 215px;
|
||||
|
||||
/**
|
||||
* Misc.
|
||||
*/
|
||||
|
@ -126,7 +112,6 @@ $tooltipsZ: 401;
|
|||
$dropdownMaskZ: 900;
|
||||
$dropdownZ: 901;
|
||||
$centeredVideoLabelZ: 1010;
|
||||
$notificationZ: 1011;
|
||||
$jitsipopoverZ: 1012;
|
||||
$popoverZ: 1015;
|
||||
$overlayZ: 1016;
|
||||
|
|
|
@ -159,15 +159,4 @@
|
|||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move toastr closer to the bottom of the screen and move left to avoid
|
||||
* overlapping of videos when they are configured at default height.
|
||||
*/
|
||||
#toast-container {
|
||||
&.notification-bottom-right {
|
||||
bottom: 25px;
|
||||
right: 130 + 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
/* Modules BEGIN */
|
||||
|
||||
@import 'dial-out';
|
||||
@import 'toastr';
|
||||
@import 'base';
|
||||
@import 'utils';
|
||||
@import 'overlay/overlay';
|
||||
|
|
|
@ -3,7 +3,7 @@ Index: jitsi-meet/index.html
|
|||
===================================================================
|
||||
--- jitsi-meet.orig/index.html
|
||||
+++ jitsi-meet/index.html
|
||||
@@ -10,14 +10,14 @@
|
||||
@@ -10,13 +10,13 @@
|
||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
<script src="https://api.callstats.io/static/callstats.min.js"></script>
|
||||
|
@ -19,4 +19,4 @@ Index: jitsi-meet/index.html
|
|||
+ <script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
- <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
|
||||
/* global APP, JitsiMeetJS, $, config, interfaceConfig */
|
||||
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
|
@ -343,26 +343,6 @@ UI.start = function () {
|
|||
}
|
||||
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
toastr.options = {
|
||||
"closeButton": true,
|
||||
"debug": false,
|
||||
"positionClass": "notification-bottom-right",
|
||||
"onclick": null,
|
||||
"showDuration": "300",
|
||||
"hideDuration": "1000",
|
||||
"timeOut": "2000",
|
||||
"extendedTimeOut": "1000",
|
||||
"showEasing": "swing",
|
||||
"hideEasing": "linear",
|
||||
"showMethod": "fadeIn",
|
||||
"hideMethod": "fadeOut",
|
||||
"newestOnTop": false,
|
||||
// this is the default toastr close button html, just adds tabIndex
|
||||
"closeHtml": '<button type="button" tabIndex="-1">×</button>'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -868,7 +848,7 @@ UI.notifyInitiallyMuted = function () {
|
|||
"connected",
|
||||
"notify.muted",
|
||||
null,
|
||||
{ timeOut: 120000 });
|
||||
120000);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
/* global $, APP, toastr */
|
||||
/* global $, APP */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import UIUtil from './UIUtil';
|
||||
import jitsiLocalStorage from '../../util/JitsiLocalStorage';
|
||||
|
||||
import {
|
||||
Notification,
|
||||
showNotification
|
||||
} from '../../../react/features/notifications';
|
||||
|
||||
/**
|
||||
* Flag for enable/disable of the notifications.
|
||||
* @type {boolean}
|
||||
|
@ -448,31 +452,25 @@ var messageHandler = {
|
|||
* @param messageKey the key from the language file for the text of the
|
||||
* message.
|
||||
* @param messageArguments object with the arguments for the message.
|
||||
* @param options passed to toastr (e.g. timeOut)
|
||||
* @param optional configurations for the notification (e.g. timeout)
|
||||
*/
|
||||
participantNotification: function(displayName, displayNameKey, cls,
|
||||
messageKey, messageArguments, options) {
|
||||
|
||||
// If we're in ringing state we skip all toaster notifications.
|
||||
if(!notificationsEnabled || APP.UI.isOverlayVisible())
|
||||
messageKey, messageArguments, timeout) {
|
||||
// If we're in ringing state we skip all notifications.
|
||||
if (!notificationsEnabled || APP.UI.isOverlayVisible()) {
|
||||
return;
|
||||
|
||||
var displayNameSpan = '<span class="title" ';
|
||||
if (displayName) {
|
||||
displayNameSpan += ">" + UIUtil.escapeHtml(displayName);
|
||||
} else {
|
||||
displayNameSpan += "data-i18n='" + displayNameKey + "'>";
|
||||
}
|
||||
displayNameSpan += "</span>";
|
||||
let element = toastr.info(
|
||||
displayNameSpan + '<br>' +
|
||||
'<span class=' + cls + ' data-i18n="' + messageKey + '"' +
|
||||
(messageArguments?
|
||||
" data-i18n-options='"
|
||||
+ JSON.stringify(messageArguments) + "'"
|
||||
: "") + "></span>", null, options);
|
||||
APP.translation.translateElement(element);
|
||||
return element;
|
||||
|
||||
APP.store.dispatch(
|
||||
showNotification(
|
||||
Notification,
|
||||
{
|
||||
defaultTitleKey: displayNameKey,
|
||||
descriptionArguments: messageArguments,
|
||||
descriptionKey: messageKey,
|
||||
title: displayName
|
||||
},
|
||||
timeout));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -488,28 +486,12 @@ var messageHandler = {
|
|||
*/
|
||||
notify: function(titleKey, messageKey, messageArguments) {
|
||||
|
||||
// If we're in ringing state we skip all toaster notifications.
|
||||
// If we're in ringing state we skip all notifications.
|
||||
if(!notificationsEnabled || APP.UI.isOverlayVisible())
|
||||
return;
|
||||
|
||||
const options = messageArguments
|
||||
? `data-i18n-options='${JSON.stringify(messageArguments)}'` : "";
|
||||
let element = toastr.info(`
|
||||
<span class="title" data-i18n="${titleKey}"></span>
|
||||
<br>
|
||||
<span data-i18n="${messageKey}" ${options}></span>
|
||||
`
|
||||
);
|
||||
APP.translation.translateElement(element);
|
||||
return element;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the toaster.
|
||||
* @param toasterElement
|
||||
*/
|
||||
remove: function(toasterElement) {
|
||||
toasterElement.remove();
|
||||
this.participantNotification(
|
||||
null, titleKey, null, messageKey, messageArguments);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"@atlaskit/button-group": "1.1.3",
|
||||
"@atlaskit/dropdown-menu": "1.4.0",
|
||||
"@atlaskit/field-text": "2.7.0",
|
||||
"@atlaskit/flag": "3.0.0",
|
||||
"@atlaskit/icon": "7.0.0",
|
||||
"@atlaskit/inline-dialog": "3.2.0",
|
||||
"@atlaskit/inline-message": "2.1.1",
|
||||
|
@ -67,7 +68,6 @@
|
|||
"strophe": "1.2.4",
|
||||
"strophejs-plugins": "0.0.7",
|
||||
"styled-components": "1.3.0",
|
||||
"toastr": "2.1.2",
|
||||
"url-polyfill": "github/url-polyfill",
|
||||
"xmldom": "0.1.27"
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import { connect, disconnect } from '../../base/connection';
|
|||
import { DialogContainer } from '../../base/dialog';
|
||||
import { Filmstrip } from '../../filmstrip';
|
||||
import { LargeVideo } from '../../large-video';
|
||||
import { NotificationsContainer } from '../../notifications';
|
||||
import { OverlayContainer } from '../../overlay';
|
||||
import { Toolbox } from '../../toolbox';
|
||||
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
||||
|
@ -78,6 +79,7 @@ class Conference extends Component {
|
|||
{ filmStripOnly ? null : <Toolbox /> }
|
||||
|
||||
<DialogContainer />
|
||||
{ filmStripOnly ? null : <NotificationsContainer /> }
|
||||
<OverlayContainer />
|
||||
|
||||
{/*
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* The type of (redux) action which signals that a specific notification should
|
||||
* not be displayed anymore.
|
||||
*
|
||||
* {
|
||||
* type: HIDE_NOTIFICATION,
|
||||
* uid: string
|
||||
* }
|
||||
*/
|
||||
export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION');
|
||||
|
||||
/*
|
||||
* The type of (redux) action which signals that a notification component should
|
||||
* be displayed.
|
||||
*
|
||||
* {
|
||||
* type: SHOW_NOTIFICATION,
|
||||
* component: ReactComponent,
|
||||
* props: Object,
|
||||
* timeout: number,
|
||||
* uid: number
|
||||
* }
|
||||
*/
|
||||
export const SHOW_NOTIFICATION = Symbol('SHOW_NOTIFICATION');
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
HIDE_NOTIFICATION,
|
||||
SHOW_NOTIFICATION
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Removes the notification with the passed in id.
|
||||
*
|
||||
* @param {string} uid - The unique identifier for the notification to be
|
||||
* removed.
|
||||
* @returns {{
|
||||
* type: HIDE_NOTIFICATION,
|
||||
* uid: string
|
||||
* }}
|
||||
*/
|
||||
export function hideNotification(uid) {
|
||||
return {
|
||||
type: HIDE_NOTIFICATION,
|
||||
uid
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a notification for display.
|
||||
*
|
||||
* @param {ReactComponent} component - The notification component to be
|
||||
* displayed.
|
||||
* @param {Object} props - The props needed to show the notification component.
|
||||
* @param {number} timeout - How long the notification should display before
|
||||
* automatically being hidden.
|
||||
* @returns {{
|
||||
* type: SHOW_NOTIFICATION,
|
||||
* component: ReactComponent,
|
||||
* props: Object,
|
||||
* timeout: number,
|
||||
* uid: number
|
||||
* }}
|
||||
*/
|
||||
export function showNotification(component, props = {}, timeout) {
|
||||
return {
|
||||
type: SHOW_NOTIFICATION,
|
||||
component,
|
||||
props,
|
||||
timeout,
|
||||
uid: window.Date.now()
|
||||
};
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
import Flag from '@atlaskit/flag';
|
||||
import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class Notification extends Component {
|
||||
/**
|
||||
* {@code Notification} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The translation key to display as the title of the notification if
|
||||
* no title is provided.
|
||||
*/
|
||||
defaultTitleKey: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* The translation arguments that may be necessary for the description.
|
||||
*/
|
||||
descriptionArguments: React.PropTypes.object,
|
||||
|
||||
/**
|
||||
* The translation key to use as the body of the notification.
|
||||
*/
|
||||
descriptionKey: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether or not the dismiss button should be displayed. This is passed
|
||||
* in by {@code FlagGroup}.
|
||||
*/
|
||||
isDismissAllowed: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Callback invoked when the user clicks to dismiss the notification.
|
||||
* this is passed in by {@code FlagGroup}.
|
||||
*/
|
||||
onDismissed: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The text to display at the top of the notification. If not passed in,
|
||||
* the passed in defaultTitleKey will be used.
|
||||
*/
|
||||
title: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* The unique identifier for the notification. Passed back by the
|
||||
* {@code Flag} component in the onDismissed callback.
|
||||
*/
|
||||
uid: React.PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
defaultTitleKey,
|
||||
descriptionArguments,
|
||||
descriptionKey,
|
||||
isDismissAllowed,
|
||||
onDismissed,
|
||||
t,
|
||||
title,
|
||||
uid
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Flag
|
||||
appearance = 'normal'
|
||||
description = { t(descriptionKey, descriptionArguments) }
|
||||
icon = { (
|
||||
<EditorInfoIcon
|
||||
label = 'info'
|
||||
size = 'medium' />
|
||||
) }
|
||||
id = { uid }
|
||||
isDismissAllowed = { isDismissAllowed }
|
||||
onDismissed = { onDismissed }
|
||||
title = { title || t(defaultTitleKey) } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Notification);
|
|
@ -0,0 +1,157 @@
|
|||
import { FlagGroup } from '@atlaskit/flag';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { hideNotification } from '../actions';
|
||||
|
||||
/**
|
||||
* The duration for which a notification should be displayed before being
|
||||
* dismissed automatically.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
const DEFAULT_NOTIFICATION_TIMEOUT = 2500;
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays notifications and handles
|
||||
* automatic dismissmal after a notification is shown for a defined timeout
|
||||
* period.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class NotificationsContainer extends Component {
|
||||
/**
|
||||
* {@code NotificationsContainer} component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The notifications to be displayed, with the first index being the
|
||||
* notification at the top and the rest shown below it in order.
|
||||
*/
|
||||
_notifications: React.PropTypes.array,
|
||||
|
||||
/**
|
||||
* Invoked to update the redux store in order to remove notifications.
|
||||
*/
|
||||
dispatch: React.PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code NotificationsContainer} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
/**
|
||||
* The timeout set for automatically dismissing a displayed
|
||||
* notification. This value is set on the instance and not state to
|
||||
* avoid additional re-renders.
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
this._notificationDismissTimeout = null;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timeout if the currently displayed notification has changed.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentDidUpdate() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
if (_notifications.length && !this._notificationDismissTimeout) {
|
||||
const notification = _notifications[0];
|
||||
const { timeout, uid } = notification;
|
||||
|
||||
this._notificationDismissTimeout = setTimeout(() => {
|
||||
this._onDismissed(uid);
|
||||
}, timeout || DEFAULT_NOTIFICATION_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any dismissal timeout that is still active.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._notificationDismissTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
const flags = _notifications.map(notification => {
|
||||
const Notification = notification.component;
|
||||
const { props, uid } = notification;
|
||||
|
||||
// The id attribute is necessary as {@code FlagGroup} looks for
|
||||
// either id or key to set a key on notifications, but accessing
|
||||
// props.key will cause React to print an error.
|
||||
return (
|
||||
<Notification
|
||||
{ ...props }
|
||||
id = { uid }
|
||||
key = { uid }
|
||||
uid = { uid } />
|
||||
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<FlagGroup onDismissed = { this._onDismissed }>
|
||||
{ flags }
|
||||
</FlagGroup>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an action to remove the notification from the redux store so it
|
||||
* stops displaying.
|
||||
*
|
||||
* @param {number} flagUid - The id of the notification to be removed.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed(flagUid) {
|
||||
clearTimeout(this._notificationDismissTimeout);
|
||||
this._notificationDismissTimeout = null;
|
||||
|
||||
this.props.dispatch(hideNotification(flagUid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated NotificationsContainer's
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _notifications: React.PropTypes.array
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_notifications: state['features/notifications']
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(NotificationsContainer);
|
|
@ -0,0 +1,2 @@
|
|||
export { default as Notification } from './Notification';
|
||||
export { default as NotificationsContainer } from './NotificationsContainer';
|
|
@ -0,0 +1,5 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
|
||||
import './reducer';
|
|
@ -0,0 +1,43 @@
|
|||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
HIDE_NOTIFICATION,
|
||||
SHOW_NOTIFICATION
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* The initial state of the feature notifications.
|
||||
*
|
||||
* @type {array}
|
||||
*/
|
||||
const DEFAULT_STATE = [];
|
||||
|
||||
/**
|
||||
* Reduces redux actions which affect the display of notifications.
|
||||
*
|
||||
* @param {Object} state - The current redux state.
|
||||
* @param {Object} action - The redux action to reduce.
|
||||
* @returns {Object} The next redux state which is the result of reducing the
|
||||
* specified {@code action}.
|
||||
*/
|
||||
ReducerRegistry.register('features/notifications',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case HIDE_NOTIFICATION:
|
||||
return state.filter(
|
||||
notification => notification.uid !== action.uid);
|
||||
|
||||
case SHOW_NOTIFICATION:
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
component: action.component,
|
||||
props: action.props,
|
||||
timeout: action.timeout,
|
||||
uid: action.uid
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
Loading…
Reference in New Issue