Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into develop

This commit is contained in:
Jonathan Kolyer 2016-06-18 14:25:04 +00:00
commit 4f7c494ace
48 changed files with 1462 additions and 22 deletions

View File

@ -43,6 +43,7 @@ gem 'jquery-rails' # , '2.3.0' # pinned because jquery-ui-rails was split from j
gem 'jquery-ui-rails', '4.2.1'
gem 'rails3-jquery-autocomplete'
gem 'activeadmin' #, github: 'activeadmin', branch: '0-6-stable'
#gem 'activeadmin', github: 'activeadmin
gem 'mime-types', '1.25'
gem 'meta_search'
gem 'fog'

View File

@ -0,0 +1,87 @@
ActiveAdmin.register_page "CampaignSpend" do
menu :parent => 'JamClass'
page_action :create_spend, :method => :post do
campaign = params[:jam_ruby_campaign_spend][:campaign]
year = params[:jam_ruby_campaign_spend][:year]
month = params[:jam_ruby_campaign_spend][:month]
spend = params[:jam_ruby_campaign_spend][:spend]
if campaign.blank?
redirect_to admin_campaignspend_path, :notice => "No campaign defined! Nothing done."
return
elsif spend.blank?
redirect_to admin_campaignspend_path, :notice => "No spend defined! Nothing done."
return
elsif year.blank? || month.blank?
spend = spend.to_f
# get all cohorts for a given campaign
campaign_cohorts = JamClassReport.where(campaign: campaign).where("cohort IS NOT NULL")
year_months = []
campaign_cohorts.each do |cohort|
year_month = {year: cohort.cohort.year, month: cohort.cohort.month}
year_months << year_month
end
if campaign_cohorts.length > 0
per_month = spend / campaign_cohorts.length
year_months.each do |year_month|
campaign_spend = CampaignSpend.where(campaign: campaign).where(year: year_month[:year]).where(month: year_month[:month]).first
if campaign_spend.nil?
campaign_spend = CampaignSpend.new
end
campaign_spend.campaign = campaign
campaign_spend.month = year_month[:month]
campaign_spend.year = year_month[:year]
campaign_spend.spend = per_month
campaign_spend.save!
end
else
redirect_to admin_campaignspend_path, :notice => "No data found for campaign: #{campaign}"
return
end
redirect_to admin_campaignspend_path, :notice => "Campaign #{campaign} updated with a per month value of $#{per_month} (#{year_months.length} months worth of data found)"
else
campaign_spend = CampaignSpend.where(campaign: campaign).where(year: year).where(month: month).first
if campaign_spend.nil?
campaign_spend = CampaignSpend.new
end
campaign_spend.campaign = campaign
campaign_spend.month = month
campaign_spend.year = year
campaign_spend.spend = spend
campaign_spend.save!
redirect_to admin_campaignspend_path, :notice => "Campaign spend updated: #{campaign}:#{year}-#{month} = $#{spend}"
end
end
content do
para do
link_to "JamClass Report", admin_jamclassreports_path
end
para do
semantic_form_for CampaignSpend.new, :url => admin_campaignspend_create_spend_path, :builder => ActiveAdmin::FormBuilder do |f|
f.inputs "Campaign Spend" do
f.input :spend, :required => true, hint: "If you leave year or month blank, the system will divide up the specified spend amount here across all months seen for this campaign."
f.input :campaign, :as => :select, hint: "If this appears empty or incomplete, visit the JamClass Report page (link above) and come back.", :required => true, :collection => JamClassReport.select('campaign').group('campaign').map(&:campaign)
f.input :year, :as => :select, :hint => "Year of campaign spend (optional)", :collection => [Date.today.year, Date.today.year - 1]
f.input :month, :as => :select, :hint => "Month of campaign (optional)", :collection => (1..12).map { |m| [Date::MONTHNAMES[m], m] }
end
f.actions
end
end
end
end

View File

@ -0,0 +1,66 @@
ActiveAdmin.register_page "JamClassReports", as: "JamClass Cohort Report" do
menu :parent => 'JamClass'
content :title => "JamClass Report" do
para do
link_to "Campaign Spend", admin_campaignspend_path
end
para do
table_for JamClassReport.analyse do
column "Campaign" do |r|
if r.campaign.nil?
"N/A"
else
r.campaign
end
end
column "Cohort" do |r|
if r.cohort.nil?
"Total"
else
"#{Date::ABBR_MONTHNAMES[r.cohort.month]} #{r.cohort.year}"
end
end
column "Spend" do |r|
if r.spend.nil?
"N/A"
else
r.spend
end
end
column "Registrations", :registrations
column "TD Customers", :td_customers
column "JamClass Revenues", :jamclass_rev
column "TD4", :td4
column "TD2", :td2
column "TD1", :td1
column "Spend/TD" do |r|
if r.spend_td.nil?
"N/A"
else
r.spend_td
end
end
column "% 0 BC" do |r|
(r.purchases0 * 100).round
end
column "% 1 BC" do |r|
(r.purchases1 * 100).round
end
column "% 2 BC" do |r|
(r.purchases2 * 100).round
end
column "% 3 BC" do |r|
(r.purchases3 * 100).round
end
column "% 4+ BC" do |r|
(r.purchases_rest * 100).round
end
end
end
end
end

View File

@ -357,4 +357,6 @@ track_user_on_lesson.sql
audio_in_music_notations.sql
lesson_time_tracking.sql
packaged_test_drive.sql
packaged_test_drive2.sql
packaged_test_drive2.sql
jamclass_report.sql
jamblasters_network.sql

View File

@ -0,0 +1,3 @@
ALTER TABLE jamblasters ADD COLUMN ipv6_link_local VARCHAR;
ALTER TABLE jamblasters ADD COLUMN ipv4_link_local VARCHAR;
ALTER TABLE jamblasters ADD COLUMN display_name VARCHAR;

33
db/up/jamclass_report.sql Normal file
View File

