merge conflict

This commit is contained in:
Jonathan Kolyer 2014-02-28 07:07:26 +00:00
commit b4aae340f7
134 changed files with 4337 additions and 1258 deletions

View File

@ -121,3 +121,7 @@ scores_mod_connections.sql
scores_create_schemas_and_extensions.sql
scores_create_tables.sql
remove_is_downloadable.sql
scores_mod_connections2.sql
track_download_counts.sql
scores_mod_users2.sql
user_bio.sql

View File

@ -0,0 +1,6 @@
-- fix locidispid should be bigint
ALTER TABLE connections DROP COLUMN locidispid;
ALTER TABLE connections ADD COLUMN locidispid BIGINT;
ALTER TABLE connections ALTER COLUMN locidispid SET NOT NULL;
CREATE INDEX connections_locidispid_ndx ON connections (locidispid);

View File

@ -0,0 +1,7 @@
-- locidispid must be bigint
ALTER TABLE users DROP COLUMN locidispid;
ALTER TABLE users ADD COLUMN locidispid BIGINT;
ALTER TABLE users ALTER COLUMN locidispid SET DEFAULT 0;
UPDATE users SET locidispid = 0;
ALTER TABLE users ALTER COLUMN locidispid SET NOT NULL;

View File

@ -0,0 +1,5 @@
ALTER TABLE recorded_tracks ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0;
ALTER TABLE recorded_tracks ADD COLUMN last_downloaded_at TIMESTAMP;
ALTER TABLE mixes ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0;
ALTER TABLE mixes ADD COLUMN last_downloaded_at TIMESTAMP;

1
db/up/user_bio.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE users ALTER COLUMN biography TYPE TEXT;

View File

@ -33,6 +33,7 @@ gem 'carrierwave'
gem 'aasm', '3.0.16'
gem 'devise', '>= 1.1.2'
gem 'postgres-copy'
gem 'geokit'
gem 'geokit-rails'
gem 'postgres_ext'
gem 'resque'
@ -41,6 +42,7 @@ gem 'resque-failed-job-mailer' #, :path => "/Users/seth/workspace/resque_failed_
gem 'resque-lonely_job', '~> 1.0.0'
gem 'oj'
gem 'builder'
gem 'fog'
group :test do
gem 'simplecov', '~> 0.7.1'

View File

@ -11,6 +11,7 @@ require "action_mailer"
require "devise"
require "sendgrid"
require "postgres-copy"
require "geokit"
require "geokit-rails"
require "postgres_ext"
require 'builder'
@ -124,6 +125,9 @@ require "jam_ruby/models/recording_play"
require "jam_ruby/models/feed"
require "jam_ruby/models/jam_isp"
require "jam_ruby/models/geo_ip_blocks"
require "jam_ruby/models/geo_ip_locations"
require "jam_ruby/models/score"
require "jam_ruby/models/get_work"
include Jampb

View File

@ -135,7 +135,7 @@
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
mail(:to => email, :subject => subject) do |format|
mail(:bcc => email, :subject => subject) do |format|
format.text
format.html
end
@ -154,53 +154,54 @@
end
end
def musician_session_join(email, msg)
def musician_session_join(email, msg, session_id)
subject = "Someone you know is in a session on JamKazam"
unique_args = {:type => "musician_session_join"}
@body = msg
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session_id}"
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
mail(:to => email, :subject => subject) do |format|
mail(:bcc => email, :subject => subject) do |format|
format.text
format.html
end
end
def band_session_join(email, msg)
def band_session_join(email, msg, session_id)
subject = "A band that you follow has joined a session"
unique_args = {:type => "band_session_join"}
@body = msg
@session_url = "#{APP_CONFIG.external_root_url}/sessions/#{session_id}"
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
mail(:to => email, :subject => subject) do |format|
mail(:bcc => email, :subject => subject) do |format|
format.text
format.html
end
end
def musician_recording_saved(email, msg)
subject = msg
subject = "A musician has saved a new recording on JamKazam"
unique_args = {:type => "musician_recording_saved"}
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
mail(:to => email, :subject => subject) do |format|
mail(:bcc => email, :subject => subject) do |format|
format.text
format.html
end
end
def band_recording_saved(email, msg)
subject = msg
subject = "A band has saved a new recording on JamKazam"
unique_args = {:type => "band_recording_saved"}
@body = msg
sendgrid_category "Notification"
sendgrid_unique_args :type => unique_args[:type]
mail(:to => email, :subject => subject) do |format|
mail(:bcc => email, :subject => subject) do |format|
format.text
format.html
end
@ -236,7 +237,7 @@
# @body = msg
# sendgrid_category "Notification"
# sendgrid_unique_args :type => unique_args[:type]
# mail(:to => email, :subject => subject) do |format|
# mail(:bcc => email, :subject => subject) do |format|
# format.text
# format.html
# end

View File

@ -1,3 +1,3 @@
<% provide(:title, 'New Band Session') %>
<p><%= @body %></p>
<p><%= @body %>&nbsp;<a href="<%= @session_url %>">Listen in.</a></p>

View File

@ -1 +1 @@
<%= @body %>
<%= @body %> Listen at <%= @session_url %>.

View File

@ -1,3 +1,3 @@
<% provide(:title, 'Musician in Session') %>
<p><%= @body %></p>
<p><%= @body %>&nbsp;<a href="<%= @session_url %>">Listen in.</a></p>

View File

@ -1 +1 @@
<%= @body %>
<%= @body %> Listen at <%= @session_url %>.

View File

@ -56,27 +56,44 @@ module JamRuby
end
if ip_address
# todo turn ip_address string into a number, then fetch the locid and ispid and the other stuff...
# turn ip_address string into a number, then fetch the isp and block records and update location info
addr = JamIsp.ip_to_num(ip_address)
puts("============= JamIsp.ip_to_num returns #{addr} for #{ip_address} =============")
#puts("============= JamIsp.ip_to_num returns #{addr} for #{ip_address} =============")
isp = JamIsp.lookup(addr)
#puts("============= JamIsp.lookup returns #{isp.inspect} for #{addr} =============")
if isp.nil? then ispid = 0 else ispid = isp.coid end
puts("============= JamIsp.lookup returns #{ispid} for #{addr} =============")
block = GeoIpBlocks.lookup(addr)
#puts("============= GeoIpBlocks.lookup returns #{block.inspect} for #{addr} =============")
if block.nil? then locid = 0 else locid = block.locid end
puts("============= GeoIpBlocks.lookup returns #{locid} for #{addr} =============")
locidispid = 0
latitude = 0.0
longitude = 0.0
countrycode = 'US'
region = 'TX'
city = 'Austin'
location = GeoIpLocations.lookup(locid)
if location.nil?
locidispid = 0
latitude = 0.0
longitude = 0.0
countrycode = 'US'
region = 'TX'
city = 'Austin'
else
locidispid = locid*1000000+ispid
latitude = location.latitude
longitude = location.longitude
countrycode = location.countrycode
region = location.region
city = location.city
end
# todo stuff this stuff into the connection records
conn.ip_address = ip_address
conn.locidispid = locidispid
conn.latitude = latitude
conn.longitude = longitude
conn.countrycode = countrycode
conn.region = region
conn.city = city
conn.save!(validate: false)
end
sql =<<SQL
@ -188,30 +205,40 @@ SQL
ConnectionManager.active_record_transaction do |connection_manager|
conn = connection_manager.pg_conn
# todo turn ip_address string into a number, then fetch the locid and ispid and the other stuff...
# turn ip_address string into a number, then fetch the isp and block records
addr = JamIsp.ip_to_num(ip_address)
puts("============= JamIsp.ip_to_num returns #{addr} for #{ip_address} =============")
#puts("============= JamIsp.ip_to_num returns #{addr} for #{ip_address} =============")
isp = JamIsp.lookup(addr)
#puts("============= JamIsp.lookup returns #{isp.inspect} for #{addr} =============")
if isp.nil? then ispid = 0 else ispid = isp.coid end
puts("============= JamIsp.lookup returns #{ispid} for #{addr} =============")
block = GeoIpBlocks.lookup(addr)
#puts("============= GeoIpBlocks.lookup returns #{block.inspect} for #{addr} =============")
if block.nil? then locid = 0 else locid = block.locid end
puts("============= GeoIpBlocks.lookup returns #{locid} for #{addr} =============")
locidispid = 0
latitude = 0.0
longitude = 0.0
countrycode = 'US'
region = 'TX'
city = 'Austin'
location = GeoIpLocations.lookup(locid)
if location.nil?
locidispid = 0
latitude = 0.0
longitude = 0.0
countrycode = 'US'
region = 'TX'
city = 'Austin'
else
locidispid = locid*1000000+ispid
latitude = location.latitude
longitude = location.longitude
countrycode = location.countrycode
region = location.region
city = location.city
end
lock_connections(conn)
conn.exec("INSERT INTO connections (user_id, client_id, addr, locidispid, latitude, longitude, countrycode, region, city, aasm_state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
[user_id, client_id, addr, locidispid, latitude, longitude, countrycode, region, city, Connection::CONNECT_STATE.to_s]).clear
conn.exec("INSERT INTO connections (user_id, client_id, addr, locidispid, latitude, longitude, countrycode, region, city, aasm_state, ip_address) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
[user_id, client_id, addr, locidispid, latitude, longitude, countrycode, region, city, Connection::CONNECT_STATE.to_s, ip_address]).clear
# we just created a new connection-if this is the first time the user has shown up, we need to send out a message to his friends
conn.exec("SELECT count(user_id) FROM connections WHERE user_id = $1", [user_id]) do |result|

View File

@ -20,6 +20,7 @@ module JamRuby
validates_uniqueness_of :user_id, :scope => :recording_id
validate :user_belongs_to_recording
before_create :generate_share_token
SHARE_TOKEN_LENGTH = 8
@ -67,7 +68,6 @@ module JamRuby
!ClaimedRecording.find_by_user_id_and_recording_id(some_user.id, recording_id).nil?
end
def remove_non_alpha_num(token)
token.gsub(/[^0-9A-Za-z]/, '')
end

View File

@ -102,15 +102,15 @@ module JamRuby
errors.add(:music_session, ValidationMessages::CANT_JOIN_RECORDING_SESSION)
end
unless user.admin?
num_sessions = Connection.where(:user_id => user_id)
.where(["(music_session_id IS NOT NULL) AND (aasm_state != ?)",EXPIRED_STATE.to_s])
.count
if 0 < num_sessions
errors.add(:music_session, ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS)
return false;
end
end
# unless user.admin?
# num_sessions = Connection.where(:user_id => user_id)
# .where(["(music_session_id IS NOT NULL) AND (aasm_state != ?)",EXPIRED_STATE.to_s])
# .count
# if 0 < num_sessions
# errors.add(:music_session, ValidationMessages::CANT_JOIN_MULTIPLE_SESSIONS)
# return false;
# end
# end
return true
end

View File

@ -4,17 +4,14 @@ module JamRuby
self.table_name = 'geoipblocks'
def self.lookup(ipnum)
GeoIpBlocks.select(:locid)
.where('geom && ST_MakePoint(?, 0) AND ? BETWEEN beginip AND endip', ipnum, ipnum)
GeoIpBlocks.where('geom && ST_MakePoint(?, 0) AND ? BETWEEN beginip AND endip', ipnum, ipnum)
.limit(1)
.first
end
def self.make_row(beginip, endip, locid)
c = ActiveRecord::Base.connection.raw_connection
c.prepare('blah', 'insert into geoipblocks (beginip, endip, locid, geom) values($1::bigint, $2::bigint, $3, ST_MakeEnvelope($1::bigint, -1, $2::bigint, 1))')
c.exec_prepared('blah', [beginip, endip, locid])
c.exec("deallocate blah")
def self.createx(beginip, endip, locid)
c = connection.raw_connection
c.exec_params('insert into geoipblocks (beginip, endip, locid, geom) values($1::bigint, $2::bigint, $3, ST_MakeEnvelope($1::bigint, -1, $2::bigint, 1))', [beginip, endip, locid])
end
end
end

View File

@ -0,0 +1,18 @@
module JamRuby
class GeoIpLocations < ActiveRecord::Base
self.table_name = 'geoiplocations'
def self.lookup(locid)
GeoIpLocations.where(locid: locid)
.limit(1)
.first
end
def self.createx(locid, countrycode, region, city, postalcode, latitude, longitude, metrocode, areacode)
c = connection.raw_connection
c.exec_params('insert into geoiplocations (locid, countrycode, region, city, postalcode, latitude, longitude, metrocode, areacode, geog) values($1, $2, $3, $4, $5, $6, $7, $8, $9, ST_SetSRID(ST_MakePoint($7, $6), 4326)::geography)',
[locid, countrycode, region, city, postalcode, latitude, longitude, metrocode, areacode])
end
end
end

View File

@ -0,0 +1,22 @@
module JamRuby
class GetWork < ActiveRecord::Base
self.table_name = "connections"
def self.get_work(mylocidispid)
list = self.get_work_list(mylocidispid)
return nil if list.nil?
return nil if list.length == 0
return list[0]
end
def self.get_work_list(mylocidispid)
r = GetWork.select(:client_id).find_by_sql("select get_work(#{mylocidispid}) as client_id")
#puts("r = #{r}")
a = r.map {|i| i.client_id}
#puts("a = #{a}")
a
#return ["blah1", "blah2", "blah3", "blah4", "blah5"]
end
end
end

View File

@ -110,7 +110,7 @@ module JamRuby
def listener_add
with_lock do
sourced_needs_changing_at = Time.now if listeners == 0
self.sourced_needs_changing_at = Time.now if listeners == 0
# this is completely unsafe without that 'with_lock' statement above
self.listeners = self.listeners + 1
@ -126,7 +126,7 @@ module JamRuby
end
with_lock do
sourced_needs_changing_at = Time.now if listeners == 1
self.sourced_needs_changing_at = Time.now if listeners == 1
# this is completely unsafe without that 'with_lock' statement above
self.listeners = self.listeners - 1

View File

