diff --git a/admin/app/admin/broadcast_notifications.rb b/admin/app/admin/broadcast_notifications.rb
index b6b373218..211d9298a 100644
--- a/admin/app/admin/broadcast_notifications.rb
+++ b/admin/app/admin/broadcast_notifications.rb
@@ -22,12 +22,5 @@ ActiveAdmin.register JamRuby::BroadcastNotification, :as => 'BroadcastNotificati
end
end
- controller do
- def create
- resource_class.create(params[:broadcast_notification])
- redirect_to(admin_broadcast_notifications_path)
- end
-
- end
end
diff --git a/db/Gemfile.lock b/db/Gemfile.lock
index eb6aee107..8d6d039c2 100644
--- a/db/Gemfile.lock
+++ b/db/Gemfile.lock
@@ -16,3 +16,6 @@ PLATFORMS
DEPENDENCIES
pg_migrate (= 0.1.13)
+
+BUNDLED WITH
+ 1.10.3
diff --git a/db/up/broadcast_notifications.sql b/db/up/broadcast_notifications.sql
index 12ca9b623..5a7cb80aa 100644
--- a/db/up/broadcast_notifications.sql
+++ b/db/up/broadcast_notifications.sql
@@ -14,6 +14,7 @@ CREATE TABLE broadcast_notification_views (
user_id varchar(64) NOT NULL REFERENCES users(id),
broadcast_notification_id varchar(64) NOT NULL REFERENCES broadcast_notifications(id) ON DELETE CASCADE,
view_count INTEGER DEFAULT 0,
+ active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
diff --git a/ruby/lib/jam_ruby/models/broadcast_notification.rb b/ruby/lib/jam_ruby/models/broadcast_notification.rb
index 7eeaf1de8..24c19cc71 100644
--- a/ruby/lib/jam_ruby/models/broadcast_notification.rb
+++ b/ruby/lib/jam_ruby/models/broadcast_notification.rb
@@ -2,6 +2,7 @@ module JamRuby
class BroadcastNotification < ActiveRecord::Base
attr_accessible :title, :message, :button_label, :frequency, :button_url, as: :admin
+
has_many :user_views, class_name: 'JamRuby::BroadcastNotificationView', dependent: :destroy
validates :button_label, presence: true, length: {maximum: 14}
@@ -16,8 +17,9 @@ module JamRuby
self.select('broadcast_notifications.*, bnv.updated_at AS bnv_updated_at')
.joins("LEFT OUTER JOIN broadcast_notification_views AS bnv ON bnv.broadcast_notification_id = broadcast_notifications.id AND (bnv.user_id IS NULL OR (bnv.user_id = '#{user.id}'))")
.where(['broadcast_notifications.frequency > 0'])
+ .where(['bnv.user_id IS NULL OR bnv.active_at < NOW()'])
.where(['bnv.user_id IS NULL OR broadcast_notifications.frequency > bnv.view_count'])
- .order('bnv_updated_at')
+ .order('bnv_updated_at NULLS FIRST')
end
def did_view(user)
@@ -25,6 +27,13 @@ module JamRuby
.where(broadcast_notification_id: self.id, user_id: user.id)
.limit(1)
.first
+
+ unless bnv
+ bnv = user_views.new()
+ bnv.user = user
+ bnv.active_at = Time.now - 10
+ end
+
bnv = user_views.new(user: user) unless bnv
bnv.view_count += 1
bnv.save
diff --git a/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb b/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb
index 083daa08f..4ec86db70 100644
--- a/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb
+++ b/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb
@@ -23,7 +23,7 @@ describe BroadcastNotification do
it 'gets view incremented' do
bnv = broadcast1.did_view(user1)
bnv = broadcast1.did_view(user1)
- expect(bnv.view_count).to be eq(2)
+ bnv.view_count.should eq(2)
end
it 'loads viewable broadcasts for a user' do
diff --git a/web/app/assets/javascripts/client_init.js.coffee b/web/app/assets/javascripts/client_init.js.coffee
index da8e59c3a..5d1bf6354 100644
--- a/web/app/assets/javascripts/client_init.js.coffee
+++ b/web/app/assets/javascripts/client_init.js.coffee
@@ -1,26 +1,34 @@
# one time init stuff for the /client view
-
$ = jQuery
context = window
context.JK ||= {};
-
-broadcastActions = BroadcastNotificationActions # require('./react-components/actions/BroadcastNotificationActions')
+broadcastActions = context.JK.Actions.broadcast
context.JK.ClientInit = class ClientInit
constructor: () ->
@logger = context.JK.logger
@gearUtils = context.JK.GearUtils
+ @ALERT_NAMES = context.JK.ALERT_NAMES;
+ @lastCheckedBroadcast = null
init: () =>
if context.gon.isNativeClient
this.nativeClientInit()
- setTimeout(this.checkBroadcastNotification, 3000)
+ context.JK.onBackendEvent(@ALERT_NAMES.WINDOW_OPEN_FOREGROUND_MODE, 'client_init', @watchBroadcast);
- checkBroadcastNotification: () =>
+ this.watchBroadcast()
+
+ checkBroadcast: () =>
+ broadcastActions.load.triggerPromise()
+
+ watchBroadcast: () =>
if context.JK.currentUserId
- broadcastActions.load.triggerPromise()
+ # create a 15 second buffer to not check too fast for some reason (like the client firing multiple foreground events)
+ if !@lastCheckedBroadcast? || @lastCheckedBroadcast.getTime() < new Date().getTime() - 15000
+ @lastCheckedBroadcast = new Date()
+ setTimeout(@checkBroadcast, 3000)
nativeClientInit: () =>
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 820dd360c..82773d503 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -64,7 +64,19 @@
});
}
- function uploadMusicNotations(formData) {
+ function quietBroadcastNotification(options) {
+ var userId = getId(options);
+ var broadcast_id = options.broadcast_id;
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ contentType: 'application/json',
+ url: "/api/users/" + userId + "/broadcast_notification/" + broadcast_id + '/quiet',
+ data: JSON.stringify({}),
+ });
+ }
+
+ function uploadMusicNotations(formData) {
return $.ajax({
type: "POST",
processData: false,
@@ -1789,6 +1801,7 @@
this.uploadMusicNotations = uploadMusicNotations;
this.getMusicNotation = getMusicNotation;
this.getBroadcastNotification = getBroadcastNotification;
+ this.quietBroadcastNotification = quietBroadcastNotification;
this.legacyJoinSession = legacyJoinSession;
this.joinSession = joinSession;
this.cancelSession = cancelSession;
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index ac49684e6..51ce008dd 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -202,7 +202,12 @@
var gridRows = layout.split('x')[0];
var gridCols = layout.split('x')[1];
- $grid.children().each(function () {
+ var gutterWidth = 0;
+
+ var findCardLayout;
+ var feedCardLayout;
+
+ $grid.find('.homecard').each(function () {
var childPosition = $(this).attr("layout-grid-position");
var childRow = childPosition.split(',')[1];
var childCol = childPosition.split(',')[0];
@@ -211,6 +216,13 @@
var childLayout = me.getCardLayout(gridWidth, gridHeight, gridRows, gridCols,
childRow, childCol, childRowspan, childColspan);
+ if($(this).is('.feed')) {
+ feedCardLayout = childLayout;
+ }
+ else if($(this).is('.findsession')) {
+ findCardLayout = childLayout;
+ }
+
$(this).animate({
width: childLayout.width,
height: childLayout.height,
@@ -218,6 +230,18 @@
left: childLayout.left
}, opts.animationDuration);
});
+
+ var broadcastWidth = findCardLayout.width + feedCardLayout.width;
+
+ layoutBroadcast(broadcastWidth, findCardLayout.left);
+ }
+
+ function layoutBroadcast(width, left) {
+ var css = {
+ width:width + opts.gridPadding * 2,
+ left:left
+ }
+ $('[data-react-class="BroadcastHolder"]').animate(css, opts.animationDuration)
}
function layoutSidebar(screenWidth, screenHeight) {
diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js
index 9b3a9c63e..042a685db 100644
--- a/web/app/assets/javascripts/react-components.js
+++ b/web/app/assets/javascripts/react-components.js
@@ -1,7 +1,30 @@
//= require_self
//= require react_ujs
-React = require('react');
+// this pulls in react + addons (like CSS transitions)
+React = require('react/addons');
+
+context = window
+
+var actions = {}
+var stores = {}
+var components = {}
+
+// create globally available references to all actions, stores, and components
+context.JK.Actions = actions
+context.JK.Stores = stores
+context.JK.Components = components
+
+// FLUX ACTIONS
+actions.broadcast = require('./react-components/actions/BroadcastActions')
+
+// FLUX STORES
+stores.broadcast = require('./react-components/stores/BroadcastStore');
+
+// REACT COMPONENTS
+// NOTE: be sure to give each component a global name so that you can use the <%= react_component "ComponentName" %> directive or in JSX
+components.broadcastHolder = BroadcastHolder = require('./react-components/BroadcastHolder')
+components.broadcast = Broadcast = require('./react-components/Broadcast')
+
+
-BroadcastNotificationActions = require('./react-components/actions/BroadcastNotificationActions')
-BroadcastNotification = require('./react-components/BroadcastNotification')
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/Broadcast.jsx b/web/app/assets/javascripts/react-components/Broadcast.jsx
new file mode 100644
index 000000000..0c04296ba
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/Broadcast.jsx
@@ -0,0 +1,51 @@
+var React = require('react');
+
+var broadcastActions = window.JK.Actions.broadcast;
+var rest = window.JK.Rest();
+
+var Broadcast = React.createClass({
+ displayName: 'Broadcast Notification',
+
+ handleNavigate: function (e) {
+
+ var href = $(e.currentTarget).attr('href')
+
+ if (href.indexOf('http') == 0) {
+ e.preventDefault()
+ window.JK.popExternalLink(href)
+ }
+
+ broadcastActions.hide.trigger()
+ },
+
+ notNow: function (e) {
+
+ e.preventDefault();
+
+ rest.quietBroadcastNotification({broadcast_id: this.props.notification.id})
+
+ broadcastActions.hide.trigger()
+ },
+
+ createMarkup: function() {
+ return {__html: this.props.notification.message};
+ },
+
+ render: function () {
+ return
+
+ }
+});
+
+// each file will export exactly one component
+module.exports = Broadcast;
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/BroadcastHolder.jsx b/web/app/assets/javascripts/react-components/BroadcastHolder.jsx
new file mode 100644
index 000000000..a13c26538
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/BroadcastHolder.jsx
@@ -0,0 +1,27 @@
+var React = require('react');
+
+var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+
+var broadcastStore = window.JK.Stores.broadcast;
+
+var BroadcastHolder = React.createClass({displayName: 'Broadcast Notification Holder',
+ mixins: [Reflux.connect(broadcastStore, 'notification')],
+
+ render: function() {
+ var notification = []
+ if(this.state.notification) {
+ notification.push(
)
+ }
+
+ return (
+
+
+ {notification}
+
+
+ )
+ }
+});
+
+// each file will export exactly one component
+module.exports = BroadcastHolder;
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/BroadcastNotification.jsx b/web/app/assets/javascripts/react-components/BroadcastNotification.jsx
deleted file mode 100644
index 841b3be4e..000000000
--- a/web/app/assets/javascripts/react-components/BroadcastNotification.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-var React = require('react');
-
-var BroadcastNotificationStore = require('./stores/BroadcastNotificationStore');
-
-var BroadcastNotification = React.createClass({displayName: 'Broadcast Notification',
- mixins: [Reflux.connect(BroadcastNotificationStore, 'notification')],
- render: function() {
- if(!this.state.notification) {
- return HAHAHAAH
- }
-
- return
-
-
- }
-});
-
-// each file will export exactly one component
-module.exports = BroadcastNotification;
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/DemoComponent.jsx b/web/app/assets/javascripts/react-components/DemoComponent.jsx
deleted file mode 100644
index 59d296fbe..000000000
--- a/web/app/assets/javascripts/react-components/DemoComponent.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-var React = require('react');
-
-var DemoComponent = React.createClass({displayName: 'Demo Component',
- render: function() {
- return
Demo Component
;
- }
-});
-
-// each file will export exactly one component
-module.exports = DemoComponent;
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/actions/BroadcastActions.js.coffee b/web/app/assets/javascripts/react-components/actions/BroadcastActions.js.coffee
new file mode 100644
index 000000000..d6982172d
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/actions/BroadcastActions.js.coffee
@@ -0,0 +1,8 @@
+
+BroadcastActions = Reflux.createActions({
+ load: {asyncResult: true},
+ hide: {}
+})
+
+module.exports = BroadcastActions
+
diff --git a/web/app/assets/javascripts/react-components/actions/BroadcastNotificationActions.js b/web/app/assets/javascripts/react-components/actions/BroadcastNotificationActions.js
deleted file mode 100644
index cf5e97b01..000000000
--- a/web/app/assets/javascripts/react-components/actions/BroadcastNotificationActions.js
+++ /dev/null
@@ -1,8 +0,0 @@
-//var rest = context.JK.Rest()
-
-var BroadcastNotificationActions = Reflux.createActions({
- load: {asyncResult: true}
-})
-
-module.exports = BroadcastNotificationActions
-
diff --git a/web/app/assets/javascripts/react-components/stores/BroadcastNotificationStore.js.coffee b/web/app/assets/javascripts/react-components/stores/BroadcastNotificationStore.js.coffee
deleted file mode 100644
index 6f8254755..000000000
--- a/web/app/assets/javascripts/react-components/stores/BroadcastNotificationStore.js.coffee
+++ /dev/null
@@ -1,35 +0,0 @@
-$ = jQuery
-context = window
-logger = context.JK.logger
-broadcastActions = BroadcastNotificationActions # require('../actions/BroadcastNotificationActions')
-
-rest = context.JK.Rest()
-
-# see if this shows up elsewhere
-broadcastActions.blah = 'hahah'
-
-broadcastActions.load.listenAndPromise(rest.getBroadcastNotification);
-
-console.log("broadcastActions!!", broadcastActions)
-BroadcastNotificationStore = Reflux.createStore(
- {
- listenables: broadcastActions
-
- init: () =>
- logger.debug("broadcast notification store init")
- #this.listenTo(broadcastActions.load, 'onSync')
-
- onLoad: () =>
- logger.debug("loading broadcast notification...")
-
- onLoadCompleted: () =>
- logger.debug("broadcast notification sync completed")
-
- onLoadFailed: (jqXHR) =>
- if jqXHR.status != 404
- logger.error("broadcast notification sync failed")
- }
-)
-
-module.exports = BroadcastNotificationStore
-
diff --git a/web/app/assets/javascripts/react-components/stores/BroadcastStore.js.coffee b/web/app/assets/javascripts/react-components/stores/BroadcastStore.js.coffee
new file mode 100644
index 000000000..52953af5d
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/BroadcastStore.js.coffee
@@ -0,0 +1,31 @@
+$ = jQuery
+context = window
+logger = context.JK.logger
+broadcastActions = context.JK.Actions.broadcast
+
+rest = context.JK.Rest()
+
+broadcastActions.load.listenAndPromise(rest.getBroadcastNotification);
+
+BroadcastStore = Reflux.createStore(
+ {
+ listenables: broadcastActions
+
+ onLoad: () ->
+ logger.debug("loading broadcast notification...")
+
+ onLoadCompleted: (response) ->
+ logger.debug("broadcast notification sync completed")
+ this.trigger(response)
+
+ onLoadFailed: (jqXHR) ->
+ if jqXHR.status != 404
+ logger.error("broadcast notification sync failed")
+
+ onHide: () ->
+ this.trigger(null)
+ }
+)
+
+module.exports = BroadcastStore
+
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index 1b1c05606..276bfb66a 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -81,4 +81,5 @@
*= require ./jamTrackPreview
*= require users/signinCommon
*= require landings/partner_agreement_v1
+ *= require_directory ./react-components
*/
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/react-components/broadcast.css.scss b/web/app/assets/stylesheets/client/react-components/broadcast.css.scss
new file mode 100644
index 000000000..25d43ad5f
--- /dev/null
+++ b/web/app/assets/stylesheets/client/react-components/broadcast.css.scss
@@ -0,0 +1,79 @@
+@import 'client/common';
+
+[data-react-class="BroadcastHolder"] {
+ position:absolute;
+ min-height:60px;
+ top:62px;
+ @include border_box_sizing;
+
+ .broadcast-notification {
+ position:absolute;
+ border-width:1px;
+ border-color:$ColorScreenPrimary;
+ border-style:solid;
+ padding:5px 12px;
+ height:100%;
+ width:100%;
+ left:0;
+ top:0;
+ overflow:hidden;
+ margin-left:60px;
+ @include border_box_sizing;
+
+ }
+ .message {
+ float:left;
+ width:calc(100% - 150px);
+ font-size:12px;
+ }
+
+ .actions {
+ float:right;
+ text-align: right;
+ vertical-align: middle;
+ display:inline;
+ height:100%;
+ width:150px;
+ }
+
+ .actionsAligner {
+ display:inline-block;
+ vertical-align:middle;
+ text-align:center;
+ }
+
+ a { display:inline-block; }
+
+ .button-orange {
+ font-size:16px;
+ position:relative;
+ top:8px;
+ }
+
+ .not-now {
+ font-size:11px;
+ top:13px;
+ position:relative;
+ }
+}
+
+
+.bn-slidedown-enter {
+ max-height:0;
+ opacity: 0.01;
+ transition: max-height .5s ease-in;
+}
+
+.bn-slidedown-enter.bn-slidedown-enter-active {
+ max-height:60px;
+ opacity: 1;
+}
+
+.bn-slidedown-leave {
+ max-height:60px;
+ transition: max-height .5s ease-in;
+}
+
+.bn-slidedown-leave.bn-slidedown-leave-active {
+ max-height:0;
+}
\ No newline at end of file
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index 52b475875..5d8e1cd00 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -814,7 +814,18 @@ class ApiUsersController < ApiController
else
render json: { message: 'Not Found'}, status: 404
end
+ end
+ # used to hide a broadcast notification from rotation temporarily
+ def quiet_broadcast_notification
+ @broadcast = BroadcastNotificationView.find_by_broadcast_notification_id_and_user_id(params[:broadcast_id], current_user.id)
+
+ if @broadcast
+ @broadcast.active_at = Date.today + 14 # 14 days in the future we'll re-instas
+ @broadcast.save
+ end
+
+ render json: { }, status: 200
end
###################### RECORDINGS #######################
diff --git a/web/app/views/api_users/broadcast_notification.rabl b/web/app/views/api_users/broadcast_notification.rabl
index 6fd4faabf..21a9c3f0d 100644
--- a/web/app/views/api_users/broadcast_notification.rabl
+++ b/web/app/views/api_users/broadcast_notification.rabl
@@ -1,3 +1,3 @@
object @broadcast
-attributes :message, :button_label, :button_url
\ No newline at end of file
+attributes :id, :message, :button_label, :button_url
\ No newline at end of file
diff --git a/web/app/views/clients/_home.html.slim b/web/app/views/clients/_home.html.slim
index 9247d18f0..72ea35d4d 100644
--- a/web/app/views/clients/_home.html.slim
+++ b/web/app/views/clients/_home.html.slim
@@ -10,7 +10,7 @@
/ Grid is the count of the smallest spaces, then
/ individual spells span those spaces
-if @nativeClient
- .grid layout-grid="2x12"
+ .grid layout-grid="2x12"
.homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}"
h2 create session
.homebox-info
@@ -45,7 +45,7 @@
.homebox-info
/! free service level
-else
- .grid layout-grid="2x12"
+ .grid layout-grid="2x12"
.homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}"
h2 create session
.homebox-info
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 804a1eb75..fc303cf0e 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -9,6 +9,7 @@
<%= render "header" %>
+<%= react_component 'BroadcastHolder', {} %>
<%= render "home" %>
<%= render "footer" %>
<%= render "paginator" %>
@@ -81,7 +82,6 @@
<%= render 'dialogs/dialogs' %>
-<%= react_component 'BroadcastNotification', {} %>