@ -0,0 +1,33 @@
CREATE TABLE campaign_spends (
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
campaign VARCHAR NOT NULL,
spend NUMERIC(8,2) NOT NULL,
month INTEGER NOT NULL,
year INTEGER NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE jam_class_reports (
cohort DATE,
campaign VARCHAR,
spend NUMERIC (8,2),
registrations INTEGER,
td_customers INTEGER,
jamclass_rev NUMERIC (8,2),
td4 INTEGER,
td2 INTEGER,
td1 INTEGER,
spend_td NUMERIC (8,2),
purchases0 NUMERIC (8,2),
purchases1 NUMERIC (8,2),
purchases2 NUMERIC (8,2),
purchases3 NUMERIC (8,2),
purchases_rest NUMERIC (8,2),
purchases0_count INTEGER,
purchases1_count INTEGER,
purchases2_count INTEGER,
purchases3_count INTEGER,
purchases_rest_count INTEGER,
purchases_count INTEGER
);

View File

@ -303,6 +303,8 @@ require "jam_ruby/models/teacher_instrument"
require "jam_ruby/models/teacher_subject"
require "jam_ruby/models/teacher_language"
require "jam_ruby/models/teacher_genre"
require "jam_ruby/models/jam_class_report"
require "jam_ruby/models/campaign_spend"
include Jampb
module JamRuby

View File

@ -505,6 +505,11 @@ module JamRuby
@storage_format == 'Tency'
end
def is_helbing_storage?
assert_storage_set
@storage_format == 'Helbing'
end
def is_paris_storage?
assert_storage_set
@storage_format == 'Paris'
@ -550,7 +555,7 @@ module JamRuby
song = metalocation[(first_dash+3)..-1].strip
bits << song
elsif is_tency_storage? || is_tim_tracks_storage?
elsif is_tency_storage? || is_tim_tracks_storage? || is_helbing_storage?
suffix = '/meta.yml'
@ -582,6 +587,9 @@ module JamRuby
if is_tim_tracks_storage?
song = metalocation[(first_dash+3)..-1].strip
bits << song
elsif is_helbing_storage?
song = metalocation[(first_dash+3)..-1].strip
bits << song
elsif is_tency_storage?
last_dash = metalocation.rindex('-')
if last_dash
@ -881,6 +889,9 @@ module JamRuby
elsif is_tim_tracks_storage?
jam_track.vendor_id = metadata[:id]
jam_track.licensor = JamTrackLicensor.find_by_name!('Tim Waurick')
elsif is_helbing_storage?
jam_track.vendor_id = metadata[:id]
jam_track.licensor = JamTrackLicensor.find_by_name!('Stockton Helbing')
elsif is_drumma_storage?
jam_track.vendor_id = metadata[:id]
jam_track.licensor = JamTrackLicensor.find_by_name!('Drumma Boy')
@ -2231,6 +2242,8 @@ module JamRuby
tim_tracks_s3_manager
elsif is_drumma_storage?
drumma_s3_manager
elsif is_helbing_storage?
helbing_s3_manager
else
s3_manager
end
@ -2256,6 +2269,10 @@ module JamRuby
@tim_tracks_s3_manager ||= S3Manager.new('jamkazam-timtracks', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
def helbing_s3_manager
@tim_tracks_s3_manager ||= S3Manager.new('jamkazam-helbing', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
def s3_manager
@s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_jamtracks, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
@ -2328,6 +2345,12 @@ module JamRuby
@storage_format == 'TimTracks'
end
def is_helbing_storage?
assert_storage_set
@storage_format == 'Helbing'
end
def assert_storage_set
raise "no storage_format set" if @storage_format.nil?
end
@ -2418,6 +2441,22 @@ module JamRuby
end
end
def iterate_helbing_song_storage(&blk)
count = 0
song_storage_manager.list_directories('mapped').each do |song|
@@log.debug("searching through song directory '#{song}'")
metalocation = "#{song}meta.yml"
metadata = load_metalocation(metalocation)
blk.call(metadata, metalocation)
count += 1
#break if count > 100
end
end
def iterate_song_storage(&blk)
if is_tency_storage?
iterate_tency_song_storage do |metadata, metalocation|
@ -2435,6 +2474,10 @@ module JamRuby
iterate_drumma_song_storage do |metadata, metalocation|
blk.call(metadata, metalocation)
end
elsif is_helbing_storage?
iterate_helbing_song_storage do |metadata, metalocation|
blk.call(metadata, metalocation)
end
else
iterate_default_song_storage do |metadata, metalocation|
blk.call(metadata, metalocation)
@ -3392,7 +3435,10 @@ module JamRuby
if is_tim_tracks_storage?
meta[:genres] = ['acapella']
elsif is_helbing_storage?
meta[:genres] = ['jazz']
end
meta
rescue AWS::S3::Errors::NoSuchKey
return nil

View File

@ -0,0 +1,5 @@
module JamRuby
class CampaignSpend < ActiveRecord::Base
end
end

View File

@ -20,6 +20,7 @@ module JamRuby
has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
has_many :backing_tracks, :class_name => "JamRuby::BackingTrack", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
has_many :video_sources, :class_name => "JamRuby::VideoSource", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all
has_one :jamblaster, class_name: "JamRuby::Jamblaster", foreign_key: "client_id"
validates :metronome_open, :inclusion => {:in => [true, false]}
validates :as_musician, :inclusion => {:in => [true, false, nil]}
@ -87,6 +88,12 @@ module JamRuby
joining_session
end
def same_network_jamblasters
# return all jamblasters that are currently connected with the same public IP address (don't include this one though)
Jamblaster.joins(:connection).where("connections.ip_address = ?", ip_address).where("connections.id != ?", id).limit(100)
end
def can_join_music_session
# puts "can_join_music_session: #{music_session_id} was #{music_session_id_was}" if music_session_id_changed?

View File

@ -0,0 +1,104 @@
# CREATE FUNCTION jamclass_report RETURNS TABLE (campaign VARCHAR, spend numeric(8,2), registrations INTEGER, td_customers INTEGER, jamclass_rev NUMERIC(8,2), td4 INTEGER, td2 INTEGER, td1 INTEGER, spend_td NUMERIC(8,2), purchases_0 INTEGER, purchases_1 INTEGER, purchases_2 INTEGER, purchases_3 INTEGER, purchases_rest INTEGER) VOLATILE AS $$
module JamRuby
class JamClassReport < ActiveRecord::Base
def self.update_spend
end
def self.analyse(campaign_filter = nil)
User.transaction do
user_purchase = "CREATE TEMPORARY TABLE user_jamclass_purchases (user_id VARCHAR(64) NOT NULL, purchases INTEGER DEFAULT 0) ON COMMIT DROP"
user_inserts = "INSERT INTO user_jamclass_purchases (user_id, purchases) (SELECT id, COUNT(user_jamclass_purchases.user_id) FROM users LEFT OUTER JOIN user_jamclass_purchases ON users.id = user_jamclass_purchases.user_id GROUP BY users.id)"
User.connection.execute(user_purchase)
User.connection.execute(user_inserts)
jamclass_revenue = "(SELECT SUM(price) * 0.25 FROM lesson_package_purchases WHERE lesson_package_purchases.lesson_package_type_id = 'single') + (SELECT SUM(6) FROM lesson_package_purchases WHERE lesson_package_purchases.lesson_package_type_id = 'test-drive-1') + (SELECT SUM(10) FROM lesson_package_purchases WHERE lesson_package_purchases.lesson_package_type_id = 'test-drive-2') + (SELECT SUM(10) FROM lesson_package_purchases WHERE lesson_package_purchases.lesson_package_type_id = 'test-drive')"
td_users = "COUNT(td_purchases.id)"
td4 = "COUNT(td4_purchases.id)"
td2 = "COUNT(td2_purchases.id)"
td1 = "COUNT(td1_purchases.id)"
spend_td = "SELECT (CASE WHEN COUNT(td_purchases.id) = 0 THEN NULL ELSE avg(campaign_spends.spend) / COUNT(td_purchases.id) END)"
purchases0 = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 0 THEN 1 ELSE NULL END) / COUNT(user_jamclass_purchases.purchases)"
purchases1 = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 1 THEN 1 ELSE NULL END) / COUNT(user_jamclass_purchases.purchases)"
purchases2 = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 2 THEN 1 ELSE NULL END) / COUNT(user_jamclass_purchases.purchases)"
purchases3 = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 3 THEN 1 ELSE NULL END) / COUNT(user_jamclass_purchases.purchases)"
purchases_rest = "COUNT(CASE WHEN user_jamclass_purchases.purchases >= 3 THEN 1 ELSE NULL END) / COUNT(user_jamclass_purchases.purchases)"
purchases0_count = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 0 THEN 1 ELSE NULL END)"
purchases1_count = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 1 THEN 1 ELSE NULL END)"
purchases2_count = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 2 THEN 1 ELSE NULL END)"
purchases3_count = "COUNT(CASE WHEN user_jamclass_purchases.purchases = 3 THEN 1 ELSE NULL END)"
purchases_rest_count = "COUNT(CASE WHEN user_jamclass_purchases.purchases >= 3 THEN 1 ELSE NULL END)"
purchases_count = "COUNT(user_jamclass_purchases.purchases)"
query = User.select("date_trunc( 'month', users.created_at ) as cohort, origin_utm_campaign AS campaign, avg(campaign_spends.spend) as spend, count(users.id) AS registrations, (#{td_users}) as td_customers, (#{jamclass_revenue}) as jamclass_rev, (#{td4}) AS td4, (#{td2}) AS td2, (#{td1}) AS td1, (#{spend_td}) as spend_td, (#{purchases0}) as purchases0, (#{purchases1}) as purchases1, (#{purchases2}) as purchases2, (#{purchases3}) as purchases3, (#{purchases_rest}) as purchases_rest, (#{purchases0_count}) as purchases0_count, (#{purchases1_count}) as purchases1_count, (#{purchases2_count}) as purchases2_count, (#{purchases3_count}) as purchases3_count, (#{purchases_rest_count}) as purchases_rest_count, (#{purchases_count}) as purchases_count")
.joins(%Q{
LEFT OUTER JOIN
campaign_spends
ON
campaign_spends.month = date_part('month', users.created_at) AND year = date_part('year', users.created_at) AND campaign_spends.campaign = users.origin_utm_campaign
})
.joins(%Q{
LEFT OUTER JOIN
lesson_package_purchases
ON
lesson_package_purchases.user_id = users.id
})
.joins(%Q{
LEFT OUTER JOIN
lesson_package_purchases AS td4_purchases
ON
lesson_package_purchases.user_id = users.id AND lesson_package_purchases.id = 'test-drive'
})
.joins(%Q{
LEFT OUTER JOIN
lesson_package_purchases AS td2_purchases
ON
lesson_package_purchases.user_id = users.id AND lesson_package_purchases.id = 'test-drive-2'
})
.joins(%Q{
LEFT OUTER JOIN
lesson_package_purchases AS td1_purchases
ON
lesson_package_purchases.user_id = users.id AND lesson_package_purchases.id = 'test-drive-1'
})
.joins(%Q{
LEFT OUTER JOIN
lesson_package_purchases AS td_purchases
ON
lesson_package_purchases.user_id = users.id AND lesson_package_purchases.id in ('test-drive', 'test-drive-2', 'test-drive-1')
})
.joins(%Q{
INNER JOIN
user_jamclass_purchases AS user_jamclass_purchases
ON
user_jamclass_purchases.user_id = users.id
})
.group('users.origin_utm_campaign, cohort')
user_inserts = "INSERT INTO jam_class_reports (cohort, campaign, spend, registrations, td_customers, jamclass_rev, td4, td2, td1, spend_td, purchases0, purchases1, purchases2, purchases3, purchases_rest, purchases0_count, purchases1_count, purchases2_count, purchases3_count, purchases_rest_count, purchases_count) (#{query.to_sql})"
User.connection.execute("DELETE FROM jam_class_reports")
User.connection.execute(user_inserts)
purchases0 = "SUM(jam_class_reports.purchases0_count) / SUM(jam_class_reports.purchases_count)"
purchases1 = "SUM(jam_class_reports.purchases1_count) / SUM(jam_class_reports.purchases_count)"
purchases2 = "SUM(jam_class_reports.purchases2_count) / SUM(jam_class_reports.purchases_count)"
purchases3 = "SUM(jam_class_reports.purchases3_count) / SUM(jam_class_reports.purchases_count)"
purchases_rest = "SUM(jam_class_reports.purchases_rest_count) / SUM(jam_class_reports.purchases_count)"
group_inserts = "INSERT INTO jam_class_reports (cohort, campaign, spend, registrations, td_customers, jamclass_rev, td4, td2, td1, spend_td, purchases0, purchases1, purchases2, purchases3, purchases_rest)
(SELECT NULL, jam_class_reports.campaign, SUM(spend), SUM(registrations), SUM(td_customers), SUM(jamclass_rev), SUM(td4), SUM(td2), SUM(td1), CASE WHEN SUM(td4) + SUM (td2) + SUM(td1) = 0 THEN NULL ELSE (SUM(spend) / (SUM(td4) + SUM (td2) + SUM(td1))) END,
#{purchases0}, #{purchases1}, #{purchases2}, #{purchases3}, #{purchases_rest} FROM jam_class_reports
GROUP BY campaign)"
User.connection.execute(group_inserts)
reports = JamClassReport.order('campaign, cohort DESC NULLS LAST')
if campaign_filter
reports = reports.where(campaign: campaign_filter)
end
reports
end
end
end
end