@ -18,11 +18,10 @@ module JamRuby
.first
end
def self.make_row(beginip, endip, coid)
c = ActiveRecord::Base.connection.raw_connection
c.prepare('blah', 'insert into jamisp (beginip, endip, coid, geom) values($1::bigint, $2::bigint, $3, ST_MakeEnvelope($1::bigint, -1, $2::bigint, 1))')
c.exec_prepared('blah', [beginip, endip, coid])
c.exec("deallocate blah")
def self.createx(beginip, endip, coid)
c = connection.raw_connection
c.exec_params('insert into jamisp (beginip, endip, coid, geom) values($1::bigint, $2::bigint, $3, ST_MakeEnvelope($1::bigint, -1, $2::bigint, 1))',
[beginip, endip, coid])
end
end
end

View File

@ -10,14 +10,24 @@ module JamRuby
attr_accessible :ogg_url, :should_retry, as: :admin
attr_accessor :is_skip_mount_uploader
attr_writer :current_user
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :mixes, :foreign_key => 'recording_id'
validates :download_count, presence: true
validate :verify_download_count
skip_callback :save, :before, :store_picture!, if: :is_skip_mount_uploader
mount_uploader :ogg_url, MixUploader
def verify_download_count
if (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
errors.add(:download_count, "must be less than or equal to 100")
end
end
before_validation do
# this should be an activeadmin only path, because it's using the mount_uploader (whereas the client does something completely different)
if !is_skip_mount_uploader && ogg_url.present? && ogg_url.respond_to?(:file) && ogg_url_changed?
@ -67,7 +77,6 @@ module JamRuby
!ClaimedRecording.find_by_user_id_and_recording_id(some_user.id, recording_id).nil?
end
def errored(reason, detail)
self.error_reason = reason
self.error_detail = detail
@ -148,6 +157,11 @@ module JamRuby
Mix.construct_filename(self.created_at, self.recording_id, self.id, type)
end
def update_download_count(count=1)
self.download_count = self.download_count + count
self.last_downloaded_at = Time.now
end
private
def delete_s3_files

View File

@ -43,19 +43,23 @@ module JamRuby
self.comments.size
end
def tracks
def grouped_tracks
tracks = []
self.music_session_user_histories.each do |msuh|
user = User.find(msuh.user_id)
t = Track.new
t.musician = user
t.instrument_ids = []
# this treats each track as a "user", which has 1 or more instruments in the session
unless msuh.instruments.blank?
instruments = msuh.instruments.split(SEPARATOR)
instruments.each do |instrument|
t = Track.new
t.musician = user
t.instrument_id = instrument
tracks << t
if !t.instrument_ids.include? instrument
t.instrument_ids << instrument
end
end
end
tracks << t
end
tracks
end
@ -146,7 +150,7 @@ module JamRuby
end
def is_over?
!session_removed_at.nil?
music_session.nil? || !session_removed_at.nil?
end
def end_history
@ -173,8 +177,6 @@ module JamRuby
hist.end_history if hist
puts "**************NOTIFICATION SESSION ENDED**************"
Notification.send_session_ended(session_id)
end

View File

@ -1,6 +1,8 @@
module JamRuby
class Notification < ActiveRecord::Base
@@log = Logging.logger[Notification]
self.primary_key = 'id'
default_scope order('created_at DESC')
@ -357,7 +359,6 @@ module JamRuby
# publish to all users who have a notification for this session
# TODO: do this in BULK or in async block
notifications.each do |n|
puts "*************SENDING SESSION_ENDED TO #{n.target_user_id}***************"
msg = @@message_factory.session_ended(n.target_user_id, session_id)
@@mq_router.publish_to_user(n.target_user_id, msg)
end
@ -505,7 +506,11 @@ module JamRuby
# send email notifications
unless offline_ff.empty?
UserMailer.musician_session_join(offline_ff.map! {|f| f.email}, notification_msg).deliver
begin
UserMailer.musician_session_join(offline_ff.map! {|f| f.email}, notification_msg, music_session.id).deliver if APP_CONFIG.send_join_session_email_notifications
rescue => e
@@log.error("unable to send email to offline participants #{e}")
end
end
end
end
@ -547,7 +552,7 @@ module JamRuby
# send email notifications
unless offline_followers.empty?
UserMailer.band_session_join(offline_followers.map! {|f| f.email}, notification_msg).deliver
UserMailer.band_session_join(offline_followers.map! {|f| f.email}, notification_msg, music_session.id).deliver if APP_CONFIG.send_join_session_email_notifications
end
end
end

View File

@ -81,6 +81,14 @@ class JamRuby::PromoLatest < JamRuby::Promotional
attr_accessible :latest
def music_session_history
@music_session_history ||= MusicSessionHistory.find_by_id(latest_id)
end
def recording
@recording ||= Recording.find_by_id(latest_id)
end
def self.latest_candidates
recordings = Recording
.where('music_session_id IS NOT NULL')

View File

@ -8,10 +8,13 @@ module JamRuby
self.table_name = "recorded_tracks"
self.primary_key = 'id'
default_scope order('user_id ASC')
attr_accessor :marking_complete
attr_writer :is_skip_mount_uploader
attr_accessible :discard, :user, :user_id, :instrument_id, :sound, :client_id, :track_id, :client_track_id, :url, as: :admin
attr_writer :current_user
SOUND = %w(mono stereo)
MAX_PART_FAILURES = 3
@ -31,11 +34,13 @@ module JamRuby
validates :length, length: {minimum: 1, maximum: 1024 * 1024 * 256 }, if: :upload_starting? # 256 megs max. is this reasonable? surely...
validates :user, presence: true
validates :instrument, presence: true
validates :download_count, presence: true
before_destroy :delete_s3_files
validate :validate_fully_uploaded
validate :validate_part_complete
validate :validate_too_many_upload_failures
validate :verify_download_count
before_save :sanitize_active_admin
skip_callback :save, :before, :store_picture!, if: :is_skip_mount_uploader?
@ -97,6 +102,12 @@ module JamRuby
end
end
def verify_download_count
if (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
errors.add(:download_count, "must be less than or equal to 100")
end
end
def sanitize_active_admin
self.user_id = nil if self.user_id == ''
end
@ -187,6 +198,11 @@ module JamRuby
RecordedTrack.construct_filename(self.created_at, self.recording.id, self.client_track_id)
end
def update_download_count(count=1)
self.download_count = self.download_count + count
self.last_downloaded_at = Time.now
end
private
def delete_s3_files

View File

@ -43,6 +43,35 @@ module JamRuby
self.comments.size
end
# this can probably be done more efficiently, but David needs this asap for a video
def grouped_tracks
tracks = []
sorted_tracks = self.recorded_tracks.sort { |a,b| a.user.id <=> b.user.id }
t = Track.new
t.instrument_ids = []
sorted_tracks.each_with_index do |track, index|
if index > 0
if sorted_tracks[index-1].user.id != sorted_tracks[index].user.id
t = Track.new
t.instrument_ids = []
t.instrument_ids << track.instrument.id
t.musician = track.user
tracks << t
else
if !t.instrument_ids.include? track.instrument.id
t.instrument_ids << track.instrument.id
end
end
else
t.musician = track.user
t.instrument_ids << track.instrument.id
tracks << t
end
end
tracks
end
def not_already_recording
if music_session && music_session.is_recording?
errors.add(:music_session, ValidationMessages::ALREADY_BEING_RECORDED)
@ -309,6 +338,12 @@ module JamRuby
save
end
# meant to be used as a way to 'pluck' a claimed_recording appropriate for user.
def candidate_claimed_recording
claimed_recordings.where(is_public: true).first
end
private
def self.validate_user_is_band_member(user, band)
unless band.users.exists? user

View File

@ -0,0 +1,29 @@
require 'ipaddr'
module JamRuby
class Score < ActiveRecord::Base
self.table_name = 'scores'
attr_accessible :alocidispid, :anodeid, :aaddr, :blocidispid, :bnodeid, :baddr, :score, :score_dt, :scorer
default_scope order('score_dt desc')
def self.createx(alocidispid, anodeid, aaddr, blocidispid, bnodeid, baddr, score, score_dt)
score_dt = Time.new.utc if score_dt.nil?
Score.create(alocidispid: alocidispid, anodeid: anodeid, aaddr: aaddr, blocidispid: blocidispid, bnodeid: bnodeid, baddr: baddr, score: score, scorer: 0, score_dt: score_dt)
Score.create(alocidispid: blocidispid, anodeid: bnodeid, aaddr: baddr, blocidispid: alocidispid, bnodeid: anodeid, baddr: aaddr, score: score, scorer: 1, score_dt: score_dt) if alocidispid != blocidispid
end
def self.deletex(alocidispid, blocidispid)
Score.where(alocidispid: alocidispid, blocidispid: blocidispid).delete_all
Score.where(alocidispid: blocidispid, blocidispid: alocidispid).delete_all if alocidispid != blocidispid
end
def self.findx(alocidispid, blocidispid)
s = Score.where(alocidispid: alocidispid, blocidispid: blocidispid).first
return -1 if s.nil?
return s.score
end
end
end

View File

@ -7,7 +7,7 @@ module JamRuby
default_scope order('created_at ASC')
attr_accessor :musician
attr_accessor :musician, :instrument_ids
SOUND = %w(mono stereo)

View File

@ -102,6 +102,7 @@ module JamRuby
validates :first_name, presence: true, length: {maximum: 50}, no_profanity: true
validates :last_name, presence: true, length: {maximum: 50}, no_profanity: true
validates :biography, length: {maximum: 4000}, no_profanity: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
validates :update_email, presence: true, format: {with: VALID_EMAIL_REGEX}, :if => :updating_email

View File

@ -171,10 +171,28 @@ FactoryGirl.define do
association :user, factory: :user
before(:create) { |claimed_recording|
claimed_recording.recording = FactoryGirl.create(:recording_with_track, owner: claimed_recording.user) unless claimed_recording.recording
}
end
factory :mix, :class => JamRuby::Mix do
started_at Time.now
completed_at Time.now
ogg_md5 'abc'
ogg_length 1
sequence(:ogg_url) { |n| "recordings/ogg/#{n}" }
mp3_md5 'abc'
mp3_length 1
sequence(:mp3_url) { |n| "recordings/mp3/#{n}" }
completed true
before(:create) {|mix|
user = FactoryGirl.create(:user)
mix.recording = FactoryGirl.create(:recording_with_track, owner: user)
mix.recording.claimed_recordings << FactoryGirl.create(:claimed_recording, user: user, recording: mix.recording)
}
end
factory :musician_instrument, :class => JamRuby::MusicianInstrument do

View File

@ -426,6 +426,7 @@ describe ConnectionManager do
end
it "join_music_session fails if user has music_session already active" do
pending
user_id = create_user("test", "user11", "user11@jamkazam.com")
user = User.find(user_id)

View File

@ -4,16 +4,22 @@ describe GeoIpBlocks do
before do
GeoIpBlocks.delete_all
GeoIpBlocks.make_row(0x00000000, 0xffffffff, 17192)
GeoIpBlocks.createx(0x01020300, 0x010203ff, 1)
GeoIpBlocks.createx(0x02030400, 0x020304ff, 2)
end
it "count" do GeoIpBlocks.count.should == 1 end
after do
GeoIpBlocks.delete_all
GeoIpBlocks.createx(0x00000000, 0xffffffff, 17192)
end
it "count" do GeoIpBlocks.count.should == 2 end
let(:first) { GeoIpBlocks.lookup(0x01020304) }
let(:second) { GeoIpBlocks.lookup(0x02030405) }
let(:seventh) { GeoIpBlocks.lookup(9999999999) } # bogus
let(:third) { GeoIpBlocks.lookup(9999999999) } # bogus
it "first.locid" do first.locid.should == 17192 end
it "second.locid" do second.locid.should == 17192 end
it "seventh" do seventh.should be_nil end
it "first.locid" do first.locid.should == 1 end
it "second.locid" do second.locid.should == 2 end
it "third" do third.should be_nil end
end

View File

@ -0,0 +1,36 @@
require 'spec_helper'
describe GeoIpLocations do
before do
GeoIpLocations.delete_all
GeoIpLocations.createx(17192, 'US', 'TX', 'Austin', '78749', 30.2076, -97.8587, 635, '512')
GeoIpLocations.createx(48086, 'MX', '28', 'Matamoros', '', 25.8833, -97.5000, nil, '')
end
it "count" do GeoIpLocations.count.should == 2 end
let(:first) { GeoIpLocations.lookup(17192) }
let(:second) { GeoIpLocations.lookup(48086) }
let(:third) { GeoIpLocations.lookup(999999) } # bogus
it "first" do
first.locid.should == 17192
first.countrycode.should eql('US')
first.region.should eql('TX')
first.city.should eql('Austin')
first.latitude.should == 30.2076
first.longitude.should == -97.8587
end
it "second" do
second.locid.should == 48086
second.countrycode.should eql('MX')
second.region.should eql('28')
second.city.should eql('Matamoros')
second.latitude.should == 25.8833
second.longitude.should == -97.5000
end
it "third" do third.should be_nil end
end

View File

@ -0,0 +1,20 @@
require 'spec_helper'
describe GetWork do
before(:each) do
end
it "get_work_1" do
x = GetWork.get_work(1)
puts x.inspect
x.should be_nil
end
it "get_work_list_1" do
x = GetWork.get_work_list(1)
puts x.inspect
x.should eql([])
end
end

View File

@ -4,17 +4,17 @@ describe JamIsp do
before do
JamIsp.delete_all
JamIsp.make_row(0x01020300, 0x010203ff, 1)
JamIsp.make_row(0x02030400, 0x020304ff, 2)
JamIsp.make_row(0x03040500, 0x030405ff, 3)
JamIsp.make_row(0x04050600, 0x040506ff, 4)
JamIsp.make_row(0xc0A80100, 0xc0A801ff, 5)
JamIsp.make_row(0xfffefd00, 0xfffefdff, 6)
JamIsp.createx(0x01020300, 0x010203ff, 1)
JamIsp.createx(0x02030400, 0x020304ff, 2)
JamIsp.createx(0x03040500, 0x030405ff, 3)
JamIsp.createx(0x04050600, 0x040506ff, 4)
JamIsp.createx(0xc0A80100, 0xc0A801ff, 5)
JamIsp.createx(0xfffefd00, 0xfffefdff, 6)
end
after do
JamIsp.delete_all
JamIsp.make_row(0x00000000, 0xffffffff, 1)
JamIsp.createx(0x00000000, 0xffffffff, 1)
end
it "count" do JamIsp.count.should == 6 end

View File

@ -63,6 +63,16 @@ describe Mix do
recordings.length.should == 0
end
describe "download count" do
it "will fail if too high" do
mix = FactoryGirl.create(:mix)
mix.current_user = mix.recording.owner
mix.update_download_count(APP_CONFIG.max_audio_downloads + 1)
mix.save
mix.errors[:download_count].should == ["must be less than or equal to 100"]
end
end
end

View File

@ -0,0 +1,95 @@
require 'spec_helper'
describe Score do
before do
Score.delete_all
Score.createx(1234, 'anodeid', 0x01020304, 2345, 'bnodeid', 0x02030405, 20, nil)
Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 30, nil)
Score.createx(1234, 'anodeid', 0x01020304, 3456, 'cnodeid', 0x03040506, 40, Time.new.utc-3600)
end
it "count" do
Score.count.should == 6
end
it 'a to b' do
s = Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first
s.should_not be_nil
s.alocidispid.should == 1234
s.anodeid.should eql('anodeid')
s.aaddr.should == 0x01020304
s.blocidispid.should == 2345
s.bnodeid.should eql('bnodeid')
s.baddr.should == 0x02030405
s.score.should == 20
s.scorer.should == 0
s.score_dt.should_not be_nil
end
it 'b to a' do
s = Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first
s.should_not be_nil
s.alocidispid.should == 2345
s.anodeid.should eql('bnodeid')
s.aaddr.should == 0x02030405
s.blocidispid.should == 1234
s.bnodeid.should eql('anodeid')
s.baddr.should == 0x01020304
s.score.should == 20
s.scorer.should == 1
s.score_dt.should_not be_nil
end
it 'a to c' do
s = Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first
s.should_not be_nil
s.alocidispid.should == 1234
s.anodeid.should eql('anodeid')
s.aaddr.should == 0x01020304
s.blocidispid.should == 3456
s.bnodeid.should eql('cnodeid')
s.baddr.should == 0x03040506
s.score.should == 30
s.scorer.should == 0
s.score_dt.should_not be_nil
end
it 'c to a' do
s = Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first
s.should_not be_nil
s.alocidispid.should == 3456
s.anodeid.should eql('cnodeid')
s.aaddr.should == 0x03040506
s.blocidispid.should == 1234
s.bnodeid.should eql('anodeid')
s.baddr.should == 0x01020304
s.score.should == 30
s.scorer.should == 1
s.score_dt.should_not be_nil
end
it 'delete a to c' do
Score.deletex(1234, 3456)
Score.count.should == 2
Score.where(alocidispid: 1234, blocidispid: 3456).limit(1).first.should be_nil
Score.where(alocidispid: 3456, blocidispid: 1234).limit(1).first.should be_nil
Score.where(alocidispid: 1234, blocidispid: 2345).limit(1).first.should_not be_nil
Score.where(alocidispid: 2345, blocidispid: 1234).limit(1).first.should_not be_nil
end
it 'findx' do
Score.findx(1234, 1234).should == -1
Score.findx(1234, 2345).should == 20
Score.findx(1234, 3456).should == 30
Score.findx(2345, 1234).should == 20
Score.findx(2345, 2345).should == -1
Score.findx(2345, 3456).should == -1
Score.findx(3456, 1234).should == 30
Score.findx(3456, 2345).should == -1
Score.findx(3456, 3456).should == -1
end
end

View File

@ -13,6 +13,9 @@ SpecDb::recreate_database
# initialize ActiveRecord's db connection
ActiveRecord::Base.establish_connection(YAML::load(File.open('config/database.yml'))["test"])
# so jam_ruby models that use APP_CONFIG in metadata will load. this is later stubbed pre test run
APP_CONFIG = app_config
require 'jam_ruby'
require 'factory_girl'
require 'rubygems'

View File

@ -101,6 +101,14 @@ def app_config
'315576000'
end
def max_audio_downloads
100
end
def send_join_session_email_notifications
true
end
private
def audiomixer_workspace_path

View File

@ -209,7 +209,7 @@
var password_confirmation_errors = context.JK.format_errors("password_confirmation", errors)
if(current_password_errors != null) {
$('#account-edit-password-form #account-forgot-password').closest('div.field').addClass('error').end().after(current_password_errors);
$('#account-edit-password-form input[name=current_password]').closest('div.field').addClass('error').end().after(current_password_errors);
}
if(password_errors != null) {

View File

@ -47,55 +47,53 @@
/****************** MAIN PORTION OF SCREEN *****************/
function addFollowing(isBand, id) {
var newFollowing = {};
var newFollowing = {};
if (!isBand) {
newFollowing.user_id = id;
}
else {
newFollowing.band_id = id;
}
if (!isBand) {
newFollowing.user_id = id;
}
else {
newFollowing.band_id = id;
}
rest.addFollowing(newFollowing)
.done(function() {
if (isBand) {
var newCount = parseInt($("#band-profile-follower-stats").text()) + 1;
var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower";
$('#band-profile-follower-stats').html(newCount + text);
configureBandFollowingButton(true);
}
else {
configureMemberFollowingButton(true, id);
}
})
.fail(app.ajaxError);
rest.addFollowing(newFollowing)
.done(function() {
if (isBand) {
var newCount = parseInt($("#band-profile-follower-stats").text()) + 1;
var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower";
$('#band-profile-follower-stats').html(newCount + text);
configureBandFollowingButton(true);
}
else {
configureMemberFollowingButton(true, id);
}
renderActive();
})
.fail(app.ajaxError);
}
function removeFollowing(isBand, id) {
var following = {};
following.target_entity_id = id;
rest.removeFollowing(following)
.done(function() {
renderActive(); // refresh stats
if (isBand) {
var newCount = parseInt($("#band-profile-follower-stats").text()) - 1;
var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower";
$('#band-profile-follower-stats').html(newCount + text);
configureBandFollowingButton(false);
}
else {
configureMemberFollowingButton(false, id);
}
})
.fail(app.ajaxError);
rest.removeFollowing(id)
.done(function() {
if (isBand) {
var newCount = parseInt($("#band-profile-follower-stats").text()) - 1;
var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower";
$('#band-profile-follower-stats').html(newCount + text);
configureBandFollowingButton(false);
}
else {
configureMemberFollowingButton(false, id);
}
renderActive();
})
.fail(app.ajaxError);
}
function configureBandFollowingButton(following) {
$('#btn-follow-band').unbind("click");
if (following) {
$('#btn-follow-band').text('STOP FOLLOWING');
$('#btn-follow-band').text('UNFOLLOW');
$('#btn-follow-band').click(function() {
removeFollowing(true, bandId);
return false;
@ -121,7 +119,7 @@
$btnFollowMember.unbind("click");
if (following) {
$btnFollowMember.text('UN-FOLLOW');
$btnFollowMember.text('UNFOLLOW');
$btnFollowMember.click(function() {
removeFollowing(false, userId);
return false;

View File

@ -0,0 +1,60 @@
(function(context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.FeedItemRecording = function($parentElement, options){
var $feedItem = $parentElement;
var $description = $('.description', $feedItem)
var $musicians = $('.musician-detail', $feedItem)
var toggledOpen = false;
if(!$feedItem.is('.feed-entry')) {
throw "$parentElement must be a .feed-entry"
}
function toggleDetails() {
if(toggledOpen) {
$feedItem.css('height', $feedItem.height() + 'px')
$feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() {
$feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
$musicians.hide();
$description.css('height', $description.data('original-height'));
$description.dotdotdot();
});
}
else {
$description.trigger('destroy.dot');
$description.data('original-height', $description.css('height')).css('height', 'auto');
$musicians.show();
$feedItem.animate({'max-height': '1000px'});
}
toggledOpen = !toggledOpen;
return false;
}
function events() {
$('.details', $feedItem).click(toggleDetails);
$('.details-arrow', $feedItem).click(toggleDetails);
}
function initialize() {
$('.timeago', $feedItem).timeago();
$('.dotdotdot', $feedItem).dotdotdot();
context.JK.prettyPrintElements($('time.duration', $feedItem));
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$feedItem.data('original-max-height', $feedItem.css('height'));
events();
}
initialize();
return this;
}
})(window, jQuery);

View File

@ -0,0 +1,123 @@
(function(context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.FeedItemSessionTimer = null;
context.JK.FeedItemSession = function($parentElement, options){
var logger = context.JK.logger;
var rest = new context.JK.Rest();
var $feedItem = $parentElement;
var $description = $('.description', $feedItem)
var $musicians = $('.musician-detail', $feedItem)
var $controls = $('.session-controls', $feedItem);
var $status = $('.session-status', $feedItem);
var playing = false;
var toggledOpen = false;
var musicSessionId = $feedItem.attr('data-music-session');
if(!$feedItem.is('.feed-entry')) {
throw "$parentElement must be a .feed-entry"
}
function startPlay() {
var img = $('.play-icon', $feedItem);
img.attr('src', '/assets/content/icon_pausebutton.png');
$controls.trigger('play.listenBroadcast');
playing = true;
}
function stopPlay() {
var img = $('.play-icon', $feedItem);
img.attr('src', '/assets/content/icon_playbutton.png');
$controls.trigger('pause.listenBroadcast');
playing = false;
}
function togglePlay() {
if(playing) {
$status.text('SESSION IN PROGRESS');
stopPlay();
}
else {
startPlay();
}
return false;
}
function toggleDetails() {
if(toggledOpen) {
$feedItem.css('height', $feedItem.height() + 'px')
$feedItem.animate({'height': $feedItem.data('original-max-height')}).promise().done(function() {
$feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
$musicians.hide();
$description.css('height', $description.data('original-height'));
$description.dotdotdot();
});
}
else {
$description.trigger('destroy.dot');
$description.data('original-height', $description.css('height')).css('height', 'auto');
$musicians.show();
$feedItem.animate({'max-height': '1000px'});
}
toggledOpen = !toggledOpen;
return false;
}
function stateChange(e, data) {
if(data.displayText) $status.text(data.displayText);
if(data.isEnd) stopPlay();
if(data.isSessionOver) {
$controls.removeClass('inprogress').addClass('ended')
}
}
function events() {
$('.details', $feedItem).click(toggleDetails);
$('.details-arrow', $feedItem).click(toggleDetails);
$('.play-button', $feedItem).click(togglePlay);
$controls.bind('statechange.listenBroadcast', stateChange);
}
function initialize() {
$('.timeago', $feedItem).timeago();
$('.dotdotdot', $feedItem).dotdotdot();
$controls.listenBroadcast();
context.JK.prettyPrintElements($('time.duration', $feedItem));
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$feedItem.data('original-max-height', $feedItem.css('height'));
events();
// this is a bit lame, because this is a singleton.
// the idea is, if there are any session widgets on the page, then we start ticking time
if(!context.JK.FeedItemSessionTimer) {
context.JK.FeedItemSessionTimer = setInterval(function() {
$.each($('.feed-entry.music-session-history-entry .inprogress .session-duration'), function(index, item) {
var $duration = $(item);
var createdAt = new Date(Number($duration.attr('data-created-at')) * 1000)
var millisElapsed = (new Date()).getTime() - createdAt.getTime();
console.log("createdAt, millis", createdAt, millisElapsed);
$duration.text(context.JK.prettyPrintSeconds( parseInt(millisElapsed / 1000)));
});
}, 333);
}
}
initialize();
return this;
}
})(window, jQuery);

View File

@ -108,9 +108,9 @@
for (var jj=0, ilen=mm['followings'].length; jj<ilen; jj++) {
aFollow = mm['followings'][jj];
followVals = {
user_id: aFollow.id,
user_id: aFollow.user_id,
musician_name: aFollow.name,
profile_url: '/client#/profile/' + aFollow.id,
profile_url: '/client#/profile/' + aFollow.user_id,
avatar_url: context.JK.resolveAvatarUrl(aFollow.photo_url),
};
follows += context.JK.fillTemplate(fTemplate, followVals);

View File

@ -487,7 +487,7 @@
* Load available drivers and populate the driver select box.
*/
function loadAudioDrivers() {
var drivers = jamClient.FTUEGetDevices();
var drivers = jamClient.FTUEGetDevices(false);
var driverOptionFunc = function (driverKey, index, list) {
optionsHtml += '<option title="' + drivers[driverKey] + '"value="' + driverKey + '">' +

View File

@ -49,20 +49,23 @@
});
var bandHtml = context.JK.fillTemplate(template, {
avatar_url: context.JK.resolveBandAvatarUrl(response.photo_url),
name: response.name,
location: response.location,
genres: genres.join(', '),
musicians: musicianHtml,
like_count: response.liker_count,
follower_count: response.follower_count,
recording_count: response.recording_count,
session_count: response.session_count,
biography: response.biography,
profile_url: "/client#/bandProfile/" + response.id
bandId: response.id,
avatar_url: context.JK.resolveBandAvatarUrl(response.photo_url),
name: response.name,
location: response.location,
genres: genres.join(', '),
musicians: musicianHtml,
like_count: response.liker_count,
follower_count: response.follower_count,
recording_count: response.recording_count,
session_count: response.session_count,
biography: response.biography,
followAction: response.is_following ? "removeBandFollowing" : "addBandFollowing",
profile_url: "/client#/bandProfile/" + response.id
});
$(hoverSelector).append('<h2>Band Detail</h2>' + bandHtml);
configureActionButtons(response);
})
.fail(function(xhr) {
if(xhr.status >= 500) {
@ -77,6 +80,20 @@
});
};
function configureActionButtons(band) {
var btnFollowSelector = "#btnFollow";
// if unauthenticated or authenticated user is viewing his own profile
if (!context.JK.currentUserId) {
$(btnFollowSelector, hoverSelector).hide();
}
else {
if (band.is_following) {
$(btnFollowSelector, hoverSelector).html('UNFOLLOW');
}
}
}
this.hideBubble = function() {
$(hoverSelector).hide();
};

View File

@ -56,12 +56,15 @@
location: response.location,
friend_count: response.friend_count,
follower_count: response.follower_count,
friendAction: response.is_friend ? "removeFanFriend" : (response.pending_friend_request ? "" : "sendFanFriendRequest"),
followAction: response.is_following ? "removeFanFollowing" : "addFanFollowing",
biography: response.biography,
followings: response.followings && response.followings.length > 0 ? followingHtml : "<tr><td>N/A</td></tr>",
profile_url: "/client#/profile/" + response.id
});
$(hoverSelector).append('<h2>Fan Detail</h2>' + fanHtml);
configureActionButtons(response);
})
.fail(function(xhr) {
if(xhr.status >= 500) {
@ -76,6 +79,33 @@
});
};
function configureActionButtons(user) {
var btnFriendSelector = "#btnFriend";
var btnFollowSelector = "#btnFollow";
if (!context.JK.currentUserId || context.JK.currentUserId === user.id) {
$(btnFriendSelector, hoverSelector).hide();
$(btnFollowSelector, hoverSelector).hide();
}
else {
if (user.is_friend) {
$(btnFriendSelector, hoverSelector).html('DISCONNECT');
}
if (user.is_following) {
$(btnFollowSelector, hoverSelector).html('UNFOLLOW');
$(btnFollowSelector, hoverSelector).click(function(evt) {
rest.removeFollowing(user.id);
});
}
if (user.pending_friend_request) {
$(btnFriendSelector, hoverSelector).hide();
}
}
}
this.hideBubble = function() {
$(hoverSelector).hide();
};

View File

@ -58,9 +58,15 @@
var sessionDisplayStyle = 'none';
var sessionId = '';
var joinDisplayStyle = 'none';
if (response.sessions !== undefined && response.sessions.length > 0) {
sessionDisplayStyle = 'block';
sessionId = response.sessions[0].id;
var session = response.sessions[0];
sessionId = session.id;
if (context.jamClient && session.musician_access) {
joinDisplayStyle = 'inline';
}
}
var musicianHtml = context.JK.fillTemplate(template, {
@ -74,13 +80,17 @@
recording_count: response.recording_count,
session_count: response.session_count,
session_display: sessionDisplayStyle,
session_id: sessionId,
join_display: joinDisplayStyle,
sessionId: sessionId,
friendAction: response.is_friend ? "removeMusicianFriend" : (response.pending_friend_request ? "" : "sendMusicianFriendRequest"),
followAction: response.is_following ? "removeMusicianFollowing" : "addMusicianFollowing",
biography: response.biography,
followings: response.followings && response.followings.length > 0 ? followingHtml : "<tr><td>N/A</td></tr>",
profile_url: "/client#/profile/" + response.id
});
$(hoverSelector).append('<h2>Musician Detail</h2>' + musicianHtml);
configureActionButtons(response);
})
.fail(function(xhr) {
if(xhr.status >= 500) {
@ -95,6 +105,28 @@
});
};
function configureActionButtons(user) {
var btnFriendSelector = "#btnFriend";
var btnFollowSelector = "#btnFollow";
// if unauthenticated or authenticated user is viewing his own profile
if (!context.JK.currentUserId || context.JK.currentUserId === user.id) {
$(btnFriendSelector, hoverSelector).hide();
$(btnFollowSelector, hoverSelector).hide();
}
else {
if (user.is_friend) {
$(btnFriendSelector, hoverSelector).html('DISCONNECT');
}
if (user.is_following) {
$(btnFollowSelector, hoverSelector).html('UNFOLLOW');
}
if (user.pending_friend_request) {
$(btnFriendSelector, hoverSelector).hide();
}
}
}
this.hideBubble = function() {
$(hoverSelector).hide();
};

View File

@ -8,6 +8,36 @@
var instrumentLogoMap = context.JK.getInstrumentIconMap24();
var hoverSelector = "#recording-hover";
function deDupTracks(recordedTracks) {
var tracks = [];
// this is replicated in recording.rb model
var t = {};
t.instrument_ids = []
$.each(recordedTracks, function(index, track) {
if (index > 0) {
if (recordedTracks[index-1].user.id !== recordedTracks[index].user.id) {
t = {};
t.instrument_ids = [];
t.instrument_ids.push(track.instrument_id);
t.user = track.user;
tracks.push(t);
}
else {
if ($.inArray(track.instrument_id, t.instrument_ids)) {
t.instrument_ids.push(track.instrument_id);
}
}
}
else {
t.user = track.user;
t.instrument_ids.push(track.instrument_id);
tracks.push(t);
}
});
return tracks;
}
this.showBubble = function() {
$(hoverSelector).css({left: position.left-100, top: position.top+20});
$(hoverSelector).fadeIn(500);
@ -18,9 +48,11 @@
var recording = response.recording;
$(hoverSelector).html('');
var deDupedTracks = deDupTracks(recording.recorded_tracks);
// musicians
var musicianHtml = '';
$.each(recording.recorded_tracks, function(index, val) {
$.each(deDupedTracks, function(index, val) {
var instrumentHtml = '';
var musician = val.user;
@ -28,7 +60,9 @@
musicianHtml += '<td width="75"><a href="#">' + musician.name + '</a></td>';
instrumentHtml = '<td><div class="nowrap">';
instrumentHtml += '<img src="' + instrumentLogoMap[val.instrument_id] + '" width="24" height="24" />&nbsp;';
$.each(val.instrument_ids, function(index, val) {
instrumentHtml += '<img src="' + instrumentLogoMap[val] + '" width="24" height="24" />&nbsp;&nbsp;';
})
instrumentHtml += '</div></td>';
musicianHtml += instrumentHtml;
@ -44,7 +78,7 @@
name: claimedRecording.name,
genre: claimedRecording.genre_id.toUpperCase(),
created_at: context.JK.formatDateTime(recording.created_at),
description: response.description,
description: response.description ? response.description : "",
play_count: recording.play_count,
comment_count: recording.comment_count,
like_count: recording.like_count,
@ -55,6 +89,7 @@
});
$(hoverSelector).append('<h2>Recording Detail</h2>' + recordingHtml);
toggleActionButtons();
})
.fail(function(xhr) {
if(xhr.status >= 500) {
@ -69,6 +104,13 @@
});
};
function toggleActionButtons() {
if (!context.JK.currentUserId) {
$("#btnLike", hoverSelector).hide();
$("#btnShare", hoverSelector).hide();
}
}
this.hideBubble = function() {
$(hoverSelector).hide();
};

View File

@ -50,6 +50,7 @@
});
$(hoverSelector).append('<h2>Session Detail</h2>' + sessionHtml);
toggleActionButtons();
})
.fail(function(xhr) {
if(xhr.status >= 500) {
@ -64,6 +65,13 @@
});
};
function toggleActionButtons() {
if (!context.JK.currentUserId) {
$("#btnLike", hoverSelector).hide();
$("#btnShare", hoverSelector).hide();
}
}
this.hideBubble = function() {
$(hoverSelector).hide();
};

