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
} from '../../react/features/toolbox';
import {
maybeShowNotificationWithDoNotDisplay
maybeShowNotificationWithDoNotDisplay,
setNotificationsEnabled
} from '../../react/features/notifications';
var EventEmitter = require("events");
@ -51,17 +52,6 @@ import FollowMe from "../FollowMe";
var eventEmitter = new 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 sharedVideoManager;
@ -335,7 +325,7 @@ UI.start = function () {
$("body").addClass("filmstrip-only");
UI.showToolbar();
Filmstrip.setFilmstripOnly();
messageHandler.enableNotifications(false);
APP.store.dispatch(setNotificationsEnabled(false));
JitsiPopover.enabled = false;
}
@ -1307,19 +1297,6 @@ UI.onSharedVideoStop = function (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.
*/

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
/*
/**
* The type of (redux) action which signals that a specific notification should
* not be displayed anymore.
*
@ -9,7 +9,7 @@
*/
export const HIDE_NOTIFICATION = Symbol('HIDE_NOTIFICATION');
/*
/**
* The type of (redux) action which signals that a notification component should
* be displayed.
*
@ -22,3 +22,14 @@ export const HIDE_NOTIFICATION = Symbol('HIDE_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 {
HIDE_NOTIFICATION,
SET_NOTIFICATIONS_ENABLED,
SHOW_NOTIFICATION
} from './actionTypes';
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.
*

View File

@ -24,6 +24,12 @@ class NotificationsContainer extends Component {
*/
_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.
*/
@ -59,18 +65,27 @@ class NotificationsContainer extends Component {
* returns {void}
*/
componentDidUpdate() {
const { _notifications } = this.props;
const { _notifications, _showNotifications } = this.props;
if (_notifications.length && !this._notificationDismissTimeout) {
if (_notifications.length) {
const notification = _notifications[0];
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);
if (!_showNotifications) {
this._onDismissed(notification.uid);
} else if (this._notificationDismissTimeout) {
// No-op because there should already be a notification that
// 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}
*/
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 }
{ this._renderFlags() }
</FlagGroup>
);
}
@ -131,6 +127,38 @@ class NotificationsContainer extends Component {
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) {
// 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 {
_notifications: state['features/notifications']
_notifications: notifications,
_showNotifications: enabled && !isAnyOverlayVisible
};
}

View File

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

View File

@ -11,7 +11,6 @@ import UserMediaPermissionsFilmstripOnlyOverlay
from './UserMediaPermissionsFilmstripOnlyOverlay';
import UserMediaPermissionsOverlay from './UserMediaPermissionsOverlay';
declare var APP: 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()}.
*