ref(notifications): bring hiding of notifications into redux

This commit is contained in:
Leonard Kim 2017-08-02 11:15:55 -07:00
parent 74ddae4a6a
commit cd66a7fcb7
9 changed files with 138 additions and 112 deletions

View File

@ -40,7 +40,8 @@ import {
showToolbox showToolbox
} from '../../react/features/toolbox'; } from '../../react/features/toolbox';
import { import {
maybeShowNotificationWithDoNotDisplay maybeShowNotificationWithDoNotDisplay,
setNotificationsEnabled
} from '../../react/features/notifications'; } from '../../react/features/notifications';
var EventEmitter = require("events"); var EventEmitter = require("events");
@ -51,17 +52,6 @@ import FollowMe from "../FollowMe";
var eventEmitter = new EventEmitter(); var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter; UI.eventEmitter = eventEmitter;
/**
* Whether an overlay is visible or not.
*
* FIXME: This is temporary solution. Don't use this variable!
* Should be removed when all the code is move to react.
*
* @type {boolean}
* @public
*/
UI.overlayVisible = false;
let etherpadManager; let etherpadManager;
let sharedVideoManager; let sharedVideoManager;
@ -335,7 +325,7 @@ UI.start = function () {
$("body").addClass("filmstrip-only"); $("body").addClass("filmstrip-only");
UI.showToolbar(); UI.showToolbar();
Filmstrip.setFilmstripOnly(); Filmstrip.setFilmstripOnly();
messageHandler.enableNotifications(false); APP.store.dispatch(setNotificationsEnabled(false));
JitsiPopover.enabled = false; JitsiPopover.enabled = false;
} }
@ -1307,19 +1297,6 @@ UI.onSharedVideoStop = function (id, attributes) {
sharedVideoManager.onSharedVideoStop(id, attributes); sharedVideoManager.onSharedVideoStop(id, attributes);
}; };
/**
* Indicates if any the "top" overlays are currently visible. The check includes
* the call/ring overlay, the suspended overlay, the GUM permissions overlay,
* and the page-reload overlay.
*
* @returns {*|boolean} {true} if an overlay is visible; {false}, otherwise
*/
UI.isOverlayVisible = function () {
return (
this.overlayVisible
|| APP.store.getState()['features/jwt'].callOverlayVisible);
};
/** /**
* Handles user's features changes. * Handles user's features changes.
*/ */

View File

