diff --git a/VERSION.md b/VERSION.md index 7918c7ab..12566ed7 100755 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -3.13.1 \ No newline at end of file +3.14.0 \ No newline at end of file diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py index 473af375..d5cfd2e5 100755 --- a/mopidy_iris/core.py +++ b/mopidy_iris/core.py @@ -101,7 +101,16 @@ class IrisCore(object): callback = kwargs.get('callback', None) for connection in self.connections.itervalues(): - connection['connection'].write_message( json_encode(data) ) + + send_to_this_connection = True + + # Don't send the broadcast to the origin, naturally + if 'connection_id' in data: + if connection['connection_id'] == data["connection_id"]: + send_to_this_connection = False + + if send_to_this_connection: + connection['connection'].write_message(json_encode(data)) response = { 'message': 'Broadcast to '+str(len(self.connections))+' connections' @@ -141,6 +150,7 @@ class IrisCore(object): new_connection = { 'client': client, + 'connection_id': connection_id, 'connection': connection } self.connections[connection_id] = new_connection diff --git a/src/js/App.js b/src/js/App.js index 4ad9940a..3ccd708d 100755 --- a/src/js/App.js +++ b/src/js/App.js @@ -157,13 +157,13 @@ class App extends React.Component{ case 32: // spacebar if (e.ctrlKey || e.metaKey){ this.props.mopidyActions.stop(); - this.props.uiActions.createNotification('stop', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'stop', type: 'shortcut', key: 'shortcut'}); } else if (this.props.play_state == 'playing'){ this.props.mopidyActions.pause(); - this.props.uiActions.createNotification('pause', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'pause', type: 'shortcut', key: 'shortcut'}); } else { this.props.mopidyActions.play(); - this.props.uiActions.createNotification('play', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'play', type: 'shortcut', key: 'shortcut'}); } break; @@ -179,7 +179,7 @@ class App extends React.Component{ case 40: // down if ((e.ctrlKey || e.metaKey) && e.shiftKey){ this.props.mopidyActions.setMute(true); - this.props.uiActions.createNotification('volume-off', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'volume-off', type: 'shortcut', key: 'shortcut'}); } else if (e.ctrlKey){ var volume = this.props.volume; if (volume !== 'false'){ @@ -191,7 +191,7 @@ class App extends React.Component{ if (this.props.mute){ this.props.mopidyActions.setMute(false); } - this.props.uiActions.createNotification('volume-down', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'volume-down', type: 'shortcut', key: 'shortcut'}); } } break; @@ -202,7 +202,7 @@ class App extends React.Component{ if (this.props.mute){ this.props.mopidyActions.setMute(false); } - this.props.uiActions.createNotification('volume-up', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'volume-up', type: 'shortcut', key: 'shortcut'}); } else if (e.ctrlKey || e.metaKey){ var volume = this.props.volume if (volume !== 'false'){ @@ -214,7 +214,7 @@ class App extends React.Component{ if (this.props.mute){ this.props.mopidyActions.setMute(false); } - this.props.uiActions.createNotification('volume-up', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'volume-up', type: 'shortcut', key: 'shortcut'}); } } break; @@ -226,20 +226,20 @@ class App extends React.Component{ new_position = 0;; } this.props.mopidyActions.seek(new_position); - this.props.uiActions.createNotification('fast-backward', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'fast-backward', type: 'shortcut', key: 'shortcut'}); } else if (e.ctrlKey || e.metaKey){ this.props.mopidyActions.previous(); - this.props.uiActions.createNotification('step-backward', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'step-backward', type: 'shortcut', key: 'shortcut'}); } break; case 39: // right if ((e.ctrlKey || e.metaKey) && e.shiftKey){ this.props.mopidyActions.seek(this.props.play_time_position + 30000); - this.props.uiActions.createNotification('fast-forward', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'fast-forward', type: 'shortcut', key: 'shortcut'}); } else if (e.ctrlKey || e.metaKey){ this.props.mopidyActions.next(); - this.props.uiActions.createNotification('step-forward', 'shortcut', 'shortcut'); + this.props.uiActions.createNotification({content: 'step-forward', type: 'shortcut', key: 'shortcut'}); } break; diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 1c3bf2c3..83323266 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -307,7 +307,7 @@ class ContextMenu extends React.Component{ document.execCommand("copy"); temp.remove(); - this.props.uiActions.createNotification("Copied "+this.props.menu.uris.length+" URIs" ) + this.props.uiActions.createNotification({content: "Copied "+this.props.menu.uris.length+" URIs"}); this.props.uiActions.hideContextMenu() } diff --git a/src/js/components/FollowButton.js b/src/js/components/FollowButton.js index 195f9df4..a927372f 100755 --- a/src/js/components/FollowButton.js +++ b/src/js/components/FollowButton.js @@ -41,7 +41,7 @@ class FollowButton extends React.Component{ } if (!this.props.spotify_authorized){ - return + return } else if (this.props.is_following === true){ return } else { diff --git a/src/js/components/LastfmAuthenticationFrame.js b/src/js/components/LastfmAuthenticationFrame.js index 2eecd9eb..341b7f15 100755 --- a/src/js/components/LastfmAuthenticationFrame.js +++ b/src/js/components/LastfmAuthenticationFrame.js @@ -39,13 +39,13 @@ class LastfmAuthenticationFrame extends React.Component{ // Only allow incoming data from our authorized authenticator proxy var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8)) if (event.origin != authorization_domain){ - this.props.uiActions.createNotification('Authorization failed. '+event.origin+' is not the configured authorization_url.','bad') + this.props.uiActions.createNotification({content: 'Authorization failed. '+event.origin+' is not the configured authorization_url.', type: 'bad'}); return false } // Bounced with an error if (data.error !== undefined){ - this.props.uiActions.createNotification(data.message,'bad') + this.props.uiActions.createNotification({content: data.message, type: 'bad'}); // No errors? We're in! } else { @@ -80,7 +80,7 @@ class LastfmAuthenticationFrame extends React.Component{ // Popup does not exist, so must have been blocked } else { - self.props.uiActions.createNotification('Popup blocked. Please allow popups and try again.','bad') + self.props.uiActions.createNotification({content: 'Popup blocked. Please allow popups and try again.', type: 'bad'}); self.setState({authorizing: false}) clearInterval(timer); } diff --git a/src/js/components/LastfmLoveButton.js b/src/js/components/LastfmLoveButton.js index 2ef3447d..536228ba 100755 --- a/src/js/components/LastfmLoveButton.js +++ b/src/js/components/LastfmLoveButton.js @@ -36,7 +36,7 @@ class FollowButton extends React.Component{ } if (!this.props.lastfm_authorized){ - return + return } else if (this.props.is_loved && this.props.is_loved !== "0"){ return } else { diff --git a/src/js/components/SpotifyAuthenticationFrame.js b/src/js/components/SpotifyAuthenticationFrame.js index 86a74d6a..f182ca42 100755 --- a/src/js/components/SpotifyAuthenticationFrame.js +++ b/src/js/components/SpotifyAuthenticationFrame.js @@ -39,13 +39,13 @@ class SpotifyAuthenticationFrame extends React.Component{ // Only allow incoming data from our authorized authenticator proxy var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8)) if (event.origin != authorization_domain){ - this.props.uiActions.createNotification('Authorization failed. '+event.origin+' is not the configured authorization_url.','bad') + this.props.uiActions.createNotification({content: 'Authorization failed. '+event.origin+' is not the configured authorization_url.', type: 'bad'}); return false } // Spotify bounced with an error if (data.error !== undefined){ - this.props.uiActions.createNotification(data.error,'bad') + this.props.uiActions.createNotification({content: data.error, type: 'bad'}); // No errors? We're in! } else { @@ -95,7 +95,7 @@ class SpotifyAuthenticationFrame extends React.Component{ // Popup does not exist, so must have been blocked } else { - self.props.uiActions.createNotification('Popup blocked. Please allow popups and try again.','bad') + self.props.uiActions.createNotification({content: 'Popup blocked. Please allow popups and try again.', type: 'bad'}); self.setState({authorizing: false}) clearInterval(timer); } diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js index a6028c39..dbc830f5 100755 --- a/src/js/components/TrackList.js +++ b/src/js/components/TrackList.js @@ -293,7 +293,7 @@ class TrackList extends React.Component{ var selected_tracks_indexes = helpers.arrayOf('index', selected_tracks); if (selected_tracks.length <= 0){ - return this.props.uiActions.createNotification('No tracks selected', 'bad'); + return this.props.uiActions.createNotification({content: 'No tracks selected', type: 'bad'}); } // Our parent handles playing diff --git a/src/js/services/core/middleware.js b/src/js/services/core/middleware.js index adc5c777..d23dd957 100755 --- a/src/js/services/core/middleware.js +++ b/src/js/services/core/middleware.js @@ -87,13 +87,7 @@ const CoreMiddleware = (function(){ nonInteraction: true }); - store.dispatch(uiActions.createNotification( - message, - 'bad', - null, - null, - description - )); + store.dispatch(uiActions.createNotification({content: message, type: 'bad', description: description})); console.error(message, description, data); break; @@ -176,7 +170,7 @@ const CoreMiddleware = (function(){ break case 'PLAYLIST_TRACKS_ADDED': - store.dispatch(uiActions.createNotification('Added '+action.tracks_uris.length+' tracks to playlist')) + store.dispatch(uiActions.createNotification({content: 'Added '+action.tracks_uris.length+' tracks to playlist'})) switch(helpers.uriSource(action.key)){ case 'spotify': store.dispatch(spotifyActions.getPlaylist(action.key)) diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js index 978f4068..9dabb9f5 100755 --- a/src/js/services/mopidy/actions.js +++ b/src/js/services/mopidy/actions.js @@ -42,7 +42,10 @@ export function debug(call, value){ **/ export function changeTrack(tlid){ - return instruct('playback.play', {tlid: tlid}) + return { + type: 'MOPIDY_CHANGE_TRACK', + tlid: tlid + } } export function playURIs(uris, from_uri = null){ @@ -85,7 +88,10 @@ export function playAlbum(uri){ } export function removeTracks(tlids){ - return instruct('tracklist.remove', {tlid: tlids}) + return { + type: 'MOPIDY_REMOVE_TRACKS', + tlids: tlids + } } export function reorderTracklist(indexes, insert_before){ @@ -104,7 +110,9 @@ export function clearTracklist(){ } export function play(){ - return instruct('playback.play') + return { + type: 'MOPIDY_TRIGGER_PLAY' + } } export function pause(){ diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index ebbebd82..0247151e 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -274,26 +274,69 @@ const MopidyMiddleware = (function(){ * General playback **/ - case 'MOPIDY_NEXT': + case 'MOPIDY_TRIGGER_PLAY': + instruct(socket, store, 'playback.play'); var data = { - type: 'browser_notification', - title: 'Track skipped', - body: store.getState().pusher.username +' skipped this track', + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +(store.getState().mopidy.play_state == 'paused' ? ' resumed' : ' started')+' playback', icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) } - store.dispatch(pusherActions.deliverBroadcast(data) ) + store.dispatch(pusherActions.deliverBroadcast(data)); + break + + case 'MOPIDY_PAUSE': + var data = { + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +' paused playback', + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) + } + store.dispatch(pusherActions.deliverBroadcast(data)); + break + + case 'MOPIDY_NEXT': + var data = { + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +' skipped '+store.getState().core.current_track.name+'', + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) + } + store.dispatch(pusherActions.deliverBroadcast(data)); break case 'MOPIDY_STOP': var data = { - type: 'browser_notification', - title: 'Playback stopped', - body: store.getState().pusher.username +' stopped playback', + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +' stopped playback', icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) } - store.dispatch(pusherActions.deliverBroadcast(data) ) + store.dispatch(pusherActions.deliverBroadcast(data)); break + case 'MOPIDY_CHANGE_TRACK': + instruct(socket, store, 'playback.play', {tlid: action.tlid}); + + var broadcast_data = { + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +' changed track' + } + store.dispatch(pusherActions.deliverBroadcast(broadcast_data)); + break; + + case 'MOPIDY_REMOVE_TRACKS': + instruct(socket, store, 'tracklist.remove', {tlid: action.tlids}); + + var broadcast_data = { + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +' removed '+action.tlids.length+' tracks' + } + store.dispatch(pusherActions.deliverBroadcast(broadcast_data)); + break; + case 'MOPIDY_PLAY_PLAYLIST': // Clear tracklist (if set) @@ -326,7 +369,7 @@ const MopidyMiddleware = (function(){ .then( response => { if (response.tracks === undefined){ - store.dispatch(uiActions.createNotification('Failed to load playlist tracks','bad')) + store.dispatch(uiActions.createNotification({content: 'Failed to load playlist tracks', type: 'bad'})); } else { var tracks_uris = helpers.arrayOf('uri',response.tracks) store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri)) @@ -344,10 +387,18 @@ const MopidyMiddleware = (function(){ case 'MOPIDY_ENQUEUE_URIS': if (!action.uris || action.uris.length <= 0){ - this.props.uiActions.createNotification("No URIs to enqueue","warning"); + this.props.uiActions.createNotification({content: "No URIs to enqueue", type: "warning"}); break; } + var broadcast_data = { + type: 'notification', + notification_type: 'info', + content: store.getState().pusher.username +' is adding '+action.uris.length+' URIs to queue', + icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false) + } + store.dispatch(pusherActions.deliverBroadcast(broadcast_data)); + // split into batches var uris = Object.assign([], action.uris) var batches = [] @@ -477,7 +528,7 @@ const MopidyMiddleware = (function(){ case 'MOPIDY_PLAY_URIS': if (!action.uris || action.uris.length <= 0){ - this.props.uiActions.createNotification("No URIs to play","warning"); + this.props.uiActions.createNotification({content: "No URIs to play", type: "warning"}); break; } @@ -491,7 +542,7 @@ const MopidyMiddleware = (function(){ store.dispatch(mopidyActions.clearTracklist()); } - var first_uri = action.uris[0] + var first_uri = action.uris[0]; // add our first track instruct(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 }) @@ -576,7 +627,7 @@ const MopidyMiddleware = (function(){ var uri_scheme = uri_schemes.shift(); if (uri_schemes_total <= 0){ - store.dispatch(uiActions.createNotification('No sources selected', 'warning')); + store.dispatch(uiActions.createNotification({content: 'No sources selected', type: 'warning'})); } else { store.dispatch(uiActions.startProcess( 'MOPIDY_GET_SEARCH_RESULTS_PROCESSOR', @@ -1287,7 +1338,7 @@ const MopidyMiddleware = (function(){ hashHistory.push(global.baseURL+'playlist/'+encodeURIComponent(response.uri)); } - store.dispatch(uiActions.createNotification('Saved')); + store.dispatch(uiActions.createNotification({content: 'Saved'})); }) }); break @@ -1337,7 +1388,7 @@ const MopidyMiddleware = (function(){ case 'MOPIDY_CREATE_PLAYLIST': instruct(socket, store, 'playlists.create', { name: action.name, uri_scheme: action.scheme }) .then(response => { - store.dispatch(uiActions.createNotification('Created playlist')) + store.dispatch(uiActions.createNotification({content: 'Created playlist'})) store.dispatch({ type: 'PLAYLIST_LOADED', @@ -1356,7 +1407,7 @@ const MopidyMiddleware = (function(){ case 'MOPIDY_DELETE_PLAYLIST': instruct(socket, store, 'playlists.delete', { uri: action.uri }) .then(response => { - store.dispatch(uiActions.createNotification('Deleted playlist')) + store.dispatch(uiActions.createNotification({content: 'Deleted playlist'})) store.dispatch({ type: 'MOPIDY_LIBRARY_PLAYLIST_DELETED', key: action.uri diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index 99a5a3d8..7a51cea3 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -228,7 +228,7 @@ const PusherMiddleware = (function(){ request(store, 'deliver_message', action.data) .then( response => { - store.dispatch(uiActions.createNotification('Message delivered') ) + store.dispatch(uiActions.createNotification({content: 'Message delivered'})); }, error => { store.dispatch(coreActions.handleException( @@ -278,9 +278,9 @@ const PusherMiddleware = (function(){ } if (response.upgrade_successful){ - store.dispatch(uiActions.createNotification('Upgrade complete') ) + store.dispatch(uiActions.createNotification({content: 'Upgrade complete'})); } else { - store.dispatch(uiActions.createNotification('Upgrade failed, please upgrade manually','bad') ) + store.dispatch(uiActions.createNotification({content: 'Upgrade failed, please upgrade manually', type: 'bad'})); } response.type = 'PUSHER_VERSION' @@ -381,7 +381,7 @@ const PusherMiddleware = (function(){ response => { store.dispatch(uiActions.processFinished('PUSHER_RADIO_PROCESS')); if (response.status == 0){ - store.dispatch(uiActions.createNotification(response.message, 'bad')); + store.dispatch(uiActions.createNotification({content: response.message, type: 'bad'})); } }, error => { @@ -395,8 +395,8 @@ const PusherMiddleware = (function(){ break case 'PUSHER_STOP_RADIO': - store.dispatch(uiActions.createNotification('Stopping radio')) - ReactGA.event({ category: 'Pusher', action: 'Stop radio' }) + store.dispatch(uiActions.createNotification({content: 'Stopping radio'})); + ReactGA.event({ category: 'Pusher', action: 'Stop radio' }); var data = { seed_artists: [], @@ -420,6 +420,16 @@ const PusherMiddleware = (function(){ store.dispatch(uiActions.createBrowserNotification(action)) break + case 'PUSHER_NOTIFICATION': + var data = Object.assign( + {}, + action, { + type: action.notification_type + } + ); + store.dispatch(uiActions.createNotification(data)); + break + case 'PUSHER_RESTART': // Hard reload. This doesn't strictly clear the cache, but our compiler's // cache buster should handle that @@ -430,7 +440,7 @@ const PusherMiddleware = (function(){ ReactGA.event({ category: 'Pusher', action: 'Version', label: action.version.current }) if (action.version.upgrade_available){ - store.dispatch(uiActions.createNotification('Version '+action.version.latest+' is available. See settings to upgrade.' ) ) + store.dispatch(uiActions.createNotification({content: 'Version '+action.version.latest+' is available. See settings to upgrade.'})); } next(action ) break diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 0686c965..aa952e6d 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -1456,7 +1456,7 @@ export function createPlaylist(name, description, is_public, is_collaborative){ uris: [response.uri] }) - dispatch(uiActions.createNotification('Created playlist')) + dispatch(uiActions.createNotification({content: 'Created playlist'})); }, error => { dispatch(coreActions.handleException( @@ -1491,7 +1491,7 @@ export function savePlaylist(uri, name, description, is_public, is_collaborative description: description } }) - dispatch(uiActions.createNotification('Saved')) + dispatch(uiActions.createNotification({content: 'Saved'})); }, error => { dispatch(coreActions.handleException( diff --git a/src/js/services/ui/actions.js b/src/js/services/ui/actions.js index fd780f49..1234aa07 100755 --- a/src/js/services/ui/actions.js +++ b/src/js/services/ui/actions.js @@ -175,20 +175,22 @@ export function createBrowserNotification(data){ } } -export function createNotification(content, type = 'default', key = null, title = null, description = null, sticky = false){ - if (!key){ - key = helpers.generateGuid() - } +// content, type = 'default', key = null, title = null, description = null, sticky = false + +export function createNotification(data){ return { type: 'CREATE_NOTIFICATION', - notification: { - key: key, - type: type, - title: title, - description: description, - content: content, - sticky: sticky - } + notification: Object.assign( + { + key: helpers.generateGuid(), + type: 'default', + title: null, + content: null, + description: null, + sticky: false + }, + data + ) } } diff --git a/src/js/services/ui/middleware.js b/src/js/services/ui/middleware.js index 409d2679..fbc7224f 100755 --- a/src/js/services/ui/middleware.js +++ b/src/js/services/ui/middleware.js @@ -11,7 +11,6 @@ const UIMiddleware = (function(){ * The actual middleware inteceptor **/ return store => next => action => { - switch(action.type){ case 'MOPIDY_STATE': @@ -117,14 +116,14 @@ const UIMiddleware = (function(){ if (!suppressed_broadcasts.includes(broadcast.key)){ if (broadcast.message){ - store.dispatch(uiActions.createNotification( - broadcast.message, - 'broadcast', - (broadcast.key ? broadcast.key : null), - (broadcast.title ? broadcast.title : null), - null, - true - )) + var data = { + key: (broadcast.key ? broadcast.key : null), + title: (broadcast.title ? broadcast.title : null), + content: broadcast.message, + type: 'broadcast', + sticky: true + } + store.dispatch(uiActions.createNotification(data)); } } } diff --git a/src/js/views/Debug.js b/src/js/views/Debug.js index e436da2b..e2964b47 100755 --- a/src/js/views/Debug.js +++ b/src/js/views/Debug.js @@ -112,7 +112,7 @@ class Debug extends React.Component{
- this.props.uiActions.createNotification('Test notification')}>Create notification + this.props.uiActions.createNotification({content: 'Test notification'})}>Create notification this.props.uiActions.startProcess('test_process', 'Test process', {total: 100, remaining: 17})}>Start process this.props.uiActions.processFinished('test_process')}>Stop process
@@ -169,7 +169,8 @@ class Debug extends React.Component{ - + + diff --git a/src/scss/components/_notifications.scss b/src/scss/components/_notifications.scss index 91dfeb29..0f3577c2 100755 --- a/src/scss/components/_notifications.scss +++ b/src/scss/components/_notifications.scss @@ -54,12 +54,15 @@ background: $green; } - &.broadcast, - &.info { + &.broadcast { background: $yellow; color: $black; } + &.info { + background: $grey; + } + &.warning { background: $orange; } diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss index c9bc837f..d0c8f159 100755 --- a/src/scss/global/_variables.scss +++ b/src/scss/global/_variables.scss @@ -105,8 +105,14 @@ $bp_shallow: 650px; } @keyframes fadein { - 0% { opacity: 0; } - 100% { opacity: 1; } + 0% { + opacity: 0; + transform: translateY(5px); + } + 100% { + opacity: 1; + transform: translateY(0px); + } } @mixin fadein($duration: 0.2s){