* merged
This commit is contained in:
commit
21b338a5ea
|
|
@ -41,6 +41,7 @@ gem 'rails3-jquery-autocomplete'
|
|||
gem 'activeadmin'
|
||||
gem "meta_search", '>= 1.1.0.pre'
|
||||
gem 'fog', "~> 1.3.1"
|
||||
gem 'unf' #optional fog dependency
|
||||
gem 'country-select'
|
||||
gem 'aasm', '3.0.16'
|
||||
gem 'postgres-copy'
|
||||
|
|
@ -56,6 +57,9 @@ gem 'ruby-protocol-buffers', '1.2.2'
|
|||
|
||||
gem 'sendgrid', '1.1.0'
|
||||
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext'
|
||||
|
||||
group :libv8 do
|
||||
gem 'libv8', "~> 3.11.8"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ FactoryGirl.define do
|
|||
musician true
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
terms_of_service true
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -74,4 +74,5 @@ crash_dumps_idx.sql
|
|||
music_sessions_user_history_add_session_removed_at.sql
|
||||
user_progress_tracking.sql
|
||||
whats_next.sql
|
||||
add_user_bio.sql
|
||||
add_user_bio.sql
|
||||
users_geocoding.sql
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ alter table users alter column birth_date drop not null;
|
|||
update users set state = 'NC';
|
||||
alter table users alter column state set not null;
|
||||
|
||||
update users set country = 'USA';
|
||||
update users set country = 'US';
|
||||
alter table users alter column country set not null;
|
||||
|
||||
alter table bands alter column city drop default;
|
||||
|
|
@ -15,7 +15,7 @@ alter table bands alter column city drop default;
|
|||
update users set state = 'NC';
|
||||
alter table bands alter column state set not null;
|
||||
|
||||
update users set country = 'USA';
|
||||
update users set country = 'US';
|
||||
alter table bands alter column country set not null;
|
||||
|
||||
--alter table users drop column account_id;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
ALTER TABLE users ADD COLUMN lat NUMERIC(15,10);
|
||||
ALTER TABLE users ADD COLUMN lng NUMERIC(15,10);
|
||||
|
||||
ALTER TABLE max_mind_geo ADD COLUMN lat NUMERIC(15,10);
|
||||
ALTER TABLE max_mind_geo ADD COLUMN lng NUMERIC(15,10);
|
||||
ALTER TABLE max_mind_geo DROP COLUMN ip_bottom;
|
||||
ALTER TABLE max_mind_geo DROP COLUMN ip_top;
|
||||
ALTER TABLE max_mind_geo ADD COLUMN ip_start INET;
|
||||
ALTER TABLE max_mind_geo ADD COLUMN ip_end INET;
|
||||
|
||||
UPDATE users SET country = 'US' WHERE country = 'USA';
|
||||
|
|
@ -24,6 +24,9 @@ gem 'aasm', '3.0.16'
|
|||
gem 'devise', '>= 1.1.2'
|
||||
gem 'postgres-copy'
|
||||
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext'
|
||||
|
||||
if devenv
|
||||
gem 'jam_db', :path=> "../db/target/ruby_package"
|
||||
gem 'jampb', :path => "../pb/target/ruby/jampb"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ require "action_mailer"
|
|||
require "devise"
|
||||
require "sendgrid"
|
||||
require "postgres-copy"
|
||||
require "geokit-rails"
|
||||
require "postgres_ext"
|
||||
|
||||
require "jam_ruby/lib/module_overrides"
|
||||
require "jam_ruby/constants/limits"
|
||||
require "jam_ruby/constants/notification_types"
|
||||
|
|
@ -82,4 +85,4 @@ include Jampb
|
|||
|
||||
module JamRuby
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ module JamRuby
|
|||
validates :as_musician, :inclusion => {:in => [true, false]}
|
||||
validate :can_join_music_session, :if => :joining_session?
|
||||
after_save :require_at_least_one_track_when_in_session, :if => :joining_session?
|
||||
after_create :did_create
|
||||
|
||||
include AASM
|
||||
IDLE_STATE = :idle
|
||||
|
|
@ -112,6 +113,10 @@ module JamRuby
|
|||
return self.music_session.users.exists?(user)
|
||||
end
|
||||
|
||||
def did_create
|
||||
self.user.update_lat_lng(self.ip_address) if self.user && self.ip_address
|
||||
end
|
||||
|
||||
private
|
||||
def require_at_least_one_track_when_in_session
|
||||
if tracks.count == 0
|
||||
|
|
|
|||
|
|
@ -3,36 +3,44 @@ module JamRuby
|
|||
|
||||
self.table_name = 'max_mind_geo'
|
||||
|
||||
def self.ip_lookup(ip_addy)
|
||||
self.where(["ip_start <= ? AND ip_end >= ?",
|
||||
ip_addy, ip_addy])
|
||||
.limit(1)
|
||||
.first
|
||||
end
|
||||
|
||||
def self.import_from_max_mind(file)
|
||||
# File Geo-124
|
||||
# File Geo-139
|
||||
# Format:
|
||||
# startIpNum,endIpNum,country,region,city,postalCode,latitude,longitude,dmaCode,areaCode
|
||||
|
||||
MaxMindGeo.transaction do
|
||||
cols = [:startIpNum, :endIpNum, :country, :region, :city, :latitude, :longitude]
|
||||
MaxMindGeo.delete_all
|
||||
File.open(file, 'r:ISO-8859-1') do |io|
|
||||
MaxMindGeo.pg_copy_from io, :map => { 'startIpNum' => 'ip_bottom', 'endIpNum' => 'ip_top', 'country' => 'country', 'region' => 'region', 'city' => 'city'}, :columns => [:startIpNum, :endIpNum, :country, :region, :city] do |row|
|
||||
row[0] = ip_address_to_int(row[0])
|
||||
row[1] = ip_address_to_int(row[1])
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
row.delete_at(5)
|
||||
MaxMindGeo.pg_copy_from(io, :map => {
|
||||
'startIpNum' => 'ip_start',
|
||||
'endIpNum' => 'ip_end',
|
||||
'country' => 'country',
|
||||
'region' => 'region',
|
||||
'city' => 'city',
|
||||
'latitude' => 'lat',
|
||||
'longitude' => 'lng'},
|
||||
:columns => cols) do |row|
|
||||
row[0] = row[0]
|
||||
row[1] = row[1]
|
||||
row[2] = MaxMindIsp.strip_quotes(row[2])
|
||||
row[3] = MaxMindIsp.strip_quotes(row[3])
|
||||
row[4] = MaxMindIsp.strip_quotes(row[4])
|
||||
row.delete_at(5)
|
||||
row.delete_at(-1) if 8 <= row.count
|
||||
row.delete_at(-1) if 8 <= row.count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Make an IP address fit in a signed int. Just divide it by 2, as the least significant part
|
||||
# just can't possibly matter. We can verify this if needed. My guess is the entire bottom octet is
|
||||
# actually irrelevant
|
||||
def self.ip_address_to_int(ip)
|
||||
ip.split('.').inject(0) {|total,value| (total << 8 ) + value.to_i} / 2
|
||||
User.find_each { |usr| usr.update_lat_lng }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,17 @@ module JamRuby
|
|||
|
||||
LIMIT = 10
|
||||
|
||||
ORDER_FOLLOWS = ['Most Followed', :followed]
|
||||
ORDER_PLAYS = ['Most Plays', :plays]
|
||||
ORDER_PLAYING = ['Playing Now', :playing]
|
||||
ORDERINGS = [ORDER_FOLLOWS, ORDER_PLAYS, ORDER_PLAYING]
|
||||
ORDERING_KEYS = ORDERINGS.collect { |oo| oo[1] }
|
||||
|
||||
def self.order_param(params)
|
||||
ordering = params[:orderby]
|
||||
ordering.blank? ? ORDERING_KEYS[0] : ordering
|
||||
end
|
||||
|
||||
# performs a site-white search
|
||||
def self.search(query, user_id = nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ module JamRuby
|
|||
devise :database_authenticatable,
|
||||
:recoverable, :rememberable
|
||||
|
||||
include Geokit::ActsAsMappable::Glue unless defined?(acts_as_mappable)
|
||||
acts_as_mappable
|
||||
|
||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_s3_path, :photo_url, :crop_selection
|
||||
after_save :check_lat_lng
|
||||
|
||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_s3_path, :photo_url, :crop_selection, :lat, :lng
|
||||
|
||||
# updating_password corresponds to a lost_password
|
||||
attr_accessor :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field
|
||||
|
|
@ -900,6 +904,50 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def self.musician_search(params={}, current_user=nil)
|
||||
rel = User.where(:musician => true)
|
||||
unless (instrument = params[:instrument]).blank?
|
||||
rel = rel.joins("RIGHT JOIN musicians_instruments AS minst ON minst.user_id = users.id")
|
||||
.where(['minst.instrument_id = ? AND users.id IS NOT NULL', instrument])
|
||||
end
|
||||
|
||||
location_distance, location_city = params[:distance], params[:city]
|
||||
if location_distance && location_city
|
||||
if geo = MaxMindGeo.where(:city => params[:city]).limit(1).first
|
||||
citylatlng = [geo.lat, geo.lng]
|
||||
rel = rel.within(location_distance, :origin => citylatlng)
|
||||
end
|
||||
elsif current_user
|
||||
latlng = []
|
||||
if current_user.lat.nil?
|
||||
if params[:remote_ip]
|
||||
if geo = MaxMindGeo.ip_lookup(params[:remote_ip])
|
||||
latlng = [geo.lat, geo.lng]
|
||||
end
|
||||
end
|
||||
else
|
||||
latlng = [current_user.lat, current_user.lng]
|
||||
end
|
||||
distance = location_distance || 50
|
||||
rel = rel.within(distance, :origin => latlng) unless latlng.blank?
|
||||
end
|
||||
|
||||
case ordering = Search.order_param(params)
|
||||
when :plays
|
||||
when :followed
|
||||
rel = rel.select("COUNT(follows) AS fcount, users.id")
|
||||
rel = rel.joins("LEFT JOIN users_followers AS follows ON follows.user_id = users.id")
|
||||
rel = rel.group("users.id")
|
||||
rel = rel.order("COUNT(follows) DESC")
|
||||
when :playing
|
||||
end
|
||||
perpage = params[:per_page] || 20
|
||||
page = [params[:page].to_i, 1].max
|
||||
rel = rel.paginate(:page => page, :per_page => perpage)
|
||||
# puts rel.to_sql
|
||||
rel
|
||||
end
|
||||
|
||||
def self.search(query, options = { :limit => 10 })
|
||||
|
||||
# only issue search if at least 2 characters are specified
|
||||
|
|
@ -925,6 +973,44 @@ module JamRuby
|
|||
.limit(options[:limit])
|
||||
end
|
||||
|
||||
def provides_location?
|
||||
!self.city.blank? && (!self.state.blank? || !self.country.blank?)
|
||||
end
|
||||
|
||||
def check_lat_lng
|
||||
if (city_changed? || state_changed? || country_changed?) && !lat_changed? && !lng_changed?
|
||||
update_lat_lng
|
||||
end
|
||||
end
|
||||
|
||||
def update_lat_lng(ip_addy=nil)
|
||||
if provides_location? # ip_addy argument ignored in this case
|
||||
return false unless ip_addy.nil? # do nothing if attempting to set latlng from an ip address
|
||||
query = { :city => self.city }
|
||||
query[:region] = self.state unless self.state.blank?
|
||||
query[:country] = self.country unless self.country.blank?
|
||||
if geo = MaxMindGeo.where(query).limit(1).first
|
||||
if geo.lat && geo.lng && (self.lat != geo.lat || self.lng != geo.lng)
|
||||
self.update_attributes({ :lat => geo.lat, :lng => geo.lng })
|
||||
return true
|
||||
end
|
||||
end
|
||||
elsif ip_addy
|
||||
if geo = MaxMindGeo.ip_lookup(ip_addy)
|
||||
if self.lat != geo.lat || self.lng != geo.lng
|
||||
self.update_attributes({ :lat => geo.lat, :lng => geo.lng })
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.lat || self.lng
|
||||
self.update_attributes({ :lat => nil, :lng => nil })
|
||||
return true
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# devise compatibility
|
||||
|
||||
#def encrypted_password
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ FactoryGirl.define do
|
|||
email_confirmed true
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
musician true
|
||||
terms_of_service true
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ FactoryGirl.define do
|
|||
biography "My Biography"
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
end
|
||||
|
||||
factory :genre, :class => JamRuby::Genre do
|
||||
|
|
@ -121,4 +121,15 @@ FactoryGirl.define do
|
|||
|
||||
factory :crash_dump, :class => JamRuby::CrashDump do
|
||||
end
|
||||
|
||||
factory :geocoder, :class => JamRuby::MaxMindGeo do
|
||||
country 'US'
|
||||
sequence(:region) { |n| ['NC', 'CA'][(n-1).modulo(2)] }
|
||||
sequence(:city) { |n| ['Apex', 'San Francisco'][(n-1).modulo(2)] }
|
||||
sequence(:ip_start) { |n| ['1.1.0.0', '1.1.255.255'][(n-1).modulo(2)] }
|
||||
sequence(:ip_end) { |n| ['1.2.0.0', '1.2.255.255'][(n-1).modulo(2)] }
|
||||
sequence(:lat) { |n| [35.73265, 37.7742075][(n-1).modulo(2)] }
|
||||
sequence(:lng) { |n| [-78.85029, -122.4155311][(n-1).modulo(2)] }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ describe ConnectionManager do
|
|||
end
|
||||
|
||||
def create_user(first_name, last_name, email, options = {:musician => true})
|
||||
@conn.exec("INSERT INTO users (first_name, last_name, email, musician, encrypted_password, city, state, country) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id", [first_name, last_name, email, options[:musician], '1', 'Apex', 'NC', 'USA']) do |result|
|
||||
@conn.exec("INSERT INTO users (first_name, last_name, email, musician, encrypted_password, city, state, country) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id", [first_name, last_name, email, options[:musician], '1', 'Apex', 'NC', 'US']) do |result|
|
||||
return result.getvalue(0, 0)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ describe User do
|
|||
|
||||
before(:each) do
|
||||
|
||||
@band = Band.save(nil, "Example Band", "www.bands.com", "zomg we rock", "Apex", "NC", "USA", ["hip hop"], user.id, nil, nil)
|
||||
@band = Band.save(nil, "Example Band", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ describe User do
|
|||
end
|
||||
|
||||
it "should tokenize correctly" do
|
||||
@band2 = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "USA", ["hip hop"], user.id, nil, nil)
|
||||
@band2 = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
|
||||
ws = Band.search("pea")
|
||||
ws.length.should == 1
|
||||
user_result = ws[0]
|
||||
|
|
@ -102,7 +102,7 @@ describe User do
|
|||
|
||||
|
||||
it "should not return anything with a 1 character search" do
|
||||
@band2 = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "USA", ["hip hop"], user.id, nil, nil)
|
||||
@band2 = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
|
||||
ws = Band.search("pe")
|
||||
ws.length.should == 1
|
||||
user_result = ws[0]
|
||||
|
|
@ -111,4 +111,4 @@ describe User do
|
|||
ws = Band.search("p")
|
||||
ws.length.should == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,4 +33,18 @@ describe Connection do
|
|||
connection.destroyed?.should be_true
|
||||
end
|
||||
|
||||
it 'updates user lat/lng' do
|
||||
uu = FactoryGirl.create(:user)
|
||||
uu.lat.should == nil
|
||||
msess = FactoryGirl.create(:music_session, :creator => uu)
|
||||
geocode = FactoryGirl.create(:geocoder)
|
||||
connection = FactoryGirl.create(:connection,
|
||||
:user => uu,
|
||||
:music_session => msess,
|
||||
:ip_address => "1.1.1.1",
|
||||
:client_id => "1")
|
||||
user.lat.should == geocode.lat
|
||||
user.lng.should == geocode.lng
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,31 +9,30 @@ describe MaxMindGeo do
|
|||
in_directory_with_file(GEO_CSV)
|
||||
|
||||
before do
|
||||
|
||||
content_for_file('startIpNum,endIpNum,country,region,city,postalCode,latitude,longitude,dmaCode,areaCode
|
||||
0.116.0.0,0.119.255.255,"AT","","","",47.3333,13.3333,,
|
||||
0.116.0.0,0.119.255.255,"AT","","","",47.3333,13.3333,123,123
|
||||
1.0.0.0,1.0.0.255,"AU","","","",-27.0000,133.0000,,
|
||||
1.0.1.0,1.0.1.255,"CN","07","Fuzhou","",26.0614,119.3061,,'.encode(Encoding::ISO_8859_1))
|
||||
|
||||
MaxMindGeo.import_from_max_mind(GEO_CSV)
|
||||
end
|
||||
|
||||
let(:first) { MaxMindGeo.find_by_ip_bottom(MaxMindGeo.ip_address_to_int('0.116.0.0')) }
|
||||
let(:second) { MaxMindGeo.find_by_ip_bottom(MaxMindGeo.ip_address_to_int('1.0.0.0')) }
|
||||
let(:third) { MaxMindGeo.find_by_ip_bottom(MaxMindGeo.ip_address_to_int('1.0.1.0')) }
|
||||
|
||||
it { MaxMindGeo.count.should == 3 }
|
||||
|
||||
let(:first) { MaxMindGeo.find_by_ip_start('0.116.0.0') }
|
||||
let(:second) { MaxMindGeo.find_by_ip_start('1.0.0.0') }
|
||||
let(:third) { MaxMindGeo.find_by_ip_start('1.0.1.0') }
|
||||
|
||||
it { first.country.should == 'AT' }
|
||||
it { first.ip_bottom.should == MaxMindGeo.ip_address_to_int('0.116.0.0') }
|
||||
it { first.ip_top.should == MaxMindGeo.ip_address_to_int('0.119.255.255') }
|
||||
it { first.ip_start.should == '0.116.0.0' }
|
||||
it { first.ip_end.should == '0.119.255.255' }
|
||||
|
||||
it { second.country.should == 'AU' }
|
||||
it { second.ip_bottom.should == MaxMindGeo.ip_address_to_int('1.0.0.0') }
|
||||
it { second.ip_top.should == MaxMindGeo.ip_address_to_int('1.0.0.255') }
|
||||
it { second.ip_start.should == '1.0.0.0' }
|
||||
it { second.ip_end.should == '1.0.0.255' }
|
||||
|
||||
it { third.country.should == 'CN' }
|
||||
it { third.ip_bottom.should == MaxMindGeo.ip_address_to_int('1.0.1.0') }
|
||||
it { third.ip_top.should == MaxMindGeo.ip_address_to_int('1.0.1.255') }
|
||||
it { third.ip_start.should == '1.0.1.0' }
|
||||
it { third.ip_end.should == '1.0.1.255' }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe User do
|
||||
|
||||
before(:each) do
|
||||
@geocode1 = FactoryGirl.create(:geocoder)
|
||||
@geocode2 = FactoryGirl.create(:geocoder)
|
||||
params = {
|
||||
first_name: "Example",
|
||||
last_name: "User",
|
||||
email: "user1@example.com",
|
||||
password: "foobar",
|
||||
password_confirmation: "foobar",
|
||||
musician: true,
|
||||
email_confirmed: true,
|
||||
city: "Apex",
|
||||
state: "NC",
|
||||
country: "US"
|
||||
}
|
||||
@users = []
|
||||
@users << @user1 = FactoryGirl.create(:user, params)
|
||||
params[:email] = "user2@example.com"
|
||||
@users << @user2 = FactoryGirl.create(:user, params)
|
||||
params[:email] = "user3@example.com"
|
||||
@users << @user3 = FactoryGirl.create(:user, params)
|
||||
params[:email] = "user4@example.com"
|
||||
@users << @user4 = FactoryGirl.create(:user, params)
|
||||
end
|
||||
|
||||
it "should find all musicians sorted by followers with pagination" do
|
||||
# establish sorting order
|
||||
@user4.followers.concat([@user2, @user3, @user4])
|
||||
@user3.followers.concat([@user3, @user4])
|
||||
@user2.followers.concat([@user1])
|
||||
@user4.followers.count.should == 3
|
||||
|
||||
UserFollower.count.should == 6
|
||||
|
||||
# get all the users in correct order
|
||||
params = { :per_page => @users.size }
|
||||
results = User.musician_search(params)
|
||||
results.all.count.should == @users.size
|
||||
|
||||
results.each_with_index do |uu, idx|
|
||||
uu.id.should == @users.reverse[idx].id
|
||||
end
|
||||
|
||||
# refresh the order to ensure it works right
|
||||
@user2.followers.concat([@user3, @user4, @user2])
|
||||
results = User.musician_search(params)
|
||||
results[0].id.should == @user2.id
|
||||
|
||||
# make sure pagination works right
|
||||
params = { :per_page => 2, :page => 1 }
|
||||
results = User.musician_search(params)
|
||||
results.all.count.should == 2
|
||||
end
|
||||
|
||||
it "should find all musicians sorted by plays " do
|
||||
pending
|
||||
end
|
||||
|
||||
it "should find all musicians sorted by now playing" do
|
||||
pending
|
||||
end
|
||||
|
||||
it "should find musicians with an instrument" do
|
||||
minst = FactoryGirl.create(:musician_instrument, {
|
||||
:user => @user1,
|
||||
:instrument => Instrument.find('tuba') })
|
||||
@user1.musician_instruments << minst
|
||||
@user1.reload
|
||||
ii = @user1.instruments.detect { |inst| inst.id == 'tuba' }
|
||||
ii.should_not be_nil
|
||||
params = { :instrument => ii.id }
|
||||
results = User.musician_search(params)
|
||||
results.all.each do |rr|
|
||||
rr.instruments.detect { |inst| inst.id=='tuba' }.id.should == ii.id
|
||||
end
|
||||
results.all.count.should == 1
|
||||
end
|
||||
|
||||
it "should find musicians within a given distance of location" do
|
||||
@user1.lat.should_not == nil
|
||||
params = { :distance => 10, :city => 'San Francisco' }
|
||||
results = User.musician_search(params)
|
||||
results.all.count.should == 0
|
||||
|
||||
params = { :distance => 10, :city => 'Apex' }
|
||||
results = User.musician_search(params)
|
||||
results.all.count.should == User.count
|
||||
|
||||
params = { :distance => 10 }
|
||||
results = User.musician_search(params)
|
||||
results.all.count.should == User.count
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -7,9 +7,9 @@ describe Search do
|
|||
|
||||
|
||||
def create_peachy_data
|
||||
@user = FactoryGirl.create(:user, first_name: "Peach", last_name: "Pit", email: "user@example.com", musician: true, city: "Apex", state: "NC", country:"USA")
|
||||
@fan = FactoryGirl.create(:user, first_name: "Peach Peach", last_name: "Pit", email: "fan@example.com", musician: false, city: "Apex", state: "NC", country:"USA")
|
||||
@band = FactoryGirl.create(:band, name: "Peach pit", website: "www.bands.com", biography: "zomg we rock", city: "Apex", state: "NC", country:"USA")
|
||||
@user = FactoryGirl.create(:user, first_name: "Peach", last_name: "Pit", email: "user@example.com", musician: true, city: "Apex", state: "NC", country:"US")
|
||||
@fan = FactoryGirl.create(:user, first_name: "Peach Peach", last_name: "Pit", email: "fan@example.com", musician: false, city: "Apex", state: "NC", country:"US")
|
||||
@band = FactoryGirl.create(:band, name: "Peach pit", website: "www.bands.com", biography: "zomg we rock", city: "Apex", state: "NC", country:"US")
|
||||
end
|
||||
|
||||
def assert_peachy_data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe User do
|
||||
|
||||
=begin
|
||||
X If user provides profile location data, that will be used for lat/lng lookup
|
||||
X If the user changes their profile location, we update their lat/lng address
|
||||
X If no profile location is provided, then we populate lat/lng from their IP address
|
||||
X If no profile location is provided, and the user creates/joins a music session, then we update their lat/lng from the IP address
|
||||
=end
|
||||
|
||||
before do
|
||||
@geocode1 = FactoryGirl.create(:geocoder)
|
||||
@geocode2 = FactoryGirl.create(:geocoder)
|
||||
@user = User.new(first_name: "Example", last_name: "User", email: "user@example.com",
|
||||
password: "foobar", password_confirmation: "foobar",
|
||||
city: "Apex", state: "NC", country: "US",
|
||||
terms_of_service: true, musician: true)
|
||||
@user.save!
|
||||
end
|
||||
|
||||
describe "with profile location data" do
|
||||
it "should have lat/lng values" do
|
||||
geo = MaxMindGeo.find_by_city(@user.city)
|
||||
@user.lat.should == geo.lat
|
||||
@user.lng.should == geo.lng
|
||||
end
|
||||
it "should have updated lat/lng values" do
|
||||
@user.update_attributes({ :city => @geocode2.city,
|
||||
:state => @geocode2.region,
|
||||
:country => @geocode2.country,
|
||||
})
|
||||
geo = MaxMindGeo.find_by_city(@user.city)
|
||||
@user.lat.should == geo.lat
|
||||
@user.lng.should == geo.lng
|
||||
end
|
||||
end
|
||||
|
||||
describe "without profile location data" do
|
||||
it "should have lat/lng values from ip_address" do
|
||||
@user.update_attributes({ :city => nil,
|
||||
:state => nil,
|
||||
:country => nil,
|
||||
})
|
||||
@user.lat.should == nil
|
||||
@user.lng.should == nil
|
||||
geo = JamRuby::MaxMindGeo.ip_lookup('1.1.0.0')
|
||||
geo.should_not be_nil
|
||||
geo = JamRuby::MaxMindGeo.ip_lookup('1.1.0.255')
|
||||
geo.should_not be_nil
|
||||
@user.update_lat_lng('1.1.0.255')
|
||||
@user.lat.should == geo.lat
|
||||
@user.lng.should == geo.lng
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ describe User do
|
|||
before(:each) do
|
||||
@user = FactoryGirl.create(:user, first_name: "Example", last_name: "User", email: "user@example.com",
|
||||
password: "foobar", password_confirmation: "foobar", musician: true, email_confirmed: true,
|
||||
city: "Apex", state: "NC", country: "USA")
|
||||
city: "Apex", state: "NC", country: "US")
|
||||
end
|
||||
|
||||
it "should allow search of one user" do
|
||||
|
|
@ -54,7 +54,7 @@ describe User do
|
|||
it "should tokenize correctly" do
|
||||
@user2 = FactoryGirl.create(:user, first_name: "peaches", last_name: "test", email: "peach@example.com",
|
||||
password: "foobar", password_confirmation: "foobar", musician: true, email_confirmed: true,
|
||||
city: "Apex", state: "NC", country: "USA")
|
||||
city: "Apex", state: "NC", country: "US")
|
||||
ws = User.search("pea")
|
||||
ws.length.should == 1
|
||||
user_result = ws[0]
|
||||
|
|
@ -64,7 +64,7 @@ describe User do
|
|||
it "users who have signed up, but not confirmed should show up in search index due to VRFS-378" do
|
||||
@user3 = FactoryGirl.create(:user, first_name: "unconfirmed", last_name: "unconfirmed", email: "unconfirmed@example.com",
|
||||
password: "foobar", password_confirmation: "foobar", musician: true, email_confirmed: false,
|
||||
city: "Apex", state: "NC", country: "USA")
|
||||
city: "Apex", state: "NC", country: "US")
|
||||
ws = User.search("unconfirmed")
|
||||
ws.length.should == 1
|
||||
|
||||
|
|
@ -77,4 +77,4 @@ describe User do
|
|||
user_result = ws[0]
|
||||
user_result.id.should == @user3.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ describe User do
|
|||
|
||||
before do
|
||||
@user = User.new(first_name: "Example", last_name: "User", email: "user@example.com",
|
||||
password: "foobar", password_confirmation: "foobar", city: "Apex", state: "NC", country: "USA", terms_of_service: true, musician: true)
|
||||
password: "foobar", password_confirmation: "foobar", city: "Apex", state: "NC", country: "US", terms_of_service: true, musician: true)
|
||||
@user.musician_instruments << FactoryGirl.build(:musician_instrument, user: @user)
|
||||
end
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ describe User do
|
|||
end
|
||||
|
||||
describe "create_dev_user" do
|
||||
before { @dev_user = User.create_dev_user("Seth", "Call", "seth@jamkazam.com", "Jam123", "Austin", "Texas", "USA", nil, nil) }
|
||||
before { @dev_user = User.create_dev_user("Seth", "Call", "seth@jamkazam.com", "Jam123", "Austin", "Texas", "US", nil, nil) }
|
||||
|
||||
subject { @dev_user }
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ describe User do
|
|||
end
|
||||
|
||||
describe "updates record" do
|
||||
before { @dev_user = User.create_dev_user("Seth", "Call2", "seth@jamkazam.com", "Jam123", "Austin", "Texas", "USA", nil, nil) }
|
||||
before { @dev_user = User.create_dev_user("Seth", "Call2", "seth@jamkazam.com", "Jam123", "Austin", "Texas", "US", nil, nil) }
|
||||
|
||||
it { should be_valid }
|
||||
|
||||
|
|
|
|||
|
|
@ -47,12 +47,15 @@ gem 'aws-sdk', '1.8.0'
|
|||
gem 'aasm', '3.0.16'
|
||||
gem 'carrierwave'
|
||||
gem 'fog'
|
||||
gem 'unf' #optional fog dependency
|
||||
gem 'devise', '>= 1.1.2'
|
||||
#gem 'thin' # the presence of this gem on mac seems to prevent normal startup of rails.
|
||||
gem 'postgres-copy'
|
||||
#group :libv8 do
|
||||
# gem 'libv8', "~> 3.11.8"
|
||||
#end
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext'
|
||||
|
||||
gem 'quiet_assets', :group => :development
|
||||
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@
|
|||
$('#band-profile-biography').html(band.biography);
|
||||
}
|
||||
else {
|
||||
|
||||
logger.debug("No band found with bandId = " + bandId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +266,7 @@
|
|||
|
||||
function bindSocial() {
|
||||
// FOLLOWERS
|
||||
url = "/api/bands/" + bandId + "/followers";
|
||||
var url = "/api/bands/" + bandId + "/followers";
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
|
|
|
|||
|
|
@ -178,6 +178,13 @@
|
|||
function submitForm(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
// If user hasn't completed FTUE - do so now.
|
||||
if (!(context.jamClient.FTUEGetStatus())) {
|
||||
app.afterFtue = function() { submitForm(evt); };
|
||||
app.layout.showDialog('ftue');
|
||||
return;
|
||||
}
|
||||
|
||||
var isValid = validateForm();
|
||||
if (!isValid) {
|
||||
// app.notify({
|
||||
|
|
@ -435,4 +442,4 @@
|
|||
return this;
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
(function(context,$) {
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.FindMusicianScreen = function(app) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var musicians = {};
|
||||
var musicianList;
|
||||
|
||||
function removeSpinner() {
|
||||
$('<div[layout-id=findMusician] .content .spinner').remove();
|
||||
}
|
||||
|
||||
function addSpinner() {
|
||||
removeSpinner();
|
||||
$('<div[layout-id=findMusician] .content').append('<div class="spinner spinner-large"></div>')
|
||||
}
|
||||
|
||||
function loadMusicians(queryString) {
|
||||
addSpinner();
|
||||
|
||||
// squelch nulls and undefines
|
||||
queryString = !!queryString ? queryString : "";
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/api/users?" + queryString,
|
||||
async: true,
|
||||
success: afterLoadMusicians,
|
||||
complete: removeSpinner,
|
||||
error: app.ajaxError
|
||||
});
|
||||
}
|
||||
|
||||
function search() {
|
||||
logger.debug("Searching for musicians...");
|
||||
clearResults();
|
||||
var queryString = 'musicians=1&';
|
||||
|
||||
// order by
|
||||
var orderby = $('.musician-order-by').val();
|
||||
if (orderby !== null && orderby.length() > 0) {
|
||||
queryString += "orderby=" + orderby + '&';
|
||||
}
|
||||
// instrument filter
|
||||
var instrument = $('.instrument-list').val();
|
||||
if (instruments !== null && instruments.length() > 0) {
|
||||
queryString += "instrument=" + instrument;
|
||||
}
|
||||
// distance filter
|
||||
var query_param = $('#musician-query-distance').val();
|
||||
if (query_param !== null && query_param.length > 0) {
|
||||
var matches = query_param.match(/(\d)/);
|
||||
if (0 < matches.length()) {
|
||||
var distance = matches[0];
|
||||
query_param = $('#musician-query-center').val();
|
||||
if (query_param !== null && query_param.length > 0) {
|
||||
matches = query_param.match(/\\d{5}(-\\d{4})?/);
|
||||
if (0 < matches.length()) {
|
||||
var zip = matches[0];
|
||||
queryString += "zip=" + query_param + '&';
|
||||
queryString += "distance=" + query_param + '&';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadMusicians(queryString);
|
||||
}
|
||||
|
||||
function refreshDisplay() {
|
||||
var priorVisible;
|
||||
}
|
||||
|
||||
function afterLoadMusicians(musicianList) {
|
||||
// display the 'no musicians' banner if appropriate
|
||||
var $noMusiciansFound = $('#musicians-none-found');
|
||||
if(musicianList.length == 0) {
|
||||
$noMusiciansFound.show();
|
||||
}
|
||||
else {
|
||||
$noMusiciansFound.hide();
|
||||
}
|
||||
|
||||
startMusicianLatencyChecks(musicianList);
|
||||
context.JK.GA.trackFindMusicians(musicianList.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single musician line into the table.
|
||||
* It will be inserted at the appropriate place according to the
|
||||
* sortScore in musicianLatency.
|
||||
*/
|
||||
function renderMusician(musicianId) {
|
||||
// store musician in the appropriate bucket and increment category counts
|
||||
var musician = musicians[musicianId];
|
||||
|
||||
refreshDisplay();
|
||||
}
|
||||
|
||||
function beforeShow(data) {
|
||||
context.JK.InstrumentSelectorHelper.render('#find-musician-instrument');
|
||||
}
|
||||
|
||||
function afterShow(data) {
|
||||
clearResults();
|
||||
refreshDisplay();
|
||||
loadMusicians();
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
musicians = {};
|
||||
}
|
||||
|
||||
function events() {
|
||||
$('#musician-keyword-srch').focus(function() {
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
$("#musician-keyword-srch").keypress(function(evt) {
|
||||
if (evt.which === 13) {
|
||||
evt.preventDefault();
|
||||
search();
|
||||
}
|
||||
});
|
||||
$('#btn-refresh').on("click", search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize, providing an instance of the MusicianLatency class.
|
||||
*/
|
||||
function initialize(latency) {
|
||||
|
||||
var screenBindings = {
|
||||
'beforeShow': beforeShow,
|
||||
'afterShow': afterShow
|
||||
};
|
||||
app.bindScreen('findMusician', screenBindings);
|
||||
|
||||
musicianList = new context.JK.MusicianList(app);
|
||||
|
||||
events();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
this.renderMusician = renderMusician;
|
||||
this.afterShow = afterShow;
|
||||
|
||||
// Following exposed for easier testing.
|
||||
this.setMusician = setMusician;
|
||||
this.clearResults = clearResults;
|
||||
this.getCategoryEnum = getCategoryEnum;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -25,6 +25,8 @@
|
|||
};
|
||||
|
||||
var faderMap = {
|
||||
'ftue-2-audio-input-fader': jamClient.FTUESetInputVolume,
|
||||
'ftue-2-voice-input-fader': jamClient.FTUESetOutputVolume,
|
||||
'ftue-audio-input-fader': jamClient.FTUESetInputVolume,
|
||||
'ftue-voice-input-fader': jamClient.FTUESetChatInputVolume,
|
||||
'ftue-audio-output-fader': jamClient.FTUESetOutputVolume
|
||||
|
|
@ -44,6 +46,10 @@
|
|||
|
||||
function beforeShow(data) {
|
||||
var vuMeters = [
|
||||
'#ftue-2-audio-input-vu-left',
|
||||
'#ftue-2-audio-input-vu-right',
|
||||
'#ftue-2-voice-input-vu-left',
|
||||
'#ftue-2-voice-input-vu-right',
|
||||
'#ftue-audio-input-vu-left',
|
||||
'#ftue-audio-input-vu-right',
|
||||
'#ftue-voice-input-vu-left',
|
||||
|
|
@ -80,6 +86,8 @@
|
|||
// Always reset the driver select box to "Choose..." which forces everything
|
||||
// to sync properly when the user reselects their driver of choice.
|
||||
// VRFS-375 and VRFS-561
|
||||
$('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-device select').val("");
|
||||
$('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-voice select').val("");
|
||||
$('[layout-wizard="ftue"] [layout-wizard-step="2"] .asio-settings .settings-driver select').val("");
|
||||
}
|
||||
|
||||
|
|
@ -317,6 +325,15 @@
|
|||
$('#asio-framesize').on('change', setAsioFrameSize);
|
||||
$('#asio-input-latency').on('change', setAsioInputLatency);
|
||||
$('#asio-output-latency').on('change', setAsioOutputLatency);
|
||||
// New FTUE events
|
||||
$('.ftue-new .settings-2-device select').on('change', newFtueAudioDeviceChanged);
|
||||
$('.ftue-new .settings-2-voice select').on('change', newFtueAudioDeviceChanged);
|
||||
$('#btn-ftue-2-asio-resync').on('click', newFtueAsioResync);
|
||||
$('#btn-ftue-2-asio-control-panel').on('click', openASIOControlPanel);
|
||||
$('#ftue-2-asio-framesize').on('change', newFtueSetAsioFrameSize);
|
||||
$('#ftue-2-asio-input-latency').on('change', newFtueSetAsioInputLatency);
|
||||
$('#ftue-2-asio-output-latency').on('change', newFtueSetAsioOutputLatency);
|
||||
$('#btn-ftue-2-save').on('click', newFtueSaveSettingsHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -368,7 +385,6 @@
|
|||
* Load available drivers and populate the driver select box.
|
||||
*/
|
||||
function loadAudioDrivers() {
|
||||
|
||||
var drivers = jamClient.FTUEGetDevices();
|
||||
|
||||
var driverOptionFunc = function(driverKey, index, list) {
|
||||
|
|
@ -377,11 +393,249 @@
|
|||
};
|
||||
|
||||
var optionsHtml = '<option selected="selected" value="">Choose...</option>';
|
||||
var $select = $('[layout-wizard-step="2"] .settings-driver select');
|
||||
$select.empty();
|
||||
var selectors = [
|
||||
'[layout-wizard-step="0"] .settings-2-device select',
|
||||
'[layout-wizard-step="0"] .settings-2-voice select',
|
||||
'[layout-wizard-step="2"] .settings-driver select'
|
||||
];
|
||||
var sortedDeviceKeys = context._.keys(drivers).sort();
|
||||
context._.each(sortedDeviceKeys, driverOptionFunc);
|
||||
$select.html(optionsHtml);
|
||||
$.each(selectors, function(index, selector) {
|
||||
var $select = $(selector);
|
||||
$select.empty();
|
||||
$select.html(optionsHtml);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the new FTUE save button.
|
||||
*/
|
||||
function newFtueSaveSettingsHandler(evt) {
|
||||
evt.preventDefault();
|
||||
var $saveButton = $('#btn-ftue-2-save');
|
||||
if ($saveButton.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
var selectedAudioDevice = $('.ftue-new .settings-2-device select').val();
|
||||
if (!(selectedAudioDevice)) {
|
||||
app.notify({
|
||||
title: "Please select an audio device",
|
||||
text: "Please choose a usable audio device, or select cancel."
|
||||
});
|
||||
return false;
|
||||
}
|
||||
jamClient.FTUESave(true);
|
||||
jamClient.FTUESetStatus(true); // No FTUE wizard next time
|
||||
rest.userCertifiedGear({success:true});
|
||||
app.layout.closeDialog('ftue');
|
||||
if (app.afterFtue) {
|
||||
// If there's a function to invoke, invoke it.
|
||||
app.afterFtue();
|
||||
app.afterFtue = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handler for when the audio device is changed in the new FTUE screen
|
||||
// This works differently from the old FTUE. There is no input/output selection,
|
||||
// as soon as the user chooses a driver, we auto-assign inputs and outputs.
|
||||
// We also call jamClient.FTUEGetExpectedLatency, which returns a structure like:
|
||||
// { latency: 11.1875, latencyknown: true, latencyvar: 1}
|
||||
function newFtueAudioDeviceChanged(evt) {
|
||||
var $select = $(evt.currentTarget);
|
||||
|
||||
var $audioSelect = $('.ftue-new .settings-2-device select');
|
||||
var $voiceSelect = $('.ftue-new .settings-2-voice select');
|
||||
var audioDriverId = $audioSelect.val();
|
||||
var voiceDriverId = $voiceSelect.val();
|
||||
jamClient.FTUESetMusicDevice(audioDriverId);
|
||||
jamClient.FTUESetChatInput(voiceDriverId);
|
||||
if (voiceDriverId) { // Let the back end know whether a voice device is selected
|
||||
jamClient.TrackSetChatEnable(true);
|
||||
} else {
|
||||
jamClient.TrackSetChatEnable(false);
|
||||
}
|
||||
if (!audioDriverId) {
|
||||
// reset back to 'Choose...'
|
||||
newFtueEnableControls(false);
|
||||
return;
|
||||
}
|
||||
var musicInputs = jamClient.FTUEGetMusicInputs();
|
||||
var musicOutputs = jamClient.FTUEGetMusicOutputs();
|
||||
|
||||
// set the music input to the first available input,
|
||||
// and output to the first available output
|
||||
var kin = null, kout = null, k = null;
|
||||
// TODO FIXME - this jamClient call returns a dictionary.
|
||||
// It's difficult to know what to auto-choose.
|
||||
// For example, with my built-in audio, the keys I get back are
|
||||
// digital in, line in, mic in and stereo mix. Which should we pick for them?
|
||||
for (k in musicInputs) {
|
||||
kin = k;
|
||||
break;
|
||||
}
|
||||
for (k in musicOutputs) {
|
||||
kout = k;
|
||||
break;
|
||||
}
|
||||
var result;
|
||||
if (kin && kout) {
|
||||
jamClient.FTUESetMusicInput(kin);
|
||||
jamClient.FTUESetMusicOutput(kout);
|
||||
} else {
|
||||
// TODO FIXME - how to handle a driver selection where we are unable to
|
||||
// autoset both inputs and outputs? (I'd think this could happen if either
|
||||
// the input or output side returned no values)
|
||||
return;
|
||||
}
|
||||
|
||||
newFtueEnableControls(true);
|
||||
newFtueOsSpecificSettings();
|
||||
setLevels(0);
|
||||
newFtueUpdateLatencyView('loading');
|
||||
jamClient.FTUESave(false);
|
||||
setVuCallbacks();
|
||||
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
newFtueUpdateLatencyView(latency);
|
||||
|
||||
}
|
||||
|
||||
function newFtueSave(persist) {
|
||||
newFtueUpdateLatencyView('loading');
|
||||
jamClient.FTUESave(persist);
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
newFtueUpdateLatencyView(latency);
|
||||
}
|
||||
|
||||
function newFtueAsioResync(evt) {
|
||||
// In theory, we should be calling the following, but it causes
|
||||
// us to not have both inputs/outputs loaded, and simply calling
|
||||
// FTUE Save appears to resync things.
|
||||
//jamClient.FTUERefreshDevices();
|
||||
newFtueSave(false);
|
||||
}
|
||||
|
||||
function newFtueSetAsioFrameSize(evt) {
|
||||
var val = parseFloat($(evt.currentTarget).val(),10);
|
||||
if (isNaN(val)) {
|
||||
return;
|
||||
}
|
||||
logger.debug("Calling FTUESetFrameSize(" + val + ")");
|
||||
jamClient.FTUESetFrameSize(val);
|
||||
newFtueSave(false);
|
||||
}
|
||||
function newFtueSetAsioInputLatency(evt) {
|
||||
var val = parseInt($(evt.currentTarget).val(),10);
|
||||
if (isNaN(val)) {
|
||||
return;
|
||||
}
|
||||
logger.debug("Calling FTUESetInputLatency(" + val + ")");
|
||||
jamClient.FTUESetInputLatency(val);
|
||||
newFtueSave(false);
|
||||
}
|
||||
function newFtueSetAsioOutputLatency(evt) {
|
||||
var val = parseInt($(evt.currentTarget).val(),10);
|
||||
if (isNaN(val)) {
|
||||
return;
|
||||
}
|
||||
logger.debug("Calling FTUESetOutputLatency(" + val + ")");
|
||||
jamClient.FTUESetOutputLatency(val);
|
||||
newFtueSave(false);
|
||||
}
|
||||
|
||||
// Enable or Disable the frame/buffer controls in the new FTUE screen
|
||||
function newFtueEnableControls(enable) {
|
||||
var $frame = $('#ftue-2-asio-framesize');
|
||||
var $bin = $('#ftue-2-asio-input-latency');
|
||||
var $bout = $('#ftue-2-asio-output-latency');
|
||||
if (enable) {
|
||||
$frame.removeAttr("disabled");
|
||||
$bin.removeAttr("disabled");
|
||||
$bout.removeAttr("disabled");
|
||||
} else {
|
||||
$frame.attr("disabled", "disabled");
|
||||
$bin.attr("disabled", "disabled");
|
||||
$bout.attr("disabled", "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
// Based on OS and Audio Hardware, set Frame/Buffer settings appropriately
|
||||
// and show/hide the ASIO button.
|
||||
function newFtueOsSpecificSettings() {
|
||||
var $frame = $('#ftue-2-asio-framesize');
|
||||
var $bin = $('#ftue-2-asio-input-latency');
|
||||
var $bout = $('#ftue-2-asio-output-latency');
|
||||
var $asioBtn = $('#btn-ftue-2-asio-control-panel');
|
||||
if (jamClient.GetOSAsString() === "Win32") {
|
||||
if (jamClient.FTUEHasControlPanel()) {
|
||||
// Win32 + ControlPanel = ASIO
|
||||
// frame=2.5, buffers=0
|
||||
$asioBtn.show();
|
||||
$frame.val(2.5);
|
||||
$bin.val(0);
|
||||
$bout.val(0);
|
||||
} else {
|
||||
// Win32, no ControlPanel = WDM/Kernel Streaming
|
||||
// frame=10, buffers=0
|
||||
$asioBtn.hide();
|
||||
$frame.val(10);
|
||||
// TODO FIXME - the old FTUE set the buffers to 1 for WDM/Kernel streaming
|
||||
// The new FTUE spec says to use 0, as I've done here...
|
||||
$bin.val(0);
|
||||
$bout.val(0);
|
||||
}
|
||||
} else { // Assuming Mac. TODO: Linux check here
|
||||
// frame=2.5, buffers=0
|
||||
$asioBtn.hide();
|
||||
$frame.val(2.5);
|
||||
$bin.val(0);
|
||||
$bout.val(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Given a latency structure, update the view.
|
||||
function newFtueUpdateLatencyView(latency) {
|
||||
var $report = $('.ftue-new .latency .report');
|
||||
var $instructions = $('.ftue-new .latency .instructions');
|
||||
var latencyClass = "neutral";
|
||||
var latencyValue = "N/A";
|
||||
var $saveButton = $('#btn-ftue-2-save');
|
||||
if (latency && latency.latencyknown) {
|
||||
latencyValue = latency.latency;
|
||||
// Round latency to two decimal places.
|
||||
latencyValue = Math.round(latencyValue * 100) / 100;
|
||||
if (latency.latency <= 10) {
|
||||
latencyClass = "good";
|
||||
$saveButton.removeClass('disabled');
|
||||
} else if (latency.latency <= 20) {
|
||||
latencyClass = "acceptable";
|
||||
$saveButton.removeClass('disabled');
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
$saveButton.addClass('disabled');
|
||||
}
|
||||
} else {
|
||||
latencyClass = "unknown";
|
||||
$saveButton.addClass('disabled');
|
||||
}
|
||||
|
||||
$('.ms-label', $report).html(latencyValue);
|
||||
$('p', $report).html('milliseconds');
|
||||
|
||||
$report.removeClass('good acceptable bad');
|
||||
$report.addClass(latencyClass);
|
||||
|
||||
var instructionClasses = ['neutral', 'good', 'acceptable', 'bad', 'start', 'loading'];
|
||||
$.each(instructionClasses, function(idx, val) {
|
||||
$('p.' + val, $instructions).hide();
|
||||
});
|
||||
if (latency === 'loading') {
|
||||
$('p.loading', $instructions).show();
|
||||
} else {
|
||||
$('p.' + latencyClass, $instructions).show();
|
||||
}
|
||||
}
|
||||
|
||||
function audioDriverChanged(evt) {
|
||||
|
|
@ -459,6 +713,7 @@
|
|||
var dialogBindings = { 'beforeShow': beforeShow,
|
||||
'afterShow': afterShow, 'afterHide': afterHide };
|
||||
app.bindDialog('ftue', dialogBindings);
|
||||
app.registerWizardStepFunction("0", settingsInit);
|
||||
app.registerWizardStepFunction("2", settingsInit);
|
||||
app.registerWizardStepFunction("4", testLatency);
|
||||
app.registerWizardStepFunction("6", testComplete);
|
||||
|
|
@ -484,6 +739,8 @@
|
|||
};
|
||||
|
||||
context.JK.ftueAudioInputVUCallback = function(dbValue) {
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-2-audio-input-vu-left');
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-2-audio-input-vu-right');
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-left');
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-right');
|
||||
};
|
||||
|
|
@ -492,6 +749,8 @@
|
|||
context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-right');
|
||||
};
|
||||
context.JK.ftueChatInputVUCallback = function(dbValue) {
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-2-voice-input-vu-left');
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-2-voice-input-vu-right');
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-left');
|
||||
context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-right');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
(function(context,$) {
|
||||
|
||||
/**
|
||||
* Javascript for managing genre selectors.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.GenreSelectorHelper = (function() {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var _genres = []; // will be list of structs: [ {label:xxx, value:yyy}, {...}, ... ]
|
||||
|
||||
function loadGenres() {
|
||||
var url = "/api/genres";
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
async: false, // do this synchronously so the event handlers in events() can be wired up
|
||||
success: genresLoaded
|
||||
});
|
||||
}
|
||||
|
||||
function reset(parentSelector, defaultGenre) {
|
||||
defaultGenre = typeof(defaultGenre) == 'undefined' ? '' : defaultGenre;
|
||||
$('select', parentSelector).val(defaultGenre);
|
||||
}
|
||||
|
||||
function genresLoaded(response) {
|
||||
$.each(response, function(index) {
|
||||
_genres.push({
|
||||
value: this.id,
|
||||
label: this.description
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function render(parentSelector) {
|
||||
$('select', parentSelector).empty();
|
||||
$('select', parentSelector).append('<option value="">Select Genre</option>');
|
||||
var template = $('#template-genre-option').html();
|
||||
$.each(_genres, function(index, value) {
|
||||
// value will be a dictionary entry from _genres:
|
||||
// { value: xxx, label: yyy }
|
||||
var genreOptionHtml = context.JK.fillTemplate(template, value);
|
||||
$('select', parentSelector).append(genreOptionHtml);
|
||||
});
|
||||
}
|
||||
|
||||
function getSelectedGenres(parentSelector) {
|
||||
var selectedGenres = [];
|
||||
var selectedVal = $('select', parentSelector).val();
|
||||
if (selectedVal !== '') {
|
||||
selectedGenres.push(selectedVal);
|
||||
}
|
||||
return selectedGenres;
|
||||
}
|
||||
|
||||
function setSelectedGenres(parentSelector, genreList) {
|
||||
if (!genreList) {
|
||||
return;
|
||||
}
|
||||
var values = [];
|
||||
$.each(genreList, function(index, value) {
|
||||
values.push(value.toLowerCase());
|
||||
});
|
||||
var selectedVal = $('select', parentSelector).val(values);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
loadGenres();
|
||||
}
|
||||
|
||||
var me = { // This will be our singleton.
|
||||
initialize: initialize,
|
||||
getSelectedGenres: getSelectedGenres,
|
||||
setSelectedGenres: setSelectedGenres,
|
||||
reset: reset,
|
||||
render: render,
|
||||
loadGenres: loadGenres
|
||||
};
|
||||
|
||||
return me;
|
||||
|
||||
})();
|
||||
})(window,jQuery);
|
||||
|
|
@ -168,6 +168,14 @@
|
|||
});
|
||||
}
|
||||
|
||||
function getMusicianFollowers(userId) {
|
||||
|
||||
}
|
||||
|
||||
function getBandFollowers(bandId) {
|
||||
|
||||
}
|
||||
|
||||
function getClientDownloads(options) {
|
||||
|
||||
return $.ajax({
|
||||
|
|
|
|||
|
|
@ -255,10 +255,6 @@
|
|||
}
|
||||
logger.debug("Changing screen to " + url);
|
||||
context.location = url;
|
||||
|
||||
if (!(context.jamClient.FTUEGetStatus())) {
|
||||
app.layout.showDialog('ftue');
|
||||
}
|
||||
}
|
||||
|
||||
this.unloadFunction = function() {
|
||||
|
|
@ -294,6 +290,9 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Holder for a function to invoke upon successfully completing the FTUE.
|
||||
// See createSession.submitForm as an example.
|
||||
this.afterFtue = null;
|
||||
|
||||
// enable temporary suspension of heartbeat for fine-grained control
|
||||
this.heartbeatActive = true;
|
||||
|
|
|
|||
|
|
@ -303,7 +303,9 @@
|
|||
padding: '0px'
|
||||
};
|
||||
$('[layout]').css(layoutStyle);
|
||||
$('[layout="notify"]').css({"z-index": "9", "padding": "20px"});
|
||||
// JW: Setting z-index of notify to 1001, so it will appear above the dialog overlay.
|
||||
// This allows dialogs to use the notification.
|
||||
$('[layout="notify"]').css({"z-index": "1001", "padding": "20px"});
|
||||
$('[layout="panel"]').css({position: 'relative'});
|
||||
$('[layout-panel="expanded"] [layout-panel="header"]').css({
|
||||
margin: "0px",
|
||||
|
|
@ -381,12 +383,20 @@
|
|||
|
||||
function linkClicked(evt) {
|
||||
evt.preventDefault();
|
||||
var $currentTarget = $(evt.currentTarget);
|
||||
|
||||
// allow links to be disabled
|
||||
if($(evt.currentTarget).hasClass("disabled") ) {
|
||||
if($currentTarget.hasClass("disabled") ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If link requires FTUE, show that first.
|
||||
if ($currentTarget.hasClass("requires-ftue")) {
|
||||
if (!(context.jamClient.FTUEGetStatus())) {
|
||||
app.layout.showDialog('ftue');
|
||||
}
|
||||
}
|
||||
|
||||
var destination = $(evt.currentTarget).attr('layout-link');
|
||||
var destinationType = $('[layout-id="' + destination + '"]').attr("layout");
|
||||
if (destinationType === "screen") {
|
||||
|
|
@ -595,10 +605,11 @@
|
|||
}
|
||||
|
||||
notifyQueue.push({message: message, descriptor: descriptor});
|
||||
$notify.slideDown(2000)
|
||||
.delay(2000)
|
||||
// JW - speeding up the in/out parts of notify. Extending non-moving time.
|
||||
$notify.slideDown(250)
|
||||
.delay(4000)
|
||||
.slideUp({
|
||||
duration: 2000,
|
||||
duration: 400,
|
||||
queue: true,
|
||||
complete: function() {
|
||||
notifyDetails = notifyQueue.shift();
|
||||
|
|
@ -614,7 +625,7 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function setNotificationInfo(message, descriptor) {
|
||||
var $notify = $('[layout="notify"]');
|
||||
|
|
|
|||
|
|
@ -307,22 +307,22 @@
|
|||
$('#profile-location').html(user.location);
|
||||
|
||||
// stats
|
||||
var text = user.friend_count > 1 || user.friend_count == 0 ? " Friends" : " Friend";
|
||||
var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend";
|
||||
$('#profile-friend-stats').html(user.friend_count + text);
|
||||
|
||||
text = user.follower_count > 1 || user.follower_count == 0 ? " Followers" : " Follower";
|
||||
text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower";
|
||||
$('#profile-follower-stats').html(user.follower_count + text);
|
||||
|
||||
text = user.session_count > 1 || user.session_count == 0 ? " Sessions" : " Session";
|
||||
text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session";
|
||||
$('#profile-session-stats').html(user.session_count + text);
|
||||
|
||||
text = user.recording_count > 1 || user.recording_count == 0 ? " Recordings" : " Recording";
|
||||
text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording";
|
||||
$('#profile-recording-stats').html(user.recording_count + text);
|
||||
|
||||
$('#profile-biography').html(user.biography);
|
||||
}
|
||||
else {
|
||||
|
||||
logger.debug("No user found with userId = " + userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -621,8 +621,6 @@
|
|||
$connection.addClass(connectionClass);
|
||||
|
||||
} else if (eventName === 'add' || eventName === 'remove') {
|
||||
//logger.dbg('non-vu event: ' + eventName + ',' + mixerId + ',' + value);
|
||||
|
||||
// TODO - _renderSession. Note I get streams of these in
|
||||
// sequence, so have Nat fix, or buffer/spam protect
|
||||
// Note - this is already handled from websocket events.
|
||||
|
|
@ -632,7 +630,7 @@
|
|||
} else {
|
||||
// Examples of other events
|
||||
// Add media file track: "add", "The_Abyss_4T", 0
|
||||
logger.dbg('non-vu event: ' + eventName + ',' + mixerId + ',' + value);
|
||||
logger.debug('non-vu event: ' + eventName + ',' + mixerId + ',' + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,8 +137,16 @@
|
|||
// wire up the Join Link to the T&Cs dialog
|
||||
var $parentRow = $('tr[id=' + session.id + ']', tbGroup);
|
||||
|
||||
|
||||
$('.join-link', $parentRow).click(function(evt) {
|
||||
joinClick(session.id);
|
||||
// If no FTUE, show that first.
|
||||
if (!(context.jamClient.FTUEGetStatus())) {
|
||||
app.afterFtue = function() { joinClick(session.id); };
|
||||
app.layout.showDialog('ftue');
|
||||
return;
|
||||
} else {
|
||||
joinClick(session.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -201,7 +209,7 @@
|
|||
}
|
||||
|
||||
function openAlert(sessionId) {
|
||||
var alertDialog = new context.JK.AlertDialog(app, "YES",
|
||||
var alertDialog = new context.JK.AlertDialog(app, "YES",
|
||||
"You must be approved to join this session. Would you like to send a request to join?",
|
||||
sessionId, onCreateJoinRequest);
|
||||
|
||||
|
|
@ -210,7 +218,7 @@
|
|||
}
|
||||
|
||||
function sessionNotJoinableAlert() {
|
||||
var alertDialog = new context.JK.AlertDialog(app, "OK",
|
||||
var alertDialog = new context.JK.AlertDialog(app, "OK",
|
||||
"This session is over or is no longer public and cannot be joined. Please click Refresh to update the session list.",
|
||||
null,
|
||||
function(evt) {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,155 @@ div.dialog.ftue {
|
|||
margin-top: 12px;
|
||||
}
|
||||
|
||||
p.intro {
|
||||
margin-top:0px;
|
||||
}
|
||||
.ftue-new {
|
||||
clear:both;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height: 54px;
|
||||
margin-top: 12px;
|
||||
select {
|
||||
font-size: 15px;
|
||||
padding: 3px;
|
||||
}
|
||||
.latency {
|
||||
position: absolute;
|
||||
top: 120px;
|
||||
font-size: 12px;
|
||||
.report {
|
||||
color:#fff;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0px;
|
||||
width: 105px;
|
||||
height: 50px;
|
||||
background-color: #72a43b;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
.ms-label {
|
||||
padding-top: 10px;
|
||||
font-size: 34px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
p {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
.report.neutral, .report.start, .report.unknown {
|
||||
background-color: #666;
|
||||
}
|
||||
.report.good {
|
||||
background-color: #72a43b;
|
||||
}
|
||||
.report.acceptable {
|
||||
background-color: #D6A800;
|
||||
}
|
||||
.report.bad {
|
||||
background-color: #7B0C00;
|
||||
}
|
||||
.instructions {
|
||||
color:#fff;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 125px;
|
||||
width: 595px;
|
||||
height: 50px;
|
||||
padding: 10px;
|
||||
background-color: #666;
|
||||
}
|
||||
.instructions p.start, .instructions p.neutral {
|
||||
padding-top: 4px;
|
||||
}
|
||||
.instructions p.unknown {
|
||||
margin-top:0px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.instructions p.good {
|
||||
padding-top: 4px;
|
||||
}
|
||||
.instructions p.acceptable {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.instructions p.bad {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.instructions p a {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.column {
|
||||
position:absolute;
|
||||
width: 220px;
|
||||
height: 50px;
|
||||
margin-right:8px;
|
||||
}
|
||||
.settings-2-device {
|
||||
left:0px;
|
||||
}
|
||||
.settings-2-center {
|
||||
left:50%;
|
||||
margin-left: -110px;
|
||||
.buttons {
|
||||
margin-top: 14px;
|
||||
a {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.settings-2-voice {
|
||||
top: 0px;
|
||||
right:0px;
|
||||
}
|
||||
.controls {
|
||||
margin-top: 16px;
|
||||
background-color: #222;
|
||||
height: 48px;
|
||||
width: 220px;
|
||||
}
|
||||
.ftue-vu-left {
|
||||
position:relative;
|
||||
top: 0px;
|
||||
}
|
||||
.ftue-vu-right {
|
||||
position:relative;
|
||||
top: 22px;
|
||||
}
|
||||
.ftue-fader {
|
||||
position:relative;
|
||||
top: 14px;
|
||||
left: 8px;
|
||||
}
|
||||
.gain-label {
|
||||
color: $ColorScreenPrimary;
|
||||
position:absolute;
|
||||
top: 76px;
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
.subcolumn {
|
||||
position:absolute;
|
||||
top: 60px;
|
||||
font-size: 12px !important;
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
.subcolumn select {
|
||||
width: 68px;
|
||||
}
|
||||
.subcolumn.first {
|
||||
left:0px;
|
||||
}
|
||||
.subcolumn.second {
|
||||
left:50%;
|
||||
margin-left:-34px;
|
||||
}
|
||||
.subcolumn.third {
|
||||
right:0px;
|
||||
}
|
||||
}
|
||||
|
||||
.asio-settings {
|
||||
clear:both;
|
||||
position:relative;
|
||||
|
|
|
|||
|
|
@ -55,3 +55,21 @@
|
|||
font-size: 90%;
|
||||
}
|
||||
|
||||
.query-distance-params {
|
||||
float:left;
|
||||
width:140px;
|
||||
margin-left: 10px;
|
||||
-webkit-border-radius: 6px;
|
||||
border-radius: 6px;
|
||||
background-color:$ColorTextBoxBackground;
|
||||
border: none;
|
||||
color:#333;
|
||||
font-weight:400;
|
||||
padding:0px 0px 0px 8px;
|
||||
height:18px;
|
||||
line-height:18px;
|
||||
overflow:hidden;
|
||||
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
|
||||
box-shadow: inset 2px 2px 3px 0px #888;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,15 @@ class ApiUsersController < ApiController
|
|||
respond_to :json
|
||||
|
||||
def index
|
||||
@users = User.paginate(page: params[:page])
|
||||
respond_with @users, responder: ApiResponder, :status => 200
|
||||
if 1 == params[:musicians].to_i
|
||||
query = params.clone
|
||||
query[:remote_ip] = request.remote_ip
|
||||
@users = User.musician_search(query, current_user)
|
||||
respond_with @users, responder: ApiResponder, :status => 200
|
||||
else
|
||||
@users = User.paginate(page: params[:page])
|
||||
respond_with @users, responder: ApiResponder, :status => 200
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
|||
|
|
@ -7,8 +7,128 @@
|
|||
<!-- inner wrapper -->
|
||||
<div class="ftue-inner" layout-wizard="ftue">
|
||||
|
||||
<!-- NEW FTUE first screen, which has latency approximation -->
|
||||
<!-- Audio device selection and level-setting -->
|
||||
<div layout-wizard-step="0" dialog-title="audio gear settings" dialog-purpose="GearSettings" style="display:block;">
|
||||
<p os="win32" class="intro">
|
||||
Choose a device to capture and play your session audio. If
|
||||
you’re not using a mic with this device, then also choose a
|
||||
voice chat input to talk with others during sessions. Then play
|
||||
and speak, and adjust the gain faders so that you hear both your
|
||||
instrument and voice in your headphones at comfortable volumes.
|
||||
</p>
|
||||
<p os="mac" class="intro" style="display:none;">
|
||||
Choose a device to capture and play your session audio. If
|
||||
you’re not using a mic with this device, then also choose a
|
||||
voice chat input to talk with others during sessions. Then play
|
||||
and speak, and adjust the gain faders so that you hear both your
|
||||
instrument and voice in your headphones at comfortable volumes.
|
||||
</p>
|
||||
|
||||
<div class="ftue-new">
|
||||
<div class="column settings-2-device">
|
||||
Session Audio Device:<br />
|
||||
<select></select>
|
||||
<!-- VU/Fader for audio device -->
|
||||
<div class="controls">
|
||||
<div id="ftue-2-audio-input-vu-left" class="ftue-vu-left"></div>
|
||||
<div id="ftue-2-audio-input-fader" class="ftue-fader"></div>
|
||||
<div class="gain-label">GAIN</div>
|
||||
<div id="ftue-2-audio-input-vu-right" class="ftue-vu-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column settings-2-center">
|
||||
<div class="buttons">
|
||||
<a class="button-grey" id="btn-ftue-2-asio-resync">
|
||||
<%= image_tag "content/icon_resync.png", {:width => 12, :height => 14} %>
|
||||
RESYNC
|
||||
</a>
|
||||
<a class="button-grey" id="btn-ftue-2-asio-control-panel">ASIO SETTINGS</a>
|
||||
</div>
|
||||
<div class="subcolumn first">
|
||||
Frame<br />
|
||||
<select disabled="disabled" id="ftue-2-asio-framesize">
|
||||
<option value=""></option>
|
||||
<option value="2.5">2.5</option>
|
||||
<option value="5">5</option>
|
||||
<option value="10">10</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="subcolumn second">
|
||||
Buffer/In<br />
|
||||
<select disabled="disabled" id="ftue-2-asio-input-latency">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="subcolumn third">
|
||||
Buffer/Out<br />
|
||||
<select disabled="disabled" id="ftue-2-asio-output-latency">
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Colum 3: Voice -->
|
||||
<div class="column settings-2-voice">
|
||||
Voice Chat Input:<br />
|
||||
<select></select>
|
||||
<div class="controls">
|
||||
<div id="ftue-2-voice-input-vu-left" class="ftue-vu-left"></div>
|
||||
<div id="ftue-2-voice-input-fader" class="ftue-fader"></div>
|
||||
<div class="gain-label">GAIN</div>
|
||||
<div id="ftue-2-voice-input-vu-right" class="ftue-vu-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Latency -->
|
||||
<div class="latency">
|
||||
Latency:<br />
|
||||
<div class="report neutral">
|
||||
<div class="ms-label"></div>
|
||||
<p></p>
|
||||
</div>
|
||||
<div class="instructions">
|
||||
<p class="start" style="display:block;">Choose an audio device to continue...</p>
|
||||
<p class="unknown" style="display:none;">Unable to determine latency. Please click <a href="#" layout-wizard-link="2">here</a> to try another test which may be able to determine your audio performance.</p>
|
||||
<p class="neutral loading" style="display:none;">Estimating latency...</p>
|
||||
<p class="good" style="display:none;">Your audio speed is good. When done with settings, click Save Settings to continue.</p>
|
||||
<p class="acceptable" style="display:none;">Your audio speed is acceptable, but borderline. Try setting Frame to 2.5 and Buffers to 0 and see if your audio quality is still OK. When done with settings, click Save Settings to continue. You can also view the <a href="#">Choosing an Audio Device</a> article for information on faster audio devices.</p>
|
||||
<p class="bad" style="display:none;">We're sorry, but your audio speed is too slow to use JamKazam. Try setting Frame to 2.5 and Buffers to 0 and see if your audio quality is still OK. You can also view the <a href="#">Choosing an Audio Device</a> article for information on faster audio devices.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right mr30 buttonbar">
|
||||
<a class="button-grey" layout-action="close">CANCEL</a>
|
||||
<a class="button-grey">HELP</a>
|
||||
<a class="button-orange" id="btn-ftue-2-save">SAVE SETTINGS</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- First screen of the FTUE wizard -->
|
||||
<div layout-wizard-step="1" dialog-title="welcome!" dialog-purpose="Intro" style="display:block;">
|
||||
<div layout-wizard-step="1" dialog-title="welcome!" dialog-purpose="Intro" style="display:none;">
|
||||
<p class="intro">
|
||||
Please identify which of the three types of audio gear below you
|
||||
are going to use with the JamKazam service, and click one to
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<div style="min-width:770px;">
|
||||
<div class="left ml35" style="padding-top:3px;">Filter Musician List:</div>
|
||||
<!-- order by filter -->
|
||||
<%= select_tag(:musician_order_by, options_for_select(Search::ORDERINGS), {:class => 'musician-order-by'} ) %>
|
||||
<!-- instrument filter -->
|
||||
<div id="find-musician-instrument" class="right ml10">
|
||||
<%= select_tag(:instrument,
|
||||
options_for_select(['Select Instrument', ''].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] })),
|
||||
{:class => 'instrument-list'} ) %>
|
||||
</div>
|
||||
|
||||
<!-- distance filter -->
|
||||
<div class="query-distance-params" style="height:25px;">
|
||||
Within
|
||||
<input id="musician-query-distance" type="text" name="query-distance" placeholder="100" />
|
||||
miles of
|
||||
<input id="musician-query-zip" type="text" name="query-zip" placeholder="zip" />
|
||||
</div>
|
||||
<div class="right mr10">
|
||||
<a id="btn-refresh" href="#/findMusician" style="text-decoration:none;" class="button-grey">REFRESH</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<!-- Musician Screen -->
|
||||
<div layout="screen" layout-id="musicians" class="screen secondary">
|
||||
<div class="content">
|
||||
<div class="content-head">
|
||||
|
||||
<div class="content-icon">
|
||||
<%= image_tag "content/icon_musicians.png", {:height => 19, :width => 19} %>
|
||||
</div>
|
||||
|
|
@ -9,5 +9,18 @@
|
|||
<h1>musicians</h1>
|
||||
<%= render "screen_navigation" %>
|
||||
</div>
|
||||
<p>This feature not yet implemented</p>
|
||||
<form id="find-musician-form">
|
||||
<div class="musician-filter">
|
||||
<%= render :partial => "musician_filter" %>
|
||||
</div>
|
||||
<div class="content-scroller">
|
||||
<div class="content-wrapper" style="padding-left:35px;padding-top:10px;">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="musicians-none-found">
|
||||
There are no musicians found.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class MaxMindManager < BaseManager
|
|||
ActiveRecord::Base.connection_pool.with_connection do |connection|
|
||||
pg_conn = connection.instance_variable_get("@connection")
|
||||
ip_as_int = ip_address_to_int(ip_address)
|
||||
pg_conn.exec("SELECT country, region, city FROM max_mind_geo WHERE ip_bottom <= $1 AND ip_top >= $2", [ip_as_int, ip_as_int]) do |result|
|
||||
pg_conn.exec("SELECT country, region, city FROM max_mind_geo WHERE ip_start <= $1 AND ip_end >= $2", [ip_address, ip_address]) do |result|
|
||||
if !result.nil? && result.ntuples > 0
|
||||
country = result.getvalue(0, 0)
|
||||
state = result[0]['region']
|
||||
|
|
@ -95,10 +95,10 @@ class MaxMindManager < BaseManager
|
|||
def create_phony_database()
|
||||
clear_location_table
|
||||
(0..255).each do |top_octet|
|
||||
@pg_conn.exec("INSERT INTO max_mind_geo (ip_bottom, ip_top, country, region, city) VALUES ($1, $2, $3, $4, $5)",
|
||||
@pg_conn.exec("INSERT INTO max_mind_geo (ip_start, ip_end, country, region, city) VALUES ($1, $2, $3, $4, $5)",
|
||||
[
|
||||
self.class.ip_address_to_int("#{top_octet}.0.0.0"),
|
||||
self.class.ip_address_to_int("#{top_octet}.255.255.255"),
|
||||
"#{top_octet}.0.0.0",
|
||||
"#{top_octet}.255.255.255",
|
||||
"US",
|
||||
"Region #{(top_octet / 2).floor}",
|
||||
"City #{top_octet}"
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ describe ApiClaimedRecordingsController do
|
|||
it "should show the right thing when one recording just finished" do
|
||||
controller.current_user = @user
|
||||
get :show, :id => @claimed_recording.id
|
||||
# puts response.body
|
||||
response.should be_success
|
||||
response.should be_success
|
||||
json = JSON.parse(response.body)
|
||||
json.should_not be_nil
|
||||
json["id"].should == @claimed_recording.id
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ FactoryGirl.define do
|
|||
musician true
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
terms_of_service true
|
||||
subscribe_email true
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ FactoryGirl.define do
|
|||
musician false
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
terms_of_service true
|
||||
end
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ FactoryGirl.define do
|
|||
biography "Established 1978"
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
end
|
||||
|
||||
factory :join_request, :class => JamRuby::JoinRequest do
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ describe "Band API", :type => :api do
|
|||
|
||||
it "should allow band creation" do
|
||||
|
||||
last_response = create_band(user, "My Band", "http://www.myband.com", "Bio", "Apex", "NC", "USA", ["country"], "www.photos.com", "www.logos.com")
|
||||
last_response = create_band(user, "My Band", "http://www.myband.com", "Bio", "Apex", "NC", "US", ["country"], "www.photos.com", "www.logos.com")
|
||||
last_response.status.should == 201
|
||||
|
||||
new_band = JSON.parse(last_response.body)
|
||||
|
|
@ -117,14 +117,14 @@ describe "Band API", :type => :api do
|
|||
end
|
||||
|
||||
it "should prevent bands with less than 1 genre" do
|
||||
last_response = create_band(user, "My Band", "http://www.myband.com", "Bio", "Apex", "NC", "USA", nil, "www.photos.com", "www.logos.com")
|
||||
last_response = create_band(user, "My Band", "http://www.myband.com", "Bio", "Apex", "NC", "US", nil, "www.photos.com", "www.logos.com")
|
||||
last_response.status.should == 400
|
||||
error_msg = JSON.parse(last_response.body)
|
||||
error_msg["message"].should == ValidationMessages::GENRE_MINIMUM_NOT_MET
|
||||
end
|
||||
|
||||
it "should prevent bands with more than 1 genre" do
|
||||
last_response = create_band(user, "My Band", "http://www.myband.com", "Bio", "Apex", "NC", "USA", ["african", "country"], "www.photos.com", "www.logos.com")
|
||||
last_response = create_band(user, "My Band", "http://www.myband.com", "Bio", "Apex", "NC", "US", ["african", "country"], "www.photos.com", "www.logos.com")
|
||||
last_response.status.should == 400
|
||||
error_msg = JSON.parse(last_response.body)
|
||||
error_msg["message"].should == ValidationMessages::GENRE_LIMIT_EXCEEDED
|
||||
|
|
@ -145,7 +145,7 @@ describe "Band API", :type => :api do
|
|||
|
||||
band.genres.size.should == 1
|
||||
|
||||
last_response = update_band(user, band.id, "Brian's Band", "http://www.briansband.com", "Bio", "Apex", "NC", "USA", ["african"], "www.photos.com", "www.logos.com")
|
||||
last_response = update_band(user, band.id, "Brian's Band", "http://www.briansband.com", "Bio", "Apex", "NC", "US", ["african"], "www.photos.com", "www.logos.com")
|
||||
last_response.status.should == 200
|
||||
|
||||
updated_band = JSON.parse(last_response.body)
|
||||
|
|
@ -319,4 +319,4 @@ describe "Band API", :type => :api do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ describe "Search API", :type => :api do
|
|||
it "simple search" do
|
||||
@musician = FactoryGirl.create(:user, first_name: "Peach", last_name: "Nothing", email: "user@example.com", musician: true)
|
||||
@fan = FactoryGirl.create(:user, first_name: "Peach Peach", last_name: "Grovery", email: "fan@example.com", musician: false)
|
||||
@band = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "USA", ["hip hop"], user.id, nil, nil)
|
||||
@band2 = Band.save(nil, "Peach", "www.bands2.com", "zomg we rock", "Apex", "NC", "USA", ["hip hop"], user.id, nil, nil)
|
||||
@band = Band.save(nil, "Peach pit", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
|
||||
@band2 = Band.save(nil, "Peach", "www.bands2.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
|
||||
|
||||
get '/api/search.json?query=peach'
|
||||
last_response.status.should == 200
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ gem 'devise'
|
|||
gem 'postgres-copy'
|
||||
gem 'aws-sdk'
|
||||
gem 'bugsnag'
|
||||
gem 'geokit-rails'
|
||||
gem 'postgres_ext'
|
||||
|
||||
group :development do
|
||||
gem 'pry'
|
||||
|
|
|
|||
|
|
@ -437,7 +437,6 @@ module JamWebsockets
|
|||
reconnect_music_session_id = login.client_id if login.value_for_tag(5)
|
||||
|
||||
@log.info("*** handle_login: token=#{token}; client_id=#{client_id}")
|
||||
connection = nil
|
||||
reconnected = false
|
||||
|
||||
# you don't have to supply client_id in login--if you don't, we'll generate one
|
||||
|
|
@ -445,9 +444,26 @@ module JamWebsockets
|
|||
# give a unique ID to this client. This is used to prevent session messages
|
||||
# from echoing back to the sender, for instance.
|
||||
client_id = UUIDTools::UUID.random_create.to_s
|
||||
else
|
||||
end
|
||||
|
||||
user = valid_login(username, password, token, client_id)
|
||||
|
||||
connection = JamRuby::Connection.find_by_client_id(client_id)
|
||||
# if this connection is reused by a different user, then whack the connection
|
||||
# because it will recreate a new connection lower down
|
||||
if !connection.nil? && !user.nil? && connection.user != user
|
||||
@log.debug("user #{user.email} took client_id #{client_id} from user #{connection.user.email}")
|
||||
connection.delete
|
||||
connection = nil
|
||||
end
|
||||
|
||||
client.client_id = client_id
|
||||
|
||||
if !user.nil?
|
||||
@log.debug "user #{user} logged in with client_id #{client_id}"
|
||||
|
||||
# check if there's a connection for the client... if it's stale, reconnect it
|
||||
if connection = JamRuby::Connection.find_by_client_id(client_id)
|
||||
unless connection.nil?
|
||||
# FIXME: I think connection table needs to updated within connection_manager
|
||||
# otherwise this would be 1 line of code (connection.connect!)
|
||||
|
||||
|
|
@ -467,21 +483,10 @@ module JamWebsockets
|
|||
|
||||
end if connection.stale?
|
||||
end
|
||||
# if there's a client_id but no connection object, create new client_id
|
||||
client_id = UUIDTools::UUID.random_create.to_s if !connection
|
||||
end
|
||||
|
||||
client.client_id = client_id
|
||||
|
||||
user = valid_login(username, password, token, client_id)
|
||||
|
||||
if !user.nil?
|
||||
@log.debug "user #{user} logged in"
|
||||
|
||||
# respond with LOGIN_ACK to let client know it was successful
|
||||
remote_ip = extract_ip(client)
|
||||
|
||||
|
||||
@semaphore.synchronize do
|
||||
# remove from pending_queue
|
||||
@pending_clients.delete(client)
|
||||
|
|
@ -696,7 +701,7 @@ module JamWebsockets
|
|||
|
||||
@@log.debug "publishing to session:#{music_session_id} client:#{client_id} from client:#{sender_client_id}"
|
||||
# put it on the topic exchange3 for clients
|
||||
self.class.client_exchange.publish(client_msg, :routing_key => "client.#{music_session_id}")
|
||||
self.class.client_exchange.publish(client_msg, :routing_key => "client.#{client_id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ FactoryGirl.define do
|
|||
musician true
|
||||
city "Apex"
|
||||
state "NC"
|
||||
country "USA"
|
||||
country "US"
|
||||
terms_of_service true
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue