From 2a95af89de248fdf5691816e7292e65023fea1c4 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Wed, 28 Feb 2018 08:44:38 +1300 Subject: [PATCH] Notifications as object for better targetting; Notifications closing vs removing for animation window --- src/js/bootstrap.js | 2 +- src/js/components/Notifications.js | 17 ++++++++---- src/js/services/ui/actions.js | 10 ++++++- src/js/services/ui/middleware.js | 37 +++++++++++++------------ src/js/services/ui/reducer.js | 23 ++++++++------- src/scss/components/_notifications.scss | 13 ++++----- src/scss/global/_variables.scss | 14 ++-------- 7 files changed, 62 insertions(+), 54 deletions(-) diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js index 2334dbff..508bd910 100755 --- a/src/js/bootstrap.js +++ b/src/js/bootstrap.js @@ -50,7 +50,7 @@ var initialState = { show_initial_setup: true, slim_mode: false, selected_tracks: [], - notifications: [], + notifications: {}, processes: {} }, mopidy: { diff --git a/src/js/components/Notifications.js b/src/js/components/Notifications.js index 7ae3e742..b08c44e3 100755 --- a/src/js/components/Notifications.js +++ b/src/js/components/Notifications.js @@ -11,21 +11,28 @@ export default class Notifications extends React.Component{ renderNotifications(){ if (!this.props.notifications || this.props.notifications.length <= 0) return null + var notifications = [] + for (var key in this.props.notifications){ + if (this.props.notifications.hasOwnProperty(key)){ + notifications.push(this.props.notifications[key]) + } + } + return ( { - this.props.notifications.map(notification => { + notifications.map(notification => { switch (notification.type){ case 'shortcut': return ( -
+
) default: return ( -
+
this.props.uiActions.removeNotification(notification.key) } /> {notification.title ?

{notification.title}

: null}

@@ -49,7 +56,7 @@ export default class Notifications extends React.Component{ switch (process.status){ case 'running': return( -
+
@@ -62,7 +69,7 @@ export default class Notifications extends React.Component{ case 'cancelling': return( -
+
Cancelling
diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index 01f80722..94e03f79 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -188,13 +188,21 @@ export function createNotification(data){ title: null, content: null, description: null, - sticky: false + sticky: false, + closing: false }, data ) } } +export function closeNotification(key){ + return { + type: 'CLOSE_NOTIFICATION', + key: key + } +} + export function removeNotification(key){ return { type: 'REMOVE_NOTIFICATION', diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js index 216d7c2f..93ef5598 100755 --- a/src/js/services/ui/middleware.js +++ b/src/js/services/ui/middleware.js @@ -70,40 +70,41 @@ const UIMiddleware = (function(){ case 'CREATE_NOTIFICATION': - // start a timeout to remove this notification + // start a timeout to close this notification if (!action.notification.sticky){ var timeout = setTimeout( function(){ - store.dispatch(uiActions.removeNotification(action.notification.key)) + store.dispatch(uiActions.closeNotification(action.notification.key)) }, action.notification.duration * 1000 ) } - next(action) - break + next(action); + break; - case 'REMOVE_NOTIFICATION': - var notifications = Object.assign([], store.getState().ui.notifications) - - function getByKey(notification){ - return notification.key === action.key - } - var index = notifications.findIndex(getByKey) - - // Save our index for the reducer to use. Saves us from re-finding by key - action.index = index + case 'CLOSE_NOTIFICATION': + var notifications = Object.assign({}, store.getState().ui.notifications); // If a broadcast, add to suppressed_broadcasts - if (index > -1 && typeof(notifications[index]) !== 'undefined' && notifications[index].type == 'broadcast'){ + if (notifications[action.key] && notifications[action.key].type == 'broadcast'){ store.dispatch({ type: 'SUPPRESS_BROADCAST', - key: notifications[index].key + key: action.key }) } - next(action) - break + // start a timeout to remove this notification + // This gives us time to animate out the notification before we remove the data + var timeout = setTimeout( + function(){ + store.dispatch(uiActions.removeNotification(action.key)) + }, + 200 + ) + + next(action); + break; case 'BROADCASTS_LOADED': var suppressed_broadcasts = [] diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index ab9296d7..077f53f8 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -98,18 +98,21 @@ export default function reducer(ui = {}, action){ **/ case 'CREATE_NOTIFICATION': - var notifications = [...ui.notifications, action.notification] - notifications = helpers.mergeDuplicates(notifications,'key') - return Object.assign({}, ui, { notifications: notifications }) + var notifications = Object.assign({}, ui.notifications); + notifications[action.notification.key] = action.notification; + return Object.assign({}, ui, { notifications: notifications }); + + case 'CLOSE_NOTIFICATION': + var notifications = Object.assign({}, ui.notifications); + if (notifications[action.key]){ + notifications[action.key].closing = true; + } + return Object.assign({}, ui, { notifications: notifications }); case 'REMOVE_NOTIFICATION': - var notifications = Object.assign([], ui.notifications) - - if (action.index > -1){ - notifications.splice(action.index, 1) - } - - return Object.assign({}, ui, {notifications: notifications}) + var notifications = Object.assign({}, ui.notifications); + delete notifications[action.key]; + return Object.assign({}, ui, {notifications: notifications}); diff --git a/src/scss/components/_notifications.scss b/src/scss/components/_notifications.scss index 90187315..7cc107f0 100755 --- a/src/scss/components/_notifications.scss +++ b/src/scss/components/_notifications.scss @@ -9,6 +9,7 @@ width: 300px; .notification { + @include fadein(); display: block; position: relative; background: $blue; @@ -17,14 +18,6 @@ color: $white; pointer-events: all; border-radius: 3px; - - &[data-duration="1"]{ - @include fadeinout(1s); - } - - &[data-duration="3"]{ - @include fadeinout(3s); - } h4 { padding-top: 0; @@ -78,6 +71,10 @@ background: $red; } + &.closing { + @include fadeout(); + } + &.process { position: relative; padding-right: 18px; diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss index eb8d432b..addfad3a 100755 --- a/src/scss/global/_variables.scss +++ b/src/scss/global/_variables.scss @@ -122,17 +122,9 @@ $bp_shallow: 650px; animation-iteration-count: 1; } -@mixin fadeinout($duration: 3s){ - @keyframes fadeinout { +@mixin fadeout($duration: 0.2s){ + @keyframes fadeout { 0% { - opacity: 0; - transform: translateY(5px); - } - 6% { - opacity: 1; - transform: translateY(0px); - } - 94% { opacity: 1; transform: translateY(0px); } @@ -142,7 +134,7 @@ $bp_shallow: 650px; } } - animation-name: fadeinout; + animation-name: fadeout; animation-duration: $duration; animation-timing-function: linear; animation-iteration-count: 1;