View File

@ -8,7 +8,7 @@ module JamRuby
has_many :jamblasters_users, class_name: "JamRuby::JamblasterUser"
has_many :users, class_name: 'JamRuby::User', through: :jamblasters_users
has_many :jamblaster_pairing_requests, class_name: "JamRuby::JamblasterPairingRequest", foreign_key: :jamblaster_id
belongs_to :connection, class_name: "JamRuby::Connection", foreign_key: "client_id"
validates :user, presence: true
validates :serial_no, uniqueness: true

View File

@ -42,7 +42,6 @@ module JamRuby
# updating_password corresponds to a lost_password
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card
belongs_to :icecast_server_group, class_name: "JamRuby::IcecastServerGroup", inverse_of: :users, foreign_key: 'icecast_server_group_id'
has_many :controlled_sessions, :class_name => "JamRuby::MusicSession", inverse_of: :session_controller, foreign_key: :session_controller_id

View File

@ -0,0 +1,48 @@
require 'spec_helper'
describe JamClassReport do
it "wee bit of data" do
user = FactoryGirl.create(:user, origin_utm_campaign: 'legacy')
query = JamClassReport.analyse
query.length.should eql 2
r1 = query[0]
r1.cohort.should eql Date.new(user.created_at.year, user.created_at.month, 1)
r1.registrations.should eql 1
r1.campaign.should eql 'legacy'
r1.spend.should be_nil
r1.registrations.should eql 1
r1.td_customers.should eql 0
r1.jamclass_rev.should be_nil
r1.td4.should eql 0
r1.td2.should eql 0
r1.td1.should eql 0
r1.spend_td.should be_nil
r1.purchases0.should eql 1
r1.purchases1.should eql 0
r1.purchases2.should eql 0
r1.purchases3.should eql 0
r1.purchases_rest.should eql 0
r2 = query[1]
r2.cohort.should be_nil
r2.registrations.should eql 1
r2.campaign.should eql 'legacy'
r2.spend.should be_nil
r2.registrations.should eql 1
r2.td_customers.should eql 0
r2.jamclass_rev.should be_nil
r2.td4.should eql 0
r2.td2.should eql 0
r2.td1.should eql 0
r2.spend_td.should be_nil
r2.purchases0.should eql 1
r2.purchases1.should eql 0
r2.purchases2.should eql 0
r2.purchases3.should eql 0
r2.purchases_rest.should eql 0
end
end

View File

@ -102,6 +102,13 @@ gem 'zip-codes'
gem 'email_validator'
#gem "browserify-rails", "~> 0.7"
if ENV['FASTER_PATH'] == '1'
# https://github.com/danielpclark/faster_path
# supposed to dramatically speed up page load time. Gotta install rust. go to github if interested
gem 'faster_path', '~> 0.1.0', :group => :development
end
source 'https://rails-assets.org' do
gem 'rails-assets-reflux', '0.3.0'
gem 'rails-assets-classnames'

View File

@ -63,7 +63,8 @@
PREVIEW_PLAYED: 'preview_played',
VST_OPERATION_SELECTED: 'vst_operation_selected',
VST_EFFECT_SELECTED: 'vst_effect_selected',
LESSON_SESSION_ACTION: 'lesson_session_action'
LESSON_SESSION_ACTION: 'lesson_session_action',
JAMBLASTER_ACTION: 'jamblaster_action'
};
context.JK.PLAYBACK_MONITOR_MODE = {

View File

@ -595,6 +595,19 @@
return detail;
}
function getUserJamBlasters(options) {
if(!options) {
options = {}
}
var id = getId(options);
return $.ajax({
type: "GET",
dataType: "json",
url: "/api/users/" + id + '/jamblasters?' + $.param(options),
processData: false
});
}
function getUserProfile(options) {
if (!options) {
options = {}
@ -2550,6 +2563,7 @@
this.cancelSession = cancelSession;
this.updateScheduledSession = updateScheduledSession;
this.getUserDetail = getUserDetail;
this.getUserJamBlasters = getUserJamBlasters;
this.getUserAuthorizations = getUserAuthorizations;
this.getGoogleAuth = getGoogleAuth;
this.getUserProfile = getUserProfile;

View File

@ -0,0 +1,79 @@
(function(context, $) {
"use strict";
context.JK = context.JK || {};
// creates an iconic/graphical instrument selector. useful when there is minimal real-estate
$.fn.jamblasterOptions = function(options) {
return this.each(function(index) {
function close() {
$parent.btOff();
$parent.focus();
}
var $parent = $(this);
if($parent.data('jamblasterOptions')) {
//return;
}
$parent.data('jamblasterOptions', options)
function onJamBlasterOptionSelected() {
var $li = $(this);
var option = $li.attr('data-jamblaster-option');
close();
$parent.triggerHandler(context.JK.EVENTS.JAMBLASTER_ACTION, {option: option, options: $parent.data('jamblasterOptions')});
return false;
};
// if the user goes into the bubble, remove
function waitForBubbleHover($bubble) {
$bubble.hoverIntent({
over: function() {
if(timeout) {
clearTimeout(timeout);
timeout = null;
}
},
out: function() {
$parent.btOff();
}});
}
var timeout = null;
var html = context._.template($('#template-jamblaster-options').html(), options, { variable: 'data' })
context.JK.hoverBubble($parent, html, {
trigger:'none',
cssClass: 'jamblaster-options-popup',
spikeGirth:0,
spikeLength:0,
overlap: -10,
width:120,
closeWhenOthersOpen: true,
offsetParent: $parent.closest('.screen'),
positions:['bottom'],
preShow: function() {
},
postShow:function(container) {
$(container).find('li').click(onJamBlasterOptionSelected)
if(timeout) {
clearTimeout(timeout);
timeout = null;
}
waitForBubbleHover($(container))
timeout = setTimeout(function() {$parent.btOff()}, 6000)
}
});
});
}
})(window, jQuery);

View File

@ -12,7 +12,7 @@
return this.each(function(index) {
function close() {
//$parent.btOff();
$parent.btOff();
$parent.focus();
}
@ -41,7 +41,7 @@
}
},
out: function() {
//$parent.btOff();
$parent.btOff();
}});
}
@ -103,7 +103,7 @@
timeout = null;
}
waitForBubbleHover($(container))
//timeout = setTimeout(function() {$parent.btOff()}, 6000)
timeout = setTimeout(function() {$parent.btOff()}, 6000)
}
});
});

View File

@ -22,6 +22,7 @@
//= require jquery.exists
//= require jquery.manageVsts
//= require jquery.lessonSessionActions
//= require jquery.jamblasterOptions
//= require ResizeSensor
//= require AAA_Log
//= require AAC_underscore

View File