View File

@ -451,7 +451,7 @@
});
}
function removeLike(options) {
function removeLike(likableId, options) {
var id = getId(options);
return $.ajax({
type: "DELETE",
@ -476,15 +476,13 @@
});
}
function removeFollowing(options) {
function removeFollowing(followableId, options) {
var id = getId(options);
return $.ajax({
type: "DELETE",
dataType: "json",
contentType: 'application/json',
url: "/api/users/" + id + "/followings",
data: JSON.stringify(options),
url: "/api/users/" + id + "/followings/" + followableId,
processData: false
});
}

View File

@ -198,12 +198,13 @@
* Generic error handler for Ajax calls.
*/
function ajaxError(jqXHR, textStatus, errorMessage) {
logger.error("Unexpected ajax error: " + textStatus);
if (jqXHR.status == 404) {
logger.error("Unexpected ajax error: " + textStatus + ", msg:" + errorMessage);
app.notify({title: "Oops!", text: "What you were looking for is gone now."});
}
else if (jqXHR.status = 422) {
logger.error("Unexpected ajax error: " + textStatus + ", msg: " + errorMessage + ", response: " + jqXHR.responseText);
// present a nicer message
try {
var text = "<ul>";
@ -231,6 +232,7 @@
}
}
else {
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
}
}
@ -291,6 +293,7 @@
if (jqXHR.status == 422) {
var errors = JSON.parse(jqXHR.responseText);
var $errors = context.JK.format_all_errors(errors);
console.log("$errors", $errors)
this.notify({title: title, text: $errors, icon_url: "/assets/content/icon_alert_big.png"})
}
else {

View File

@ -0,0 +1,416 @@
(function(context, $) {
"use strict";
/**
* when session ends
* - in chrome:
* onsuspend
* onpause
* onended
* - in firefox
* onstalled
* onplaying
* onsuspend
* onpause
* onended
*/
context.JK = context.JK || {};
// the purpose of this code is to simply the interaction between user and server
// it provides methods to call on user events (primarily play/pause), and will fire
// events (such as 'stream_gone'). This element does nothing with UI; only fires events for you to handle
// $parentElement: the parent element needs to contain one audio element; this will take control of it.
// pass in music_session as a 'data-music-session' attribute on the $parentElement
// this will also interact with any REST APIs needed to
context.JK.ListenBroadcast = function($parentElement, options){
var WAIT_FOR_BUFFER_TIMEOUT = 5000;
var RETRY_ATTEMPTS = 5; // we try 4 times, so the user will wait up until RETRY_ATTEMPTS * WAIT_FOR_BUFFER_TIMEOUTS
var logger = context.JK.logger;
var rest = context.JK.Rest();
var $parent = $parentElement;
var $audio = null;
var audioDomElement = null;
var musicSessionId = null;
var waitForBufferingTimeout = null;
var retryAttempts = 0;
var self = this;
var destroyed = false;
var PlayStateNone = 'none';
var PlayStateInitializing = 'initializing'; // user clicked play--nothing has happened yet
var PlayStateBuffering = 'buffering'; // user clicked play--the stream is being read, buffer is being built
var PlayStatePlaying = 'playing'; // we are playing
var PlayStateStalled = 'stalled'; // we were playing, but the stream is stalled
var PlayStateEnded = 'ended'; // we were playing, but the stream ended
var PlayStateRetrying = 'retrying_play'; // we are retrying to play.
var PlayStateFailedStart = 'failed_start'; // we could not start the stream. no more events are coming
var PlayStateFailedPlaying = 'failed_playing'; // failed while playing.
var PlayStateSessionOver = 'session_over'; // session is done
var PlayStateNetworkError = 'network_error'; // network error
var PlayStateServerError = 'server_error'; // server error
var PlayStateConcurrentStop = 'concurrent_stop'; // stopped by another play attempt
var playState = PlayStateNone; // tracks if the stream is actually playing
function play(e) {
if(e) {
e.preventDefault();
e.stopPropagation();
}
if(destroyed) return;
if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a play"
if(context.JK.ListenBroadcastCurrentlyPlaying) {
context.JK.ListenBroadcastCurrentlyPlaying.forcedPause();
}
context.JK.ListenBroadcastCurrentlyPlaying = self;
checkServer()
.done(function(response) {
audioDomElement.play();
retryAttempts = 0;
transition(PlayStateInitializing);
// keep this after transition, because any transition clears this timer
waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT);
})
}
function forcedPause() {
transition(PlayStateConcurrentStop);
pause();
}
function pause(e) {
if(e) {
e.preventDefault();
e.stopPropagation();
}
if(destroyed) return;
if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause"
transition(PlayStateNone);
recreateAudioElement();
}
function destroy() {
if(!destroyed) {
$audio.remove();
$audio = null;
audioDomElement = null;
destroyed = true;
}
}
// this is the only way to make audio stop buffering after the user hits pause
function recreateAudioElement() {
// jeez: http://stackoverflow.com/questions/4071872/html5-video-force-abort-of-buffering/13302599#13302599
var originalSource = $audio.html()
audioDomElement.pause();
audioDomElement.src = '';
audioDomElement.load();
var $parent = $audio.parent();
$audio.remove();
$parent.append('<audio preload="none"></audio>');
$audio = $('audio', $parent);
$audio.append(originalSource);
audioDomElement = $audio.get(0);
audioBind();
}
function clearBufferTimeout() {
if(waitForBufferingTimeout) {
clearTimeout (waitForBufferingTimeout);
waitForBufferingTimeout = null;
}
}
function transition(newState) {
logger.debug("transitioning from " + playState + " to " + newState);
playState = newState;
clearBufferTimeout();
if( playState == PlayStateNone ||
playState == PlayStateEnded ||
playState == PlayStateFailedStart ||
playState == PlayStateFailedPlaying ||
playState == PlayStateSessionOver ||
playState == PlayStateNetworkError ||
playState == PlayStateServerError ) {
context.JK.ListenBroadcastCurrentlyPlaying = null;
}
triggerStateChange();
}
function noBuffer() {
if(retryAttempts >= RETRY_ATTEMPTS) {
logger.debug("never received indication of buffering or playing");
transition(PlayStateFailedStart);
}
else {
retryAttempts++;
clearBufferTimeout();
// tell audio to stop/start, in attempt to retry
//audioDomElement.stop();
audioDomElement.load();
audioDomElement.play();
transition(PlayStateRetrying);
waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT);
}
}
function isNoisyEvent(eventName) {
if(playState == PlayStateNone) {
console.log("ignoring: " + eventName)
return true;
}
return false;
}
function onPlay() {
// this just confirms that the user tried to play
}
function onPlaying() {
if(isNoisyEvent('playing')) return;
logger.debug("playing", arguments);
transition(PlayStatePlaying);
}
function onPause() {
if(isNoisyEvent('pause')) return;
logger.debug("pause", arguments);
transition(PlayStateStalled);
}
function onError() {
if(isNoisyEvent('error')) return;
logger.debug("error", arguments);
if(playState == PlayStatePlaying || playState == PlayStateStalled) {
transition(PlayStateFailedPlaying);
}
else {
transition(PlayStateFailedStart);
}
}
function onEnded() {
if(isNoisyEvent('ended')) return;
logger.debug("ended", arguments);
transition(PlayStateEnded);
}
function onEmptied() {
if(isNoisyEvent('emptied')) return;
logger.debug("emptied", arguments);
}
function onAbort() {
if(isNoisyEvent('abort')) return;
logger.debug("abort", arguments);
}
function onStalled() {
if(isNoisyEvent('stalled')) return;
logger.debug("stalled", arguments);
// fires in Chrome on page load
if(playState == PlayStateBuffering || playState == PlayStatePlaying) {
transition(PlayStateStalled);
}
}
function onSuspend() {
if(isNoisyEvent('suspend')) return;
logger.debug("onsuspend", arguments);
// fires in FF on page load
transition(PlayStateStalled);
}
function onTimeUpdate() {
//logger.debug("ontimeupdate", arguments);
}
function onProgress() {
if(isNoisyEvent('progress')) return;
if(playState == PlayStateInitializing) {
transition(PlayStateBuffering);
}
}
function checkServer() {
return rest.getSession(musicSessionId)
.fail(function(jqXHR) {
if(jqXHR.status == 404 || jqXHR.status == 403) {
transition(PlayStateSessionOver);
destroy();
}
else if(jqXHR.status >= 500 && jqXHR.status <= 599){
transition(PlayStateServerError);
}
else {
transition(PlayStateNetworkError);
}
})
}
function triggerStateChange() {
var isEnd = false;
var isSessionOver = false;
var displayText = null;
var refresh = false;
if(playState == 'none') {
//$status.text('SESSION IN PROGRESS');
}
else if(playState == 'initializing') {
displayText = 'PREPARING AUDIO';
}
else if(playState == 'buffering') {
}
else if(playState == 'playing') {
displayText = 'SESSION IN PROGRESS';
}
else if(playState == 'stalled') {
displayText = 'RECONNECTING';
}
else if(playState == 'ended') {
displayText = 'STREAM DISCONNECTED';
isEnd = true;
refresh = true;
}
else if(playState == 'session_over') {
displayText = 'SESSION ENDED';
isEnd = true;
isSessionOver = true;
}
else if(playState == 'retrying_play') {
if(retryAttempts == 2) {
displayText = 'STILL TRYING, HANG ON';
}
}
else if(playState == 'failed_start') {
displayText = 'AUDIO DID NOT START';
isEnd = true;
}
else if(playState == 'failed_playing') {
displayText = 'AUDIO FAILED';
isEnd = true;
refresh = true;
}
else if(playState == 'network_error') {
displayText = 'STREAM DISCONNECTED';
isEnd = true;
}
else if(playState == 'server_error') {
displayText = 'SERVER ERROR';
isEnd = true;
}
else if(playState == 'concurrent_stop') {
displayText = 'SESSION IN PROGRESS';
isEnd = true;
}
else {
logger.error("unknown state: " + playState)
}
$parent.triggerHandler('statechange.listenBroadcast',
{
state: playState,
displayText: displayText,
isEnd: isEnd,
isSessionOver: isSessionOver
})
// we have cause to believe the session is done; check against the server
if(refresh) {
checkServer();
}
}
function audioBind() {
$audio.bind('play', onPlay);
$audio.bind('playing', onPlaying);
$audio.bind('error', onError);
$audio.bind('emptied', onEmptied);
$audio.bind('abort', onAbort);
$audio.bind('ended', onEnded);
$audio.bind('pause', onPause);
$audio.bind('suspend', onSuspend);
$audio.bind('stalled', onStalled);
$audio.bind('timeupdate', onTimeUpdate);
$audio.bind('progress', onProgress);
}
function initialize() {
musicSessionId = $parent.attr('data-music-session');
if(!musicSessionId) throw "data-music-session must be specified on $parentElement";
$audio = $('audio', $parent);
if($audio.length == 0) {
logger.debug("listen_broadcast: no audio element. deactivating")
return;
}
if($audio.length > 1) {
throw "more than one <audio> element found";
}
audioDomElement = $audio.get(0);
audioBind();
$parent.bind('play.listenBroadcast', play);
$parent.bind('pause.listenBroadcast', pause);
$parent.bind('destroy.listenBroadcast', destroy);
}
initialize();
this.play = play;
this.pause = pause;
this.forcedPause = forcedPause; // meant only be called by this code; not from external
return this;
}
context.JK.ListenBroadcastCurrentlyPlaying = null;
$.fn.listenBroadcast = function(options) {
new context.JK.ListenBroadcast(this, options);
}
})(window, jQuery);

View File

@ -456,6 +456,8 @@
function onHashChange(e, postFunction) {
if(currentHash == context.location.hash) { return }
if(resettingHash) {
resettingHash = false;
e.preventDefault();
@ -474,7 +476,7 @@
var accepted = screenEvent(currentScreen, 'beforeLeave', {screen:screen, hash: context.location.hash});
if(accepted === false) {
console.log("navigation to " + context.location.hash + " rejected by " + currentScreen);
resettingHash = true;
//resettingHash = true;
// reset the hash to where it just was
context.location.hash = currentHash;
}
@ -500,6 +502,8 @@
var accepted = screenEvent(previousScreen, 'beforeHide', data);
if(accepted === false) return;
logger.debug("Changing screen to " + currentScreen);
screenEvent(currentScreen, 'beforeShow', data);
// For now -- it seems we want it open always.

File diff suppressed because it is too large Load Diff

View File

@ -117,23 +117,23 @@
selector = isSidebar ? '#sidebar-search-results' : '#search-results';
$(selector).append(invitationSentHtml);
// wire up button click handler if search result is not a friend or the current use
// wire up button click handler if search result is not a friend or the current user
if (isSidebar) {
var $sidebar = $('div[layout=sidebar] div[user-id=' + val.id + ']');
if (!val.is_friend && val.id !== context.JK.currentUserId) {
$sidebar.find('.btn-connect-friend').click(sendFriendRequest);
if (val.is_friend || val.pending_friend_request || val.id === context.JK.currentUserId) {
// hide the button if the search result is already a friend
$sidebar.find('.btn-connect-friend').hide();
}
else {
// hide the button if the search result is already a friend
$sidebar.find('.btn-connect-friend').hide();
$sidebar.find('.btn-connect-friend').click(sendFriendRequest);
}
}
else {
if (!val.is_friend && val.id !== context.JK.currentUserId) {
$('div[user-id=' + val.id + ']').find('.btn-connect-friend').click(sendFriendRequest);
if (val.is_friend || val.pending_friend_request || val.id === context.JK.currentUserId) {
$('div[user-id=' + val.id + ']').find('.btn-connect-friend').hide();
}
else {
$('div[user-id=' + val.id + ']').find('.btn-connect-friend').hide();
$('div[user-id=' + val.id + ']').find('.btn-connect-friend').click(sendFriendRequest);
}
}
resultDivVisibility(val, isSidebar);

View File

@ -31,7 +31,7 @@
var playbackControls = null;
var promptLeave = false;
var rest = JK.Rest();
var rest = context.JK.Rest();
var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there.
@ -279,6 +279,10 @@
// a client can only be in one session at a time,
// and other parts of the code want to know at any certain times
// about the current session, if any (for example, reconnect logic)
if(context.JK.CurrentSessionModel) {
context.JK.CurrentSessionModel.unsubscribe();
}
context.JK.CurrentSessionModel = sessionModel = new context.JK.SessionModel(
context.JK.app,
context.JK.JamServer,
@ -432,6 +436,7 @@
});
}
// not leave session but leave screen
function beforeLeave(data) {
if(promptLeave) {
var leaveSessionWarningDialog = new context.JK.LeaveSessionWarningDialog(context.JK.app,
@ -441,6 +446,7 @@
app.layout.showDialog('leave-session-warning');
return false;
}
return true;
}
function beforeHide(data) {
@ -1209,6 +1215,15 @@
}
}
function sessionLeave(evt) {
evt.preventDefault();
promptLeave = false;
context.window.location = '/client#/home';
return false;
}
function sessionResync(evt) {
evt.preventDefault();
var response = context.jamClient.SessionAudioResync();
@ -1388,7 +1403,8 @@
}
function events() {
$('#session-resync').on('click', sessionResync);
$('#session-leave').on('click', sessionLeave);
$('#session-resync').on('click', sessionResync);
$('#session-contents').on("click", '[action="delete"]', deleteSession);
$('#tracks').on('click', 'div[control="mute"]', toggleMute);
$('#recording-start-stop').on('click', startStopRecording);

View File

@ -74,7 +74,6 @@
deferred
.done(function(){
logger.debug("calling jamClient.JoinSession");
if(!alreadyInSession()) {
// on temporary disconnect scenarios, a user may already be in a session when they enter this path
// so we avoid double counting
@ -101,12 +100,17 @@
logger.debug("SessionModel.leaveCurrentSession()");
// TODO - sessionChanged will be called with currentSession = null
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, refreshCurrentSession);
//server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
// leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long
// time, for that entire duration you'll still be sending voice data to the other users.
// this may be bad if someone decides to badmouth others in the left-session during this time
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
console.time('jamClient.LeaveSession');
client.LeaveSession({ sessionID: currentSessionId });
console.timeEnd('jamClient.LeaveSession');
leaveSessionRest(currentSessionId)
.done(function() {
sessionChanged();
@ -144,7 +148,7 @@
*/
function refreshCurrentSession() {
// XXX use backend instead: https://jamkazam.atlassian.net/browse/VRFS-854
logger.debug("SessionModel.refreshCurrentSession()");
logger.debug("SessionModel.refreshCurrentSession(" + currentSessionId +")");
refreshCurrentSessionRest(sessionChanged);
}
@ -172,6 +176,7 @@
if(sessionData != null) {
currentOrLastSession = sessionData;
}
currentSession = sessionData;
}
@ -190,7 +195,6 @@
$.ajax({
type: "GET",
url: url,
async: false,
success: function(response) {
sendClientParticipantChanges(currentSession, response);
updateCurrentSession(response);
@ -198,7 +202,7 @@
callback();
}
},
error: ajaxError,
error: function(jqXHR) { app.notifyServerError(jqXHR, "Unable to refresh session data") },
complete: function() {
requestingSessionRefresh = false;
if(pendingSessionRefresh) {
@ -301,7 +305,7 @@
logger.debug("successfully updated tracks on the server");
//refreshCurrentSession();
},
error: ajaxError
error: function(jqXHR) { app.notifyServerError(jqXHR, "Unable to refresh session data") }
});
}
@ -318,7 +322,7 @@
success: function(response) {
logger.debug("Successfully updated track info (" + JSON.stringify(data) + ")");
},
error: ajaxError
error: function(jqXHR) { app.notifyServerError(jqXHR, "Unable to refresh session data") }
});
}
@ -379,8 +383,7 @@
var url = "/api/participants/" + clientId;
return $.ajax({
type: "DELETE",
url: url,
async: true
url: url
});
}
@ -447,10 +450,6 @@
}
}
function ajaxError(jqXHR, textStatus, errorMessage) {
logger.error("Unexpected ajax error: " + textStatus);
}
// returns a deferred object
function findUserBy(finder) {
if(finder.clientId) {
@ -494,6 +493,12 @@
this.getCurrentOrLastSession = function() {
return currentOrLastSession;
};
this.unsubscribe = function() {
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, refreshCurrentSession);
server.unregisterMessageCallback(context.JK.MessageType.SESSION_DEPART, refreshCurrentSession);
}
};
})(window,jQuery);

