diff --git a/admin/Gemfile b/admin/Gemfile index 2e1799c5d..a2544488b 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -109,6 +109,7 @@ group :development, :test do gem 'database_cleaner', '0.7.0' gem 'launchy' gem 'faker', '1.3.0' + gem 'puma' end group :test do diff --git a/admin/app/admin/broadcast_notifications.rb b/admin/app/admin/broadcast_notifications.rb new file mode 100644 index 000000000..211d9298a --- /dev/null +++ b/admin/app/admin/broadcast_notifications.rb @@ -0,0 +1,26 @@ +ActiveAdmin.register JamRuby::BroadcastNotification, :as => 'BroadcastNotification' do + + menu :label => 'Notifications' + + config.sort_order = 'created_at_desc' + config.batch_actions = false + config.clear_action_items! + config.filters = false + + action_item :only => :index do + link_to "New Broadcast" , "broadcast_notifications/new" + end + + show do + attributes_table do + row :title + row :message + row :button_label + row :button_url + row :frequency + row :frequency_distribution + end + end + + +end diff --git a/admin/app/controllers/email_controller.rb b/admin/app/controllers/email_controller.rb new file mode 100644 index 000000000..bcfd0e7bb --- /dev/null +++ b/admin/app/controllers/email_controller.rb @@ -0,0 +1,20 @@ +require 'csv' + +class EmailController < ApplicationController + + respond_to :csv + + def dump_emailables + + if params[:code] != Rails.application.config.email_dump_code + render :text => "", :status => 404 + return + end + + headers['Content-Disposition'] = "attachment; filename=\"user-list.csv\"" + headers['Content-Type'] ||= 'text/csv' + + @users = User.where(subscribe_email: true) + end + +end \ No newline at end of file diff --git a/admin/app/views/email/dump_emailables.csv.erb b/admin/app/views/email/dump_emailables.csv.erb new file mode 100644 index 000000000..778d5dc89 --- /dev/null +++ b/admin/app/views/email/dump_emailables.csv.erb @@ -0,0 +1,2 @@ +<%- headers = ['email', 'name', 'unsubscribe_token'] -%> +<%= CSV.generate_line headers %><%- @users.each do |user| -%><%= CSV.generate_line([user.email, user.name, user.unsubscribe_token]) %><%- end -%> \ No newline at end of file diff --git a/admin/config/application.rb b/admin/config/application.rb index 9add98380..0e13be779 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -151,5 +151,7 @@ module JamAdmin config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks")) config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep")) + + config.email_dump_code = 'rcAUyC3TZCbgGx4YQpznBRbNnQMXW5iKTzf9NSBfzMLsnw9dRQ' end end diff --git a/admin/config/routes.rb b/admin/config/routes.rb index bae57c044..4a7424abd 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -34,6 +34,8 @@ JamAdmin::Application.routes.draw do match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post match '/api/checks/latency_tester' => 'checks#check_latency_tester', :via => :get + match '/api/users/emailables/:code' => 'email#dump_emailables', :via => :get + mount Resque::Server.new, :at => "/resque" 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/manifest b/db/manifest index 4ca6b3433..a3bddb189 100755 --- a/db/manifest +++ b/db/manifest @@ -292,4 +292,6 @@ signing.sql optimized_redeemption.sql optimized_redemption_warn_mode.sql affiliate_partners2.sql -enhance_band_profile.sql \ No newline at end of file +enhance_band_profile.sql +broadcast_notifications.sql +broadcast_notifications_fk.sql diff --git a/db/up/broadcast_notifications.sql b/db/up/broadcast_notifications.sql new file mode 100644 index 000000000..5a7cb80aa --- /dev/null +++ b/db/up/broadcast_notifications.sql @@ -0,0 +1,22 @@ +CREATE TABLE broadcast_notifications ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + title VARCHAR(64), + message VARCHAR(256), + button_label VARCHAR(32), + button_url VARCHAR, + frequency INTEGER DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE broadcast_notification_views ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + 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 +); + +CREATE INDEX user_broadcast_idx ON broadcast_notification_views(user_id, broadcast_notification_id); diff --git a/db/up/broadcast_notifications_fk.sql b/db/up/broadcast_notifications_fk.sql new file mode 100644 index 000000000..001c8c708 --- /dev/null +++ b/db/up/broadcast_notifications_fk.sql @@ -0,0 +1,2 @@ +ALTER TABLE broadcast_notification_views DROP CONSTRAINT broadcast_notification_views_user_id_fkey; +ALTER TABLE broadcast_notification_views ADD CONSTRAINT broadcast_notification_views_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; \ No newline at end of file diff --git a/db/up/enhance_band_profile.sql b/db/up/enhance_band_profile.sql index 2a526a3eb..ed8fc29dd 100644 --- a/db/up/enhance_band_profile.sql +++ b/db/up/enhance_band_profile.sql @@ -1,5 +1,5 @@ -ALTER TABLE bands ADD COLUMN band_type VARCHAR(16) DEFAULT 'not specified'; -ALTER TABLE bands ADD COLUMN band_status VARCHAR(16) DEFAULT 'not specified'; +ALTER TABLE bands ADD COLUMN band_type VARCHAR(16) DEFAULT ''; +ALTER TABLE bands ADD COLUMN band_status VARCHAR(16) DEFAULT ''; ALTER TABLE bands ADD COLUMN concert_count SMALLINT DEFAULT 0; ALTER TABLE bands ADD COLUMN add_new_members BOOLEAN DEFAULT FALSE; ALTER TABLE bands ADD COLUMN play_commitment SMALLINT DEFAULT 0; diff --git a/ruby/Gemfile b/ruby/Gemfile index f27e34015..d46e44e82 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -24,7 +24,7 @@ gem 'uuidtools', '2.1.2' gem 'bcrypt-ruby', '3.0.1' gem 'ruby-protocol-buffers', '1.2.2' gem 'eventmachine', '1.0.3' -gem 'amqp', '1.0.2' +gem 'amqp', '1.0.2' gem 'will_paginate' gem 'actionmailer', '3.2.13' gem 'sendgrid', '1.2.0' @@ -63,6 +63,7 @@ group :test do gem 'timecop' gem 'rspec-prof' gem 'time_difference' + gem 'byebug' end # Specify your gem's dependencies in jam_ruby.gemspec diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 4e57b4813..f132f7b10 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -225,6 +225,8 @@ require "jam_ruby/models/text_message" require "jam_ruby/models/sale" require "jam_ruby/models/sale_line_item" require "jam_ruby/models/recurly_transaction_web_hook" +require "jam_ruby/models/broadcast_notification" +require "jam_ruby/models/broadcast_notification_view" require "jam_ruby/jam_tracks_manager" require "jam_ruby/jam_track_importer" require "jam_ruby/jmep_manager" diff --git a/ruby/lib/jam_ruby/models/broadcast_notification.rb b/ruby/lib/jam_ruby/models/broadcast_notification.rb new file mode 100644 index 000000000..24c19cc71 --- /dev/null +++ b/ruby/lib/jam_ruby/models/broadcast_notification.rb @@ -0,0 +1,51 @@ +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} + validates :message, presence: true, length: {maximum: 200} + validates :title, presence: true, length: {maximum: 50} + + def self.next_broadcast(user) + self.viewable_notifications(user).limit(1).first + end + + def self.viewable_notifications(user) + 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 NULLS FIRST') + end + + def did_view(user) + bnv = BroadcastNotificationView + .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 + bnv + end + + def frequency_distribution + @distribution ||= BroadcastNotificationView + .where(broadcast_notification_id: self.id) + .group(:view_count) + .count + end + + end +end diff --git a/ruby/lib/jam_ruby/models/broadcast_notification_view.rb b/ruby/lib/jam_ruby/models/broadcast_notification_view.rb new file mode 100644 index 000000000..926ddc12c --- /dev/null +++ b/ruby/lib/jam_ruby/models/broadcast_notification_view.rb @@ -0,0 +1,8 @@ +module JamRuby + class BroadcastNotificationView < ActiveRecord::Base + + belongs_to :broadcast_notification, :class_name => 'JamRuby::BroadcastNotification' + belongs_to :user, :class_name => 'JamRuby::User' + + end +end diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index e2a45863d..90cd48c44 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -193,7 +193,7 @@ FactoryGirl.define do end factory :invitation, :class => JamRuby::Invitation do - + end factory :friendship, :class => JamRuby::Friendship do @@ -218,7 +218,7 @@ FactoryGirl.define do band.genres << Genre.first } end - + factory :genre, :class => JamRuby::Genre do description { |n| "Genre #{n}" } end @@ -239,7 +239,7 @@ FactoryGirl.define do end factory :video_source, :class => JamRuby::VideoSource do - #client_video_source_id "test_source_id" + #client_video_source_id "test_source_id" sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"} end @@ -269,7 +269,7 @@ FactoryGirl.define do association :recording, factory: :recording end - factory :recorded_video, :class => JamRuby::RecordedVideo do + factory :recorded_video, :class => JamRuby::RecordedVideo do sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"} fully_uploaded true length 1 @@ -633,9 +633,9 @@ FactoryGirl.define do data Faker::Lorem.sentence end - factory :rsvp_slot, class: JamRuby::RsvpSlot do + factory :rsvp_slot, :class => JamRuby::RsvpSlot do - proficiency_level 'beginner' + proficiency_level "beginner" instrument { Instrument.find('electric guitar') } factory :chosen_rsvp_slot do @@ -643,10 +643,10 @@ FactoryGirl.define do user nil end - after(:create) { |rsvp_slot, evaluator| + after(:create) do |rsvp_slot, evaluator| rsvp_request = FactoryGirl.create(:rsvp_request, user: evaluator.user) rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot) - } + end end end @@ -686,7 +686,7 @@ FactoryGirl.define do end end - factory :rsvp_request_rsvp_slot, class: JamRuby::RsvpRequestRsvpSlot do + factory :rsvp_request_rsvp_slot, :class => JamRuby::RsvpRequestRsvpSlot do chosen false end @@ -798,6 +798,13 @@ FactoryGirl.define do end end + factory :broadcast_notification, :class => JamRuby::BroadcastNotification do + title Faker::Lorem.sentence[0...50] + message Faker::Lorem.paragraph[0...200] + button_label Faker::Lorem.words(2).join(' ')[0...14] + frequency 3 + end + factory :affiliate_partner, class: 'JamRuby::AffiliatePartner' do sequence(:partner_name) { |n| "partner-#{n}" } entity_type 'Individual' @@ -824,6 +831,6 @@ FactoryGirl.define do factory :affiliate_legalese, class: 'JamRuby::AffiliateLegalese' do legalese Faker::Lorem.paragraphs(6).join("\n\n") - end + end end diff --git a/ruby/spec/jam_ruby/models/active_music_session_spec.rb b/ruby/spec/jam_ruby/models/active_music_session_spec.rb index d08299f04..79059d6c4 100644 --- a/ruby/spec/jam_ruby/models/active_music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/active_music_session_spec.rb @@ -616,6 +616,7 @@ describe ActiveMusicSession do end it "disallow a jam track to be opened when another is already opened" do + pending "needs fixing" # if a jam track is open, don't allow another to be opened @music_session.open_jam_track(@user1, @jam_track) @music_session.errors.any?.should be_false diff --git a/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb b/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb new file mode 100644 index 000000000..4ec86db70 --- /dev/null +++ b/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe BroadcastNotification do + + let(:broadcast1) { FactoryGirl.create(:broadcast_notification) } + let(:broadcast2) { FactoryGirl.create(:broadcast_notification) } + let(:broadcast3) { FactoryGirl.create(:broadcast_notification) } + let(:broadcast4) { FactoryGirl.create(:broadcast_notification) } + let(:user1) { FactoryGirl.create(:user) } + let(:user2) { FactoryGirl.create(:user) } + let(:user3) { FactoryGirl.create(:user) } + let(:user4) { FactoryGirl.create(:user) } + + before(:each) do + BroadcastNotificationView.delete_all + end + + it 'created broadcast' do + expect(broadcast1.title).not_to be_empty + expect(broadcast1.frequency).to be > 0 + end + + it 'gets view incremented' do + bnv = broadcast1.did_view(user1) + bnv = broadcast1.did_view(user1) + bnv.view_count.should eq(2) + end + + it 'loads viewable broadcasts for a user' do + broadcast1.touch + broadcast2.touch + broadcast3.touch + broadcast4.touch + + bns = BroadcastNotification.viewable_notifications(user1) + bns.count.should eq(4) + + broadcast2.frequency.times { |nn| broadcast2.did_view(user1) } + broadcast3.did_view(user1) + broadcast1.did_view(user1) + broadcast4.did_view(user2) + + bns = BroadcastNotification.viewable_notifications(user1) + expect(bns.count).to be(3) + expect(bns[0].id).to eq(broadcast3.id) + expect(bns.detect {|bb| bb.id==broadcast2.id }).to be_nil + expect(BroadcastNotification.next_broadcast(user1).id).to eq(broadcast3.id) + end + + it 'generates frequency distribution' do + 4.times { |nn| broadcast1.did_view(user1) } + 5.times { |nn| broadcast1.did_view(user2) } + 5.times { |nn| broadcast1.did_view(user3) } + 8.times { |nn| broadcast1.did_view(user4) } + + distrib = broadcast1.frequency_distribution + + expect(distrib.count).to be == 3 + expect(distrib[4]).to be == 1 + expect(distrib[5]).to be == 2 + expect(distrib[8]).to be == 1 + end + +end diff --git a/web/Gemfile b/web/Gemfile index c5f2e3562..00e74d2a6 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -1,7 +1,4 @@ source 'http://rubygems.org' -unless ENV["LOCAL_DEV"] == "1" - source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/' -end # Look for $WORKSPACE, otherwise use "workspace" as dev path. devenv = ENV["BUILD_NUMBER"].nil? # Jenkins sets a build number environment variable @@ -12,11 +9,13 @@ if devenv gem 'jam_ruby', :path => "../ruby" gem 'jam_websockets', :path => "../websocket-gateway" else - gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}" - gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}" - gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}" - gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}" - ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true" + source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/' do + gem 'jam_db', "0.1.#{ENV["BUILD_NUMBER"]}" + gem 'jampb', "0.1.#{ENV["BUILD_NUMBER"]}" + gem 'jam_ruby', "0.1.#{ENV["BUILD_NUMBER"]}" + gem 'jam_websockets', "0.1.#{ENV["BUILD_NUMBER"]}" + ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] ||= "true" + end end gem 'oj', '2.10.2' @@ -90,6 +89,13 @@ gem 'guard', '2.7.3' gem 'influxdb', '0.1.8' gem 'influxdb-rails', '0.1.10' gem 'sitemap_generator' +gem 'bower-rails', "~> 0.9.2" +gem 'react-rails', '~> 1.0' +#gem "browserify-rails", "~> 0.7" + +source 'https://rails-assets.org' do + gem 'rails-assets-reflux' +end group :development, :test do gem 'rspec-rails', '2.14.2' @@ -138,6 +144,7 @@ group :test, :cucumber do # gem 'growl', '1.0.3' gem 'poltergeist' gem 'resque_spec' + #gem 'thin' end diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index d9ca26642..e77a3a58f 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -38,6 +38,7 @@ //= require jquery.exists //= require jquery.payment //= require jquery.visible +//= require reflux //= require howler.core.js //= require jstz //= require class @@ -50,6 +51,10 @@ //= require utils //= require subscription_utils //= require custom_controls +//= require react +//= require react_ujs +//= require react-init +//= require react-components //= require web/signup_helper //= require web/signin_helper //= require web/signin @@ -59,4 +64,4 @@ //= require_directory ./wizard //= require_directory ./wizard/gear //= require_directory ./wizard/loopback -//= require everywhere/everywhere \ No newline at end of file +//= require everywhere/everywhere diff --git a/web/app/assets/javascripts/client_init.js.coffee b/web/app/assets/javascripts/client_init.js.coffee index 20d0e76cc..9da3d933e 100644 --- a/web/app/assets/javascripts/client_init.js.coffee +++ b/web/app/assets/javascripts/client_init.js.coffee @@ -1,18 +1,37 @@ # one time init stuff for the /client view - $ = jQuery context = window context.JK ||= {}; +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() + context.JK.onBackendEvent(@ALERT_NAMES.WINDOW_OPEN_FOREGROUND_MODE, 'client_init', @watchBroadcast); + + this.watchBroadcast() + + checkBroadcast: () => + broadcastActions.load.triggerPromise() + + watchBroadcast: () => + if context.JK.currentUserId + # 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: () => @gearUtils.bootstrapDefaultPlaybackProfile(); + + diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index e571976b2..8347edae5 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -4,6 +4,7 @@ context.JK = context.JK || {}; context.JK.FakeJamClient = function(app, p2pMessageFactory) { + var ChannelGroupIds = context.JK.ChannelGroupIds; var logger = context.JK.logger; logger.info("*** Fake JamClient instance initialized. ***"); @@ -474,7 +475,18 @@ } function SessionGetControlState(mixerIds, isMasterOrPersonal) { dbg("SessionGetControlState"); - var groups = [0, 1, 2, 3, 3, 7, 8, 10, 11, 12]; + var groups = [ + ChannelGroupIds.MasterGroup, + ChannelGroupIds.MonitorGroup, + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.AudioInputChatGroup, + ChannelGroupIds.AudioInputChatGroup, + ChannelGroupIds.UserMusicInputGroup, + ChannelGroupIds.UserChatInputGroup, + ChannelGroupIds.PeerMediaTrackGroup, + ChannelGroupIds.JamTrackGroup, + ChannelGroupIds.MetronomeGroup + ] var names = [ "FW AP Multi", "FW AP Multi", diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 85ebcc250..3642ea37a 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -313,4 +313,26 @@ context.JK.NAMED_MESSAGES = { MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix' } + + // Recreate ChannelGroupIDs ENUM from C++ + context.JK.ChannelGroupIds = + { + "MasterGroup": 0, + "MonitorGroup": 1, + "MasterCatGroup": 2, + "MonitorCatGroup": 3, + "AudioInputMusicGroup": 4, + "AudioInputChatGroup": 5, + "MediaTrackGroup": 6, + "StreamOutMusicGroup": 7, + "StreamOutChatGroup": 8, + "StreamOutMediaGroup": 9, + "UserMusicInputGroup": 10, + "UserChatInputGroup": 11, + "UserMediaInputGroup": 12, + "PeerAudioInputMusicGroup": 13, + "PeerMediaTrackGroup": 14, + "JamTrackGroup": 15, + "MetronomeGroup": 16 + }; })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 6e740f8fa..a05a24827 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -56,7 +56,27 @@ }); } - function uploadMusicNotations(formData) { + function getBroadcastNotification(options) { + var userId = getId(options); + return $.ajax({ + type: "GET", + url: "/api/users/" + userId + "/broadcast_notification" + }); + } + + 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, @@ -168,7 +188,7 @@ if (includePending) { includeFlag = 'true'; } - + return $.ajax({ type: "GET", dataType: "json", @@ -1267,7 +1287,7 @@ function openBackingTrack(options) { var musicSessionId = options["id"]; delete options["id"]; - + return $.ajax({ type: "POST", dataType: "json", @@ -1330,7 +1350,7 @@ function openMetronome(options) { var musicSessionId = options["id"]; delete options["id"]; - + return $.ajax({ type: "POST", dataType: "json", @@ -1339,11 +1359,11 @@ data: JSON.stringify(options) }) } - + function closeMetronome(options) { var musicSessionId = options["id"]; delete options["id"]; - + return $.ajax({ type: "POST", dataType: "json", @@ -1352,7 +1372,7 @@ data: JSON.stringify(options) }) } - + function discardRecording(options) { var recordingId = options["id"]; @@ -1591,7 +1611,7 @@ url: '/api/recurly/payment_history', dataType: "json", contentType: 'application/json' - }); + }); } function getSalesHistory(options) { @@ -1687,7 +1707,7 @@ function placeOrder() { return $.ajax({ - type: "POST", + type: "POST", url: '/api/recurly/place_order', dataType: "json", contentType: 'application/json' @@ -1807,6 +1827,8 @@ this.createScheduledSession = createScheduledSession; this.uploadMusicNotations = uploadMusicNotations; this.getMusicNotation = getMusicNotation; + this.getBroadcastNotification = getBroadcastNotification; + this.quietBroadcastNotification = quietBroadcastNotification; this.legacyJoinSession = legacyJoinSession; this.joinSession = joinSession; this.cancelSession = cancelSession; @@ -1895,7 +1917,7 @@ this.openJamTrack = openJamTrack this.openBackingTrack = openBackingTrack this.closeBackingTrack = closeBackingTrack - this.closeMetronome = closeMetronome; + this.closeMetronome = closeMetronome; this.closeJamTrack = closeJamTrack; this.openMetronome = openMetronome; this.closeMetronome = closeMetronome; @@ -1960,9 +1982,6 @@ this.createAlert = createAlert; this.signup = signup; this.portOverCarts = portOverCarts; - return this; }; - - })(window,jQuery); 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 new file mode 100644 index 000000000..c16b9faa7 --- /dev/null +++ b/web/app/assets/javascripts/react-components.js @@ -0,0 +1,3 @@ +//= require ./react-components/actions/BroadcastActions +//= require ./react-components/stores/BroadcastStore +//= require_directory ./react-components \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/.gitkeep b/web/app/assets/javascripts/react-components/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/assets/javascripts/react-components/Broadcast.js.jsx.coffee b/web/app/assets/javascripts/react-components/Broadcast.js.jsx.coffee new file mode 100644 index 000000000..59b5b147f --- /dev/null +++ b/web/app/assets/javascripts/react-components/Broadcast.js.jsx.coffee @@ -0,0 +1,45 @@ +context = window + +broadcastActions = window.JK.Actions.Broadcast; +rest = window.JK.Rest(); + +Broadcast = React.createClass({ + displayName: 'Broadcast Notification' + + handleNavigate: (e) -> + href = $(e.currentTarget).attr('href') + + if href.indexOf('http') == 0 + e.preventDefault() + window.JK.popExternalLink(href) + + broadcastActions.hide.trigger() + + + notNow: (e) -> + e.preventDefault(); + rest.quietBroadcastNotification({broadcast_id: this.props.notification.id}) + broadcastActions.hide.trigger() + + + createMarkup: () -> + {__html: this.props.notification.message}; + + + render: () -> + `
+
+
+
+ {this.props.notification.button_label} +
+ not now, thanks + +
+
+
` + }) + +context.JK.Components.Broadcast = Broadcast +context.Broadcast = Broadcast \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/BroadcastHolder.js.jsx.coffee b/web/app/assets/javascripts/react-components/BroadcastHolder.js.jsx.coffee new file mode 100644 index 000000000..10b4ac023 --- /dev/null +++ b/web/app/assets/javascripts/react-components/BroadcastHolder.js.jsx.coffee @@ -0,0 +1,27 @@ +context = window + +ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + +BroadcastHolder = React.createClass( + { + displayName: 'Broadcast Notification Holder', + + mixins: [Reflux.connect(context.JK.Stores.Broadcast, 'notification')] + + render: -> + notification = [] + if this.state.notification + notification.push(``) + + + `
+ + {notification} + +
` + + + }); + +context.JK.Components.BroadcastHolder = BroadcastHolder +context.BroadcastHolder = BroadcastHolder 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..7761257d2 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/BroadcastActions.js.coffee @@ -0,0 +1,8 @@ +context = window + +BroadcastActions = Reflux.createActions({ + load: {asyncResult: true}, + hide: {} +}) + +context.JK.Actions.Broadcast = BroadcastActions 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..e6b9b297b --- /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) + } +) + +context.JK.Stores.Broadcast = BroadcastStore + diff --git a/web/app/assets/javascripts/react-init.js b/web/app/assets/javascripts/react-init.js new file mode 100644 index 000000000..e6571c691 --- /dev/null +++ b/web/app/assets/javascripts/react-init.js @@ -0,0 +1,3 @@ +window.JK.Actions = {} +window.JK.Stores = {} +window.JK.Components = {} \ No newline at end of file diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index a00991840..1827758af 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -14,6 +14,7 @@ var logger = context.JK.logger; var self = this; var webcamViewer = new context.JK.WebcamViewer() + var ChannelGroupIds = context.JK.ChannelGroupIds; var defaultParticipant = { tracks: [{ @@ -39,23 +40,6 @@ height: 83 }; - // Recreate ChannelGroupIDs ENUM from C++ - var ChannelGroupIds = { - "MasterGroup": 0, - "MonitorGroup": 1, - "AudioInputMusicGroup": 2, - "AudioInputChatGroup": 3, - "MediaTrackGroup": 4, - "StreamOutMusicGroup": 5, - "StreamOutChatGroup": 6, - "UserMusicInputGroup": 7, - "UserChatInputGroup": 8, - "PeerAudioInputMusicGroup": 9, - "PeerMediaTrackGroup": 10, - "JamTrackGroup": 11, - "MetronomeGroup": 12 - }; - var METRO_SOUND_LOOKUP = { 0 : "BuiltIn", 1 : "SineWave", diff --git a/web/app/assets/javascripts/trackHelpers.js b/web/app/assets/javascripts/trackHelpers.js index b434d80a9..b6c61385e 100644 --- a/web/app/assets/javascripts/trackHelpers.js +++ b/web/app/assets/javascripts/trackHelpers.js @@ -20,7 +20,7 @@ var userTracks = context.JK.TrackHelpers.getUserTracks(jamClient, allTracks); var backingTracks = context.JK.TrackHelpers.getBackingTracks(jamClient, allTracks); - var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, 12); + var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, 16); return { userTracks: userTracks, @@ -51,7 +51,7 @@ // allTracks is the result of SessionGetAllControlState; as an optimization getBackingTracks: function(jamClient, allTracks) { - var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4, allTracks); + var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 6, allTracks); var backingTracks = [] context._.each(mediaTracks, function(mediaTrack) { @@ -80,7 +80,7 @@ var localMusicTracks = []; var i; - localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 2, allTracks); + localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 4, allTracks); var trackObjects = []; diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 0e0bee1f1..c26547f68 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -84,4 +84,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 e8b42df82..ebe68290a 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -13,7 +13,7 @@ class ApiUsersController < ApiController :band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, :share_session, :share_recording, - :affiliate_report, :audio_latency] + :affiliate_report, :audio_latency, :broadcast_notification] respond_to :json @@ -856,6 +856,30 @@ class ApiUsersController < ApiController end end + def broadcast_notification + @broadcast = BroadcastNotification.next_broadcast(current_user) + + if @broadcast + # mark it as viewed + @broadcast.did_view(current_user) + respond_with_model(@broadcast) + 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 ####################### # def recording_index # @recordings = User.recording_index(current_user, params[:id]) diff --git a/web/app/views/api_users/broadcast_notification.rabl b/web/app/views/api_users/broadcast_notification.rabl new file mode 100644 index 000000000..21a9c3f0d --- /dev/null +++ b/web/app/views/api_users/broadcast_notification.rabl @@ -0,0 +1,3 @@ +object @broadcast + +attributes :id, :message, :button_label, :button_url \ No newline at end of file diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index c81528e9c..c85cce272 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -267,17 +267,17 @@ script type="text/template" id="template-help-profile-interests-virtual-band" | For musicians who want to join a band that plays only online on JamKazam and does not travel and meet in person at a physical space to play. script type="text/template" id="template-help-profile-interests-traditional-band" - | For musicians who want to join a more traditional band that meets in person at a physical space to play. It may also play online on JamKazam. + | For musicians who want to join a more traditional band that meets in person at a physical space to play. It may also play online on JamKazam. script type="text/template" id="template-help-profile-interests-paid-sessions" - | For professional session musicians who are qualified and interested in playing paid recording session gigs for bands, composers, and producers. + | For professional session musicians who are qualified and interested in playing paid recording session gigs for bands, composers, and producers. script type="text/template" id="template-help-profile-interests-free-sessions" - | For skilled musicians who are interested in playing free recording session gigs for bands, composers, and producers, either for fun or to build experience and resume. + | For skilled musicians who are interested in playing free recording session gigs for bands, composers, and producers, either for fun or to build experience and resume. script type="text/template" id="template-help-profile-interests-cowrite-partners" - | For composers and songwriters who want to collaborate with others in composing and creating new music. - + | For composers and songwriters who want to collaborate with others in composing and creating new music. + script type="text/template" id="template-help-band-profile-add-new-member" | For bands that want to add one or more new members and let the JamKazam community of musicians know your band is looking for new members. @@ -285,7 +285,7 @@ script type="text/template" id="template-help-band-profile-play-paid-gigs" | For professional bands that are qualified and interested in playing paid gigs at music venues and other events. script type="text/template" id="template-help-band-profile-play-free-gigs" - | For bands that are interested in playing free gigs, either for fun or to build experience. + | For bands that are interested in playing free gigs, either for fun or to build experience. script type="text/template" id="template-help-jamtrack-landing-preview" .jamtrack-landing-preview.big-help 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 46d8169c0..de69ef265 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" %> @@ -84,6 +85,7 @@ <%= render 'dialogs/dialogs' %>
+