@ -22,6 +22,7 @@ import VideoLayout from '../videolayout/VideoLayout';
import Feedback from '../feedback/Feedback.js'; import Feedback from '../feedback/Feedback.js';
import { setToolboxEnabled } from '../../../react/features/toolbox'; import { setToolboxEnabled } from '../../../react/features/toolbox';
import { setNotificationsEnabled } from '../../../react/features/notifications';
/** /**
* The dialog for user input. * The dialog for user input.
@ -309,7 +310,7 @@ var Recording = {
VideoLayout.setLocalVideoVisible(false); VideoLayout.setLocalVideoVisible(false);
Feedback.enableFeedback(false); Feedback.enableFeedback(false);
APP.store.dispatch(setToolboxEnabled(false)); APP.store.dispatch(setToolboxEnabled(false));
APP.UI.messageHandler.enableNotifications(false); APP.store.dispatch(setNotificationsEnabled(false));
APP.UI.messageHandler.enablePopups(false); APP.UI.messageHandler.enablePopups(false);
} }

View File

@ -8,12 +8,6 @@ import {
showNotification showNotification
} from '../../../react/features/notifications'; } from '../../../react/features/notifications';
/**
* Flag for enable/disable of the notifications.
* @type {boolean}
*/
let notificationsEnabled = true;
/** /**
* Flag for enabling/disabling popups. * Flag for enabling/disabling popups.
* @type {boolean} * @type {boolean}
@ -456,11 +450,6 @@ var messageHandler = {
*/ */
participantNotification: function(displayName, displayNameKey, cls, participantNotification: function(displayName, displayNameKey, cls,
messageKey, messageArguments, timeout = 2500) { messageKey, messageArguments, timeout = 2500) {
// If we're in ringing state we skip all notifications.
if (!notificationsEnabled || APP.UI.isOverlayVisible()) {
return;
}
APP.store.dispatch( APP.store.dispatch(
showNotification( showNotification(
Notification, Notification,
@ -485,22 +474,10 @@ var messageHandler = {
* @returns {void} * @returns {void}
*/ */
notify: function(titleKey, messageKey, messageArguments) { notify: function(titleKey, messageKey, messageArguments) {
// If we're in ringing state we skip all notifications.
if(!notificationsEnabled || APP.UI.isOverlayVisible())
return;
this.participantNotification( this.participantNotification(
null, titleKey, null, messageKey, messageArguments); null, titleKey, null, messageKey, messageArguments);
}, },
/**
* Enables / disables notifications.
*/
enableNotifications: function (enable) {
notificationsEnabled = enable;
},
enablePopups: function (enable) { enablePopups: function (enable) {
popupEnabled = enable; popupEnabled = enable;
}, },

View File

@ -79,7 +79,7 @@ class Conference extends Component {
{ filmStripOnly ? null : <Toolbox /> } { filmStripOnly ? null : <Toolbox /> }
<DialogContainer /> <DialogContainer />
{ filmStripOnly ? null : <NotificationsContainer /> } <NotificationsContainer />
<OverlayContainer /> <OverlayContainer />
{/* {/*

View File

@ -1,4 +1,4 @@
/* /**
* The type of (redux) action which signals that a specific notification should * The type of (redux) action which signals that a specific notification should
* not be displayed anymore. * not be displayed anymore.
* *
@ -9,7 +9,7 @@
*/ */
export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION'); export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION');
/* /**
* The type of (redux) action which signals that a notification component should * The type of (redux) action which signals that a notification component should
* be displayed. * be displayed.
* *
@ -22,3 +22,14 @@ export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION');
* } * }
*/ */
export const SHOW_NOTIFICATION = Symbol('SHOW_NOTIFICATION'); export const SHOW_NOTIFICATION = Symbol('SHOW_NOTIFICATION');
/**
* The type of (redux) action which signals that notifications should not
* display.
*
* {
* type: SET_NOTIFICATIONS_ENABLED,
* enabled: Boolean
* }
*/
export const SET_NOTIFICATIONS_ENABLED = Symbol('SET_NOTIFICATIONS_ENABLED');

View File

@ -2,6 +2,7 @@ import jitsiLocalStorage from '../../../modules/util/JitsiLocalStorage';
import { import {
HIDE_NOTIFICATION, HIDE_NOTIFICATION,
SET_NOTIFICATIONS_ENABLED,
SHOW_NOTIFICATION SHOW_NOTIFICATION
} from './actionTypes'; } from './actionTypes';
import { NotificationWithToggle } from './components'; import { NotificationWithToggle } from './components';
@ -23,6 +24,22 @@ export function hideNotification(uid) {
}; };
} }
/**
* Stops notifications from being displayed.
*
* @param {boolean} enabled - Whether or not notifications should display.
* @returns {{
* type: SET_NOTIFICATIONS_ENABLED,
* enabled: boolean
* }}
*/
export function setNotificationsEnabled(enabled) {
return {
type: SET_NOTIFICATIONS_ENABLED,
enabled
};
}
/** /**
* Queues a notification for display. * Queues a notification for display.
* *

View File

@ -24,6 +24,12 @@ class NotificationsContainer extends Component {
*/ */
_notifications: React.PropTypes.array, _notifications: React.PropTypes.array,
/**
* Whether or not notifications should be displayed at all. If not,
* notifications will be dismissed immediately.
*/
_showNotifications: React.PropTypes.bool,
/** /**
* Invoked to update the redux store in order to remove notifications. * Invoked to update the redux store in order to remove notifications.
*/ */
@ -59,18 +65,27 @@ class NotificationsContainer extends Component {
* returns {void} * returns {void}
*/ */
componentDidUpdate() { componentDidUpdate() {
const { _notifications } = this.props; const { _notifications, _showNotifications } = this.props;
if (_notifications.length && !this._notificationDismissTimeout) { if (_notifications.length) {
const notification = _notifications[0]; const notification = _notifications[0];
const { timeout, uid } = notification;
this._notificationDismissTimeout = setTimeout(() => { if (!_showNotifications) {
// Perform a no-op if a timeout is not specified. this._onDismissed(notification.uid);
if (Number.isInteger(timeout)) { } else if (this._notificationDismissTimeout) {
this._onDismissed(uid);
} // No-op because there should already be a notification that
}, timeout); // is waiting for dismissal.
} else {
const { timeout, uid } = notification;
this._notificationDismissTimeout = setTimeout(() => {
// Perform a no-op if a timeout is not specified.
if (Number.isInteger(timeout)) {
this._onDismissed(uid);
}
}, timeout);
}
} }
} }
@ -91,28 +106,9 @@ class NotificationsContainer extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { 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 ( return (
<FlagGroup onDismissed = { this._onDismissed }> <FlagGroup onDismissed = { this._onDismissed }>
{ flags } { this._renderFlags() }
</FlagGroup> </FlagGroup>
); );
} }
@ -131,6 +127,38 @@ class NotificationsContainer extends Component {
this.props.dispatch(hideNotification(flagUid)); this.props.dispatch(hideNotification(flagUid));
} }
/**
* Renders notifications to display as ReactElements. An empty array will
* be returned if notifications are disabled.
*
* @private
* @returns {ReactElement[]}
*/
_renderFlags() {
const { _notifications, _showNotifications } = this.props;
if (!_showNotifications) {
return [];
}
return _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 } />
);
});
}
} }
/** /**
@ -144,8 +172,25 @@ class NotificationsContainer extends Component {
* }} * }}
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
// TODO: Per existing behavior, notifications should not display when an
// overlay is visible. This logic for checking overlay display can likely be
// simplified.
const {
connectionEstablished,
haveToReload,
isMediaPermissionPromptVisible,
suspendDetected
} = state['features/overlay'];
const isAnyOverlayVisible = (connectionEstablished && haveToReload)
|| isMediaPermissionPromptVisible
|| suspendDetected
|| state['features/jwt'].callOverlayVisible;
const { enabled, notifications } = state['features/notifications'];
return { return {
_notifications: state['features/notifications'] _notifications: notifications,
_showNotifications: enabled && !isAnyOverlayVisible
}; };
} }

View File

@ -2,6 +2,7 @@ import { ReducerRegistry } from '../base/redux';
import { import {
HIDE_NOTIFICATION, HIDE_NOTIFICATION,
SET_NOTIFICATIONS_ENABLED,
SHOW_NOTIFICATION SHOW_NOTIFICATION
} from './actionTypes'; } from './actionTypes';
@ -10,7 +11,10 @@ import {
* *
* @type {array} * @type {array}
*/ */
const DEFAULT_STATE = []; const DEFAULT_STATE = {
enabled: true,
notifications: []
};
/** /**
* Reduces redux actions which affect the display of notifications. * Reduces redux actions which affect the display of notifications.
@ -24,19 +28,31 @@ ReducerRegistry.register('features/notifications',
(state = DEFAULT_STATE, action) => { (state = DEFAULT_STATE, action) => {
switch (action.type) { switch (action.type) {
case HIDE_NOTIFICATION: case HIDE_NOTIFICATION:
return state.filter( return {
notification => notification.uid !== action.uid); ...state,
notifications: state.notifications.filter(
notification => notification.uid !== action.uid)
};
case SET_NOTIFICATIONS_ENABLED:
return {
...state,
enabled: action.enabled
};
case SHOW_NOTIFICATION: case SHOW_NOTIFICATION:
return [ return {
...state, ...state,
{ notifications: [
component: action.component, ...state.notifications,
props: action.props, {
timeout: action.timeout, component: action.component,
uid: action.uid props: action.props,
} timeout: action.timeout,
]; uid: action.uid
}
]
};
} }
return state; return state;

View File

@ -11,7 +11,6 @@ import UserMediaPermissionsFilmstripOnlyOverlay
from './UserMediaPermissionsFilmstripOnlyOverlay'; from './UserMediaPermissionsFilmstripOnlyOverlay';
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay'; import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
declare var APP: Object;
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
/** /**
@ -133,23 +132,6 @@ class OverlayContainer extends Component {
}; };
} }
/**
* React Component method that executes once component is updated.
*
* @inheritdoc
* @returns {void}
* @protected
*/
componentDidUpdate() {
if (typeof APP === 'object') {
APP.UI.overlayVisible
= (this.props._connectionEstablished
&& this.props._haveToReload)
|| this.props._suspendDetected
|| this.props._isMediaPermissionPromptVisible;
}
}
/** /**
* Implements React's {@link Component#render()}. * Implements React's {@link Component#render()}.
* *