View File

@ -12,13 +12,6 @@
var entity = null;
var remainingCap = 140 - 22 - 1; // 140 tweet max, minus 22 for link size, minus 1 for space
var textMap = {
LIVE_SESSION: "LIVE SESSION",
SESSION: "SESSION",
RECORDING: "RECORDING",
RECORDED: "RECORDED"
};
function showSpinner() {
$(dialogId + ' .dialog-inner').hide();
var spinner = $('<div class="spinner spinner-large"></div>')

View File

@ -754,7 +754,7 @@
function listenToSession(args) {
deleteNotification(args.notification_id);
context.JK.popExternalLink('/recordings/' + args.session_id);
context.JK.popExternalLink('/sessions/' + args.session_id);
}
function registerMusicianRecordingSaved() {

View File

@ -76,8 +76,8 @@
$.each(context._.keys(icon_map_base), function(index, instrumentId) {
var icon = icon_map_base[instrumentId];
instrumentIconMap24[instrumentId] = "../assets/content/icon_instrument_" + icon + "24.png";
instrumentIconMap45[instrumentId] = "../assets/content/icon_instrument_" + icon + "45.png";
instrumentIconMap24[instrumentId] = "/assets/content/icon_instrument_" + icon + "24.png";
instrumentIconMap45[instrumentId] = "/assets/content/icon_instrument_" + icon + "45.png";
});
/**
@ -224,7 +224,7 @@
}
context.JK.fetchUserNetworkOrServerFailure = function() {
app.notify({
JK.app.notify({
title: "Unable to communicate with server",
text: "Please try again later",
icon_url: "/assets/content/icon_alert_big.png"
@ -232,19 +232,23 @@
}
context.JK.entityNotFound = function(type) {
app.notify({
JK.app.notify({
title: type + " Deleted",
text: "The " + type + " no longer exists.",
icon_url: "/assets/content/icon_alert_big.png"
});
}
// Uber-simple templating
// var template = "Hey {name}";
// var vals = { name: "Jon" };
// _fillTemplate(template, vals);
// --> "Hey Jon"
//
// use context._.template for something more powerful
context.JK.fillTemplate = function(template, vals) {
if(template == null) throw 'null template in fillTemplate'
for(var val in vals)
template=template.replace(new RegExp('{'+val+'}','g'), vals[val]);
return template;
@ -282,6 +286,19 @@
return instrumentIconMap45["default"];
};
// meant to pass in a bunch of images with an instrument-id attribute on them.
context.JK.setInstrumentAssetPath = function($elements) {
$.each($elements, function(index, item) {
var $element = $(this);
if(!$element.is('img')) { throw "expected to receive an <img> in setInstrumentAssetPath" }
var instrument = $element.attr('instrument-id');
$element.attr('src', context.JK.getInstrumentIcon24(instrument))
})
}
context.JK.listInstruments = function(app, callback) {
var url = "/api/instruments";
$.ajax({
@ -361,6 +378,13 @@
return date.toLocaleTimeString();
}
context.JK.prettyPrintElements = function($elements) {
$.each($elements, function(index, item) {
var $item = $(item);
$item.text(context.JK.prettyPrintSeconds(parseInt($item.attr('duration'))))
});
}
context.JK.prettyPrintSeconds = function(seconds) {
// from: http://stackoverflow.com/questions/3733227/javascript-seconds-to-minutes-and-seconds

View File

@ -5,6 +5,7 @@
var rest = new JK.Rest();
var sessionId = null;
function like() {
rest.addSessionLike(sessionId, JK.currentUserId)
.done(function(response) {

View File

@ -8,6 +8,9 @@
//= require jquery.easydropdown
//= require jquery.carousel-1.1
//= require jquery.mousewheel-3.1.9
//= require jquery.timeago
//= require jquery.dotdotdot
//= require jquery.listenbroadcast
//= require AAA_Log
//= require AAC_underscore
//= require globals
@ -19,6 +22,8 @@
//= require web/videoDialog
//= require invitationDialog
//= require hoverMusician
//= require feed_item_recording
//= require feed_item_session
//= require hoverFan
//= require hoverBand
//= require hoverSession

View File

@ -4,7 +4,12 @@
context.JK = context.JK || {};
var welcomeRoot;
function initialize() {
welcomeRoot = $('.landing-content .wrapper .welcome')
$('#signup').click(function(e) {
context.JK.app.layout.showDialog('signup-dialog');
e.preventDefault();
@ -32,8 +37,19 @@
backOpacity:2
});
}
$.each($('.feed-entry'), function(index, feedEntry) {
var $feedEntry = $(this);
if($feedEntry.is('.recording-entry')) {
new context.JK.FeedItemRecording($feedEntry);
}
else {
new context.JK.FeedItemSession($feedEntry);
}
})
}
context.JK.WelcomePage = initialize;

View File

@ -37,5 +37,18 @@ $border: hsl(210, 50%, 45%);
$narrow-screen: 1000px; // 990 ? 1000 ?
$short-screen: 600px; // toolbars / chrome for x768
@mixin border_box_sizing {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
@mixin content_box_sizing {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
-ms-box-sizing: content-box;
box-sizing: content-box;
}

View File

@ -260,7 +260,7 @@ a.arrow-down {
}
.ftue-background {
background-image:url(../images/content/bkg_ftue.jpg);
background-image:url(../content/bkg_ftue.jpg);
background-repeat:no-repeat;
background-size:cover;
min-height:475px;

View File

@ -380,7 +380,7 @@ a.arrow-down {
}
.ftue-background {
background-image:url(../images/content/bkg_ftue.jpg);
background-image:url(../content/bkg_ftue.jpg);
background-repeat:no-repeat;
background-size:cover;
min-height:475px;
@ -476,6 +476,17 @@ ul.shortcuts {
padding:2px 8px !important;
}
a.arrow-down-orange {
margin-left:5px;
margin-top:4px;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #ED3618;
}
.whitespace {
white-space:normal;
}

View File

@ -1,5 +1,5 @@
@charset "UTF-8";
@import "compass/utilities/text/replacement";
@import "compass/typography/text/replacement";
.header {
height: 55px;

View File

@ -1,5 +1,16 @@
@import "client/common.css.scss";
#user-profile {
.profile-about-right {
textarea {
width:100%;
height:150px;
padding:0;
}
}
}
.profile-head {
}

View File

@ -122,6 +122,9 @@
float:left;
font-size:18px;
margin-top:12px;
.friend-name-label {
}
}
li .friend-status {

View File

@ -1,16 +1,10 @@
@import "bootstrap";
@import "client/common";
/* mixins, variables, etc. */
$grayMediumLight: #eaeaea;
@mixin box_sizing {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
/* universal */
html {
@ -115,7 +109,7 @@ footer {
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
@include border_box_sizing;
}
/* sidebar */
@ -184,7 +178,7 @@ input, textarea, select, .uneditable-input {
padding: 10px;
height: auto;
margin-bottom: 15px;
@include box_sizing;
@include border_box_sizing;
}
/** MSC: did this because firefox clips text if it's padding:4px on text input fields */

View File

@ -27,7 +27,7 @@ body.jam {
.dropdown-wrapper div.dropdown-container {
position: absolute;
position: relative;
height: 0;
left: -1px;
top: 100%;

View File

@ -1,9 +1,4 @@
@mixin box_sizing {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
@import "client/common.css.scss";
html {
min-height:100%;
@ -276,7 +271,7 @@ body.web {
input[type=text], input[type=password] {
margin-top:1px;
width:100%;
@include box_sizing;
@include border_box_sizing;
}
select {

View File

@ -55,4 +55,8 @@
position:absolute;
top:3px;
right:4px;
}
#btnPlayPause {
position: relative;
}

View File

@ -16,4 +16,8 @@
font-size:15px;
color:#cccc00;
margin-left:20px;
}*/
}*/
#btnPlayPause {
position: relative;
}

View File

@ -1,6 +1,7 @@
@charset "UTF-8";
@import "client/common.css.scss";
body.web {
.welcome {
.landing-tag {
@ -13,6 +14,202 @@ body.web {
}
}
.session-controls {
margin-top: 15px;
padding: 3px 5px 3px 10px;
width: 93%;
min-width: 200px;
background-color: #242323;
position: relative;
font-size: 13px;
text-align: center;
@include border_box_sizing;
height: 36px;
}
.recording-controls {
margin-top: 15px;
padding: 3px 5px 3px 10px;
width: 93%;
min-width: 200px;
background-color: #242323;
position: relative;
font-size: 13px;
text-align: center;
@include border_box_sizing;
height: 36px;
}
.feed-entry {
position:relative;
display:block;
white-space:nowrap;
min-width:700px;
border-bottom:solid 1px #666;
max-height:74px;
overflow:hidden;
margin-top:20px;
&:nth-child(1) {
margin-top:0;
}
/**
&.animate-down {
-webkit-transition: max-height height 2s;
transition: max-height height 2s;
-moz-transition: max-height height 2s;
-o-transition: max-height height 2s;
-ms-transition: max-height height 2s;
}
&.animate-up {
-webkit-transition: max-height height .4s;
transition: max-height height .4s;
-moz-transition: max-height height .4s;
-o-transition: max-height height .4s;
-ms-transition: max-height height .4s;
}
*/
.session-status {
float:left;
font-size:18px;
}
.inprogress {
.session-status {
font-size: 15px;
color: #cccc00;
margin-left:20px;
}
}
.recording-current {
top:8px;
}
.session-duration {
top:8px;
}
.recording-controls, .session-controls {
margin-top:0px;
margin-bottom:5px;
padding:8px 5px 8px 10px;
width:98%;
line-height:19px;
}
.session-controls {
&.ended {
background-color: #471f18;
.play-button {
display:none;
}
}
&.inprogress {
background-color: #4C742E;
}
}
.details {
color:#ED3618;
}
.avatar-small {
@include content_box_sizing;
margin-top:0px;
margin-left:0px;
}
.title {
font-size:16px;
color:#999;
margin-bottom:3px;
}
.artist {
font-size:12px;
font-weight:bold;
color:#ccc;
margin-bottom:10px;
overflow: hidden;
white-space: nowrap;
}
.name {
font-weight:bold;
font-size:14px;
}
.description {
font-size:12px;
white-space:normal;
line-height:14px;
overflow:hidden;
text-overflow:ellipsis;
height:60px;
}
.feed-details {
vertical-align:middle;
img {
vertical-align:middle;
}
}
.play-count {
margin-right:10px;
}
.comment-count {
margin-right:10px;
}
.like-count {
margin-right:10px;
}
.musicians {
margin-top:10px;
font-size:11px;
}
.musicians td {
border-right:none;
border-top:none;
padding:3px;
vertical-align:middle;
}
.musicians a {
color:#fff;
text-decoration:none;
}
.avatar-tiny {
float:left;
padding:1px;
width:24px;
height:24px;
background-color:#ed3618;
-webkit-border-radius:12px;
-moz-border-radius:12px;
border-radius:12px;
}
.avatar-tiny img {
width: 24px;
height: 24px;
-webkit-border-radius:12px;
-moz-border-radius:12px;
border-radius:12px;
}
}
.buzz {
width: 300px;
position:relative;
@ -33,18 +230,15 @@ body.web {
width: 750px;
position:relative;
top:-65px;
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.home-session-list {
width:100%;
height:400px;
border: solid 1px #ed3718;
background-color:#353535;
float:left;
overflow:hidden;
position:relative;
}
.latest-head {
@ -53,10 +247,14 @@ body.web {
height: 53px;
width:inherit;
}
.latest-body {
height: 100%;
width:100%;
padding-top:53px;
top:65px;
bottom:0;
position:absolute;
overflow-y:scroll;
@include border_box_sizing;
.session-list-wrapper {
padding: 0 20px;
@ -145,7 +343,7 @@ Version: 1.1
top :133px;
width :35px;
height :35px;
background : url(web/next_button.png) no-repeat center;
background : url(next_button.png) no-repeat center;
cursor :pointer ;
z-index :9999;
}
@ -157,7 +355,7 @@ Version: 1.1
top :133px;
width :35px;
height: 35px;
background : url(../images/web/prev_button.png);
background : url(prev_button.png);
cursor :pointer ;
z-index :9999;
}
@ -209,7 +407,7 @@ Version: 1.1
float :left ;
width :16px;
height :16px;
background : url(web/Bullet-White.png) no-repeat center ;
background : url(Bullet-White.png) no-repeat center ;
margin :5px;
float :left ;
cursor :pointer ;
@ -217,12 +415,12 @@ Version: 1.1
.carousel .buttonNav .bullet:hover
{
background : url(../images/web/Bullet-Black.png) no-repeat center ;
background : url(Bullet-Black.png) no-repeat center ;
}
.carousel .buttonNav .bulletActive
{
background : url(web/Bullet-Black.png) no-repeat center ;
background : url(Bullet-Black.png) no-repeat center ;
cursor :default ;
}
@ -254,7 +452,7 @@ Version: 1.1
.carousel .shadow .shadowLeft
{
background : url(web/shadowLeft.png) no-repeat;
background : url(shadowLeft.png) no-repeat;
width :100px;
height :82px;
@ -266,7 +464,7 @@ Version: 1.1
.carousel .shadow .shadowMiddle
{
height :82px;
background:url(web/shadowTile.png) repeat-x;
background:url(shadowTile.png) repeat-x;
/* fix png problems in ie */
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=../images/web/shadowTile.png, sizingmethod=scale)"; /* IE8 */
@ -278,7 +476,7 @@ Version: 1.1
{
width :100px;
height :82px;
background:url(web/shadowRight.png) no-repeat;
background:url(shadowRight.png) no-repeat;
/* fix png problems in ie */
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=../images/web/shadowRight.png, sizingmethod=scale)"; /* IE8 */

View File

@ -33,8 +33,7 @@ class ApiController < ApplicationController
def respond_with_model(model, options = {})
if model.errors.any?
response.status = :unprocessable_entity
respond_with model, layout: nil
respond_with model, status: :unprocessable_entity, layout: nil
else
status = options[:new] && options[:new] == true ? 201 : 200
redirect_on_success = options[:location]

View File

@ -24,9 +24,18 @@ class ApiMixesController < ApiController
def download
@mix = Mix.find(params[:id])
raise PermissionError, "You can only download a mix you didn't claim" unless @mix.can_download? current_user
raise PermissionError, "You can only download a mix you have claimed" unless @mix.can_download? current_user
redirect_to @mix.sign_url
@mix.current_user = current_user
@mix.update_download_count
@mix.valid?
if !@mix.errors.any?
@mix.save!
redirect_to @mix.sign_url
else
render :json => { :message => "download limit surpassed" }, :status => 404
end
end
private

View File

@ -38,7 +38,18 @@ class ApiRecordingsController < ApiController
def download
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.can_download?(current_user)
redirect_to @recorded_track.sign_url
@recorded_track.current_user = current_user
@recorded_track.update_download_count
@recorded_track.valid?
if !@recorded_track.errors.any?
@recorded_track.save!
redirect_to @recorded_track.sign_url
else
render :json => { :message => "download limit surpassed" }, :status => 404
end
end
def start

View File

@ -1,24 +1,86 @@
class ApiScoringController < ApiController
respond_to :json
# todo before_filter :api_signed_in_user
before_filter :api_signed_in_user
def work # clientid; returns another clientid
client_id = params[:clientid]
if client_id.nil? then render :json => {message: 'client_id not specified'}, :status => 400; return end
c = Connection.where(client_id: client_id).first
if c.nil? then render :json => {message: 'connection not found'}, :status => 404; return end
if !c.user.id.eql?(current_user.id) then render :json => {message: 'user does not own client_id'}, :status => 403; return end
def work # clientid returns another clientid
# todo clientid should come from the connection record of the signed in user
# todo this method is a stub
render :json => {:clientid => [params[:clientid]+'peer']}, :status => 200
#puts "ApiScoringController#work(#{client_id}) => locidispid #{c.locidispid}"
result_client_id = JamRuby::GetWork.get_work(c.locidispid)
#result_client_id = client_id+'peer'
render :json => {:clientid => result_client_id}, :status => 200
end
def worklist # clientid returns a list of clientids
# todo clientid should come from the connection record of the signed in user
def worklist # clientid; returns a list of clientid
client_id = params[:clientid]
if client_id.nil? then render :json => {message: 'client_id not specified'}, :status => 400; return end
c = Connection.where(client_id: client_id).first
if c.nil? then render :json => {message: 'connection not found'}, :status => 404; return end
if !c.user.id.eql?(current_user.id) then render :json => {message: 'user does not own client_id'}, :status => 403; return end
# todo this method is a stub
render :json => {:clientids => [params[:clientid]+'1_peer', params[:clientid]+'2_peer']}, :status => 200
result_client_ids = JamRuby::GetWork.get_work_list(c.locidispid)
#result_client_ids = [client_id+'peer1', client_id+'peer2']
render :json => {:clientids => result_client_ids}, :status => 200
end
def record # aclientid, aAddr, bclientid, bAddr, score returns nothing
# todo aclientid, aAddr should come from the connection record of the signed in user
# todo this method is a stub
aclient_id = params[:aclientid]
aip_address = params[:aAddr]
bclient_id = params[:bclientid]
bip_address = params[:bAddr]
score = params[:score]
if aclient_id.nil? then render :json => {message: 'aclient_id not specified'}, :status => 400; return end
if aip_address.nil? then render :json => {message: 'aAddr not specified'}, :status => 400; return end
if bclient_id.nil? then render :json => {message: 'bclient_id not specified'}, :status => 400; return end
if bip_address.nil? then render :json => {message: 'bAddr not specified'}, :status => 400; return end
if score.nil? then render :json => {message: 'score not specified'}, :status => 400; return end
aaddr = JamRuby::JamIsp.ip_to_num(aip_address)
if aaddr.nil? then render :json => {message: 'aAddr not valid ip_address'}, :status => 400; return end
baddr = JamRuby::JamIsp.ip_to_num(bip_address)
if baddr.nil? then render :json => {message: 'bAddr not valid ip_address'}, :status => 400; return end
if aaddr == baddr then render :json => {message: 'aAddr and bAddr are the same'}, :status => 403; return end
if !score.is_a? Numeric then render :json => {message: 'score not valid numeric'}, :status => 400; return end
aconn = Connection.where(client_id: aclient_id).first
if aconn.nil? then render :json => {message: 'a\'s session not found'}, :status => 404; return end
if aaddr != aconn.addr then render :json => {message: 'a\'s session addr does not match aAddr'}, :status => 403; return end
if !current_user.id.eql?(aconn.user.id) then render :json => {message: 'a\' session not owned by user'}, :status => 403; return end
bconn = Connection.where(client_id: bclient_id).first
if bconn.nil? then render :json => {message: 'b\'s session not found'}, :status => 404; return end
if baddr != bconn.addr then render :json => {message: 'b\'s session addr does not match bAddr'}, :status => 403; return end
if score < 0 or score > 999 then render :json => {message: 'score < 0 or score > 999'}, :status => 403; return end
aloc = JamRuby::GeoIpBlocks.lookup(aaddr)
aisp = JamRuby::JamIsp.lookup(aaddr)
if aisp.nil? or aloc.nil? then render :json => {message: 'a\'s location or isp not found'}, :status => 404; return end
alocidispid = aloc.locid*1000000+aisp.coid;
bloc = JamRuby::GeoIpBlocks.lookup(baddr)
bisp = JamRuby::JamIsp.lookup(baddr)
blocidispid = bloc.locid*1000000+bisp.coid
JamRuby::Score.createx(alocidispid, aclient_id, aaddr, blocidispid, bclient_id, baddr, score.ceil, nil)
render :json => {}, :status => 200
end
end
end

View File

@ -1,6 +1,6 @@
class ApiUsersController < ApiController
before_filter :api_signed_in_user, :except => [:create, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring]
before_filter :api_signed_in_user, :except => [:create, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring]
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete,
:liking_create, :liking_destroy, # likes
:following_create, :following_show, :following_destroy, # followings
@ -43,7 +43,7 @@ class ApiUsersController < ApiController
@user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments)
@user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
@user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
@user.biography = params[:biography] if params.has_key?(:biography)
@user.save
if @user.errors.any?
@ -202,7 +202,7 @@ class ApiUsersController < ApiController
end
def liking_destroy
User.delete_liking(params[:id], params[:target_entity_id])
User.delete_liking(params[:id], params[:likable_id])
respond_with responder: ApiResponder, :status => 204
end
@ -230,7 +230,7 @@ class ApiUsersController < ApiController
end
def following_destroy
User.delete_following(params[:id], params[:target_entity_id])
User.delete_following(params[:id], params[:followable_id])
respond_with responder: ApiResponder, :status => 204
end

View File

@ -11,6 +11,10 @@ class SpikesController < ApplicationController
def listen_in
if !current_user.admin
raise PermissionError "must be administrator"
end
#as_musician = false is the critical search criteria for sessions to list correctly
@music_sessions = MusicSession.index(current_user, as_musician: false)

View File

@ -194,6 +194,7 @@ class UsersController < ApplicationController
render :layout => "web"
end
# DO NOT USE CURRENT_USER IN THIS ROUTINE. IT'S CACHED FOR THE WHOLE SITE
def welcome
@slides = [
@ -205,12 +206,14 @@ class UsersController < ApplicationController
Slide.new("bands", "web/carousel_bands.jpg", "http://www.youtube.com/embed/eaYNM7p6Z5s")
]
@promo_buzz = Promotional.where(:type => 'JamRuby::PromoBuzz', :aasm_state => :active)
@promo_latest = Promotional.where(:type => 'JamRuby::PromoLatest', :aasm_state => :active);
if current_user
@promo_buzz = Promotional.where(:type => 'JamRuby::PromoBuzz', :aasm_state => :active).order(:position)
if Rails.application.config.use_promos_on_homepage
@promo_latest = Promotional.where(:type => 'JamRuby::PromoLatest', :aasm_state => :active).order(:position).limit(10)
else
@promo_latest, start = Feed.index(nil, limit: 10)
end
@welcome_page = true
render :layout => "web"
end

View File

@ -0,0 +1,32 @@
module AvatarHelper
def render_avatarable(avatarable)
image_tag resolve_avatarable(avatarable)
end
def resolve_user_avatar_url(user)
user.photo_url.nil? ? "shared/avatar_generic.png" : user.photo_url
end
def resolve_band_avatar_url(band)
band.photo_url.nil? ? "shared/avatar_generic_band.png" : band.photo_url
end
def resolve_avatarable(avatarable)
if avatarable.class == JamRuby::User || avatarable.class == JamRuby::MusicSessionUserHistory
resolve_user_avatar_url(avatarable)
elsif avatarable.class == JamRuby::Band
resolve_band_avatar_url(avatarable)
else
raise "unable to resolve avatarable #{avatarable}"
end
end
def resolve_avatarables(*avatarables)
avatarables.each do |avatarable|
return resolve_avatarable(avatarable) if avatarable
end
raise "at least one avatarable must be specified"
end
end

View File

@ -0,0 +1,53 @@
module FeedsHelper
def session_artist_name(music_session_history)
(music_session_history.band.nil? ? nil : music_session_history.band.name) || music_session_history.user.name
end
def session_avatar(music_session_history)
image_tag resolve_avatarables(music_session_history.band, music_session_history.user)
end
def session_duration(music_session_history, options)
if music_session_history.session_removed_at.nil?
duration(Time.now - music_session_history.created_at, options)
else
duration(music_session_history.session_removed_at - music_session_history.created_at, options)
end
end
def session_description(music_session_history)
music_session_history.description
end
# grabs 1st genre
def session_genre(music_session_history)
genres_array = music_session_history.genres.nil? ? [] : music_session_history.genres.split(MusicSessionHistory::SEPARATOR)
genre = genres_array.length > 0 ? Genre.find_by_id(genres_array[0]) : nil
genre.nil? ? '' : genre.description
end
def recording_artist_name(recording)
(recording.band.nil? ? nil : recording.band.name) || recording.candidate_claimed_recording.user.name
end
def recording_avatar(recording)
image_tag resolve_avatarables(recording.band, recording.owner)
end
def recording_duration(recording, options)
duration(recording.duration, options)
end
def recording_name(recording)
recording.candidate_claimed_recording.name
end
def recording_description(recording)
recording.candidate_claimed_recording.description
end
def recording_genre(recording)
recording.candidate_claimed_recording.genre.description
end
end

View File

@ -0,0 +1,20 @@
#http://brandonhilkert.com/blog/relative-timestamps-in-rails/
module TimeHelper
def timeago(time, options = {})
options[:class] = "#{options[:class]} timeago"
content_tag(
:time,
time.to_s,
options.merge(datetime: time.getutc.iso8601)
) if time
end
def duration(duration, options = {})
options[:class] = "#{options[:class]} duration"
content_tag(
:time,
duration.to_s,
options.merge(duration: duration.to_s)
) if duration
end
end

View File

@ -0,0 +1 @@
object @mix

View File

@ -23,6 +23,10 @@ if @search.musicians_text_search?
musician.friends?(current_user)
end
node :pending_friend_request do |musician|
musician.pending_friend_request?(current_user)
end
child :musician_instruments => :instruments do
attributes :instrument_id, :description, :proficiency_level, :priority
end
@ -30,7 +34,6 @@ if @search.musicians_text_search?
end
if @search.musicians_filter_search?
node :city do |user|
current_user.try(:location)
end
@ -50,6 +53,10 @@ if @search.musicians_filter_search?
@search.is_follower?(musician)
end
node :pending_friend_request do |musician|
musician.pending_friend_request?(current_user)
end
node :biography do |musician|
musician.biography.nil? ? "" : musician.biography
end
@ -112,6 +119,10 @@ if @search.fans_text_search?
node :is_friend do |fan|
fan.friends?(current_user)
end
node :pending_friend_request do |fan|
fan.pending_friend_request?(current_user)
end
}
end

View File

@ -1,5 +1,5 @@
<!-- Account Summary Dialog -->
<div layout="screen" layout-id="account/identity" class="screen secondary">
<div layout="screen" layout-id="account/identity" class="screen secondary" id="account-identity">
<!-- header -->
<div class="content-head">
<!-- icon -->

View File

@ -8,7 +8,7 @@
<span id="alert-message"></span>
<br clear="left" /><br />
<div class="left">
<a id="btn-alert-cancel" class="button-orange">CANCEL</a>
<a id="btn-alert-cancel" class="button-grey">CANCEL</a>
</div>
<div class="right">
<a id="btn-alert-ok" class="button-orange"></a>

View File

@ -5,25 +5,49 @@
<script type="text/javascript">
var rest = JK.Rest();
function addLike(bandId) {
rest.addLike({band_id: bandId})
// function addLike(bandId) {
// rest.addLike({band_id: bandId})
// .done(function(response) {
// $("#spnLikeCount", "#band-hover").html(parseInt($("#spnLikeCount", "#band-hover").text()) + 1);
// var $btnLikeSelector = $("#btnLike", "#band-hover");
// $btnLikeSelector.unbind("click");
// $btnLikeSelector.html("LIKED");
// });
// }
function addBandFollowing(bandId) {
rest.addFollowing({band_id: bandId})
.done(function(response) {
$("#spnLikeCount", "#band-hover").html(parseInt($("#spnLikeCount", "#band-hover").text()) + 1);
var $btnLikeSelector = $("#btnLike", "#band-hover");
$btnLikeSelector.unbind("click");
$btnLikeSelector.html("LIKED");
adjustBandFollowingCount(1);
var $btnFollowSelector = $("#btnFollow", "#band-hover");
$btnFollowSelector.unbind('click');
$btnFollowSelector.attr('onclick', '');
$btnFollowSelector.click(function() {
removeBandFollowing(bandId);
});
$btnFollowSelector.html("UNFOLLOW");
});
}
function addFollowing(bandId) {
rest.addFollowing({band_id: bandId})
function removeBandFollowing(bandId) {
rest.removeFollowing(bandId)
.done(function(response) {
$("#spnFollowCount", "#band-hover").html(parseInt($("#spnFollowCount", "#band-hover").text()) + 1);
var $btnFollowSelector = $("#btnFollow", "#band-hover");
$btnFollowSelector.unbind("click");
$btnFollowSelector.html("STOP FOLLOWING");
adjustBandFollowingCount(-1);
var $btnFollowSelector = $("#btnFollow", "#band-hover");
$btnFollowSelector.unbind('click');
$btnFollowSelector.attr('onclick', '');
$btnFollowSelector.click(function() {
addBandFollowing(bandId);
});
$btnFollowSelector.html("FOLLOW");
});
}
function adjustBandFollowingCount(value) {
$("#spnFollowCount", "#band-hover").text(parseInt($("#spnFollowCount", "#band-hover").text()) + value);
}
</script>
<script type="text/template" id="template-hover-band">
@ -33,7 +57,6 @@
<h3>{name}</h3>
<small>{location}<br /><strong>{genres}</strong></small><br />
<br clear="all" />
<span id="spnLikeCount">{like_count}</span> <img src="/assets/content/icon_like.png" align="absmiddle" />&nbsp;&nbsp;&nbsp;
<span id="spnFollowCount">{follower_count}</span> <img src="/assets/content/icon_followers.png" width="22" height="12" align="absmiddle" />&nbsp;&nbsp;&nbsp;
{recording_count} <img src="/assets/content/icon_recordings.png" width="12" height="13" align="absmiddle" />&nbsp;&nbsp;&nbsp;
{session_count} <img src="/assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle" />
@ -47,8 +70,8 @@
<br />
<div align="center">
<div class="left"><a href="{profile_url}" class="button-orange">PROFILE</a></div>
<div class="left"><a class="button-orange">LIKE</a></div>
<div class="left"><a class="button-orange">FOLLOW</a></div>
<div class="left" style="display:none;"><a class="button-orange">LIKE</a></div>
<div class="left"><a id="btnFollow" onclick="{followAction}('{bandId}');" class="button-orange">FOLLOW</a></div>
</div>
<br /><br />
</div>

View File

@ -5,19 +5,58 @@
<script type="text/javascript">
var rest = JK.Rest();
function addFollowing(userId) {
function addFanFollowing(userId) {
rest.addFollowing({user_id: userId})
.done(function(response) {
$("#spnFollowCount", "#fan-hover").html(parseInt($("#spnFollowCount", "#fan-hover").text()) + 1);
var $btnFollowSelector = $("#btnFollow", "#fan-hover");
$btnFollowSelector.unbind("click");
$btnFollowSelector.html("STOP FOLLOWING");
adjustFanFollowingCount(1);
var $btnFollowSelector = $("#btnFollow", "#fan-hover");
$btnFollowSelector.unbind('click');
$btnFollowSelector.attr('onclick', '');
$btnFollowSelector.click(function() {
removeFanFollowing(userId);
});
$btnFollowSelector.html("UNFOLLOW");
});
}
function sendFriendRequest(userId) {
rest.sendFriendRequest(JK.app, userId);
function removeFanFollowing(userId) {
rest.removeFollowing(userId)
.done(function(response) {
adjustFanFollowingCount(-1);
var $btnFollowSelector = $("#btnFollow", "#fan-hover");
$btnFollowSelector.unbind('click');
$btnFollowSelector.attr('onclick', '');
$btnFollowSelector.click(function() {
addFanFollowing(userId);
});
$btnFollowSelector.html("FOLLOW");
});
}
function adjustFanFollowingCount(value) {
$("#spnFollowCount", "#fan-hover").text(parseInt($("#spnFollowCount", "#fan-hover").text()) + value);
}
function sendFanFriendRequest(userId) {
rest.sendFriendRequest(JK.app, userId);
$("#btnFriend", "#fan-hover").hide();
}
function removeFanFriend(userId) {
rest.removeFriend({friend_id: userId})
.done(function() {
var $btnFriendSelector = $("#btnFriend", "#fan-hover");
$btnFriendSelector.unbind("click");
$btnFriendSelector.attr('onclick', '');
$btnFriendSelector.html("CONNECT");
$btnFriendSelector.click(function() {
sendFanFriendRequest(userId);
});
});
}
</script>
<script type="text/template" id="template-hover-fan">
@ -38,8 +77,8 @@
<br />
<div align="center">
<div class="left"><a href="{profile_url}" class="button-orange">PROFILE</a></div>
<div class="left"><a id="btnFriend" onclick="sendFriendRequest('{userId}');" class="button-orange">FRIEND</a></div>
<div class="left"><a id="btnFollow" onclick="addFollowing('{userId}');" class="button-orange">FOLLOW</a></div>
<div class="left"><a id="btnFriend" onclick="{friendAction}('{userId}');" class="button-orange">CONNECT</a></div>
<div class="left"><a id="btnFollow" onclick="{followAction}('{userId}');" class="button-orange">FOLLOW</a></div>
</div>
<br /><br />
</div>

View File

@ -4,29 +4,174 @@
<script type="text/javascript">
var rest = JK.Rest();
var logger = JK.logger;
function addLike(userId) {
rest.addLike({user_id: userId})
.done(function(response) {
$("#spnLikeCount", "#musician-hover").html(parseInt($("#spnLikeCount", "#musician-hover").text()) + 1);
var $btnLikeSelector = $("#btnLike", "#musician-hover");
$btnLikeSelector.unbind("click");
$btnLikeSelector.html("LIKED");
});
// function addLike(userId) {
// rest.addLike({user_id: userId})
// .done(function(response) {
// $("#spnLikeCount", "#musician-hover").text(parseInt($("#spnLikeCount", "#musician-hover").text()) + 1);
// var $btnLikeSelector = $("#btnLike", "#musician-hover");
// $btnLikeSelector.unbind("click");
// $btnLikeSelector.html("LIKED");
// });
// }
/*********** TODO: THE NEXT 6 FUNCTIONS ARE COPIED FROM sessionList.js. REFACTOR TO COMMON PLACE. *************/
function joinClick(sessionId) {
var hasInvitation = false;
var session = null;
// we need to do a real-time check of the session in case the settings have
// changed while the user was sitting on the Find Session screen
$.ajax({
type: "GET",
url: "/api/sessions/" + sessionId,
async: false,
success: function(response) {
session = response;
if ("invitations" in session) {
var invitation;
// user has invitations for this session
for (var i=0; i < session.invitations.length; i++) {
invitation = session.invitations[i];
// session contains an invitation for this user
if (invitation.receiver_id === JK.currentUserId) {
hasInvitation = true;
}
}
}
if (session) {
// if user has an invitation, always open terms and allow joining regardless of settings
if (hasInvitation) {
logger.debug("Found invitation for user " + JK.currentUserId + ", session " + sessionId);
openTerms(sessionId);
}
else {
if (session.musician_access) {
if (session.approval_required) {
openAlert(sessionId);
}
else {
openTerms(sessionId);
}
}
}
}
},
error: function(xhr, textStatus, errorMessage) {
logger.debug("xhr.status = " + xhr.status);
if (xhr.status === 404) {
sessionNotJoinableAlert();
}
else {
JK.app.notify(
{ title: "Unable to Join Session",
text: "There was an unexpected error while attempting to join the session."
},
{ no_cancel: true });
}
}
});
}
function addFollowing(userId) {
function openAlert(sessionId) {
var alertDialog = new JK.AlertDialog(JK.app, "YES",
"You must be approved to join this session. Would you like to send a request to join?",
sessionId, onCreateJoinRequest);
alertDialog.initialize();
JK.app.layout.showDialog('alert');
}
function sessionNotJoinableAlert() {
var alertDialog = new JK.AlertDialog(JK.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) {
JK.app.layout.closeDialog('alert');
}
);
alertDialog.initialize();
JK.app.layout.showDialog('alert');
}
function onCreateJoinRequest(sessionId) {
var joinRequest = {};
joinRequest.music_session = sessionId;
joinRequest.user = JK.currentUserId;
rest.createJoinRequest(joinRequest)
.done(function(response) {
}).error(JK.app.ajaxError);
JK.app.layout.closeDialog('alert');
}
function openTerms(sessionId) {
console.log("sessionId=%o", sessionId);
var termsDialog = new JK.TermsDialog(JK.app, sessionId, onTermsAccepted);
console.log("sessionId=%o", sessionId);
termsDialog.initialize();
JK.app.layout.showDialog('terms');
}
function onTermsAccepted(sessionId) {
window.location = '/client#/session/' + sessionId;
}
//////////////////////////////////////////////////////////////////////////////////////////
function addMusicianFollowing(userId) {
rest.addFollowing({user_id: userId})
.done(function(response) {
$("#spnFollowCount", "#musician-hover").html(parseInt($("#spnFollowCount", "#musician-hover").text()) + 1);
var $btnFollowSelector = $("#btnFollow", "#musician-hover");
$btnFollowSelector.unbind("click");
$btnFollowSelector.html("UNFOLLOW");
adjustMusicianFollowingCount(1);
var $btnFollowSelector = $("#btnFollow", "#musician-hover");
$btnFollowSelector.unbind('click');
$btnFollowSelector.attr('onclick', '');
$btnFollowSelector.click(function() {
removeMusicianFollowing(userId);
});
$btnFollowSelector.html("UNFOLLOW");
});
}
function sendFriendRequest(userId) {
function removeMusicianFollowing(userId) {
rest.removeFollowing(userId)
.done(function(response) {
adjustMusicianFollowingCount(-1);
var $btnFollowSelector = $("#btnFollow", "#musician-hover");
$btnFollowSelector.unbind('click');
$btnFollowSelector.attr('onclick', '');
$btnFollowSelector.click(function() {
addMusicianFollowing(userId);
});
$btnFollowSelector.html("FOLLOW");
});
}
function adjustMusicianFollowingCount(value) {
$("#spnFollowCount", "#musician-hover").html(parseInt($("#spnFollowCount", "#musician-hover").html()) + value);
}
function sendMusicianFriendRequest(userId) {
rest.sendFriendRequest(JK.app, userId);
$("#btnFriend", "#musician-hover").hide();
}
function removeMusicianFriend(userId) {
rest.removeFriend({friend_id: userId})
.done(function() {
var $btnFriendSelector = $("#btnFriend", "#musician-hover");
$btnFriendSelector.unbind("click");
$btnFriendSelector.attr('onclick', '');
$btnFriendSelector.html("CONNECT");
$btnFriendSelector.click(function() {
sendMusicianFriendRequest(userId);
});
});
}
</script>
@ -44,7 +189,11 @@
{session_count} <img src="/assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle" />
</div>
<br clear="all" /><br />
<div style="display:{session_display}" class="f12"><strong>IN SESSION &mdash; <a href="/client#/session/{session_id}">Click to Join</a></strong></div>
<div style="display:{session_display}" class="f12">
<strong>IN SESSION <span style="display:{join_display}">&mdash;</span>
<a id="btnJoinSession" onclick="joinClick('{sessionId}');" style="display:{join_display}">Click to Join</a>
</strong>
</div>
<br />
<div class="f11">{biography}</div><br />
<small><strong>FOLLOWING:</strong></small><br /><br />
@ -54,9 +203,9 @@
<br />
<div align="center">
<div class="left"><a href="{profile_url}" class="button-orange">PROFILE</a></div>
<div class="left"><a id="btnLike" onclick="addLike('{userId}');" class="button-orange">LIKE</a></div>
<div class="left"><a id="btnFriend" onclick="sendFriendRequest('{userId}');" class="button-orange">FRIEND</a></div>
<div class="left"><a id="btnFollow" onclick="addFollowing('{userId}');" class="button-orange">FOLLOW</a></div>
<div class="left" style="display:none;"><a id="btnLike" onclick="addLike('{userId}');" class="button-orange">LIKE</a></div>
<div class="left"><a id="btnFriend" onclick="{friendAction}('{userId}');" class="button-orange">CONNECT</a></div>
<div class="left"><a id="btnFollow" onclick="{followAction}('{userId}');" class="button-orange">FOLLOW</a></div>
</div>
<br /><br />
</div>

View File

@ -1,14 +1,14 @@
<!-- Session Update Invite Musicians Dialog -->
<div class="dialog invitemusicians-overlay" layout="dialog" layout-id="select-invites">
<div class="dialog invitemusicians-overlay" layout="dialog" layout-id="select-invites" style="min-height:180px;">
<div class="invitemusicians-inner" id="update-session-invite-musicians">
</div>
<br clear="all" />
<div class="left">
<a id="btn-cancel-invites" layout-action="close" class="button-grey">CANCEL</a>&nbsp;
</div>
<div class="right">
<a id="btn-save-invites" layout-action="close" class="button-orange">INVITE</a>
</div>
<div class="right">
<a id="btn-cancel-invites" layout-action="close" class="button-grey">CANCEL</a>&nbsp;
</div>
</div>
<!-- invite musician friend selector template -->

View File

@ -14,7 +14,7 @@
<a id="btn-accept-leave-session" layout-action="close" class="button-orange">OK</a>
</div>
<div class="right">
<a id="btn-cancel-leave-session" layout-action="close" class="button-orange">CANCEL</a>
<a id="btn-cancel-leave-session" layout-action="close" class="button-grey">CANCEL</a>
</div>
<br clear="all" />
</div>

View File

@ -1,5 +1,5 @@
<!-- Profile -->
<div layout="screen" layout-id="profile" layout-arg="id" class="screen secondary">
<div layout="screen" layout-id="profile" layout-arg="id" class="screen secondary" id="user-profile">
<div class="content-head">
<div class="content-icon">
<%= image_tag "content/icon_profile.png", :size => "19x19" %>
@ -61,8 +61,27 @@
<span id="profile-recording-stats"></span><br />
</div>
<div class="profile-about-right">
<p id="profile-biography"></p><br />
<div id="profile-instruments"></div>
<div class="no-bio">
<span>You have no bio to describe yourself as a musician. <a href="#" class="enter-bio">Enter one now!</a></span>
</div>
<div class="have-bio">
<p id="profile-biography"></p><a id="profile-edit-biography" class="button-orange right" href="#">Edit Bio</a>
</div>
<div class="update-biography">
<div class="field">
<textarea name="biography" class="user-biography"></textarea>
</div>
<br clear="left" /><br />
<div class="right">
<a id="btn-update-user-biography" layout-action="close" class="button-orange">OK</a>
</div>
<div class="right">
<a id="btn-cancel-user-biography" layout-action="close" class="button-grey">CANCEL</a>
</div>
<br clear="all" />
</div>
<br />
<div id="profile-instruments"></div>
</div>
<br clear="all" />
</div>

View File

@ -1,5 +1,5 @@
<!-- Actual Session Screen -->
<div layout="screen" layout-id="session" layout-arg="id" class="screen secondary">
<div layout="screen" layout-id="session" layout-arg="id" class="screen secondary" id="session-screen">
<div class="content-head">
<div class="content-icon">
<%= image_tag "shared/icon_session.png", {:height => 19, :width => 19} %>
@ -151,7 +151,7 @@
<img src="{avatar}"/>
</div>
<div class="track-instrument {preMasteredClass}">
<img src="/assets/{instrumentIcon}" width="45" height="45"/>
<img src="{instrumentIcon}" width="45" height="45"/>
</div>
<div class="track-gain" mixer-id="{mixerId}"></div>
<!--

View File

@ -173,7 +173,7 @@
<li class="{cssClass}">
<div class="avatar-small" user-id="{userId}" hoveraction="{hoverAction}"><img src="{avatar_url}" /></div>
<div class="friend-name" user-id="{userId}" hoveraction="{hoverAction}">
{userName}<br/>
<span class="friend-name-label">{userName}</span><br/>
<span class="friend-status">
{status} {extra_info}
</span>

View File

@ -13,7 +13,7 @@
<a id="btn-accept-terms" layout-action="close" class="button-orange">ACCEPT</a>
</div>
<div class="right">
<a id="btn-cancel-terms" layout-action="close" class="button-orange">CANCEL</a>
<a id="btn-cancel-terms" layout-action="close" class="button-grey">CANCEL</a>
</div>
<br clear="all" />
</div>

View File

@ -69,7 +69,6 @@
</div>
<%= render "clients/invitationDialog" %>
<%= render "clients/shareDialog" %>
<%= render "users/signupDialog" %>
<%= render "users/signinDialog" %>
<%= render "users/videoDialog" %>

Some files were not shown because too many files have changed in this diff Show More