From 6649b7d7f187f0733ea6be3c655f611a875662bb Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Sat, 21 Nov 2015 19:23:20 +0100 Subject: [PATCH] Implement pause and resume functionality to global notifications Also part of this change: * Handle the notification cooldown periods using status changes rather than emptying the queue * Refactor how notifications are rendered --- js/actions/global_notification_actions.js | 6 +- js/components/global_notification.js | 94 +++++++++-------------- js/stores/global_notification_store.js | 57 ++++++++++---- 3 files changed, 83 insertions(+), 74 deletions(-) diff --git a/js/actions/global_notification_actions.js b/js/actions/global_notification_actions.js index 2bb8d6e6..73aa9815 100644 --- a/js/actions/global_notification_actions.js +++ b/js/actions/global_notification_actions.js @@ -2,13 +2,15 @@ import { alt } from '../alt'; - class GlobalNotificationActions { constructor() { this.generateActions( 'appendGlobalNotification', + 'showNextGlobalNotification', 'shiftGlobalNotification', - 'emulateEmptyStore' + 'cooldownGlobalNotifications', + 'pauseGlobalNotifications', + 'resumeGlobalNotifications' ); } } diff --git a/js/components/global_notification.js b/js/components/global_notification.js index 59663b28..990e2d32 100644 --- a/js/components/global_notification.js +++ b/js/components/global_notification.js @@ -1,7 +1,9 @@ 'use strict'; import React from 'react'; +import classNames from 'classnames'; +import GlobalNotificationActions from '../actions/global_notification_actions'; import GlobalNotificationStore from '../stores/global_notification_store'; import Row from 'react-bootstrap/lib/Row'; @@ -9,14 +11,18 @@ import Col from 'react-bootstrap/lib/Col'; import { mergeOptions } from '../utils/general_utils'; +const MAX_NOTIFICATION_BUBBLE_CONTAINER_WIDTH = 768; + let GlobalNotification = React.createClass({ getInitialState() { + const notificationStore = GlobalNotificationStore.getState(); + return mergeOptions( { containerWidth: 0 }, - this.extractFirstElem(GlobalNotificationStore.getState().notificationQue) + notificationStore ); }, @@ -36,35 +42,8 @@ let GlobalNotification = React.createClass({ window.removeEventListener('resize', this.handleContainerResize); }, - extractFirstElem(l) { - if(l.length > 0) { - return { - show: true, - message: l[0] - }; - } else { - return { - show: false, - message: '' - }; - } - }, - onChange(state) { - let notification = this.extractFirstElem(state.notificationQue); - - // error handling for notifications - if(notification.message && notification.type === 'danger') { - console.logGlobal(new Error(notification.message.message)); - } - - if(notification.show) { - this.setState(notification); - } else { - this.setState({ - show: false - }); - } + this.setState(state); }, handleContainerResize() { @@ -73,32 +52,28 @@ let GlobalNotification = React.createClass({ }); }, - render() { - let notificationClass = 'ascribe-global-notification'; - let textClass; + renderNotification() { + const { + notificationQueue: [notification], + notificationStatus, + notificationsPaused, + containerWidth } = this.state; - if(this.state.containerWidth > 768) { - notificationClass = 'ascribe-global-notification-bubble'; + if (notification && !notificationsPaused) { + const notificationClasses = []; + let textClass; - if(this.state.show) { - notificationClass += ' ascribe-global-notification-bubble-on'; + if (this.state.containerWidth > 768) { + notificationClasses.push('ascribe-global-notification-bubble'); + notificationClasses.push(notificationStatus === 'show' ? 'ascribe-global-notification-bubble-on' + : 'ascribe-global-notification-bubble-off'); } else { - notificationClass += ' ascribe-global-notification-bubble-off'; + notificationClasses.push('ascribe-global-notification'); + notificationClasses.push(notificationStatus === 'show' ? 'ascribe-global-notification-on' + : 'ascribe-global-notification-off'); } - } else { - notificationClass = 'ascribe-global-notification'; - - if(this.state.show) { - notificationClass += ' ascribe-global-notification-on'; - } else { - notificationClass += ' ascribe-global-notification-off'; - } - - } - - if(this.state.message) { - switch(this.state.message.type) { + switch(notification.type) { case 'success': textClass = 'ascribe-global-notification-success'; break; @@ -106,18 +81,23 @@ let GlobalNotification = React.createClass({ textClass = 'ascribe-global-notification-danger'; break; default: - console.warn('Could not find a matching type in global_notification.js'); + console.warn('Could not find a matching notification type in global_notification.js'); } - } - + return ( +
+
{notification.message}
+
+ ); + } + }, + + render() { return (
-
-
{this.state.message.message}
-
+ {this.renderNotification()}
@@ -125,4 +105,4 @@ let GlobalNotification = React.createClass({ } }); -export default GlobalNotification; \ No newline at end of file +export default GlobalNotification; diff --git a/js/stores/global_notification_store.js b/js/stores/global_notification_store.js index 5a23fe1b..7414812b 100644 --- a/js/stores/global_notification_store.js +++ b/js/stores/global_notification_store.js @@ -4,36 +4,63 @@ import { alt } from '../alt'; import GlobalNotificationActions from '../actions/global_notification_actions'; +const GLOBAL_NOTIFICATION_COOLDOWN = 400; + class GlobalNotificationStore { constructor() { - this.notificationQue = []; + this.notificationQueue = []; + this.notificationStatus = 'ready'; + this.notificationsPaused = false; this.bindActions(GlobalNotificationActions); } onAppendGlobalNotification(newNotification) { - let notificationDelay = 0; - for(let i = 0; i < this.notificationQue.length; i++) { - notificationDelay += this.notificationQue[i].dismissAfter; - } + this.notificationQueue.push(newNotification); - this.notificationQue.push(newNotification); - setTimeout(GlobalNotificationActions.emulateEmptyStore, notificationDelay + newNotification.dismissAfter); + if (!this.notificationsPaused && this.notificationStatus === 'ready') { + this.showNextNotification(); + } } - onEmulateEmptyStore() { - let actualNotificitionQue = this.notificationQue.slice(); + showNextNotification() { + this.notificationStatus = 'show'; - this.notificationQue = []; + setTimeout(GlobalNotificationActions.cooldownGlobalNotifications, this.notificationQueue[0].dismissAfter); + } - setTimeout(() => { - this.notificationQue = actualNotificitionQue.slice(); - GlobalNotificationActions.shiftGlobalNotification(); - }, 400); + onCooldownGlobalNotifications() { + // When still paused on cooldown, don't shift the queue so we can repeat the current notification. + if (!this.notificationsPaused) { + this.notificationStatus = 'cooldown'; + + // Leave some time between consecutive notifications + setTimeout(GlobalNotificationActions.shiftGlobalNotification, GLOBAL_NOTIFICATION_COOLDOWN); + } else { + this.notificationStatus = 'ready'; + } } onShiftGlobalNotification() { - this.notificationQue.shift(); + this.notificationQueue.shift(); + + if (!this.notificationsPaused && this.notificationQueue.length > 0) { + this.showNextNotification(); + } else { + this.notificationStatus = 'ready'; + } + } + + onPauseGlobalNotifications() { + this.notificationsPaused = true; + } + + onResumeGlobalNotifications() { + this.notificationsPaused = false; + + if (this.notificationStatus === 'ready' && this.notificationQueue.length > 0) { + this.showNextNotification(); + } } }