From ea0c2015d16cbfe67d8a6300bbb687a09270799f Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 9 Jun 2015 17:36:11 -0500 Subject: [PATCH] * wip --- admin/app/admin/broadcast_notifications.rb | 7 -- db/Gemfile.lock | 3 + db/up/broadcast_notifications.sql | 1 + .../jam_ruby/models/broadcast_notification.rb | 11 ++- .../models/broadcast_notification_spec.rb | 2 +- .../assets/javascripts/client_init.js.coffee | 20 +++-- web/app/assets/javascripts/jam_rest.js | 15 +++- web/app/assets/javascripts/layout.js | 26 +++++- .../assets/javascripts/react-components.js | 29 ++++++- .../react-components/Broadcast.jsx | 51 ++++++++++++ .../react-components/BroadcastHolder.jsx | 27 +++++++ .../BroadcastNotification.jsx | 25 ------ .../react-components/DemoComponent.jsx | 10 --- .../actions/BroadcastActions.js.coffee | 8 ++ .../actions/BroadcastNotificationActions.js | 8 -- .../BroadcastNotificationStore.js.coffee | 35 -------- .../stores/BroadcastStore.js.coffee | 31 ++++++++ web/app/assets/stylesheets/client/client.css | 1 + .../react-components/broadcast.css.scss | 79 +++++++++++++++++++ web/app/controllers/api_users_controller.rb | 11 +++ .../api_users/broadcast_notification.rabl | 2 +- web/app/views/clients/_home.html.slim | 4 +- web/app/views/clients/index.html.erb | 2 +- web/config/application.rb | 2 +- web/config/routes.rb | 1 + web/package.json | 1 + 26 files changed, 309 insertions(+), 103 deletions(-) create mode 100644 web/app/assets/javascripts/react-components/Broadcast.jsx create mode 100644 web/app/assets/javascripts/react-components/BroadcastHolder.jsx delete mode 100644 web/app/assets/javascripts/react-components/BroadcastNotification.jsx delete mode 100644 web/app/assets/javascripts/react-components/DemoComponent.jsx create mode 100644 web/app/assets/javascripts/react-components/actions/BroadcastActions.js.coffee delete mode 100644 web/app/assets/javascripts/react-components/actions/BroadcastNotificationActions.js delete mode 100644 web/app/assets/javascripts/react-components/stores/BroadcastNotificationStore.js.coffee create mode 100644 web/app/assets/javascripts/react-components/stores/BroadcastStore.js.coffee create mode 100644 web/app/assets/stylesheets/client/react-components/broadcast.css.scss 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
+
+
+
+ {this.props.notification.button_label} +
+ not now, thanks + +
+
+
+ } +}); + +// 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', {} %>