@ -44,7 +44,7 @@ logger = context.JK.logger
for object in this.props.sourceObjects
nm = "check_#{object.id}"
checked = @isChecked(object.id)
object_options.push `<div className='checkItem'><input type='checkbox' key={object.id} name={nm} data-object-id={object.id} checked={checked}></input><label htmlFor={nm}>{object.description}</label><br className="clearall"/></div>`
object_options.push `<div key={object.id} className='checkItem'><input type='checkbox' key={object.id} name={nm} data-object-id={object.id} checked={checked}></input><label htmlFor={nm}>{object.description}</label><br className="clearall"/></div>`
`<div className="CheckBoxList react-component">
<div className="checkbox-scroller left">

View File

@ -41,11 +41,11 @@ MIDI_TRACK = context.JK.MIDI_TRACK
for midiDevice in @state.configureTracks.attachedMidiDevices.midiDevices
if midiDevice.deviceIndex == inputsForTrack.vst?.midiDeviceIndex
midiDeviceName = midiDevice.deviceName
inputs.push(`<div className="live-input">{midiDeviceName}</div>`)
inputs.push(`<div key={i} className="live-input">{midiDeviceName}</div>`)
else
trackTypeLabel = 'AUDIO'
for input in inputsForTrack
inputs.push(`<div className="live-input">{input.name}</div>`)
inputs.push(`<div key={i} className="live-input">{input.name}</div>`)
if !inputsForTrack.instrument_id?
instrument = `<span className="none">?</span>`

View File

@ -0,0 +1,92 @@
context = window
@JamBlasterNameDialog = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
teacher: null
beforeShow: (args) ->
logger.debug("JamBlasterNameDialog.beforeShow", args.d1)
@setState({name: args.d1})
afterHide: () ->
onAppInit: (@app) ->
dialogBindings = {
'beforeShow': @beforeShow,
'afterHide': @afterHide
};
@app.bindDialog('jamblaster-name-dialog', dialogBindings);
getInitialState: () ->
{
name: ''
}
componentDidMount: () ->
@root = $(@getDOMNode())
@dialog = @root.closest('.dialog')
doCancel: (e) ->
e.preventDefault()
@app.layout.closeDialog('jamblaster-name-dialog', true);
onNameChange: (e) ->
@setState({name: $(e.target).val()})
updateName: (e) ->
e.preventDefault()
# validate
name = @root.find('.name').val()
characterMatch = /^[a-z0-9,' -]+$/i
if name.length == 0 || name == ''
context.JK.Banner.showAlert('invalid name', 'Please specify a name.')
return
else if name.length < 2
context.JK.Banner.showAlert('invalid name', 'Please specify a name at least 3 characters long.')
return
else if name.length > 63
context.JK.Banner.showAlert('invalid name', 'The name must be less than 64 characters long.')
return
else if characterMatch.test(name)
context.JK.Banner.showAlert('invalid name',
'The can only contain A-Z, 0-9, commas, apostrophes, spaces, or hyphens.')
return
result = context.jamClient.setJBName(name.trim())
if !result
context.JK.Banner.showAlert('unable to set the name',
'Please email support@jamkazam.com with the name you are trying to set, or refresh the page and try again.')
else
@app.layout.closeDialog('jamblaster-name-dialog')
render: () ->
`<div>
<div className="content-head">
<img className="content-icon" src="/assets/content/icon_add.png" height={19} width={19}/>
<h1>update name of JamBlaster</h1>
</div>
<div>
<p>You can change the display name for this JamBlaster. The name can only contain A-Z, 0-9, commas, apostrophes,
spaces, or hyphens. A valid example: "John Doe's JamBlaster"</p>
<label>JamBlaster Name:</label>
<input className="name" type="text" value={this.state.name} onChange={this.onNameChange}></input>
<div className="actions">
<a onClick={this.doCancel} className="button-grey">CANCEL</a>
<a onClick={this.updateName} className="button-orange name">UPDATE</a>
</div>
</div>
</div>`
})

View File

@ -0,0 +1,162 @@
context = window
@JamBlasterPairingDialog = React.createClass({
mixins: [@BonjourMixin, Reflux.listenTo(@AppStore, "onAppInit")]
teacher: null
beforeShow: (args) ->
logger.debug("JamBlasterPairingDialog.beforeShow", args.d1)
@setState({timer: null, pairing: false, bonjourClientId: args.d1, pairingTimeout: false, paired: false})
@resyncBonjour()
@setTimer(false)
afterHide: () ->
@clearTimer()
onAppInit: (@app) ->
dialogBindings = {
'beforeShow': @beforeShow,
'afterHide': @afterHide
};
@app.bindDialog('jamblaster-pairing-dialog', dialogBindings);
getInitialState: () ->
{
timer: null
pairing: false,
pairStart: null,
clients: [],
pairingTimeout: false
paired: false,
userJamBlasters: [],
localClients: []
}
clearTimer: () ->
if @interval?
clearInterval(@interval)
@interval = null
clearPairingTimer: () ->
if @pairingInterval?
clearInterval(@pairingInterval)
@pairingInterval = null
setTimer: (connecting) ->
@clearTimer()
if connecting
time = 5000 # every 5 seconds
else
time = 60000 # every minute
@interval = setInterval((() => @resyncBonjour()), time)
pairingTimer: () ->
@clearPairingTimer()
@pairingInterval = setInterval((() => @updatePairingTimer()), 800)
updatePairingTimer: () ->
now = new Date().getTime()
delta = (now - @state.pairStart) / 1000
if delta > 60
@clearPairingTimer()
@setTimer(false)
@setState({pairing: false, pairingTimeout: true, timer: null})
else
client = @findJamBlaster(@state.bonjourClientId)
if client.isPaired
@clearPairingTimer()
@setTimer(false)
@setState({pairing: false, pairingTimeout: false, timer: null, paired: true})
else
@setState({timer: 60 - delta})
componentDidMount: () ->
@root = $(@getDOMNode())
@dialog = @root.closest('.dialog')
componentDidUpdate: () ->
doCancel: (e) ->
e.preventDefault()
if @state.pairing
return
@app.layout.closeDialog('jamblaster-pairing-dialog', true);
close: (e) ->
e.preventDefault()
@app.layout.closeDialog('jamblaster-pairing-dialog')
pair: (e) ->
e.preventDefault()
if @state.pairing
return
@setState({pairing: true, pairStart: new Date().getTime(), timer: 60})
@setTimer(true)
client = @findJamBlaster(this.state.bonjourClientId)
if client.isPaired
context.JK.Banner.showNotice("JamBlaster already paired", "This JamBlaster is already paired.")
@app.layout.closeDialog("jamblaster-pairing-dialog", true)
else if client?
if client.connect_url?
context.jamClient.startPairing(client.connect_url)
else
context.JK.Banner.showAlert("JamBlaster offline", "JamBlaster appears to be offline. Please reboot it")
else
context.JK.Banner.showAlert("JamBlaster offline", "JamBlaster appears to be offline. Please reboot it")
render: () ->
if @state.pairing
countdown = @state.timer
else
countdown = null
actions = `<div className="actions">
<a onClick={this.doCancel} className={cancelClasses}>CANCEL</a>
<a onClick={this.pair} className={connectClasses}>CONNECT</a>
<span className="countdown">{countdown}</span>
</div>`
if @state.paired
message = `<p>You have successfully connected to this JamBlaster!</p>`
actions = `<div className="actions">
<a onClick={this.close} className="button-orange">CLOSE</a>
</div>`
else if @state.pairingTimeout
message = `<p>No connection established. You may click the CONNECT button to try again. If you cannot connect, please contact us at support@jamkazam.com.</p>`
else
cancelClasses = {"button-grey": true, disabled: @state.pairing}
connectClasses = {"button-orange": true, disabled: @state.pairing}
`<div>
<div className="content-head">
<img className="content-icon" src="/assets/content/icon_add.png" height={19} width={19}/>
<h1>connect to JamBlaster</h1>
</div>
<div>
<p>To connect this application/device with the selected JamBlaster, please click the Connect button below, and then push the small black plastic button located on the back of the JamBlaster between the USB and power ports to confirm this pairing within 60 seconds of clicking the Connect button below.</p>
{message}
{actions}
</div>
</div>`
})

View File

@ -0,0 +1,184 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger
@JamBlasterScreen = React.createClass({
mixins: [
@ICheckMixin,
@PostProcessorMixin,
@BonjourMixin,
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(UserStore, "onUserChanged")
]
TILE_AUDIO: 'audio'
TILE_INTERNET: 'internet'
TILE_MANAGEMENT: 'management'
TILE_USB: 'usb'
TILES: ['audio', 'internet', 'management', 'usb']
onAppInit: (@app) ->
@app.bindScreen('jamblaster',
{beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
onUserChanged: (userState) ->
@setState({user: userState?.user})
componentDidMount: () ->
@root = $(@getDOMNode())
componentWillUpdate: (nextProps, nextState) ->
componentDidUpdate: () ->
items = @root.find('.jamtable .optionsColumn .jamblaster-options-btn')
$.each(items, (i, node) => (
$node = $(node)
jamblaster = @findJamBlaster($node.attr('data-jamblaster-id'))
$node.jamblasterOptions(jamblaster).off(context.JK.EVENTS.JAMBLASTER_ACTION).on(context.JK.EVENTS.JAMBLASTER_ACTION,
@jamblasterOptionSelected)
))
#context.JK.popExternalLinks(@root)
jamblasterOptionSelected: (e, data) ->
jamblasterId = data.options.id
jamblaster = @findJamBlaster(jamblasterId)
if data.option == 'auto-connect'
context.JK.Banner.showNotice('Auto-Connect',
'Auto-Connect is always on by default. It can not currently configurable.')
else if data.option == 'restart'
context.JK.Banner.showNotice('Restart',
'To restart the JamBlaster, you must manually cycle power (unplug, then plug).')
else if data.option == 'name'
context.layout.showDialog('jamblaster-name-dialog').one(context.JK.EVENTS.DIALOG_CLOSED, (e, data) =>
@resyncBonjour()
)
else if data.option == 'check-for-updates'
context.JK.Banner.showNotice('Check for Update',
'The JamBlaster only checks for updates on start up. Please reboot the JamBlaster')
else if data.option == 'set-static-ports'
context.layout.showDialog('jamblaster-port-dialog')
else if data.option == 'factory-reset'
context.JK.Banner.showNotice('Factory Reset',
'The JamBlaster only checks for updates when it boots up, and if there is an update available, it will automatically begin updating.<br/><br/>Please reboot the JamBlaster to initiate an update check.')
else
logger.debug("unknown action")
getInitialState: () ->
{
selected: 'management',
user: null,
userJamBlasters: [],
localClients: [],
clients: []
}
beforeHide: (e) ->
beforeShow: (e) ->
afterShow: (e) ->
@resyncBonjour()
openMenu: (client, e) ->
logger.debug("open jamblaster options menu")
$this = $(e.target)
if !$this.is('.jamblaster-options-btn')
$this = $this.closest('.jamblaster-options-btn')
$this.btOn()
connect: (client, e) ->
logger.debug("beginning pairing to #{client.connect_url}")
context.jamClient.startPairing(client.connect_url)
disconnect: (client, e) ->
logger.debug("disconnecting from currently paired client #{client.connect_url}")
context.jamClient.endPairing()
mergeClients: () ->
clientsJsx = []
for client in @state.clients
if client.display_name?
displayName = client.display_name
else
displayName = client.name
if client.serial_no? && displayName? && displayName.indexOf(client.serial_no) == -1
displayName = "#{displayName} (#{client.serial_no})"
name = `<span className="displayNameColumn">{displayName}</span>`
if client.isPaired
connect = `<span className="connectColumn"><a
onClick={this.disconnect.bind(this, client)}>disconnect</a></span>`
else if client.has_local
connect = `<span className="connectColumn"><a onClick={this.connect.bind(this, client)}>connect</a></span>`
else
connect = `<span className="connectColumn">offline</span>`
options = `<span className="optionsColumn"><a data-jamblaster-id={client.id} className="jamblaster-options-btn"
onClick={this.openMenu.bind(this, client)}>more options
<div className="details-arrow arrow-down"/>
</a></span>`
clientsJsx.push(`<tr>
<td>{name}{options}{connect}</td>
</tr>`)
clientsJsx
mainContent: () ->
if @state.selected == @TILE_AUDIO
@audio()
else if @state.selected == @TILE_INTERNET
@internet()
else if @state.selected == @TILE_MANAGEMENT
@management()
else if @state.selected == @TILE_USB
@usb()
management: () ->
clients = @mergeClients()
`<div className="management-content">
<table className="jamtable">
<thead>
<tr>
<th className="jamblasterColumn">JAMBLASTERS ON YOUR NETWORK</th>
</tr>
</thead>
<tbody>
{clients}
</tbody>
</table>
<p>If you don't see your JamBlaster listed above, please check to make sure you have power connected to your JamBlaster,
and make sure your JamBlaster is connected via an Ethernet cable to the same router/network as the device on which you are viewing this application.
</p>
</div>`
render: () ->
disabled = @state.updating
if !@state.user?.id || !@state.userJamBlasters? || !@state.localClients?
return `<div>Loading</div>`
`<div className="content-body-scroller">
<div className="">
<h2>jamblaster settings</h2>
<div className="tiles">
</div>
<div className="jamclass-section">
{this.mainContent()}
</div>
</div>
<br className="clearall"/>
</div>`
})

View File

@ -204,8 +204,8 @@
</span>
<span className="alt-time-block">
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm">{this.am_pm}</select>
<select className="hour" defaultValue="06" disabled={this.props.disabled}>{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm" defaultValue="PM">{this.am_pm}</select>
<br/>
<span>* Time will be local to {window.JK.currentTimezone()}</span>
{errorText}
@ -227,8 +227,8 @@
</span>
<span className="alt-time-block">
<span className="alt-time">Time:</span>
<select className="hour">{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm">{this.am_pm}</select>
<select className="hour" defaultValue="06" disabled={this.props.disabled}>{this.hours}</select> : <select disabled={this.props.disabled} className="minute">{this.minutes}</select>
<select disabled={this.props.disabled} className="am_pm" defaultValue="PM">{this.am_pm}</select>
<br/>
<span>*Time will be local to {window.JK.currentTimezone()}</span>
{errorText}

View File

@ -0,0 +1,97 @@
context = window
teacherActions = window.JK.Actions.Teacher
@BonjourMixin = {
resyncBonjour: () ->
rest.getUserJamBlasters({client_id: @app.clientId}).done((response) => @getUserJamBlastersDone(response)).fail((response) => @getUserJamBlastersFail(response))
getUserJamBlastersDone: (response) ->
@setState({userJamBlasters: response})
@getLocalClients(response)
findJamBlaster: (id) ->
found = null
if @clients?
for client in @clients
if client.id == id
found = client
break
if client.ipv6_addr == id
found = client
break
found
getUserJamBlastersFail: (jqXHR) ->
@app.layout.ajaxError(jqXHR)
mergeBonjourClients: (localClients, userJamBlasters) ->
console.log("@state.localClients", localClients)
console.log("@state.userJamBlasters", userJamBlasters)
# for localClient in @state.localClients
for localClient in localClients
if localClient.connect_url.indexOf(':30330') && localClient.is_jb
client = {}
client.ipv6_addr = localClient.ipv6_addr
client.isPaired = localClient.isPaired
client.name = localClient.name
client.has_local = true
client.has_server = false
client.id = client.ipv6_addr
client.connect_url = localClient.connect_url
for serverClient in userJamBlasters
# see if we can join on ipv6
if ipv6_addr == serverClient.ipv6_link_local
# ok, matched! augment with server data
client.serial_no = serverClient.serial_no
client.user_id = serverClient.user_id
client.id = serverClient.id
client.client_id = serverClient.client_id
client.ipv4_link_local = serverClient.ipv4_link_local
client.display_name = serverClient.display_name
client.has_server = true
break
clients.push(client)
for serverClient in userJamBlasters
foundLocal = false
for localClient in localClients
if ipv6_addr == serverClient.ipv6_link_local
foundLocal = true
break
if !foundLocal
# this server version of the client has not been spoken for in the earlier loop above
# so we need to add it in to the client list
client = {}
client.serial_no = serverClient.serial_no
client.user_id = serverClient.user_id
client.id = serverClient.id
client.client_id = serverClient.client_id
client.ipv4_link_local = serverClient.ipv4_link_local
client.display_name = serverClient.display_name
client.has_local = false
client.has_server = true
clients.push(client)
console.log("all client", clients)
@clients = clients
@setState({clients: clients})
getLocalClients: (userJamBlasters) ->
localClients = context.jamClient.getLocalClients()
@mergeBonjourClients(localClients, userJamBlasters)
@setState({localClients: localClients})
}

View File

@ -23,6 +23,7 @@
//= require jquery.exists
//= require jquery.visible
//= require jquery.lessonSessionActions
//= require jquery.jamblasterOptions
//= require jquery.manageVsts
//= require jquery.scrollTo
//= require jquery.pulse

View File

@ -56,6 +56,7 @@
*= require ./muteSelect
*= require ./manageVsts
*= require ./lessonSessionActions
*= require ./jamblasterOptions
*= require ./vstEffects
*= require ./metronomePlaybackModeSelect
*= require ./terms

View File

@ -0,0 +1,33 @@
@import "client/common";
.jamblaster-options-popup {
width:120px;
height:100px;
a{
color:#fc0 !important;
}
li {
border-bottom:0 !important;
}
.bt-content {
height:80px;
width:100px;
background-color:#333;
overflow:auto;
border:1px solid #ED3618;
text-align:left;
font-family: 'Raleway', Arial, Helvetica, sans-serif;
ul {
@include vertical-align-column;
height:100%;
margin-left: 0 !important;
}
li {
font-size:12px;
margin-left:0 !important;
list-style-type: none;
}
}
}

View File

@ -0,0 +1,181 @@
@import "client/common";
#jamblaster-screen {
div[data-react-class="JamBlasterScreen"] {
height:100%;
}
.content-body-scroller {
height:100%;
padding:30px;
@include border_box_sizing;
}
ol {
li {
margin-left:15px;
margin-bottom:2px;
list-style:decimal;
color:$ColorTextTypical;
}
}
.student-right-content {
p {
margin-bottom:10px;
}
ul {
margin-bottom:25px;
li {
margin-left:-10px;
margin-bottom:2px;
list-style:disc;
color:$ColorTextTypical;
}
}
}
h2 {
font-size: 20px;
font-weight:700;
margin-bottom: 20px !important;
display:inline-block;
}
.column {
@include border_box_sizing;
width:50%;
}
.column-left {
float:left;
width:70%;
}
.column-right {
float:right;
width:30%;
padding-left:20px;
.jamclass-section {
padding-top:36px;
}
}
span.price {
color:white;
}
.test-drive-main {
}
p {
line-height:125% !important;
font-size:14px !important;
margin:0 0 20px 0 !important;
color: $ColorTextTypical;
}
.avatar {
display:inline-block;
padding:1px;
width:36px;
height:36px;
background-color:#ed4818;
margin:0 10px 0 0;
-webkit-border-radius:18px;
-moz-border-radius:18px;
border-radius:18px;
float:none;
}
.avatar img {
width: 36px;
height: 36px;
-webkit-border-radius:18px;
-moz-border-radius:18px;
border-radius:18px;
}
.calender-integration-notice {
display:block;
text-align:center;
}
.actions {
display:block;
text-align:center;
}
.jamclass-section {
margin-bottom:40px;
&.my-lessons {
max-height:300px;
overflow:auto;
}
}
.management-content {
min-width:400px;
max-width:700px;
}
.jamtable {
height:100%;
width:100%;
table-layout:fixed;
margin-bottom:20px;
a {
text-decoration: none !important;
color:#fc0 !important;
}
th {
font-size:14px;
padding:3px 10px;
@include border_box_sizing;
&.role {
text-transform: uppercase;
}
}
td {
background-color: #2b2b2b;
padding:4px 15px;
font-size:14px;
vertical-align:middle;
color: $ColorTextTypical;
@include border_box_sizing;
}
tbody {
}
.displayNameColumn {
float:left;
}
.connectColumn {
float:right;
margin-right:20px;
}
.optionsColumn{
float:right;
text-align:center;
}
.jamblaster-options-btn {
white-space: nowrap;
cursor: pointer;
vertical-align: bottom;
color:#fc0;
.arrow-down {
float:none;
margin-left:5px;
margin-top:0;
margin-right:0;
border-top: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
.arrow-up {
float:none;
margin-right:0;
margin-left:5px;
margin-bottom:2px;
border-bottom: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
}
}
}

View File

@ -0,0 +1,40 @@
@import "client/common";
#jamblaster-name-dialog {
width: 600px;
max-height:600px;
h2 {
color:white;
margin-bottom:10px;
font-size:16px;
}
.dialog-inner {
width: auto;
height:calc(100% - 29px)
}
.field {
margin-bottom:10px;
}
input {
display:inline-block;
}
label {
display:inline-block;
}
.iradio_minimal {
display:inline-block;
margin-right: 5px;
top: 4px;
}
div[data-react-class="JamBlasterNameDialog"] {
}
.actions {
float:right;
margin:0 -13px 30px 0;
}
}

View File

@ -0,0 +1,48 @@
@import "client/common";
#jamblaster-pairing-dialog {
width: 600px;
max-height:600px;
h2 {
color:white;
margin-bottom:10px;
font-size:16px;
}
.dialog-inner {
width: auto;
height:calc(100% - 29px)
}
.field {
margin-bottom:10px;
}
input {
display:inline-block;
}
label {
display:inline-block;
}
.iradio_minimal {
display:inline-block;
margin-right: 5px;
top: 4px;
}
div[data-react-class="JamBlasterNameDialog"] {
}
.actions {
float:right;
margin:0 -13px 30px 0;
}
.countdown {
color:white;
font-size:18px;
font-weight:bold;
padding:0 10px;
width:30px;
display:inline-block;
}
}

View File

@ -46,6 +46,13 @@ class ApiUsersController < ApiController
respond_with @user, responder: ApiResponder, :status => 200
end
def jamblasters
@user = current_user
@connection = Connection.find_by_client_id(params[:client_id])
respond_with @user, responder: ApiResponder, :status => 200
end
def google_auth
@user = current_user
respond_with @user, responder: ApiResponder, :status => 200

View File

@ -0,0 +1,18 @@
object @user
child :jamblasters do
attributes :id, :user_id, :serial_no, :client_id, :ipv6_link_local, :ipv4_link_local, :display_name
end
node :same_network_jamblasters do
if @connection
jamblasters = @connection.same_network_jamblasters
result = []
jamblasters.each do |jamblaster|
result.push ({id: jamblaster.id, user_id: jamblaster.user_id, serial_no: jamblaster.serial_no, client_id: jamblaster.client_id, ipv4_link_local: jamblaster.ipv4_link_local, ipv6_link_local: jamblaster.ipv6_link_local, display_name: jamblaster.display_name})
end
result
else
[]
end
end

View File

@ -0,0 +1,20 @@
script type='text/template' id='template-jamblaster-options'
ul
li data-jamblaster-option="auto-connect"
a href='#' Auto Connect
li data-jamblaster-option="restart"
a href='#' Restart
li data-jamblaster-option="name"
a href='#' Name
li data-jamblaster-option="check-for-updates"
a href='#' Check for Updates
li data-jamblaster-option="set-static-ports"
a href='#' Set Static Ports
li data-jamblaster-option="factory-reset"
a href='#' Factory Reset

View File

@ -21,6 +21,7 @@
<%= render "muteSelect" %>
<%= render "manageVsts" %>
<%= render "lessonSessionActions" %>
<%= render "jamblasterOptions" %>
<%= render "vstEffects" %>
<%= render "metronome_playback_mode" %>
<%= render "clients/wizard/buttons" %>
@ -38,6 +39,7 @@
<%= render "bandProfile" %>
<%= render "band_setup" %>
<%= render "band_setup_photo" %>
<%= render "clients/jamclass/jamblaster" %>
<%= render "clients/teachers/setup/introduction" %>
<%= render "clients/teachers/setup/basics" %>
<%= render "clients/teachers/setup/experience" %>

View File

@ -0,0 +1,10 @@
#jamblaster-screen.screen.secondary layout="screen" layout-id="jamblaster"
.content-head
.content-icon
= image_tag "content/icon_jamtracks.png", :size => "24x24"
h1
| jamblaster
= render "screen_navigation"
.content-body
= react_component 'JamBlasterScreen', {}

View File

@ -55,4 +55,6 @@
= render 'dialogs/rescheduleLessonDialog'
= render 'dialogs/rateUserDialog'
= render 'dialogs/musicNotationUploadDialog'
= render 'dialogs/testDrivePackageDialog'
= render 'dialogs/testDrivePackageDialog'
= render 'dialogs/jamblasterNameDialog'
= render 'dialogs/jamblasterPairingDialog'

View File

@ -0,0 +1,2 @@
.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='jamblaster-name-dialog' id='jamblaster-name-dialog'
= react_component 'JamBlasterNameDialog', {}

View File

@ -0,0 +1,2 @@
.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='jamblaster-pairing-dialog' id='jamblaster-pairing-dialog'
= react_component 'JamBlasterPairingDialog', {}

View File

@ -42,6 +42,7 @@
</div>
<%= render "clients/lessonSessionActions" %>
<%= render "clients/jamblasterOptions" %>
<%= render "clients/manageVsts" %>
<%= render 'dialogs/dialogs' %>

View File

@ -103,6 +103,7 @@
<%= render "clients/jam_track_preview" %>
<%= render "clients/help" %>
<%= render "clients/lessonSessionActions" %>
<%= render "clients/jamblasterOptions" %>
<%= render "clients/manageVsts" %>
<%= render 'dialogs/dialogs' %>

View File

@ -119,10 +119,6 @@ if defined?(Bundler)
config.websocket_gateway_port = 6767 + ENV['JAM_INSTANCE'].to_i
config.websocket_gateway_port_ssl = 6443 + ENV['JAM_INSTANCE'].to_i
# Runs the websocket gateway within the web app
config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket"
config.websocket_gateway_trusted_uri = "ws://localhost:#{config.websocket_gateway_port + 1}/websocket"
config.websocket_gateway_uri_ssl = "wss://localhost:#{config.websocket_gateway_port_ssl}/websocket"
config.websocket_gateway_trusted_uri_ssl = "wss://localhost:#{config.websocket_gateway_port_ssl + 1}/websocket"
config.force_ssl = ENV['FORCE_SSL'].nil? ? false : ENV['FORCE_SSL'] == 'true'
config.websocket_gateway_max_connections_per_user = 20
config.lock_connections = false
@ -137,6 +133,12 @@ if defined?(Bundler)
# set this to false if you want to disable signups (lock down public user creation)
config.signup_enabled = true
config.websocket_gateway_uri = "ws://#{config.external_hostname}:#{config.websocket_gateway_port}/websocket"
config.websocket_gateway_trusted_uri = "ws://#{config.external_hostname}:#{config.websocket_gateway_port + 1}/websocket"
config.websocket_gateway_uri_ssl = "wss://#{config.external_hostname}:#{config.websocket_gateway_port_ssl}/websocket"
config.websocket_gateway_trusted_uri_ssl = "wss://#{config.external_hostname}:#{config.websocket_gateway_port_ssl + 1}/websocket"
config.storage_type = :fog
# config.storage_type = :file # or :fog, if using AWS

View File

@ -1,3 +1,10 @@
if defined? FasterPath
puts "OMG"
require "faster_path/optional/monkeypatches"
FasterPath.sledgehammer_everything!
end
# Load the rails application
require File.expand_path('../application', __FILE__)

View File

@ -325,6 +325,7 @@ SampleApp::Application.routes.draw do
match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail'
match '/users/:id/authorizations' => 'api_users#authorizations', :via => :get
match '/users/:id/jamblasters' => 'api_users#jamblasters', :via => :get
#match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#update', :via => :post
match '/users/:id' => 'api_users#delete', :via => :delete

View File

@ -18,6 +18,18 @@ namespace :jam_tracks do
end
task dry_run: :environment do |task, args|
if ENV['STORAGE']
JamTrackImporter.storage_format = ENV['STORAGE']
end
JamTrackImporter.dry_run
end
# valid values for STORAGE
# Helbing
task dry_run: :environment do |task, args|
if ENV['STORAGE']
JamTrackImporter.storage_format = ENV['STORAGE']
end
JamTrackImporter.dry_run
end
@ -192,6 +204,11 @@ namespace :jam_tracks do
JamTrackImporter.synchronize_all(skip_audio_upload: false)
end
task sync_helbing: :environment do |task, args|
JamTrackImporter.storage_format = 'Helbing'
JamTrackImporter.synchronize_all(skip_audio_upload: false)
end
task tency_dups: :environment do |task, args|
end

View File

@ -737,6 +737,8 @@ module JamWebsockets
os = options["os"]
udp_reachable = options["udp_reachable"].nil? ? true : options["udp_reachable"] == 'true'
jamblaster_serial_no = options["jamblaster_serial_no"]
ipv4_link_local = options["ipv4_link_local"]
ipv6_link_local = options["ipv6_link_local"]
# TESTING
#if jamblaster_serial_no.nil?
@ -745,8 +747,8 @@ module JamWebsockets
client.subscriptions = Set.new # list of subscriptions that this client is watching in real-time
@log.info("handle_login: client_type=#{client_type} token=#{token} client_id=#{client_id} channel_id=#{client.channel_id} udp_reachable=#{udp_reachable}")
serial_no_debug = jamblaster_serial_no ? "serial_no=#{jamblaster_serial_no}" : ''
@log.info("handle_login: type=#{client_type} username=#{username} password=#{password ? '*' : 'null' } token=#{token} client_id=#{client_id} channel_id=#{client.channel_id} udp_reachable=#{udp_reachable} #{serial_no_debug}")
if client_type == Connection::TYPE_LATENCY_TESTER
handle_latency_tester_login(client_id, client_type, client, override_ip)
return
@ -775,6 +777,8 @@ module JamWebsockets
end
if jamblaster && connecting
jamblaster.client_id = client_id
jamblaster.ipv4_link_local = ipv4_link_local
jamblaster.ipv6_link_local = ipv6_link_local
jamblaster.save
end
end