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

This commit is contained in:
Bert Owen 2014-06-29 15:55:18 +02:00
commit 156095f3c8
81 changed files with 2097 additions and 895 deletions

View File

@ -182,3 +182,4 @@ rsvp_slots_prof_level.sql
add_file_name_music_notation.sql
change_scheduled_start_music_session.sql
music_sessions_iso_639_3.sql
discardable_claimed_recordings.sql

View File

@ -0,0 +1 @@
ALTER TABLE claimed_recordings ADD COLUMN discarded BOOLEAN DEFAULT FALSE;

View File

@ -1,9 +1,18 @@
module JamRuby
# To use this, be sure to add this in before block: stub_const("APP_CONFIG", web_config)
class Nav
def self.home(options ={})
"#{APP_CONFIG.external_root_url}/client#/home#{dialog(options)}"
"#{base_url}/home#{dialog(options)}"
end
def self.profile(user)
"#{base_url}/profile/#{user.id}"
end
def self.feed
"#{base_url}/feed"
end
def self.accept_friend_request_dialog(friend_request_id)
@ -12,6 +21,10 @@ module JamRuby
private
def self.base_url
"#{APP_CONFIG.external_root_url}/client#"
end
def self.dialog(options)
dialog = ''
if options[:dialog]

View File

@ -334,6 +334,7 @@ module JamRuby
# same, sorted by score. date seems irrelevant as these are active sessions. ams_init must be called
# first.
def self.ams_query(current_user, options = {})
session_id = options[:session_id]
client_id = options[:client_id]
genre = options[:genre]
lang = options[:lang]
@ -392,6 +393,7 @@ module JamRuby
query = query.limit(limit)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", keyword + ':*') unless keyword.blank?
if !day.blank? && !timezone_offset.blank?
@ -399,6 +401,9 @@ module JamRuby
day = Date.parse(day)
next_day = day + 1
timezone_offset = timezone_offset.to_i
if timezone_offset > 0
timezone_offset = "+#{timezone_offset}"
end
query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}'
AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'")
rescue Exception => e
@ -678,6 +683,10 @@ module JamRuby
music_session.approval_required
end
def music_notations
music_session.music_notations
end
def tick_track_changes
self.track_changes_counter += 1
self.save!(:validate => false)

View File

@ -8,9 +8,9 @@ module JamRuby
belongs_to :genre, :class_name => "JamRuby::Genre"
has_many :recorded_tracks, :through => :recording, :class_name => "JamRuby::RecordedTrack"
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession"
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "claimed_recording_id"
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "claimed_recording_id", :dependent => :destroy
has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => "claimed_recording_id", :dependent => :destroy
has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id'
has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id', :dependent => :destroy
validates :name, no_profanity: true, length: {minimum: 3, maximum: 64}, presence: true
validates :description, no_profanity: true, length: {maximum: 8000}
@ -39,9 +39,9 @@ module JamRuby
raise PermissionError, "user doesn't own claimed_recording"
end
self.name = params[:name] unless params[:name].nil?
self.description = params[:description] unless params[:description].nil?
self.genre = Genre.find(params[:genre]) unless params[:genre].nil?
self.name = params[:name]
self.description = params[:description]
self.genre = Genre.find_by_id(params[:genre]) unless params[:genre].nil?
self.is_public = params[:is_public] unless params[:is_public].nil?
save
end
@ -50,13 +50,10 @@ module JamRuby
if user != self.user
raise PermissionError, "user doesn't own claimed_recording"
end
# If this is the only copy, destroy the entire recording. Otherwise, just destroy this claimed_recording
if recording.claimed_recordings.count == 1
recording.destroy
else
self.destroy
end
ClaimedRecording.where(:id => id).update_all(:discarded => true )
recording.discard(user)
end

View File

@ -8,46 +8,16 @@ module JamRuby
end
def self.import_from_iso3166(file)
# File iso3166.csv
# Format:
# countrycode,countryname
# what this does is not replace the contents of the table, but rather update the specified rows with the names.
# any rows not specified have the countryname reset to be the same as the countrycode.
self.transaction do
self.connection.execute "update #{self.table_name} set countryname = countrycode"
File.open(file, 'r:ISO-8859-1') do |io|
saved_level = ActiveRecord::Base.logger ? ActiveRecord::Base.logger.level : 0
count = 0
ncols = 2
csv = ::CSV.new(io, {encoding: 'ISO-8859-1', headers: false})
csv.each do |row|
raise "file does not have expected number of columns (#{ncols}): #{row.length}" unless row.length == ncols
countrycode = row[0]
countryname = row[1]
stmt = "UPDATE #{self.table_name} SET countryname = #{MaxMindIsp.quote_value(countryname)} WHERE countrycode = #{MaxMindIsp.quote_value(countrycode)}"
self.connection.execute stmt
count += 1
if ActiveRecord::Base.logger and ActiveRecord::Base.logger.level < Logger::INFO
ActiveRecord::Base.logger.debug "... logging updates to #{self.table_name} suspended ..."
ActiveRecord::Base.logger.level = Logger::INFO
end
end
if ActiveRecord::Base.logger
ActiveRecord::Base.logger.level = saved_level
ActiveRecord::Base.logger.debug "updated #{count} records in #{self.table_name}"
end
end # file
end # transaction
self.delete_all
File.open(file, 'r:ISO-8859-1') do |io|
csv = ::CSV.new(io, {encoding: 'ISO-8859-1', headers: false})
csv.each do |row|
cc = self.new
cc.countrycode = row[0]
cc.countryname = row[1]
cc.save
end
end # file
end
end
end

View File

@ -43,6 +43,7 @@ module JamRuby
query = Feed.joins("LEFT OUTER JOIN recordings ON recordings.id = feeds.recording_id")
.joins("LEFT OUTER JOIN music_sessions ON music_sessions.id = feeds.music_session_id")
.limit(limit)
.where('recordings is NULL OR recordings.all_discarded = false') # remove any 'all_discarded recordings from the search results'
# handle sort
if sort == 'date'
@ -72,14 +73,14 @@ module JamRuby
end
if target_user
if target_user
if target_user != user.id
require_public_recordings = "claimed_recordings.is_public = TRUE AND"
require_public_sessions = "music_sessions.fan_access = TRUE AND"
end
query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND #{require_public_recordings} (claimed_recordings.user_id = '#{target_user}' OR (recordings.band_id IN (SELECT band_id FROM bands_musicians where user_id='#{target_user}')))")
query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.discarded = FALSE AND #{require_public_recordings} (claimed_recordings.user_id = '#{target_user}' OR (recordings.band_id IN (SELECT band_id FROM bands_musicians where user_id='#{target_user}')))")
query = query.joins("LEFT OUTER JOIN music_sessions_user_history ON music_sessions.id = music_sessions_user_history.music_session_id AND #{require_public_sessions} music_sessions_user_history.user_id = '#{target_user}'")
query = query.group("feeds.id, feeds.recording_id, feeds.music_session_id, feeds.created_at, feeds.updated_at, recordings.id, music_sessions.id")
if sort == 'plays'
@ -97,7 +98,7 @@ module JamRuby
require_public_sessions = "music_sessions.fan_access = TRUE AND"
end
query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND #{require_public_recordings} recordings.band_id = '#{target_band}'")
query = query.joins("LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.discarded = FALSE AND #{require_public_recordings} recordings.band_id = '#{target_band}'")
query = query.where("music_sessions IS NULL OR #{require_public_sessions} music_sessions.band_id = '#{target_band}'")
query = query.group("feeds.id, feeds.recording_id, feeds.music_session_id, feeds.created_at, feeds.updated_at, recordings.id, music_sessions.id")
if sort == 'plays'
@ -108,7 +109,7 @@ module JamRuby
query = query.where('recordings.id is NULL OR claimed_recordings.id IS NOT NULL')
#query = query.where('music_sessions.id is NULL OR music_sessions_user_history.id IS NOT NULL')
else
query = query.joins('LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.is_public = TRUE')
query = query.joins('LEFT OUTER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id AND claimed_recordings.discarded = FALSE AND claimed_recordings.is_public = TRUE')
query = query.joins("LEFT OUTER JOIN music_sessions_user_history ON music_sessions.id = music_sessions_user_history.music_session_id AND music_sessions.fan_access = TRUE")
query = query.group("feeds.id, feeds.recording_id, feeds.music_session_id, feeds.created_at, feeds.updated_at, recordings.id, music_sessions.id")
if sort == 'plays'

View File

@ -411,7 +411,7 @@ module JamRuby
def language_description
if self.language.blank?
self.language = "en"
self.language = "eng" # iso-639-3
end
iso639Details = ISO_639.find_by_code(self.language)
@ -605,6 +605,7 @@ module JamRuby
# same, sorted by score. date seems irrelevant as these are active sessions. sms_init must be called
# first.
def self.sms_query(current_user, options = {})
session_id = options[:session_id]
client_id = options[:client_id]
genre = options[:genre]
lang = options[:lang]
@ -663,6 +664,7 @@ module JamRuby
query = query.limit(limit)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", keyword + ':*') unless keyword.blank?
if !day.blank? && !timezone_offset.blank?
@ -670,9 +672,7 @@ module JamRuby
day = Date.parse(day)
next_day = day + 1
timezone_offset = timezone_offset.to_i
if timezone_offset == 0
timezone_offset = '' # no offset to specify in this case
elsif timezone_offset > 0
if timezone_offset > 0
timezone_offset = "+#{timezone_offset}"
end
query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}'

View File

@ -5,12 +5,12 @@ module JamRuby
attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, as: :admin
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id"
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id"
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
has_one :feed, :class_name => "JamRuby::Feed", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
@ -128,7 +128,7 @@ module JamRuby
unless self.users.exists?(user)
raise PermissionError, "user was not in this session"
end
recorded_tracks.where(:user_id=> user.id)
recorded_tracks.where(:user_id => user.id)
end
def has_access?(user)
@ -211,7 +211,7 @@ module JamRuby
# check if all recorded_tracks for this recording are discarded
if recorded_tracks.where('discard = false or discard is NULL').length == 0
self.all_discarded = true
self.all_discarded = true # the feed won't pick this up; also background cleanup will find these and whack them later
self.save(:validate => false)
end
@ -238,7 +238,7 @@ module JamRuby
.order('recorded_tracks.id')
.where('recorded_tracks.fully_uploaded = TRUE')
.where('recorded_tracks.id > ?', since)
.where('claimed_recordings.user_id = ?', user).limit(limit).each do |recorded_track|
.where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_track|
downloads.push(
{
:type => "recorded_track",
@ -258,7 +258,7 @@ module JamRuby
.order('mixes.id')
.where('mixes.completed_at IS NOT NULL')
.where('mixes.id > ?', since)
.where('claimed_recordings.user_id = ?', user)
.where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user)
.limit(limit).each do |mix|
downloads.push(
{

View File

@ -8,52 +8,17 @@ module JamRuby
end
def self.import_from_region_codes(file)
# File region_codes.csv
# Format:
# countrycode,region,regionname
# what this does is replace the contents of the table with the new data.
self.transaction do
self.connection.execute "delete from #{self.table_name}"
File.open(file, 'r:ISO-8859-1') do |io|
saved_level = ActiveRecord::Base.logger ? ActiveRecord::Base.logger.level : -1
count = 0
errors = 0
ncols = 3
csv = ::CSV.new(io, {encoding: 'ISO-8859-1', headers: false})
csv.each do |row|
raise "file does not have expected number of columns (#{ncols}): #{row.length}" unless row.length == ncols
countrycode = row[0]
region = row[1]
regionname = row[2]
if countrycode.length == 2 and region.length == 2 and regionname.length >= 2 and regionname.length <= 64
stmt = "INSERT INTO #{self.table_name} (countrycode, region, regionname) VALUES (#{self.connection.quote(countrycode)}, #{self.connection.quote(region)}, #{self.connection.quote(regionname)})"
self.connection.execute stmt
count += 1
if ActiveRecord::Base.logger and ActiveRecord::Base.logger.level < Logger::INFO
ActiveRecord::Base.logger.debug "... logging updates to #{self.table_name} suspended ..."
ActiveRecord::Base.logger.level = Logger::INFO
end
else
ActiveRecord::Base.logger.warn("bogus region_codes record '#{countrycode}', '#{region}', '#{regionname}'") if ActiveRecord::Base.logger
errors += 1
end
end
if ActiveRecord::Base.logger
ActiveRecord::Base.logger.level = saved_level
ActiveRecord::Base.logger.debug "inserted #{count} records into #{self.table_name}, #{errors} errors"
end
end # file
end # transaction
self.delete_all
File.open(file, 'r:ISO-8859-1') do |io|
csv = ::CSV.new(io, {encoding: 'ISO-8859-1', headers: false})
csv.each do |row|
rr = Region.new
rr.countrycode = row[0]
rr.region = row[1]
rr.regionname = row[2]
rr.save
end
end
end
end
end

View File

@ -58,7 +58,7 @@ FactoryGirl.define do
legal_terms true
genre JamRuby::Genre.first
band nil
language 'en'
language 'eng'
end

View File

@ -403,8 +403,8 @@ describe ActiveMusicSession do
let(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) }
let(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) }
let!(:music_session_1) { FactoryGirl.create(:active_music_session, :creator => creator_1, genre: Genre.find('african'), language: 'en', description: "Bunny Jumps" ) }
let!(:music_session_2) { FactoryGirl.create(:active_music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'es', description: "Play with us as we jam to beatles and bunnies") }
let!(:music_session_1) { FactoryGirl.create(:active_music_session, :creator => creator_1, genre: Genre.find('african'), language: 'eng', description: "Bunny Jumps" ) }
let!(:music_session_2) { FactoryGirl.create(:active_music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'spa', description: "Play with us as we jam to beatles and bunnies") }
let(:good_network_score) { 20 }
let(:fair_network_score) { 30 }
@ -459,14 +459,14 @@ describe ActiveMusicSession do
music_sessions.length.should == 2
# get only english
music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, lang: 'en')
music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, lang: 'eng')
music_sessions.length.should == 1
music_sessions[0].language.should == 'en'
music_sessions[0].language.should == 'eng'
# get only ambient
music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, lang: 'es')
music_sessions, user_search = ams(searcher_1, client_id: searcher_conn_1.client_id, lang: 'spa')
music_sessions.length.should == 1
music_sessions[0].language.should == 'es'
music_sessions[0].language.should == 'spa'
end
it "keyword" do

View File

@ -314,8 +314,8 @@ describe MusicSession do
let!(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) }
let!(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) }
let!(:music_session_1) { FactoryGirl.create(:music_session, creator: creator_1, genre: Genre.find('african'), language: 'en', description: "Bunny Jumps") }
let!(:music_session_2) { FactoryGirl.create(:music_session, creator: creator_2, genre: Genre.find('ambient'), language: 'es', description: "Play with us as we jam to beatles and bunnies") }
let!(:music_session_1) { FactoryGirl.create(:music_session, creator: creator_1, genre: Genre.find('african'), language: 'eng', description: "Bunny Jumps") }
let!(:music_session_2) { FactoryGirl.create(:music_session, creator: creator_2, genre: Genre.find('ambient'), language: 'spa', description: "Play with us as we jam to beatles and bunnies") }
let!(:music_session_3) { FactoryGirl.create(:music_session, creator: creator_3) }
let(:good_network_score) { 20 }
@ -399,8 +399,8 @@ describe MusicSession do
let(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) }
let(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) }
let!(:music_session_1) { FactoryGirl.create(:music_session, :creator => creator_1, genre: Genre.find('african'), language: 'en', description: "Bunny Jumps" ) }
let!(:music_session_2) { FactoryGirl.create(:music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'es', description: "Play with us as we jam to beatles and bunnies") }
let!(:music_session_1) { FactoryGirl.create(:music_session, :creator => creator_1, genre: Genre.find('african'), language: 'eng', description: "Bunny Jumps" ) }
let!(:music_session_2) { FactoryGirl.create(:music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'spa', description: "Play with us as we jam to beatles and bunnies") }
let(:good_network_score) { 20 }
let(:fair_network_score) { 30 }
@ -448,14 +448,14 @@ describe MusicSession do
music_sessions.length.should == 2
# get only english
music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, lang: 'en')
music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, lang: 'eng')
music_sessions.length.should == 1
music_sessions[0].language.should == 'en'
music_sessions[0].language.should == 'eng'
# get only ambient
music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, lang: 'es')
music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, lang: 'spa')
music_sessions.length.should == 1
music_sessions[0].language.should == 'es'
music_sessions[0].language.should == 'spa'
end
it "keyword" do
@ -498,7 +498,6 @@ describe MusicSession do
music_sessions, user_search = sms(searcher_1, client_id: searcher_conn_1.client_id, day: (Date.today + 1).to_s, timezone_offset: DateTime.now.offset.numerator)
music_sessions.length.should == 1
music_sessions[0].should == music_session_1
end
end
end

View File

@ -188,8 +188,12 @@ describe Recording do
expect { @claimed_recordign.discard(@user2) }.to raise_error
@claimed_recording = @recording.claim(@user2, "name2", "description2", @genre, true)
@claimed_recording.discard(@user2)
@claimed_recording.reload
@claimed_recording.discarded.should == true
@recording.recorded_tracks_for_user(@user2)[0].discard.should == true
@recording.reload
@recording.claimed_recordings.length.should == 1
@recording.claimed_recordings.length.should == 2
@recording.all_discarded.should == false
end
it "should destroy the entire recording if there was only one claimed_recording which is discarded" do
@ -199,8 +203,10 @@ describe Recording do
@genre = FactoryGirl.create(:genre)
@claimed_recording = @recording.claim(@user, "name", "description", @genre, true)
@claimed_recording.discard(@user)
expect { Recording.find(@recording.id) }.to raise_error
expect { ClaimedRecording.find(@claimed_recording.id) }.to raise_error
@claimed_recording.reload
@claimed_recording.discarded.should == true
@claimed_recording.recording.all_discarded.should == true
@recording.recorded_tracks_for_user(@user)[0].discard.should == true
end
it "should use the since parameter when restricting uploads" do

View File

@ -206,8 +206,8 @@
if(!region) return;
var option = $(nilOptionStr)
option.text(region)
option.attr("value", region)
option.text(region['name'])
option.attr("value", region['region'])
regionSelect.append(option)
})
@ -431,6 +431,7 @@
var cityElement = getCityElement();
updateRegionList(selectedCountry, getRegionElement());
updateCityList(selectedCountry, null, cityElement);
}
function updateRegionList(selectedCountry, regionElement) {
@ -445,7 +446,10 @@
api.getRegions({ country: selectedCountry })
.done(getRegionsDone)
.fail(app.ajaxError)
.error(function(err) {
regionElement.children().remove()
regionElement.append($(nilOptionStr).text(nilOptionText))
})
.always(function () {
loadingRegionsData = false;
})
@ -470,14 +474,18 @@
api.getCities({ country: selectedCountry, region: selectedRegion })
.done(getCitiesDone)
.fail(app.ajaxError)
.error(function(err) {
cityElement.children().remove()
cityElement.append($(nilOptionStr).text(nilOptionText))
})
.always(function () {
loadingCitiesData = false;
})
}
else {
cityElement.children().remove()
cityElement.append($(nilOptionStr).text(nilOptionText))
cityElement.children().remove();
cityElement.append($(nilOptionStr).text(nilOptionText));
context.JK.dropdown(cityElement);
}
}

View File

@ -309,19 +309,22 @@
$.each(response["regions"], function (index, region) {
if (!region) return;
var option = $(nilOptionStr);
option.text(region);
option.attr("value", region);
option.text(region['name']);
option.attr("value", region['region']);
if (initialRegion === region) {
if (initialRegion === region['region']) {
option.attr("selected", "selected");
}
$region.append(option);
});
context.JK.dropdown($region);
if (onRegionsLoaded) {
onRegionsLoaded();
}
}).error(function(err) {
context.JK.dropdown($region);
if (onRegionsLoaded) {
onRegionsLoaded();
}
@ -338,6 +341,7 @@
var nilOption = $(nilOptionStr);
nilOption.text(nilOptionText);
$city.append(nilOption);
nilOption.attr('selected','selected');
if (selectedCountry && selectedRegion) {
rest.getCities({'country': selectedCountry, 'region': selectedRegion}).done(function (response) {
@ -355,9 +359,13 @@
});
context.JK.dropdown($city);
}).error(function(err) {
context.JK.dropdown($city);
});
} else {
context.JK.dropdown($city);
}
}
}
function addInvitation(value, data) {
if ($('#selected-band-invitees div[user-id=' + data + ']').length === 0) {

View File

@ -9,6 +9,9 @@
var self = this;
var logger = context.JK.logger;
var $banner = null;
var $closeBtn = null;
var $yesBtn = null;
var $noBtn = null;
// you can also do
// * showAlert('title', 'text')
@ -26,6 +29,19 @@
return show(options);
}
function showYesNo(options) {
if (typeof options == 'string' || options instanceof String) {
if(arguments.length == 2) {
options = {title: options, html:arguments[1]}
}
else {
options = {html:options};
}
}
options.type = 'yes_no'
return show(options);
}
// responsible for updating the contents of the update dialog
// as well as registering for any event handlers
function show(options) {
@ -33,7 +49,12 @@
var html = options.html;
if(!options.title) {
options.title = 'alert'
if(options.type == 'alert') {
options.title = 'alert'
}
else if(options.type == 'yes_no') {
options.title = 'please confirm';
}
}
var $h1 = $banner.find('h1');
@ -50,11 +71,18 @@
throw "unable to show banner for empty message";
}
var $closeBtn = $banner.find('.close-btn');
if((options.type == "alert" && !options.buttons) || options.close) {
$closeBtn.show().click(function() {
var closeButtonText = 'CLOSE';
if(options.close !== null && typeof options.close == 'object') {
// extra styling options for close button
if(options.close.name) {
closeButtonText = options.close.name;
}
}
$closeBtn.show().text(closeButtonText).unbind('click').click(function() {
hide();
return false;
});
@ -63,6 +91,27 @@
$closeBtn.hide();
}
if(options.type == "yes_no") {
$yesBtn.show().unbind('click').click(function() {
if(options.yes) {
options.yes();
}
hide();
return false;
})
$noBtn.show().unbind('click').click(function() {
if(options.no) {
options.no();
}
hide();
return false;
})
}
else {
$yesBtn.hide();
$noBtn.hide();
}
if(options.buttons) {
var $buttons = $banner.find('.buttons')
context._.each(options.buttons, function(button) {
@ -86,6 +135,7 @@
return newContent;
}
function hide() {
$banner.hide();
$banner.find('.user-btn').remove();
@ -96,6 +146,10 @@
function initialize() {
$banner = $('#banner');
$closeBtn = $banner.find('.close-btn');
$yesBtn = $banner.find('.yes-btn');
$noBtn = $banner.find('.no-btn');
return self;
}
@ -104,6 +158,7 @@
initialize: initialize,
show: show,
showAlert: showAlert,
showYesNo: showYesNo,// shows Yes and Cancel button (confirmation dialog)
hide: hide
}

View File

@ -122,7 +122,7 @@
}
function showDialog() {
app.layout.showDialog('comment-dialog');
return app.layout.showDialog('comment-dialog');
}
function initialize() {

View File

@ -0,0 +1,170 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.EditRecordingDialog = function (app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var claimedRecordingId = null;
var $dialog = null;
var $form = null;
var $name = null;
var $description = null;
var $genre = null;
var $isPublic = null;
var $cancelBtn = null;
var $saveBtn = null;
var $deleteBtn = null;
var updating = false;
var deleting = false;
function resetForm() {
// remove all display errors
$dialog.find('.error-text').remove()
$dialog.find('.error').removeClass("error")
}
function beforeShow(args) {
claimedRecordingId = args.d1;
if(!claimedRecordingId) throw "claimedRecordingId must be specified";
resetForm();
rest.getClaimedRecording(claimedRecordingId)
.done(function(data) {
var name = data.name;
var description = data.description;
var is_public = data.is_public;
var genre_id = data.genre_id;
context.JK.GenreSelectorHelper.setSelectedGenres($genre.parent(), [genre_id]);
$name.val(name);
$description.val(description);
if(is_public) {
$isPublic.attr('checked', 'checked').iCheck('check')
}
else {
$isPublic.removeAttr('checked').iCheck('uncheck')
}
})
.fail(app.ajaxError)
}
function afterHide() {
}
function attemptUpdate() {
if(updating) return;
updating = true;
var name = $name.val();
var description = $description.val();
var genre = $genre.val();
var is_public = $isPublic.is(':checked');
rest.updateClaimedRecording({id: claimedRecordingId, name: name, description: description, is_public: is_public, genre: genre })
.done(function(updated) {
resetForm();
$dialog.triggerHandler('recording_updated', {id: claimedRecordingId, name: name, description: description, is_public: is_public, genre: genre})
app.layout.closeDialog('edit-recording');
})
.fail(function(jqXHR) {
if(jqXHR.status = 422) {
// highlight fields in error
resetForm();
var errors = JSON.parse(jqXHR.responseText);
var $name_errors = context.JK.format_errors('name', errors);
if ($name_errors) $name.closest('div.field').addClass('error').end().after($name_errors);
var $description_errors = context.JK.format_errors('description', errors);
if ($description_errors) $description.closest('div.field').addClass('error').end().after($description_errors);
var $genre_errors = context.JK.format_errors('genre', errors);
if ($genre_errors) $genre.closest('div.field').addClass('error').end().after($genre_errors);
var $is_public_errors = context.JK.format_errors('is_public', errors);
if ($is_public_errors) $isPublic.closest('div.field').addClass('error').end().after($is_public_errors);
}
else {
app.ajaxError(arguments);
}
})
.always(function() {
updating = false;
})
}
function attemptDelete() {
if(deleting) return;
deleting = true;
context.JK.Banner.showYesNo({
title: "Confirm Deletion",
html: "Are you sure you want to delete this recording?",
yes: function() {
rest.deleteClaimedRecording(claimedRecordingId)
.done(function() {
$dialog.triggerHandler('recording_deleted', {id: claimedRecordingId});
app.layout.closeDialog('edit-recording');
})
.fail(app.ajaxError)
.always(function() {
deleting = false;
})
},
no : function() {
context.JK.Banner.hide();
deleting = false;
}
})
}
function cancel() {
app.layout.closeDialog('edit-recording');
}
function events() {
$saveBtn.click(attemptUpdate);
$deleteBtn.click(attemptDelete);
$cancelBtn.click(cancel)
$form.submit(false);
}
function initialize() {
var dialogBindings = {
'beforeShow': beforeShow,
'afterHide': afterHide
};
app.bindDialog('edit-recording', dialogBindings);
$dialog = $('#edit-recording-dialog');
$form = $dialog.find('form');
$cancelBtn = $dialog.find('.cancel-btn');
$saveBtn = $dialog.find('.save-btn');
$deleteBtn = $dialog.find('.delete-btn');
$name = $dialog.find('input[name="name"]');
$description = $dialog.find('textarea[name="description"]');
$genre = $dialog.find('select[name=genre]');
$isPublic = $dialog.find('input[name=is_public]');
events();
context.JK.GenreSelectorHelper.render($genre.parent());
context.JK.checkbox($isPublic);
};
this.initialize = initialize;
}
})(window, jQuery);

View File

@ -5,407 +5,15 @@
context.JK.FeedScreen = function(app) {
var logger = context.JK.logger;
var rest = new context.JK.Rest();
var ui = new context.JK.UIHelper(JK.app);
var currentQuery = null;
var currentPage = 0;
var LIMIT = 20;
var $screen = null;
var $next = null;
var $scroller = null;
var $content = null;
var $noMoreFeeds = null;
var $refresh = null;
var $sortFeedBy = null;
var $includeDate = null;
var $includeType = null;
var next = null;
function defaultQuery() {
var query = { limit:LIMIT, page:currentPage};
if(next) {
query.since = next;
}
return query;
}
function buildQuery() {
currentQuery = defaultQuery();
// specify search criteria based on form
currentQuery.sort = $sortFeedBy.val();
currentQuery.time_range = $includeDate.val();
currentQuery.type = $includeType.val();
return currentQuery;
}
var feed = null;
function beforeShow(data) {
}
function afterShow(data) {
refresh();
}
function clearResults() {
currentPage = 0;
$content.empty(); // TODO: do we need to delete audio elements?
$noMoreFeeds.hide();
next = null;
}
function handleFeedResponse(response) {
next = response.next;
renderFeeds(response);
if(response.next == null) {
// if we less results than asked for, end searching
$scroller.infinitescroll('pause');
logger.debug("end of feeds")
if(currentPage > 0) {
$noMoreFeeds.show();
// there are bugs with infinitescroll not removing the 'loading'.
// it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove();
}
}
else {
currentPage++;
buildQuery();
registerInfiniteScroll();
}
}
function refresh() {
clearResults();
currentQuery = buildQuery();
rest.getFeeds(currentQuery)
.done(function(response) {
handleFeedResponse(response);
})
.fail(function(jqXHR) {
app.notifyServerError(jqXHR, 'Feed Unavailable')
})
}
function registerInfiniteScroll() {
$scroller.infinitescroll({
behavior: 'local',
navSelector: '#feedScreen .btn-next-pager',
nextSelector: '#feedScreen .btn-next-pager',
binder: $scroller,
dataType: 'json',
appendCallback: false,
prefill: false,
bufferPx:100,
loading: {
msg: $('<div class="infinite-scroll-loader">Loading ...</div>'),
img: '/assets/shared/spinner.gif'
},
path: function(page) {
return '/api/feeds?' + $.param(buildQuery());
}
},function(json, opts) {
handleFeedResponse(json);
});
$scroller.infinitescroll('resume');
}
function toggleSessionDetails() {
var $detailsLink = $(this);
var $feedItem = $detailsLink.closest('.feed-entry');
var $musicians = $feedItem.find('.musician-detail');
var $description = $feedItem.find('.description');
var toggledOpen = $detailsLink.data('toggledOpen');
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;
$detailsLink.data('toggledOpen', toggledOpen);
return false;
}
function startSessionPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.session-controls');
img.attr('src', '/assets/content/icon_pausebutton.png');
$controls.trigger('play.listenBroadcast');
$feedItem.data('playing', true);
}
function stopSessionPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.session-controls');
img.attr('src', '/assets/content/icon_playbutton.png');
$controls.trigger('pause.listenBroadcast');
$feedItem.data('playing', false);
}
function toggleSessionPlay() {
var $playLink = $(this);
var $feedItem = $playLink.closest('.feed-entry');
var $status = $feedItem.find('.session-status')
var playing = $feedItem.data('playing');
if(playing) {
$status.text('SESSION IN PROGRESS');
stopSessionPlay($feedItem);
}
else {
startSessionPlay($feedItem);
}
return false;
}
function stateChangeSession(e, data) {
var $controls = data.element;
var $feedItem = $controls.closest('.feed-entry');
var $status = $feedItem.find('.session-status');
if(data.displayText) $status.text(data.displayText);
if(data.isEnd) stopSessionPlay();
if(data.isSessionOver) {
$controls.removeClass('inprogress').addClass('ended')
}
}
function startRecordingPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.recording-controls');
img.attr('src', '/assets/content/icon_pausebutton.png');
$controls.trigger('play.listenRecording');
$feedItem.data('playing', true);
}
function stopRecordingPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.recording-controls');
img.attr('src', '/assets/content/icon_playbutton.png');
$controls.trigger('pause.listenRecording');
$feedItem.data('playing', false);
}
function toggleRecordingPlay() {
var $playLink = $(this);
var $feedItem = $playLink.closest('.feed-entry');
var playing = $feedItem.data('playing');
if(playing) {
stopRecordingPlay($feedItem);
}
else {
startRecordingPlay($feedItem);
}
return false;
}
function stateChangeRecording(e, data) {
var $controls = data.element;
var $feedItem = $controls.closest('.feed-entry');
var $sliderBar = $('.recording-position', $feedItem);
var $statusBar = $('.recording-status', $feedItem);
var $currentTime = $('.recording-current', $feedItem);
var $status = $('.status-text', $feedItem);
var $playButton = $('.play-button', $feedItem);
if(data.isEnd) stopRecordingPlay($feedItem);
if(data.isError) {
$sliderBar.hide();
$playButton.hide();
$currentTime.hide();
$statusBar.show();
$status.text(data.displayText);
}
}
function toggleRecordingDetails() {
var $detailsLink = $(this);
var $feedItem = $detailsLink.closest('.feed-entry');
var $musicians = $feedItem.find('.musician-detail');
var $description = $feedItem.find('.description');
var $name = $feedItem.find('.name');
var toggledOpen = $detailsLink.data('toggledOpen');
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();
$name.css('height', $name.data('original-height'));
$name.dotdotdot();
});
}
else {
$description.trigger('destroy.dot');
$description.data('original-height', $description.css('height')).css('height', 'auto');
$name.trigger('destroy.dot');
$name.data('original-height', $name.css('height')).css('height', 'auto');
$musicians.show();
$feedItem.animate({'max-height': '1000px'});
}
toggledOpen = !toggledOpen;
$detailsLink.data('toggledOpen', toggledOpen);
return false;
}
function renderFeeds(feeds) {
$.each(feeds.entries, function(i, feed) {
if(feed.type == 'music_session') {
var options = {
feed_item: feed,
status_class: feed['is_over?'] ? 'ended' : 'inprogress',
mount_class: feed['has_mount?'] ? 'has-mount' : 'no-mount'
}
var $feedItem = $(context._.template($('#template-feed-music-session').html(), options, {variable: 'data'}));
var $controls = $feedItem.find('.session-controls');
// do everything we can before we attach the item to the page
$('.timeago', $feedItem).timeago();
context.JK.prettyPrintElements($('.duration', $feedItem).show());
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$('.details', $feedItem).click(toggleSessionDetails);
$('.details-arrow', $feedItem).click(toggleSessionDetails);
$('.play-button', $feedItem).click(toggleSessionPlay);
if (!feed.session_removed_at)
{
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(feed.id, 'session');
});
}
else {
$('.btn-share', $feedItem).hide();
}
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
session_id: feed.id,
entity_type: 'session'
});
});
$('.btn-like', $feedItem).click(function() {
ui.addSessionLike(feed.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem))
});
// put the feed item on the page
renderFeed($feedItem);
// these routines need the item to have height to work (must be after renderFeed)
$controls.listenBroadcast();
$controls.bind('statechange.listenBroadcast', stateChangeSession);
$('.dotdotdot', $feedItem).dotdotdot();
$feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem);
}
else if(feed.type == 'recording') {
if(feed.claimed_recordings.length == 0) {
logger.error("a recording in the feed should always have one claimed_recording")
return;
}
var options = {
feed_item: feed,
candidate_claimed_recording: feed.claimed_recordings[0],
mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix',
}
var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'}));
var $controls = $feedItem.find('.recording-controls');
$('.timeago', $feedItem).timeago();
context.JK.prettyPrintElements($('.duration', $feedItem));
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$('.details', $feedItem).click(toggleRecordingDetails);
$('.details-arrow', $feedItem).click(toggleRecordingDetails);
$('.play-button', $feedItem).click(toggleRecordingPlay);
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(options.candidate_claimed_recording.id, 'recording');
});
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
recording_id: feed.id,
claimed_recording_id: options.candidate_claimed_recording.id,
entity_type: 'recording'
});
});
$('.btn-like', $feedItem).click(function() {
ui.addRecordingLike(feed.id, options.candidate_claimed_recording.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem));
});
// put the feed item on the page
renderFeed($feedItem);
// these routines need the item to have height to work (must be after renderFeed)
$controls.listenRecording({recordingId: feed.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
$controls.bind('statechange.listenRecording', stateChangeRecording);
$('.dotdotdot', $feedItem).dotdotdot();
$feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem);
}
else {
logger.warn("skipping feed type: " + feed.type);
}
context.JK.bindProfileClickEvents();
});
}
function renderFeed(feed) {
$content.append(feed);
}
function search() {
logger.debug("Searching for feeds...");
refresh();
return false;
}
function events() {
$refresh.on("click", search);
$sortFeedBy.on('change', search);
$includeDate.on('change', search);
$includeType.on('change', search);
feed.refresh();
}
function initialize() {
@ -415,21 +23,17 @@
};
app.bindScreen('feed', screenBindings);
$screen = $('[layout-id="feed"]');
$scroller = $screen.find('.content-body-scroller');
$content = $screen.find('.feed-content');
$noMoreFeeds = $('#end-of-feeds-list');
$refresh = $screen.find('#btn-refresh-feed');
$sortFeedBy = $screen.find('#feed_order_by');
$includeDate = $screen.find('#feed_date');
$includeType = $screen.find('#feed_show');
var $screen = $('[layout-id="feed"]');
var $scroller = $screen.find('.content-body-scroller');
var $content = $screen.find('.feed-content');
var $noMoreFeeds = $('#end-of-feeds-list');
var $refresh = $screen.find('.btn-refresh-entries');
var $sortFeedBy = $screen.find('#feed_order_by');
var $includeDate = $screen.find('#feed_date');
var $includeType = $screen.find('#feed_show');
// set default search criteria
$sortFeedBy.val('date')
$includeDate.val('month')
$includeType.val('all')
events();
feed = new context.JK.Feed(app);
feed.initialize($screen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType);
}
this.initialize = initialize;

View File

@ -0,0 +1,586 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.Feed = function (app) {
var logger = context.JK.logger;
var rest = new context.JK.Rest();
var EVENTS = context.JK.EVENTS;
var ui = new context.JK.UIHelper(JK.app);
var userId = null;
var currentQuery = null;
var currentPage = 0;
var LIMIT = 20;
var $next = null;
var $screen = null;
var $scroller = null;
var $content = null;
var $noMoreFeeds = null;
var $refresh = null;
var $sortFeedBy = null;
var $includeDate = null;
var $includeType = null;
var next = null;
function defaultQuery() {
var query = { limit:LIMIT, page:currentPage};
if(next) {
query.since = next;
}
if(userId) {
query.user = userId;
}
return query;
}
function buildQuery() {
currentQuery = defaultQuery();
// specify search criteria based on form
currentQuery.sort = $sortFeedBy.val();
currentQuery.time_range = $includeDate.val();
currentQuery.type = $includeType.val();
return currentQuery;
}
function clearResults() {
currentPage = 0;
$content.empty(); // TODO: do we need to delete audio elements?
$noMoreFeeds.hide();
next = null;
}
function handleFeedResponse(response) {
next = response.next;
renderFeeds(response);
if(response.next == null) {
// if we less results than asked for, end searching
$scroller.infinitescroll('pause');
logger.debug("end of feeds")
if(currentPage == 0 && response.entries.length == 0) {
$content.append("<div class='no-feed-msg'>This user has no history.</div>") ;
}
if(currentPage > 0) {
$noMoreFeeds.show();
// there are bugs with infinitescroll not removing the 'loading'.
// it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove();
}
}
else {
currentPage++;
buildQuery();
registerInfiniteScroll();
}
}
function setUser(_userId) {
userId = _userId;
}
function refresh() {
clearResults();
currentQuery = buildQuery();
rest.getFeeds(currentQuery)
.done(function(response) {
handleFeedResponse(response);
})
.fail(function(jqXHR) {
app.notifyServerError(jqXHR, 'Feed Unavailable')
})
}
function registerInfiniteScroll() {
$scroller.infinitescroll({
behavior: 'local',
navSelector: '#feedScreen .btn-next-pager',
nextSelector: '#feedScreen .btn-next-pager',
binder: $scroller,
dataType: 'json',
appendCallback: false,
prefill: false,
bufferPx:100,
loading: {
msg: $('<div class="infinite-scroll-loader">Loading ...</div>'),
img: '/assets/shared/spinner.gif'
},
path: function(page) {
return '/api/feeds?' + $.param(buildQuery());
}
},function(json, opts) {
handleFeedResponse(json);
});
$scroller.infinitescroll('resume');
}
function toggleSessionDetails() {
var $detailsLink = $(this);
var $feedItem = $detailsLink.closest('.feed-entry');
var $musicians = $feedItem.find('.musician-detail');
var $description = $feedItem.find('.description');
var toggledOpen = $detailsLink.data('toggledOpen');
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;
$detailsLink.data('toggledOpen', toggledOpen);
return false;
}
function startSessionPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.session-controls');
img.attr('src', '/assets/content/icon_pausebutton.png');
$controls.trigger('play.listenBroadcast');
$feedItem.data('playing', true);
}
function stopSessionPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.session-controls');
img.attr('src', '/assets/content/icon_playbutton.png');
$controls.trigger('pause.listenBroadcast');
$feedItem.data('playing', false);
}
function toggleSessionPlay() {
var $playLink = $(this);
var $feedItem = $playLink.closest('.feed-entry');
var $status = $feedItem.find('.session-status')
var playing = $feedItem.data('playing');
if(playing) {
$status.text('SESSION IN PROGRESS');
stopSessionPlay($feedItem);
}
else {
startSessionPlay($feedItem);
}
return false;
}
function stateChangeSession(e, data) {
var $controls = data.element;
var $feedItem = $controls.closest('.feed-entry');
var $status = $feedItem.find('.session-status');
if(data.displayText) $status.text(data.displayText);
if(data.isEnd) stopSessionPlay();
if(data.isSessionOver) {
$controls.removeClass('inprogress').addClass('ended')
}
}
function startRecordingPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.recording-controls');
img.attr('src', '/assets/content/icon_pausebutton.png');
$controls.trigger('play.listenRecording');
$feedItem.data('playing', true);
}
function stopRecordingPlay($feedItem) {
var img = $('.play-icon', $feedItem);
var $controls = $feedItem.find('.recording-controls');
img.attr('src', '/assets/content/icon_playbutton.png');
$controls.trigger('pause.listenRecording');
$feedItem.data('playing', false);
}
function toggleRecordingPlay() {
var $playLink = $(this);
var $feedItem = $playLink.closest('.feed-entry');
var playing = $feedItem.data('playing');
if(playing) {
stopRecordingPlay($feedItem);
}
else {
startRecordingPlay($feedItem);
}
return false;
}
function isOwner() {
return userId == context.JK.currentUserId;
}
function obtainCandidate(recording) {
if(isOwner()) {
var candidate = null;
context._.each(recording.claimed_recordings, function(claimedRecording) {
if(claimedRecording.user_id == context.JK.currentUserId) {
candidate = claimedRecording;
return false;
}
})
if(!candidate) throw "unable to find candidate claimed recording, yet we can see this recording. server error..."
return candidate;
}
else {
return recording.claimed_recordings[0]
}
}
function toggleOpen($feedItem, $name, $description, $musicians) {
$description.trigger('destroy.dot');
$description.data('original-height', $description.css('height')).css('height', 'auto');
$name.trigger('destroy.dot');
$name.data('original-height', $name.css('height')).css('height', 'auto');
$musicians.show();
$feedItem.animate({'max-height': '1000px'});
}
function toggleClose($feedItem, $name, $description, $musicians, immediate) {
$feedItem.css('height', $feedItem.height() + 'px')
$feedItem.animate({'height': $feedItem.data('original-max-height')}, immediate ? 0 : 400).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();
$name.css('height', $name.data('original-height'));
$name.dotdotdot();
});
}
function stateChangeRecording(e, data) {
var $controls = data.element;
var $feedItem = $controls.closest('.feed-entry');
var $sliderBar = $('.recording-position', $feedItem);
var $statusBar = $('.recording-status', $feedItem);
var $currentTime = $('.recording-current', $feedItem);
var $status = $('.status-text', $feedItem);
var $playButton = $('.play-button', $feedItem);
if(data.isEnd) stopRecordingPlay($feedItem);
if(data.isError) {
$sliderBar.hide();
$playButton.hide();
$currentTime.hide();
$statusBar.show();
$status.text(data.displayText);
}
}
function toggleRecordingDetails() {
var $detailsLink = $(this);
var $feedItem = $detailsLink.closest('.feed-entry');
var $musicians = $feedItem.find('.musician-detail');
var $description = $feedItem.find('.description');
var $name = $feedItem.find('.name');
var toggledOpen = $detailsLink.data('toggledOpen');
if(toggledOpen) {
toggleClose($feedItem, $name, $description, $musicians)
}
else {
toggleOpen($feedItem, $name, $description, $musicians)
}
toggledOpen = !toggledOpen;
$detailsLink.data('toggledOpen', toggledOpen);
return false;
}
function updateRecordingName($feedEntry, name) {
$feedEntry.find('.name-text').text(name);
}
function updateRecordingDescription($feedEntry, description) {
$feedEntry.find('.description').text(description);
}
function updateIsPublic($feedEntry, isPublic) {
var $isPrivate = $feedEntry.find('.is_private')
if(isPublic) {
$isPrivate.removeClass('enabled')
}
else {
$isPrivate.addClass('enabled')
}
}
function updateComments($feedEntry, comments) {
$feedEntry.find('.comments').html(comments)
}
function updatePlays($feedEntry, plays) {
$feedEntry.find('.plays').html(plays);
}
function updateLikes($feedEntry, likes) {
$feedEntry.find('.likes').html(likes);
}
function updateStats($feedEntry) {
if($feedEntry.is('.recording-entry')) {
var id = $feedEntry.attr('data-claimed-recording-id');
rest.getClaimedRecording(id)
.done(function(claimedRecording) {
updateComments($feedEntry, claimedRecording.recording.comment_count);
updateLikes($feedEntry, claimedRecording.recording.like_count);
updatePlays($feedEntry, claimedRecording.recording.play_count);
})
.fail(app.ajaxError)
}
else {
var id = $feedEntry.attr('data-music-session');
rest.getSessionHistory(id)
.done(function(music_session) {
updateComments($feedEntry, music_session.comment_count);
updateLikes($feedEntry, music_session.like_count);
updatePlays($feedEntry, music_session.play_count);
})
.fail(app.ajaxError)
}
}
function updateGenre($feedEntry, genre) {
$feedEntry.find('.genre').text(context.JK.GenreSelectorHelper.getNameForId(genre));
}
function renderFeeds(feeds) {
$.each(feeds.entries, function(i, feed) {
if(feed.type == 'music_session') {
var options = {
feed_item: feed,
status_class: feed['is_over?'] ? 'ended' : 'inprogress',
mount_class: feed['has_mount?'] ? 'has-mount' : 'no-mount'
}
var $feedItem = $(context._.template($('#template-feed-music-session').html(), options, {variable: 'data'}));
var $controls = $feedItem.find('.session-controls');
// do everything we can before we attach the item to the page
$('.timeago', $feedItem).timeago();
context.JK.prettyPrintElements($('.duration', $feedItem).show());
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$('.details', $feedItem).click(toggleSessionDetails);
$('.details-arrow', $feedItem).click(toggleSessionDetails);
$('.play-button', $feedItem).click(toggleSessionPlay);
if (!feed.session_removed_at)
{
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(feed.id, 'session');
});
}
else {
$('.btn-share', $feedItem).hide();
}
$('.btn-comment', $feedItem).click(function() {
var result = ui.launchCommentDialog({
session_id: feed.id,
entity_type: 'session'
}).one(EVENTS.DIALOG_CLOSED, function() {
updateStats($feedItem);
})
});
$('.btn-like', $feedItem).click(function() {
ui.addSessionLike(feed.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem))
});
// put the feed item on the page
renderFeed($feedItem);
// these routines need the item to have height to work (must be after renderFeed)
$controls.listenBroadcast();
$controls.bind('statechange.listenBroadcast', stateChangeSession);
$('.dotdotdot', $feedItem).dotdotdot();
$feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem);
}
else if(feed.type == 'recording') {
if(feed.claimed_recordings.length == 0) {
logger.error("a recording in the feed should always have one claimed_recording")
return;
}
var options = {
feed_item: feed,
candidate_claimed_recording: obtainCandidate(feed),
mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix',
}
var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'}));
var $controls = $feedItem.find('.recording-controls');
$('.timeago', $feedItem).timeago();
context.JK.prettyPrintElements($('.duration', $feedItem));
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
$('.details', $feedItem).click(toggleRecordingDetails);
$('.details-arrow', $feedItem).click(toggleRecordingDetails);
$('.play-button', $feedItem).click(toggleRecordingPlay);
updateIsPublic($feedItem, options.candidate_claimed_recording.is_public);
$('.btn-share', $feedItem).click(function() {
ui.launchShareDialog(options.candidate_claimed_recording.id, 'recording');
});
$('.btn-comment', $feedItem).click(function() {
ui.launchCommentDialog({
recording_id: feed.id,
claimed_recording_id: options.candidate_claimed_recording.id,
entity_type: 'recording'
})
.one(EVENTS.DIALOG_CLOSED, function() {
updateStats($feedItem);
});
});
$('.btn-like', $feedItem).click(function() {
ui.addRecordingLike(feed.id, options.candidate_claimed_recording.id, JK.currentUserId, $('.likes', $feedItem), $('.btn-like', $feedItem));
});
if(isOwner()) {
$('.edit-recording-dialog', $feedItem).data('claimed_recording_id', options.candidate_claimed_recording.id).click(function() {
app.layout.showDialog('edit-recording', {d1: $(this).data('claimed_recording_id')})
.one(EVENTS.DIALOG_CLOSED, function() {
$(this).unbind('recording_updated').unbind('recording_deleted');
})
.one('recording_updated', function(e, data) {
// find recording by claimed recording id
var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]');
var $musicians = $feedEntry.find('.musician-detail');
var $description = $feedEntry.find('.description');
var $name = $feedEntry.find('.name');
var $detailsLink = $feedEntry.find('.details');
var toggledOpen = $detailsLink.data('toggledOpen');
if(toggledOpen) {
toggleClose($feedEntry, $name, $description, $musicians, true);
}
$description.trigger('destroy.dot');
$name.trigger('destroy.dot');
updateRecordingName($feedEntry, data.name);
updateRecordingDescription($feedEntry, data.description);
updateIsPublic($feedEntry, data.is_public);
updateGenre($feedEntry, data.genre);
$name.dotdotdot();
$description.dotdotdot();
$feedItem.data('original-max-height', $feedEntry.css('height'));
$detailsLink.data('toggledOpen', false);
})
.one('recording_deleted', function(e, data) {
var $feedEntry = $screen.find('.feed-entry.recording-entry[data-claimed-recording-id="'+ data.id +'"]');
$feedEntry.remove();
})
return false;
}).show();
}
// put the feed item on the page
renderFeed($feedItem);
// these routines need the item to have height to work (must be after renderFeed)
$controls.listenRecording({recordingId: feed.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
$controls.bind('statechange.listenRecording', stateChangeRecording);
$('.dotdotdot', $feedItem).dotdotdot();
$feedItem.data('original-max-height', $feedItem.css('height'));
context.JK.bindHoverEvents($feedItem);
context.JK.bindProfileClickEvents($feedItem);
}
else {
logger.warn("skipping feed type: " + feed.type);
}
context.JK.bindProfileClickEvents();
});
}
function renderFeed(feed) {
$content.append(feed);
}
function search() {
logger.debug("Searching for feeds...");
refresh();
return false;
}
function events() {
$refresh.on("click", search);
$sortFeedBy.on('change', search);
$includeDate.on('change', search);
$includeType.on('change', search);
}
function initialize(_$parent, _$scroller, _$content, _$noMorefeeds, _$refresh, _$sortFeedBy, _$includeDate, _$includeType) {
$screen = _$parent;
$scroller = _$scroller;
$content = _$content;
$noMoreFeeds = _$noMorefeeds;
$refresh = _$refresh;
$sortFeedBy = _$sortFeedBy;
$includeDate = _$includeDate;
$includeType = _$includeType;
if($screen.length == 0) throw "$screen must be specified";
if($scroller.length == 0) throw "$scroller must be specified";
if($content.length == 0) throw "$content must be specified";
if($noMoreFeeds.length == 0) throw "$noMoreFeeds must be specified";
if($refresh.length == 0) throw "$refresh must be specified";
if($sortFeedBy.length == 0) throw "$sortFeedBy must be specified";
if($includeDate.length == 0) throw "$includeDate must be specified";
if($includeType.length ==0) throw "$includeType must be specified";
// set default search criteria
$sortFeedBy.val('date')
$includeDate.val('month')
$includeType.val('all')
events();
}
this.initialize = initialize;
this.refresh = refresh;
this.setUser = setUser;
return this;
}
})(window, jQuery)

View File

@ -99,10 +99,8 @@
// date filter
var date = $('#session-date-filter').val();
if (date !== null && date.length > 0) {
console.log(date);
currentQuery.day = context.JK.formatDateYYYYMMDD(date);
// console.log("currentQuery.day=%o", currentQuery.day);
currentQuery.timezone_offset = new Date().getTimezoneOffset();
currentQuery.timezone_offset = (new Date().getTimezoneOffset()) / 60;
}
// language filter
@ -160,7 +158,6 @@
clearResults();
buildQuery();
loadSessions();
context.JK.guardAgainstBrowser(app);
}
function clearResults() {

View File

@ -7,21 +7,12 @@
"use strict";
context.JK = context.JK || {};
context.JK.GenreSelectorDeferred = null;
context.JK.GenreSelectorHelper = (function() {
var logger = context.JK.logger;
var _genres = []; // will be list of structs: [ {label:xxx, value:yyy}, {...}, ... ]
function loadGenres() {
var url = "/api/genres";
$.ajax({
type: "GET",
url: url,
async: false, // do this synchronously so the event handlers in events() can be wired up
success: genresLoaded
});
}
function reset(parentSelector, defaultGenre) {
defaultGenre = typeof(defaultGenre) == 'undefined' ? '' : defaultGenre;
$('select', parentSelector).val(defaultGenre);
@ -38,7 +29,7 @@
function render(parentSelector) {
$('select', parentSelector).empty();
$('select', parentSelector).append('<option value="">Any Genre</option>');
$('select', parentSelector).append('<option value="">Unspecified</option>');
var template = $('#template-genre-option').html();
$.each(_genres, function(index, value) {
// value will be a dictionary entry from _genres:
@ -67,6 +58,21 @@
return selectedGenres;
}
function getNameForId(genreId) {
var name = null;
context._.each(_genres, function(genre) {
if(genreId == genre.value) {
name = genre.label;
return false;
}
});
if(!name) {
logger.warn("no genre found for genreId: " + genreId);
}
return name;
}
function setSelectedGenres(parentSelector, genreList) {
if (!genreList) {
return;
@ -79,18 +85,31 @@
$('select', parentSelector).val(values[0]);
}
function initialize() {
loadGenres();
function initialize(app) {
// XXX; _instruments should be populated in a template, rather than round-trip to server
if(!context.JK.GenreSelectorDeferred) {
// this dance is to make sure there is only one server request instead of InstrumentSelector instances *
context.JK.GenreSelectorDeferred = rest.getGenres()
}
context.JK.GenreSelectorDeferred
.done(function(response) {genresLoaded(response)})
.fail(app.ajaxError)
return this;
}
var me = { // This will be our singleton.
initialize: initialize,
getSelectedGenres: getSelectedGenres,
setSelectedGenres: setSelectedGenres,
getNameForId : getNameForId,
getSelectedGenresValues: getSelectedGenresValues,
reset: reset,
render: render,
loadGenres: loadGenres
render: function() {
var _args = arguments;
context.JK.GenreSelectorDeferred.done(function(){render.apply(self, _args)})
}
};
return me;

View File

@ -139,7 +139,8 @@
}
function updateSession(id, newSession) {
return $.ajax('/api/sessions/' + id, {
return $.ajax({
url: '/api/sessions/' + id,
type: "PUT",
data : newSession,
dataType : 'json'
@ -954,6 +955,27 @@
});
}
function updateClaimedRecording(options) {
var claimedRecordingId = options["id"];
return $.ajax({
type: "PUT",
dataType: "json",
url: '/api/claimed_recordings/' + claimedRecordingId,
contentType: 'application/json',
processData: false,
data: JSON.stringify(options)
});
}
function deleteClaimedRecording(id) {
return $.ajax({
type: "DELETE",
dataType: "json",
contentType: 'application/json',
url: "/api/claimed_recordings/" + id
});
}
function claimRecording(options) {
var recordingId = options["id"];
@ -1220,6 +1242,8 @@
this.getRecording = getRecording;
this.getClaimedRecordings = getClaimedRecordings;
this.getClaimedRecording = getClaimedRecording;
this.updateClaimedRecording = updateClaimedRecording;
this.deleteClaimedRecording = deleteClaimedRecording;
this.claimRecording = claimRecording;
this.startPlayClaimedRecording = startPlayClaimedRecording;
this.stopPlayClaimedRecording = stopPlayClaimedRecording;

View File

@ -644,8 +644,9 @@
function showDialog(dialog, options) {
if (dialogEvent(dialog, 'beforeShow', options) === false) {
return;
return null;
}
logger.debug("opening dialog: " + dialog)
var $overlay = $('.dialog-overlay')
if (opts.sizeOverlayToContent) {

View File

@ -17,6 +17,7 @@
var PAYLOAD_SIZE = 100;
var MINIMUM_ACCEPTABLE_SESSION_SIZE = 2;
var gearUtils = context.JK.GearUtils;
var rest = context.JK.Rest();
var logger = context.JK.logger;
var $step = null;
@ -201,6 +202,7 @@
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "server_comm_timeout") {
gearUtils.skipNetworkTest();
context.JK.alertSupportedNeeded("Communication with the JamKazam network service has timed out." + appendContextualStatement());
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
@ -211,11 +213,13 @@
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "invalid_response") {
gearUtils.skipNetworkTest();
context.JK.alertSupportedNeeded("The JamKazam client software had an unexpected problem while scoring your Internet connection.<br/><br/>Reason: " + attempt.backend_data.reason + '.');
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == 'no_servers') {
gearUtils.skipNetworkTest();
context.JK.alertSupportedNeeded("No network test servers are available." + appendContextualStatement());
renderStopTest('', '');
testedSuccessfully = true;
@ -227,18 +231,21 @@
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.noNetwork);
}
else if(reason == "rest_api_error") {
gearUtils.skipNetworkTest();
context.JK.alertSupportedNeeded("Unable to acquire a network test server." + appendContextualStatement());
testedSuccessfully = true;
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else if(reason == "timeout") {
gearUtils.skipNetworkTest();
context.JK.alertSupportedNeeded("Communication with the JamKazam network service timed out." + appendContextualStatement());
testedSuccessfully = true;
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);
}
else {
gearUtils.skipNetworkTest();
context.JK.alertSupportedNeeded("The JamKazam client software had a logic error while scoring your Internet connection.");
renderStopTest('', '');
storeLastNetworkFailure(context.JK.GA.NetworkTestFailReasons.jamerror);

View File

@ -13,6 +13,7 @@
var sentFriendRequest = false;
var profileScreen = null;
var textMessageDialog = null;
var feed = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
@ -30,6 +31,7 @@
function beforeShow(data) {
userId = data.id;
feed.setUser(userId);
}
function afterShow(data) {
@ -37,6 +39,10 @@
resetForm();
}
function beforeHide(data) {
feed.setUser(null);
}
function resetForm() {
$('#profile-instruments').empty();
@ -571,7 +577,7 @@
}
function bindHistory() {
feed.refresh();
}
/****************** BANDS TAB *****************/
@ -752,15 +758,31 @@
function bindFavorites() {
}
function initializeFeed() {
var $scroller = profileScreen.find('.content-body-scroller');
var $content = profileScreen.find('.feed-content');
var $noMoreFeeds = $('#end-of-feeds-list');
var $refresh = profileScreen.find('.btn-refresh-entries');
var $sortFeedBy = profileScreen.find('#feed_order_by');
var $includeDate = profileScreen.find('#feed_date');
var $includeType = profileScreen.find('#feed_show');
feed = new context.JK.Feed(app);
feed.initialize(profileScreen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType);
}
function initialize(textMessageDialogInstance) {
textMessageDialog = textMessageDialogInstance;
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
'afterShow': afterShow,
'beforeHide' : beforeHide
};
app.bindScreen('profile', screenBindings);
profileScreen = $('#user-profile');
events();
initializeFeed();
}
this.initialize = initialize;

View File

@ -98,10 +98,10 @@
id: recording.id
})
.done(function () {
console.error("recording discarded by user. recordingId=%o", recording.id);
logger.debug("recording discarded by user. recordingId=%o", recording.id);
})
.fail(function (jqXHR) {
console.error("recording discard by user failed. recordingId=%o. reason: %o", recording.id, jqXHR.responseText);
logger.error("recording discard by user failed. recordingId=%o. reason: %o", recording.id, jqXHR.responseText);
})
.always(function () {
app.layout.closeDialog('recordingFinished')

View File

@ -34,7 +34,7 @@
}
function showDialog() {
app.layout.showDialog('rsvp-cancel-dialog');
return app.layout.showDialog('rsvp-cancel-dialog');
}
function events() {

View File

@ -55,7 +55,7 @@
}
function showDialog() {
app.layout.showDialog(dialogId);
return app.layout.showDialog(dialogId);
}
function events() {

View File

@ -5,6 +5,7 @@
context.JK = context.JK || {};
context.JK.CreateScheduledSession = function(app) {
var gearUtils = context.JK.GearUtils;
var logger = context.JK.logger;
var rest = JK.Rest();
var invitationDialog = null;
@ -188,9 +189,7 @@
function beforeShowStep5() {
var startType = null;
if (createSessionSettings.createType == 'start-scheduled' ||
createSessionSettings.createType == 'immediately' ||
createSessionSettings.createType == 'quick-start') {
if (willOptionStartSession()) {
startType = 'Now!';
createSessionSettings.startType = "START SESSION";
}
@ -305,6 +304,10 @@
if (createSessionSettings.createType == 'start-scheduled') {
var session = scheduledSessions[createSessionSettings.selectedSessionId];
if(session == null) {
// TODO: notify user they need to pick session? Or maybe it should be grayed out.
return false;
}
var moveToFinish = function() {
app.layout.closeDialog('confirm');
createSessionSettings.startDate = new Date(session.scheduled_start).toDateString();
@ -488,7 +491,27 @@
function beforeMoveStep5() {
}
function startSessionClicked() {
if(willOptionStartSession()) {
gearUtils.guardAgainstInvalidConfiguration(app)
.fail(function() {
app.notify(
{ title: "Unable to Start New Session",
text: "You can only start a session once you have working audio gear and a tested internet connection."
})
})
.done(function(){
startSession();
});
}
else {
startSession();
}
}
function startSession() {
var data = {};
if (createSessionSettings.createType == 'start-scheduled') {
@ -563,22 +586,15 @@
});
}
var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
if(tracks.length == 0) {
logger.error("we should never have 0 tracks and have gotten this far. Launch FTUE is the best we can do right now")
// If user hasn't completed FTUE - do so now.
app.afterFtue = function() { startSession(); };
app.layout.startNewFtue();
return false;
}
var joinSession = function(sessionId) {
var tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
var options = {};
options.client_id = app.clientId;
options.session_id = sessionId;
options.as_musician = true;
options.tracks = tracks;
rest.joinSession(options)
rest.joinSession(options)
.done(function(response) {
var invitationCount = data.invitations.length;
@ -600,16 +616,19 @@
app.notifyServerError(jqXHR, "Unable to Create Session");
}
})
};
if (createSessionSettings.createType == 'start-scheduled') {
joinSession(createSessionSettings.selectedSessionId);
$('#create-session-buttons .btn-next').off('click');
}
else {
rest.createScheduledSession(data)
.done(function(response) {
logger.debug("created session on server");
$('#create-session-buttons .btn-next').off('click');
var newSessionId = response.id;
$(".btn-next").off('click');
if (createSessionSettings.createType == 'quick-start' || createSessionSettings.createType == "immediately") {
joinSession(newSessionId);
@ -620,6 +639,7 @@
}
})
.fail(function(jqXHR){
logger.debug("unable to schedule a session")
app.notifyServerError(jqXHR, "Unable to schedule a session");
});
}
@ -734,7 +754,7 @@
if (step == STEP_SELECT_CONFIRM) {
$btnNext.html(createSessionSettings.startType);
$btnNext.on('click', startSession);
$btnNext.on('click', startSessionClicked);
}
else
$btnNext.on('click', next);
@ -764,7 +784,20 @@
return false;
}
// will this option result in a session being started?
function willOptionStartSession() {
return createSessionSettings.createType == 'start-scheduled' ||
createSessionSettings.createType == 'immediately' ||
createSessionSettings.createType == 'quick-start';
}
function next(event) {
if(willOptionStartSession()) {
if(!context.JK.guardAgainstBrowser(app)) {
return false;
}
}
var valid = beforeMoveStep();
if (!valid) {
return false;
@ -820,7 +853,7 @@
}
function afterShow() {
context.JK.guardAgainstBrowser(app);
}
function getFormattedTime(date, change) {

View File

@ -4,6 +4,7 @@
context.JK = context.JK || {};
context.JK.SessionScreen = function(app) {
var gearUtils = context.JK.GearUtils;
var logger = context.JK.logger;
var self = this;
var sessionModel = null;
@ -114,23 +115,25 @@
checkForCurrentUser();
}
function afterShow(data) {
if(!context.JK.JamServer.connected) {
promptLeave = false;
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
window.location = '/client#/home'
return;
}
if(!context.JK.JamServer.connected) {
promptLeave = false;
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
window.location = '/client#/home'
return;
}
if (!context.JK.hasOneConfiguredDevice() || context.JK.TrackHelpers.getUserTracks(context.jamClient).length == 0) {
app.afterFtue = function() { initializeSession(); };
app.cancelFtue = function() { promptLeave = false; window.location = '/client#/home' };
app.layout.startNewFtue();
}
else {
gearUtils.guardAgainstInvalidConfiguration(app)
.fail(function() {
promptLeave = false;
window.location = '/client#/home'
})
.done(function(){
initializeSession();
}
})
}
function notifyWithUserInfo(title , text, clientId) {

View File

@ -4,6 +4,7 @@
context.JK = context.JK || {};
context.JK.SessionList = function(app) {
var gearUtils = context.JK.GearUtils;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var ui = new context.JK.UIHelper(app);
@ -11,18 +12,17 @@
var $inactiveSessionTemplate = $('#template-inactive-session-row');
var $notationFileTemplate = $('#template-notation-files');
var $openSlotsTemplate = $('#template-open-slots');
var $pendingInvitationsTemplate = $('#template-pending-invitations');
var $latencyTemplate = $('#template-latency');
var $musicianTemplate = $('#template-musician-info');
var showJoinLink = true;
var showRsvpLink = true;
var LATENCY = {
GOOD : {description: "GOOD", style: "latency-green", min: 0.0, max: 20.0},
MEDIUM : {description: "MEDIUM", style: "latency-yellow", min: 20.0, max: 40.0},
POOR : {description: "POOR", style: "latency-red", min: 40.0, max: 10000000000.0},
UNREACHABLE: {description: "UNREACHABLE", style: "latency-grey", min: -1, max: -1},
UNKNOWN: {description: "UNKNOWN", style: "latency-grey", min: -2, max: -2}
GOOD : {description: "GOOD", style: "latency-green", min: 0.0, max: 20.0},
MEDIUM : {description: "MEDIUM", style: "latency-yellow", min: 20.0, max: 40.0},
POOR : {description: "POOR", style: "latency-red", min: 40.0, max: 10000000000.0},
UNREACHABLE: {description: "UNREACHABLE", style: "latency-grey", min: -1, max: -1},
UNKNOWN: {description: "UNKNOWN", style: "latency-grey", min: -2, max: -2}
};
var instrument_logo_map = context.JK.getInstrumentIconMap24();
@ -41,7 +41,7 @@
showJoinLink = session.musician_access;
// render musicians who are already in the session
if (session.active_music_session && "participants" in session.active_music_session) {
if (session.active_music_session && "participants" in session.active_music_session && session.active_music_session.participants.length > 0) {
for (i=0; i < session.active_music_session.participants.length; i++) {
inSessionUsers.push(session.active_music_session.participants[i].user.id);
var inSessionUserInfo = createInSessionUser(session.active_music_session.participants[i]);
@ -49,11 +49,16 @@
latencyHtml += inSessionUserInfo[1];
}
}
// this provides a buffer at the top to shift the first latency tag down in the event there are NO in-session musicians
else {
latencyHtml += "<div style='height:15px;'>&nbsp;</div>";
}
// render users who have approved RSVPs
if (session.approved_rsvps) {
for (i=0; i < session.approved_rsvps.length; i++) {
if (!(session.approved_rsvps[i].id in inSessionUsers)) {
// do not show the user in this section if he is already in the session
if ($.inArray(session.approved_rsvps[i].id, inSessionUsers) === -1) {
if (session.approved_rsvps[i].id === context.JK.currentUserId) {
showJoinLink = true;
}
@ -61,6 +66,9 @@
rsvpUsersHtml += rsvpUserInfo[0];
latencyHtml += rsvpUserInfo[1];
}
else {
showJoinLink = true;
}
}
}
@ -89,18 +97,25 @@
// wire up the Join Link to the T&Cs dialog
var $parentRow = $('tr[id=' + session.id + ']', tbGroup);
$('.join-link', $parentRow).click(function(evt) {
if(!context.JK.guardAgainstBrowser(app)) {
return false;
}
if (!context.JK.JamServer.connected) {
app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.');
return false;
}
// If no FTUE, show that first.
if (!context.JK.hasOneConfiguredDevice() || context.JK.TrackHelpers.getUserTracks(context.jamClient).length == 0) {
app.afterFtue = function() { joinClick(session.id); };
app.layout.startNewFtue();
}
else {
joinClick(session.id);
}
gearUtils.guardAgainstInvalidConfiguration(app)
.fail(function() {
app.notify(
{ title: "Unable to Join Session",
text: "You can only join a session once you have working audio gear and a tested internet connection."
})
})
.done(function(){
joinClick(session.id);
})
return false;
});
@ -119,10 +134,10 @@
$('#actionHeader', tbGroup).html('RSVP');
var i = 0;
var rsvpUsersHtml = '', openSlotsHtml = '', pendingInvitationsHtml = '', latencyHtml = '', notationFileHtml = '';
var rsvpUsersHtml = '', openSlotsHtml = '', latencyHtml = '', notationFileHtml = '';
// render users who have approved RSVPs
if (session.approved_rsvps) {
if (session.approved_rsvps && session.approved_rsvps.length > 0) {
for (i=0; i < session.approved_rsvps.length; i++) {
if (session.approved_rsvps[i].id === context.JK.currentUserId) {
hasApprovedRsvp = true;
@ -132,6 +147,10 @@
latencyHtml += rsvpUserInfo[1];
}
}
// this provides a buffer at the top to shift the first latency tag down in the event there are NO RSVP musicians
else {
latencyHtml += "<div style='height:15px;'>&nbsp;</div>";
}
// render open slots
if (session.open_slots) {
@ -147,7 +166,6 @@
if (session.pending_invitations[i].id === context.JK.currentUserId) {
hasInvitation = true;
}
pendingInvitationsHtml += createPendingInvitation(session.pending_invitations[i]);
}
}
@ -166,7 +184,6 @@
}
var sessionVals = buildSessionObject(session, notationFileHtml, rsvpUsersHtml, openSlotsHtml, latencyHtml);
sessionVals.pending_invitations = pendingInvitationsHtml.length > 0 ? pendingInvitationsHtml : 'N/A';
sessionVals.rsvp_link_display_style = showRsvpLink ? "block" : "none";
var row = context.JK.fillTemplate($inactiveSessionTemplate.html(), sessionVals);
@ -319,15 +336,6 @@
return context.JK.fillTemplate($openSlotsTemplate.html(), slot);
}
function createPendingInvitation(user) {
var invitationVals = {
avatar_url: context.JK.resolveAvatarUrl(user.photo_url)
};
return context.JK.fillTemplate($pendingInvitationsTemplate.html(), invitationVals);
}
function createNotationFile(notation) {
var notationVals = {
file_url: notation.file_url,

View File

@ -4,6 +4,12 @@
context.JK.SessionSettingsDialog = function(app, sessionScreen) {
var logger = context.JK.logger;
var $dialog;
var $screen = $('#session-settings');
var $selectedFilenames = $screen.find('#selected-filenames');
var $uploadSpinner = $screen.find($('.upload-spinner'));
var $selectedFilenames = $('#settings-selected-filenames');
var $inputFiles = $('#settings-select-files');
var $btnSelectFiles = $screen.find('.btn-select-files');
var rest = new JK.Rest();
function beforeShow(data) {
@ -19,7 +25,6 @@
// genre
context.JK.GenreSelectorHelper.setSelectedGenres('#session-settings-genre', currentSession.genres);
// name
$('#session-settings-name').val(currentSession.name);
@ -56,7 +61,12 @@
}
// notation files
$selectedFilenames.empty();
for (var i=0; i < currentSession.music_notations.length; i++) {
var notation = currentSession.music_notations[i];
console.log('notation.file_name %o', notation.file_name);
$selectedFilenames.append('<a href="' + notation.file_url + '" rel="external">' + notation.file_name + '</a>&nbsp;');
}
context.JK.dropdown($('#session-settings-language'));
context.JK.dropdown($('#session-settings-musician-access'));
@ -67,7 +77,7 @@
var data = {};
data.genre = $('#session-settings-genre').val();
data.genre = context.JK.GenreSelectorHelper.getSelectedGenres('#session-settings-genre')[0];
data.name = $('#session-settings-name').val();
data.description = $('#session-settings-description').val();
data.language = $('#session-settings-language').val();
@ -105,6 +115,78 @@
rest.updateSession($('#session-settings-id').val(), data).done(settingsSaved);
}
function changeSelectedFiles() {
var fileNames = [];
var files = $inputFiles.get(0).files;
var error = false;
for (var i = 0; i < files.length; ++i) {
var name = files.item(i).name;
var ext = name.split('.').pop();
if ($.inArray(ext, ["pdf", "png", "jpg", "jpeg", "gif", "xml", "mxl", "txt"]) == -1) {
error = true;
break;
}
fileNames.push(name);
}
if (error) {
app.notifyAlert("Error", "We're sorry, but we do not allow upload of that file type. Please upload only the file types listed in the Upload dialog box.");
$inputFiles.replaceWith($inputFiles.clone(true));
}
else {
}
// upload as soon as user picks their files.
uploadNotations($inputFiles.get(0).files)
.done(function() {
context._.each(fileNames, function(fileName) {
$selectedFilenames.append(fileName);
})
})
}
function uploadNotations(notations) {
var formData = new FormData();
$.each(notations, function(i, file) {
formData.append('files[]', file);
});
formData.append('client_id', app.clientId);
$btnSelectFiles.text('UPLOADING...').data('uploading', true)
$uploadSpinner.show();
return rest.uploadMusicNotations(formData)
.done(function(response) {
var error_files = [];
$.each(response, function(i, music_notation) {
if (music_notation.errors) {
//error_files.push(createSessionSettings.notations[i].name);
}
})
if (error_files.length > 0) {
app.notifyAlert("Failed to upload notations.", error_files.join(', '));
}
})
.fail(function(jqXHR) {
app.notifyServerError(jqXHR, "Unable to upload music notations");
})
.always(function() {
$btnSelectFiles.text('SELECT FILES...').data('uploading', null)
$uploadSpinner.hide();
})
}
function toggleSelectFiles(event) {
if($btnSelectFiles.data('uploading')) {
logger.debug("ignoring click of SELECT FILES... while uploading")
return false;
}
event.preventDefault();
$('#session-select-files').trigger('click');
return false;
}
function settingsSaved(response) {
// No response returned from this call. 204.
sessionScreen.refreshCurrentSession(true);
@ -113,6 +195,9 @@
function events() {
$('#session-settings-dialog-submit').on('click', saveSettings);
$inputFiles.on('change', changeSelectedFiles);
$btnSelectFiles.on('click', toggleSelectFiles);
}
this.initialize = function() {

View File

@ -336,7 +336,7 @@
}
function showDialog() {
app.layout.showDialog('share-dialog');
return app.layout.showDialog('share-dialog');
}
// function initDialog() {

View File

@ -26,25 +26,25 @@
function launchCommentDialog(options) {
var commentDialog = new JK.CommentDialog(JK.app, options);
commentDialog.initialize();
commentDialog.showDialog();
return commentDialog.showDialog();
}
function launchShareDialog(entityId, entityType) {
var shareDialog = new JK.ShareDialog(JK.app, entityId, entityType);
shareDialog.initialize(JK.FacebookHelperInstance);
shareDialog.showDialog();
return shareDialog.showDialog();
}
function launchRsvpSubmitDialog(sessionId) {
var rsvpDialog = new JK.RsvpSubmitDialog(JK.app, sessionId);
rsvpDialog.initialize();
rsvpDialog.showDialog();
return rsvpDialog.showDialog();
}
function launchRsvpCancelDialog(sessionId, rsvpRequestId) {
var rsvpDialog = new JK.RsvpCancelDialog(JK.app, sessionId, rsvpRequestId);
rsvpDialog.initialize();
rsvpDialog.showDialog();
return rsvpDialog.showDialog();
}
this.addSessionLike = addSessionLike;

View File

@ -936,8 +936,11 @@
context.JK.guardAgainstBrowser = function(app, args) {
if(!gon.isNativeClient) {
logger.debug("guarding against normal browser on screen thaht requires native client")
app.layout.showDialog('launch-app-dialog', args)
return false;
}
return true;
}
/*

View File

@ -10,8 +10,15 @@
var ui = new context.JK.UIHelper(app);
var $btnAction = $("#btn-action");
var LATENCY = {
GOOD : {description: "GOOD", style: "latency-green", min: 0.0, max: 20.0},
MEDIUM : {description: "MEDIUM", style: "latency-yellow", min: 20.0, max: 40.0},
POOR : {description: "POOR", style: "latency-red", min: 40.0, max: 10000000000.0},
UNREACHABLE: {description: "UNREACHABLE", style: "latency-grey", min: -1, max: -1},
UNKNOWN: {description: "UNKNOWN", style: "latency-grey", min: -2, max: -2}
};
function addComment(musicSessionId) {
console.log("here");
var comment = $("#txtSessionInfoComment").val();
if ($.trim(comment).length > 0) {
rest.addSessionInfoComment(musicSessionId, comment)

View File

@ -13,6 +13,11 @@
var ASSIGNMENT = context.JK.ASSIGNMENT;
var VOICE_CHAT = context.JK.VOICE_CHAT;
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
var EVENTS = context.JK.EVENTS;
gearUtils.SKIPPED_NETWORK_TEST = -1; // we store a negative 1 to mean that we let the user skip.
gearUtils.skippedNetworkTest = false; // we allow someone to play in session (for one client run) if it's our fault they can't network test score
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
gearUtils.isChannelAssigned = function (channel) {
@ -271,4 +276,88 @@
app.notifyServerError(jqXHR, "Unable to sync audio latency")
});
}
// if the user has a good user network score, immediately returns with a resolved deferred object.
// if not, the user will have the network test dialog prompted... once it's closed, then you'll be told reject() if score is still bad, or resolve() if now good
gearUtils.guardAgainstBadNetworkScore = function(app) {
var deferred = new $.Deferred();
if (!gearUtils.validNetworkScore()) {
// invalid network test score. They have to score to move on
app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, function() {
if(gearUtils.validNetworkScore()) {
deferred.resolve();
}
else {
deferred.reject();
}
});
}
else {
deferred.resolve();
}
return deferred;
}
// XXX this isn't quite right... it needs to check if a good device is *active*
// but seen too many problems so far with the backend not reporting any profile active
gearUtils.hasGoodActiveProfile = function(app) {
return context.JK.hasOneConfiguredDevice() && context.JK.TrackHelpers.getUserTracks(context.jamClient).length > 0
}
// if the user does not have a currently active, good profile, then they are made to deal with it
gearUtils.guardAgainstInvalidGearConfiguration = function(app) {
var deferred = new $.Deferred();
if (!gearUtils.hasGoodActiveProfile()) {
app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, function() {
if(gearUtils.hasGoodActiveProfile() && gearUtils.validNetworkScore()) {
deferred.resolve();
}
else {
deferred.reject();
}
});
}
else {
deferred.resolve();
}
return deferred;
}
// tests both device config, and network score
gearUtils.guardAgainstInvalidConfiguration = function(app) {
var deferred = new $.Deferred();
gearUtils.guardAgainstInvalidGearConfiguration(app)
.fail(function() {
deferred.reject();
})
.done(function() {
gearUtils.guardAgainstBadNetworkScore(app)
.fail(function() {
deferred.reject();
})
.done(function() {
deferred.resolve();
})
})
return deferred;
}
gearUtils.skipNetworkTest = function() {
context.jamClient.SetNetworkTestScore(gearUtils.SKIPPED_NETWORK_TEST);
gearUtils.skippedNetworkTest = true;
}
gearUtils.isNetworkTestSkipped = function() {
return gearUtils.skippedNetworkTest;
}
gearUtils.validNetworkScore = function() {
return gearUtils.skippedNetworkTest || context.jamClient.GetNetworkTestScore() >= 2;
}
})(window, jQuery);

View File

@ -54,6 +54,7 @@
*= require ./textMessageDialog
*= require ./acceptFriendRequestDialog
*= require ./launchAppDialog
*= require ./editRecordingDialog
*= require ./iconInstrumentSelect
*= require ./terms
*= require ./createSession

View File

@ -340,6 +340,41 @@ a.arrow-down {
width:200px;
}
.btn-select-files {
margin-top: 10px;
margin-left:0;
width:110px;
@include border_box_sizing;
}
.spinner-small.upload-spinner {
display:none;
position: absolute;
left: 0px;
margin-top: 4px;
}
.select-files-section {
position:absolute;
}
#settings-selected-filenames {
font-size:12px;
span {
white-space:nowrap;
text-overflow:ellipsis;
overflow:hidden;
display:block;
}
}
.selected-files-section {
overflow: hidden;
width: 100%;
@include border_box_sizing;
}
#session-controls {
width:100%;
padding:6px 0px 11px 0px;

View File

@ -0,0 +1,79 @@
@import "client/common";
#edit-recording-dialog {
min-height:330px;
input, textarea {
@include border_box_sizing;
}
.field {
margin-top:20px;
&:nth-of-type(1) {
margin-top:0;
}
}
.buttons {
float:right;
clear:both;
margin-top:20px;
}
label[for="name"] {
}
input[name="name"] {
margin-top:5px;
width:100%;
}
label[for="description"] {
}
textarea[name="description"] {
margin-top:5px;
width:100%;
}
label[for="genre"] {
display:inline;
float:left;
line-height:26px;
vertical-align:middle;
}
select[name="genre"] {
float:left;
margin-left:5px;
}
.genre-selector {
float:left;
.dropdown-wrapper {
margin-left:5px;
}
}
label[for="is_public"] {
display: inline;
float:right;
line-height: 26px;
padding-right: 5px;
vertical-align: middle;
}
select[name="is_public"] {
float:right;
}
div[purpose="is_public"] {
float:right;
.icheckbox_minimal {
float:right;
margin-top:4px;
}
}
}

View File

@ -2,4 +2,9 @@
.recording-current {
position:absolute; // solves a problem with duration wrapping--only in firefox
}
.btn-refresh-holder {
float:right;
margin-right:10px;
}
}

View File

@ -66,4 +66,3 @@
}
}

View File

@ -328,6 +328,7 @@ input[type="text"], input[type="password"]{
textarea {
font-size:15px;
padding:3px;
}

View File

@ -317,4 +317,60 @@
#btn-add-friend {
display:none;
}
#profile-history {
padding:0 10px 0 20px;
width:100%;
position:relative;
height:100%;
@include border_box_sizing;
#user-feed-controls {
width:100%;
@include border_box_sizing;
position:relative;
display:none;
}
.btn-refresh-holder {
left: 95%; // 5 * 19% to right-align 5 user blocks (in conjunction with the margin-left
margin-left: -65px;
position: absolute;
}
.filter-body {
bottom: 0;
right: 0;
top: 0;
left: 20px;
position: absolute;
width: 95%;
padding-top:0;
margin-top:10px;
height:auto;
}
.profile-wrapper {
padding: 10px 0;
}
.feed-entry .feed-details {
margin-right:5px;
}
.recording-current {
position:absolute; // solves a problem with duration wrapping--only in firefox
}
.content-body-scroller {
height: 100%;
width: 100%;
position: absolute;
}
.no-feed-msg {
text-align:center
}
}

View File

@ -22,9 +22,15 @@ table.findsession-table, table.local-recordings {
}
td.latency {
padding-top: 15px !important;
text-align:center !important;
}
td.latency div.center {
display: inline-block;
margin: auto;
}
.noborder {
border-right:none;
}
@ -65,6 +71,7 @@ table.findsession-table, table.local-recordings {
font-weight:200;
font-size:11px;
background-color:#868686;
text-align:center;
}
.latency-green {
@ -74,6 +81,7 @@ table.findsession-table, table.local-recordings {
font-weight:200;
font-size:11px;
background-color:#71a43b;
text-align:center;
}
.latency-yellow {
@ -83,6 +91,7 @@ table.findsession-table, table.local-recordings {
font-weight:200;
font-size:11px;
background-color:#cc9900;
text-align:center;
}
.latency-red {
@ -92,6 +101,7 @@ table.findsession-table, table.local-recordings {
font-weight:200;
font-size:11px;
background-color:#980006;
text-align:center;
}
.avatar-tiny {

View File

@ -272,6 +272,20 @@
}
}
a.edit-recording-dialog {
font-size:12px;
display:none;
}
.is_private {
display:none;
font-size:12px;
font-style:italic;
&.enabled {
display:inline;
}
}
.play-count {
margin-right:10px;
}

View File

@ -589,4 +589,8 @@ strong {
fieldset.login-error .login-error-msg {
display:block;
}
}
body.jam.web.welcome .no-websocket-connection {
display:none;
}

View File

@ -1,23 +1,54 @@
/*.session-controls {
background-color:#471f18;
}
.session-controls.inprogress {
background-color:#4C742E;
}
.session-status-ended, .session-status {
float:left;
font-size:18px;
}
.session-status-inprogress {
float:left;
font-size:15px;
color:#cccc00;
margin-left:20px;
}*/
#btnPlayPause {
position: relative;
}
table.musicians {
margin-top:-3px;
}
table.musicians td {
border-right:none;
border-top:none;
padding:2px;
vertical-align:middle !important;
}
.latency-grey {
width: 50px;
height: 10px;
font-family:Arial, Helvetica, sans-serif;
font-weight:200;
font-size:11px;
background-color:#868686;
text-align:center;
}
.latency-green {
width: 50px;
height: 10px;
font-family:Arial, Helvetica, sans-serif;
font-weight:200;
font-size:11px;
background-color:#71a43b;
text-align:center;
}
.latency-yellow {
width: 50px;
height: 10px;
font-family:Arial, Helvetica, sans-serif;
font-weight:200;
font-size:11px;
background-color:#cc9900;
text-align:center;
}
.latency-red {
width: 40px;
height: 10px;
font-family:Arial, Helvetica, sans-serif;
font-weight:200;
font-size:11px;
background-color:#980006;
text-align:center;
}

View File

@ -19,30 +19,20 @@ class ApiClaimedRecordingsController < ApiController
end
def update
if @claimed_recording.user_id != current_user.id
raise PermissionError, 'only owner of claimed_recording can update it'
end
begin
@claimed_recording.update_fields(current_user, params)
respond_with responder: ApiResponder, :status => 204
rescue
render :json => { :message => "claimed_recording could not be updated" }, :status => 403
end
@claimed_recording.update_fields(current_user, params)
respond_with @claimed_recording
end
def delete
if @claimed_recording.user_id != current_user.id
raise PermissionError, 'only owner of claimed_recording can update it'
end
#begin
#@claimed_recording.discard(current_user)
#render :json => {}, :status => 204
# respond_with responder: ApiResponder, :status => 204
#rescue
#render :json => { :message => "claimed_recording could not be deleted" }, :status => 403
#end
@claimed_recording.discard(current_user)
render :json => {}, :status => 200
end
def download

View File

@ -82,7 +82,6 @@ class ApiMusicSessionsController < ApiController
end
end
def scheduled
@music_sessions = MusicSession.scheduled(current_user)
end
@ -175,7 +174,7 @@ class ApiMusicSessionsController < ApiController
@music_session.music_session,
params[:name],
params[:description],
params[:genre],
params[:genre] ? Genre.find(params[:genre]) : nil,
params[:language],
params[:musician_access],
params[:approval_required],

View File

@ -205,6 +205,7 @@ class ApiRecordingsController < ApiController
end
end
private
def parse_filename
@recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id])

View File

@ -47,9 +47,15 @@ class MusicSessionsController < ApplicationController
# run these 3 queries only if the user has access to the page
if @can_view
@approved_rsvps = @music_session.approved_rsvps
@open_slots = @music_session.open_slots
@pending_invitations = @music_session.pending_invitations
ActiveRecord::Base.transaction do
@music_sessions, @user_scores = MusicSession.sms_index(current_user, {:session_id => params[:id], :client_id => cookies[:client_id]})
unless @music_sessions.blank?
ms = @music_sessions[0]
@approved_rsvps = ms.approved_rsvps
@open_slots = ms.open_slots
@pending_invitations = ms.pending_invitations
end
end
end
render :layout => "web"

View File

@ -140,7 +140,7 @@ glue :recording do
child(:claimed_recordings => :claimed_recordings) {
attributes :id, :name, :description, :is_public, :genre_id, :has_mix?
attributes :id, :name, :description, :is_public, :genre_id, :has_mix?, :user_id
child(:user => :creator) {
attributes :id, :first_name, :last_name, :photo_url

View File

@ -19,6 +19,14 @@ else
[item.genre.description] # XXX: need to return single genre; not array
end
child(:music_notations => :music_notations) {
attributes :id, :file_name
node do |music_notation|
{ file_url: music_notation["file_url"] }
end
}
if :is_recording?
node do |music_session|
{ :recording => partial("api_recordings/show", :object => music_session.recording) }
@ -44,7 +52,6 @@ else
}
}
child({:invitations => :invitations}) {
attributes :id, :sender_id, :receiver_id
}

View File

@ -17,7 +17,7 @@ if !current_user
else
attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat,
:band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :scheduled_start, :scheduled_duration,
:band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_start, :scheduled_duration,
:language, :recurring_mode, :language_description, :scheduled_start_time, :access_description, :timezone, :timezone_description,
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps

View File

@ -28,7 +28,7 @@ child(:comments => :comments) {
child(:claimed_recordings => :claimed_recordings) {
attributes :id, :name, :description, :is_public, :genre_id
attributes :id, :name, :description, :is_public, :genre_id, :has_mix?, :user_id
node :share_url do |claimed_recording|
unless claimed_recording.share_token.nil?

View File

@ -1,43 +0,0 @@
<!-- generic banner for use by an code -->
<div class="overlay" id="banner_overlay"></div>
<div id="banner" class="dialog-overlay-sm" data-type="">
<!-- dialog header -->
<div class="content-head">
<%= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon") %><h1></h1>
</div>
<div class="dialog-inner">
</div>
<!-- end right column -->
<br clear="all" class="end-content">
<div class="right buttons">
<a class="button-orange close-btn">CLOSE</a>
</div>
</div>
<script type="text/template" id="template-app-in-read-only-volume">
<div class="template-app-in-read-only-volume">
<p> The JamKazam application is running in a read-only volume. This stops the automatic update feature
from working, and may cause other issues because it is not a supported configuration.</p>
<p> So let's fix it. Don't worry--it's easy to do. Please read on.</p>
<p> First, here's almost certainly what happened to cause this problem: after JamKazam.dmg was downloaded, it was then double-clicked and a window opened showing the contents of the dmg.
The JamKazam application icon was double-clicked inside that opened window. Unfortunately, that isn't OK.</p>
<p>Instead, do this to move JamKazam to a good location, and run it from there:</p>
<ol>
<li class="download-dmg">Download the latest mac installer from the <a href="/downloads">Downloads</a> page.<br/><em>(the download will have a filename ending in .dmg)</em></li>
<li>Double-click the downloaded dmg file to open it.</li>
<li>In the resulting screen, drag the JamKazam icon to the Applications folder. It will show a progress bar as it copies.</li>
<li>Double-click the Applications folder to go into it.</li>
<li>If you are still running the JamKazam application, you will need to stop it before executing the last step.</li>
<li>Find the JamKazam application in the Applications folder, and double-click the icon to launch it!</li>
</ol>
<!---<p>The following animation shows the the steps, after once you've downloaded the JamKazam '.dmg'.</p>-->
</div>
</script>

View File

@ -0,0 +1,33 @@
#banner_overlay.overlay
#banner.dialog-overlay-sm{ 'data-type' => '' }
.content-head
= image_tag("content/icon_alert.png", :height => '24', :width => '24', :class => "content-icon")
%h1
.dialog-inner
%br.end-content{ clear: 'all'}
.right.buttons
%a.button-orange.close-btn CLOSE
%a.button-orange.yes-btn YES
%a.button-grey.no-btn CANCEL
%script{type: 'text/template', id: 'template-app-in-read-only-volume'}
.template-app-in-read-only-volume
%p The JamKazam application is running in a read-only volume. This stops the automatic update feature from working, and may cause other issues because it is not a supported configuration.
%p So let's fix it. Don't worry--it's easy to do--please read on.
%p First, here's almost certainly what happened to cause this problem: after JamKazam.dmg was downloaded, it was then double-clicked and a window opened showing the contents of the dmg. The JamKazam application icon was double-clicked inside that opened window. Unfortunately, that isn't OK.
%p Instead, do this to move JamKazam to a good location, and run it from there:
%ol
%li.download-dmg
Download the latest mac installer from the
%a{href:"/downloads"}Downloads
page.
%br
%em (the download will have a filename ending in .dmg)
%li Double-click the downloaded dmg file to open it.
%li In the resulting screen, drag the JamKazam icon to the Applications folder. It will show a progress bar as it copies.
%li Double-click the Applications folder to go into it.
%li If you are still running the JamKazam application, you will need to stop it before executing the last step.
%li Find the JamKazam application in the Applications folder, and double-click the icon to launch it!

View File

@ -0,0 +1,24 @@
.dialog.configure-tracks{ layout: 'dialog', 'layout-id' => 'edit-recording', id: 'edit-recording-dialog'}
.content-head
= image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' }
%h1 Edit Recording
.dialog-inner
%form
.field
%label{for: 'name'} Recording name:
%input{type: 'text', name: 'name'}
.field
%label{for: 'description'} Description:
%textarea{name: 'description', rows: '4'}
.field.genre-selector
%label{for: 'genre'} Genre:
%select{name:'genre'}
.field{purpose: 'is_public'}
%input{type: 'checkbox', name: 'is_public'}
%label{for: 'is_public'} Public Recording
.buttons
%a.button-grey.cancel-btn CANCEL
%a.button-orange.delete-btn DELETE
%a.button-orange.save-btn UPDATE
%br{clear: 'all'}

View File

@ -81,14 +81,14 @@
<td width="30%">
<table class="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td>{name}</td>
<td class="bold">{name}</td>
<td align="right" width="75">({genres})</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td colspan="2">Notation Files:</td>
<td>Notation Files:</td>
<td>{notation_files}</td>
</tr>
</table>
@ -121,21 +121,19 @@
</tr>
</table>
</td>
<td width="10%" align="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency}
</table>
<td width="10%" class="latency">
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency}
</table>
</div>
</td>
<td width="20%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr><td class="bold">Chat Language:</td></tr>
<tr><td>{language}</td></tr>
<tr><td class="bold">Musician Access:</td></tr>
<tr><td>{musician_access}</td></tr>
<tr><td class="bold">Fan Access:</td></tr>
<tr><td>{fan_access}</td></tr>
<tr><td class="bold">Legal Policy:</td></tr>
<tr><td>{legal_policy}</td></tr>
<tr><td><span class="bold">Chat Language:</span><br/>{language}</td></tr>
<tr><td><span class="bold">Musician Access:</span><br/>{musician_access}</td></tr>
<tr><td><span class="bold">Fan Access:</span><br/>{fan_access}</td></tr>
<tr><td><span class="bold">Legal Policy:</span><br/>{legal_policy}</td></tr>
</table>
</td>
<td class="noborder" style="text-align:center; vertical-align:middle;">
@ -152,7 +150,7 @@
<td width="30%">
<table class="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td>{name}</td>
<td class="bold">{name}</td>
<td align="right" width="75">({genres})</td>
</tr>
<tr>
@ -182,31 +180,21 @@
</table>
</td>
</tr>
<tr>
<td>Invited:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0">
{pending_invitations}
</table>
</td>
</tr>
</table>
</td>
<td width="10%" align="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency}
</table>
<td width="10%" class="latency pt10">
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency}
</table>
</div>
</td>
<td width="20%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr><td class="bold">Chat Language:</td></tr>
<tr><td>{language}</td></tr>
<tr><td class="bold">Musician Access:</td></tr>
<tr><td>{musician_access}</td></tr>
<tr><td class="bold">Fan Access:</td></tr>
<tr><td>{fan_access}</td></tr>
<tr><td class="bold">Legal Policy:</td></tr>
<tr><td>{legal_policy}</td></tr>
<tr><td><span class="bold">Chat Language:</span><br/>{language}</td></tr>
<tr><td><span class="bold">Musician Access:</span><br/>{musician_access}</td></tr>
<tr><td><span class="bold">Fan Access:</span><br/>{fan_access}</td></tr>
<tr><td><span class="bold">Legal Policy:</span><br/>{legal_policy}</td></tr>
</table>
</td>
<td class="noborder" style="text-align:center; vertical-align:middle;">
@ -249,10 +237,6 @@
</tr>
</script>
<script type="text/template" id="template-pending-invitations">
<img src="{avatar_url}" />&nbsp;
</script>
<script type="text/template" id="template-latency">
<tr class="mb15">
<td class="{latency_style}">

View File

@ -88,9 +88,19 @@
</div>
</div>
<div id="profile-history" class="profile-wrapper">
<%= form_tag('', {:id => 'user-feed-form', :class => 'inner-content'}) do %>
<%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'user-feed-controls'}) %>
<div class="filter-body">
<div class="content-body-scroller">
<br clear="all" />
<div class="profile-wrapper">
<div class="feed-content"></div>
<a href="/api/feeds?page=1" class="btn-next-pager">Next</a>
<div id="end-of-user-feeds-list" class="end-of-list">No more feed entries</div>
</div>
</div>
</div>
<% end %>
</div>
<div id="profile-bands" class="profile-wrapper profile-body-content">
<br clear="all" />

View File

@ -54,12 +54,20 @@
.clearall.left.w25.ib.mb10
Notation Files:
.right.w75.ib.mb10
List of existing notation files goes here
.w40.left
.selected-files-section
%div{:id => "settings-selected-filenames"}
.right.ib.mb10
%a.button-orange.btn-select-files SELECT FILES...
%input.hidden{:type => "file", :id => "settings-select-files", :value => "Select Files...", :accept => ".pdf, .png, .jpg, .jpeg, .gif, .xml, .mxl, .txt"}
.spinner-small.upload-spinner
.clearall.right.mt10
%a.button-orange{:href => 'TBD', :rel => 'external'} HELP
%a.button-grey{'layout-action' => "close"} CANCEL
%a.button-orange{:id => "session-settings-dialog-submit", :href => "#"} UPDATE SETTINGS
%a.button-orange{:id => "session-settings-dialog-submit"} UPDATE SETTINGS
.clearall
%br/
%br{:clear => 'all'}/

View File

@ -72,8 +72,8 @@
<% end -%>
<% if :feed == filter_label %>
<div class="right mr10">
<a class="button-grey btn-refresh-entries" href="/client#/feed" id="btn-refresh-feed">REFRESH</a>
<div class="btn-refresh-holder">
<a class="button-grey btn-refresh-entries" href="/client#/feed">REFRESH</a>
</div>
<% end %>
<% end -%>

View File

@ -51,6 +51,7 @@
<%= render "account_sessions" %>
<%= render "account_session_detail" %>
<%= render "configure_tracks_dialog" %>
<%= render "edit_recording_dialog" %>
<%= render "invitationDialog" %>
<%= render "inviteMusicians" %>
<%= render "hoverBand" %>
@ -131,6 +132,9 @@
if (this.didInitAfterConnect) return;
this.didInitAfterConnect = true
// This is a helper class with a singleton. No need to instantiate.
JK.GenreSelectorHelper.initialize(JK.app);
var recordingManager = new JK.RecordingManager();
var facebookHelper = new JK.FacebookHelper(JK.app);
@ -158,6 +162,8 @@
var launchAppDialog = new JK.LaunchAppDialog(JK.app);
launchAppDialog.initialize();
var editRecordingDialog = new JK.EditRecordingDialog(JK.app);
editRecordingDialog.initialize();
var userDropdown = new JK.UserDropdown(JK.app);
JK.UserDropdown = userDropdown;
@ -207,9 +213,6 @@
var searchResultScreen = new JK.SearchResultScreen(JK.app);
searchResultScreen.initialize();
// This is a helper class with a singleton. No need to instantiate.
JK.GenreSelectorHelper.initialize();
var inviteMusiciansUtil1 = new JK.InviteMusiciansUtil(JK.app);
inviteMusiciansUtil1.initialize(friendSelectorDialog);
// var createSessionScreen = new JK.CreateSessionScreen(JK.app);

View File

@ -31,7 +31,7 @@
<%= render "layouts/social_meta" %>
<% end %>
</head>
<body class="web jam">
<body class="web jam <%= yield(:page_name) %>">
<%= javascript_include_tag "web/web" %>
<div class="dialog-overlay op70" style="display:none; width:100%; height:100%; z-index:99;"></div>
@ -108,6 +108,7 @@
<% end %>
JK.app = JK.JamKazam();
var jamServer = new JK.JamServer(JK.app, $.noop);
jamServer.initialize();

View File

@ -44,7 +44,7 @@
%strong Notation Files:
.right.w75.ib.mb10.notations
- @music_session.music_notations.each do |n|
%a.gold{:href => n.file_url}= n.file_name
%a.gold{:href => n.file_url, :target => "_blank"}= n.file_name
.clearall.left.w20.ib.mb10
%strong Language:
.right.w75.ib.mb10.language
@ -61,7 +61,7 @@
%a.gold{:href => "#{@music_session.legal_policy_url}", :target => "_blank"} View full legal details
)
%br{clear:'all'}
%br{clear:'all'}/
.landing-sidebar
%br/
@ -69,11 +69,11 @@
%br/
.left.w65.ib
%strong RSVPs
.right.w30.ib.f11.center Your latency
.right.w30.ib.f11 Your latency
- if @approved_rsvps.blank?
None
- @approved_rsvps.each_with_index do |rsvp, index|
.clearall.left.w65.h20.ib.mb10.rsvp-details
.clearall.left.w100.h20.ib.mb10.rsvp-details
.avatar-tiny{'hoveraction' => "musician", 'user-id' => rsvp.id}
- if rsvp.photo_url.nil?
= image_tag 'shared/avatar_generic.png', :alt => ""
@ -84,13 +84,35 @@
.left.ml10
- rsvp.instrument_list.each do |i|
%img.instrument-icon{'instrument-id' => i[:id], height:24, width:24}
.right.w30.ib.f11.center
- if current_user.id == rsvp.id
%table.musicians{:cellpadding => 0, :cellspacing => 0}
%tr.mb15
%td.latency-green GOOD
- elsif !@user_scores[rsvp.id]
%table.musicians{:cellpadding => 0, :cellspacing => 0}
%tr.mb15
%td.latency-grey UNKNOWN
- else
- if @user_scores[rsvp.id] >= 0 && @user_scores[rsvp.id] <= 20.0
%table.musicians{:cellpadding => 0, :cellspacing => 0}
%tr.mb15
%td.latency-green GOOD
- elsif @user_scores[rsvp.id] > 20.0 && @user_scores[rsvp.id] <= 40.0
%table.musicians{:cellpadding => 0, :cellspacing => 0}
%tr.mb15
%td.latency-yellow MEDIUM
- elsif @user_scores[rsvp.id] > 40.0
%table.musicians{:cellpadding => 0, :cellspacing => 0}
%tr.mb15
%td.latency-red POOR
%br{:clear => "all"}/
%br/
.left.w65.ib.still-needed
%strong Still Needed
- if @open_slots.blank?
.clearall.left.w65.h20.ib.mb10
.clearall.left.w100.h20.ib.mb10
All slots are taken
- else
- @open_slots.each do |slot|
@ -141,4 +163,3 @@
var ss = new window.JK.ShowSessionInfo(JK.app);
ss.initialize("#{@music_session.id}");
})

View File

@ -1,5 +1,5 @@
%script{type: 'text/template', id: 'template-feed-recording'}
.feed-entry.recording-entry{:'data-claimed-recording-id' => '{{data.candidate_claimed_recording.id}}' }
.feed-entry.recording-entry{:'data-claimed-recording-id' => '{{data.candidate_claimed_recording.id}}', :'data-recording-id' => '{{data.feed_item.id}}' }
/ avatar
.avatar-small.ib
%a{:hoveraction => "{{data.feed_item.helpers.artist_hoveraction}}", :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :"{{data.feed_item.helpers.artist_datakey}}" => "{{data.feed_item.helpers.artist_id}}"}
@ -8,6 +8,7 @@
.left.ml20.w15
.title{hoveraction: 'recording', :'recording-id' => '{{data.candidate_claimed_recording.id}}' }
%a{:href => "/recordings/{{data.candidate_claimed_recording.id}}", :rel => "external"} RECORDING
%a.edit-recording-dialog{href: "#"} (edit)
.artist
%a.artist{:hoveraction => '{{data.feed_item.helpers.artist_hoveraction}}', :profileaction => "{{data.feed_item.helpers.artist_hoveraction}}", :'{{data.feed_item.helpers.artist_datakey}}' => '{{data.feed_item.helpers.artist_id}}'}
= '{{data.feed_item.helpers.artist_name}}'
@ -16,7 +17,8 @@
/ name and description
.left.ml20.w30
.name.dotdotdot
= '{{data.feed_item.helpers.name}}'
%span.name-text {{data.feed_item.helpers.name}}
%span.is_private (private)
.description.dotdotdot
= '{{data.feed_item.helpers.description}}'
/ timeline and controls
@ -54,7 +56,7 @@
0:00
/ end recording play controls
/ genre and social
.left.small= '{{data.feed_item.helpers.genre}}'
.left.small.genre= '{{data.feed_item.helpers.genre}}'
.right.small.feed-details
%a{title: 'Share', class: 'btn-share'}
= image_tag 'content/icon_share.png', :height => "12", :width => "7"

View File

@ -1,3 +1,5 @@
- provide(:page_name, 'welcome')
.welcome
.landing-tag
%h1 Play music together over the Internet as if in the same room

View File

@ -104,7 +104,7 @@ class MaxMindManager < BaseManager
#end
# returns an ordered array of Region objects (region, regionname, countrycode)
Region.get_all(country).map { |r| r.region }
Region.get_all(country).map { |r| { region: r.region, name: r.regionname } }
end

View File

@ -77,7 +77,7 @@ FactoryGirl.define do
legal_terms true
genre JamRuby::Genre.first
band nil
language 'en'
language 'eng'
end

View File

@ -56,7 +56,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
find('#btn-refresh-feed').trigger(:click)
find('[layout-id="feed"] .btn-refresh-entries').trigger(:click)
find('span.comments').should have_content('1')
# Likes
@ -142,7 +142,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
find('#btn-refresh-feed').trigger(:click)
find('[layout-id="feed"] .btn-refresh-entries').trigger(:click)
find('span.comments').should have_content('1')
# Likes
@ -173,12 +173,6 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do
find("#user-profile h2[id=profile-username]", text: user.name)
end
# it "should render play widget" do
# it " and allow recording playback" do
# end
# end
end
end

View File

@ -1,15 +1,25 @@
require 'spec_helper'
describe "Reset Password", :js => true, :type => :feature, :capybara_feature => true do
describe "Launch App", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
let(:user) { FactoryGirl.create(:user) }
let(:ams) { FactoryGirl.create(:active_music_session, creator: user) }
before(:each) do
ActiveMusicSession.delete_all
MusicSession.delete_all
end
share_examples_for :launch_not_supported do |options|
it "should indicate not supported" do
sign_in_poltergeist user
ams.touch if options[:need_session]
visit options[:screen_path]
if options[:actions]
options[:actions].call(page)
end
should have_selector('h1', text: 'Application Notice')
if options[:gear_modal] && options[:gear_modal] == true
should have_selector('p', text: 'To configure your audio gear, you must use the JamKazam application. Please download and install the application if you have not done so already.')
@ -25,7 +35,11 @@ describe "Reset Password", :js => true, :type => :feature, :capybara_feature =>
share_examples_for :launch_supported do |options|
it "should indicate supported" do
sign_in_poltergeist user
ams.touch if options[:need_session]
visit options[:screen_path]
if options[:actions]
options[:actions].call(page)
end
should have_selector('h1', text: 'Application Notice')
if options[:gear_modal] && options[:gear_modal] == true
should have_selector('p', text: 'To configure your audio gear, you must use the JamKazam application.')
@ -38,12 +52,15 @@ describe "Reset Password", :js => true, :type => :feature, :capybara_feature =>
end
describe "unsupported" do
before do
# emulate mac safari
page.driver.headers = { 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/7.0.2 Safari/537.74.9'}
end
it_behaves_like :launch_not_supported, screen_path: '/client#/createSession'
it_behaves_like :launch_not_supported, screen_path: '/client#/findSession'
it_behaves_like :launch_not_supported, screen_path: '/client#/createSession', actions: lambda { |page| page.find('li[create-type="quick-start"] ins').trigger(:click); page.find('.btn-next').trigger(:click) }
it_behaves_like :launch_not_supported, screen_path: '/client#/createSession', actions: lambda { |page| page.find('li[create-type="immediately"] ins').trigger(:click); page.find('.btn-next').trigger(:click) }
it_behaves_like :launch_not_supported, screen_path: '/client#/createSession', actions: lambda { |page| page.find('li[create-type="start-scheduled"] ins').trigger(:click); page.find('.btn-next').trigger(:click) }
it_behaves_like :launch_not_supported, screen_path: '/client#/findSession', actions: lambda { |page| page.find('.join-link').trigger(:click) }, need_session: true
it_behaves_like :launch_not_supported, screen_path: '/client#/account/audio', gear_modal: true
end
@ -52,8 +69,10 @@ describe "Reset Password", :js => true, :type => :feature, :capybara_feature =>
# emulate chrome
page.driver.headers = { 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36'}
end
it_behaves_like :launch_supported, screen_path: '/client#/createSession'
it_behaves_like :launch_supported, screen_path: '/client#/findSession'
it_behaves_like :launch_supported, screen_path: '/client#/createSession', actions: lambda { |page| page.find('li[create-type="quick-start"] ins').trigger(:click); page.find('.btn-next').trigger(:click) }
it_behaves_like :launch_supported, screen_path: '/client#/createSession', actions: lambda { |page| page.find('li[create-type="immediately"] ins').trigger(:click); page.find('.btn-next').trigger(:click) }
it_behaves_like :launch_supported, screen_path: '/client#/createSession', actions: lambda { |page| page.find('li[create-type="start-scheduled"] ins').trigger(:click); page.find('.btn-next').trigger(:click) }
it_behaves_like :launch_supported, screen_path: '/client#/findSession', actions: lambda { |page| page.find('.join-link').trigger(:click) }, need_session: true
it_behaves_like :launch_supported, screen_path: '/client#/account/audio', gear_modal: true
end
end

View File

@ -0,0 +1,196 @@
require 'spec_helper'
describe "Profile History", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
let(:user) { FactoryGirl.create(:user) }
before do
MusicSession.delete_all
Recording.delete_all
sign_in_poltergeist user
stub_const("APP_CONFIG", web_config)
end
it "loads empty history" do
nav_profile_history(user)
find('.no-feed-msg', text: 'This user has no history.')
end
# the same feedHelper instance is used to show any user's feed. so we need to make sure bouncing back and forth
# between feeds is safe
it "between users" do
create_session(creator: user)
formal_leave_by(user)
user2 = FactoryGirl.create(:user)
nav_profile_history(user)
find('.feed-entry.music-session-history-entry')
visit Nav.feed # to get around bug where you can't cause a screen to update if it's the same screen, different params
nav_profile_history(user2)
should_not have_selector('.feed-entry.music-session-history-entry')
find('.no-feed-msg', text: 'This user has no history.')
end
describe "sessions" do
before(:each) do
create_session(creator: user)
formal_leave_by(user)
end
it "should render stats" do
nav_profile_history(user)
# initial stats
find('span.plays').should have_content('0')
find('span.comments').should have_content('0')
find('span.likes').should have_content('0')
# Comments
find('a.btn-comment').trigger(:click)
comment = 'this sounds great'
fill_in "txtComment", with: comment
find('#btn-add-comment').trigger(:click)
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
find('span.comments').should have_content('1')
# Likes
find('a.btn-like').trigger(:click)
find('span.likes').should have_content('1')
end
it "should render details" do
nav_profile_history(user)
find('.feed-details a.details').trigger(:click)
# confirm user avatar exists
find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"] img")
# confirm user name exists
find("a.musician-name[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name)
# confirm instrument icons exist
find("img[instrument-id=\"electric guitar\"]")
# confirm hover bubbles show
find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").hover_intent
# confirm navigate to user profile page
find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click)
find("#user-profile h2[id=profile-username]", text: user.name)
end
end
describe "recordings" do
before(:each) do
start_recording_with(user)
stop_recording
claim_recording("my recording", "my recording description")
formal_leave_by(user)
MusicSession.delete_all
end
# it "should render avatar" do
# it " and link to profile" do
# end
# it " and render artist hover bubble" do
# end
# end
# it "should render description" do
# it " and link to recording landing" do
# end
# it " and render recording hover bubble" do
# end
# end
# it "should render artist name" do
# it " and link to profile" do
# end
# it " and render artist hover bubble"
# end
it "should render stats" do
nav_profile_history(user)
# initial stats
find('span.plays').should have_content('0')
find('span.comments').should have_content('0')
find('span.likes').should have_content('0')
# ensure Share icon exists
find('a.btn-share')
# Comments
find('a.btn-comment').trigger(:click)
comment = 'this sounds great'
fill_in "txtComment", with: comment
find('#btn-add-comment').trigger(:click)
find('div.comment-text', text: comment)
find('#dialog-close-button', '[layout-id="comment-dialog"]').trigger(:click)
find('span.comments').should have_content('1')
# Likes
find('a.btn-like').trigger(:click)
find('span.likes').should have_content('1')
end
it "should render details" do
nav_profile_history(user)
find('.feed-details a.details').trigger(:click)
# confirm user avatar exists
find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"] img")
# confirm user name exists
find("a.musician-name[user-id=\"#{user.id}\"][hoveraction=\"musician\"]", text: user.name)
# confirm instrument icons exist
find("img[instrument-id=\"electric guitar\"]")
# confirm hover bubbles show
find("a.avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").hover_intent
# confirm navigate to user profile page
find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click)
find("#user-profile h2[id=profile-username]", text: user.name)
end
it "should allow edit and delete" do
nav_profile_history(user)
# edit it's name
find('.edit-recording-dialog').trigger(:click)
find('h1', text: 'Edit Recording')
fill_in 'name', with: 'some crazy name'
find('.save-btn').trigger(:click)
should_not have_selector('h1', text: 'Edit Recording')
find('.name-text', text:'some crazy name')
# now delete it
find('.edit-recording-dialog').trigger(:click)
find('h1', text: 'Edit Recording')
find('.delete-btn').trigger(:click)
# confirm...
find('.yes-btn').trigger(:click)
should_not have_selector('h1', text: 'Edit Recording')
should_not have_selector('.feed-entry.recording-entry')
ClaimedRecording.first.discarded.should == true
end
end
end

View File

@ -85,7 +85,7 @@ describe "Session Info", :js => true, :type => :feature, :capybara_feature => tr
if options[:show_cta]
find('#btn-action', :text => options[:button_text])
else
expect {find('#btn-action')}.to raise_error(Capybara::ElementNotFound)
should_not have_selector('#btn-action')
end
# session details
@ -124,66 +124,56 @@ describe "Session Info", :js => true, :type => :feature, :capybara_feature => tr
@music_session.save
# attempt to access with musician who was invited but didn't RSVP
sign_in_poltergeist(@session_invitee)
visit @url
fast_signin(@session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})
sign_out_poltergeist
fast_signout
# attempt to access with musician who wasn't invited
sign_in_poltergeist(@non_session_invitee)
visit @url
fast_signin(@non_session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed but wasn't approved
sign_in_poltergeist(@rsvp_declined_user)
visit @url
fast_signin(@rsvp_declined_user, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed and was approved
sign_in_poltergeist(@rsvp_approved_user)
visit @url
fast_signin(@rsvp_approved_user, @url)
ensure_success({:show_cta => true, :button_text => 'CANCEL RSVP'})
sign_out_poltergeist
fast_signout
# attempt to access with session creator
sign_in_poltergeist(@session_creator)
visit @url
fast_signin(@session_creator, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
end
it "should render only for session invitees for sessions with closed RSVPs before session starts" do
# attempt to access with musician who was invited but didn't RSVP
sign_in_poltergeist(@session_invitee)
visit @url
fast_signin(@session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})
sign_out_poltergeist
fast_signout
# attempt to access with musician who wasn't invited
sign_in_poltergeist(@non_session_invitee)
visit @url
fast_signin(@non_session_invitee, @url)
ensure_failure # NON-INVITEE SHOULD NOT BE ABLE TO VIEW FOR CLOSED RSVPs
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed but wasn't approved
sign_in_poltergeist(@rsvp_declined_user)
visit @url
fast_signin(@rsvp_declined_user, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed and was approved
sign_in_poltergeist(@rsvp_approved_user)
visit @url
fast_signin(@rsvp_approved_user, @url)
ensure_success({:show_cta => true, :button_text => 'CANCEL RSVP'})
sign_out_poltergeist
fast_signout
# attempt to access with session creator
sign_in_poltergeist(@session_creator)
visit @url
fast_signin(@session_creator, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
end
########### AFTER SESSION STARTS ###########
@ -196,100 +186,85 @@ describe "Session Info", :js => true, :type => :feature, :capybara_feature => tr
@music_session.save!
# attempt to access with musician who was invited but didn't RSVP
sign_in_poltergeist(@session_invitee)
visit @url
fast_signin(@session_invitee, @url)
ensure_failure
sign_out_poltergeist
fast_signout
# attempt to access with musician who wasn't invited
sign_in_poltergeist(@non_session_invitee)
visit @url
fast_signin(@non_session_invitee, @url)
ensure_failure
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed but wasn't approved
sign_in_poltergeist(@rsvp_declined_user)
visit @url
fast_signin(@rsvp_declined_user, @url)
ensure_failure
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed and was approved
sign_in_poltergeist(@rsvp_approved_user)
visit @url
fast_signin(@rsvp_approved_user, @url)
ensure_success({:show_cta => true, :button_text => 'CANCEL RSVP'})
sign_out_poltergeist
fast_signout
# attempt to access with session creator
sign_in_poltergeist(@session_creator)
visit @url
fast_signin(@session_creator, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
end
# musician_access = true, approval_required = false
it "should allow anyone to view for 'at will' option after session starts" do
# attempt to access with musician who was invited but didn't RSVP
sign_in_poltergeist(@session_invitee)
visit @url
fast_signin(@session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})
sign_out_poltergeist
fast_signout
# attempt to access with musician who wasn't invited
sign_in_poltergeist(@non_session_invitee)
visit @url
fast_signin(@non_session_invitee, @url)
ensure_failure # NON-INVITEE SHOULD NOT BE ABLE TO VIEW FOR CLOSED RSVPs
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed but wasn't approved
sign_in_poltergeist(@rsvp_declined_user)
visit @url
fast_signin(@rsvp_declined_user, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed and was approved
sign_in_poltergeist(@rsvp_approved_user)
visit @url
fast_signin(@rsvp_approved_user, @url)
ensure_success({:show_cta => true, :button_text => 'CANCEL RSVP'})
sign_out_poltergeist
fast_signout
# attempt to access with session creator
sign_in_poltergeist(@session_creator)
visit @url
fast_signin(@session_creator, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
end
# musician_access = true, approval_required = true
it "should allow anyone to view for 'join by approval' option after session starts" do
# attempt to access with musician who was invited but didn't RSVP
sign_in_poltergeist(@session_invitee)
visit @url
fast_signin(@session_invitee, @url)
ensure_success({:show_cta => true, :button_text => 'RSVP NOW!'})
sign_out_poltergeist
fast_signout
# attempt to access with musician who wasn't invited
sign_in_poltergeist(@non_session_invitee)
visit @url
fast_signin(@non_session_invitee, @url)
ensure_failure # NON-INVITEE SHOULD NOT BE ABLE TO VIEW FOR CLOSED RSVPs
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed but wasn't approved
sign_in_poltergeist(@rsvp_declined_user)
visit @url
fast_signin(@rsvp_declined_user, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
# attempt to access with musician who RSVP'ed and was approved
sign_in_poltergeist(@rsvp_approved_user)
visit @url
fast_signin(@rsvp_approved_user, @url)
ensure_success({:show_cta => true, :button_text => 'CANCEL RSVP'})
sign_out_poltergeist
fast_signout
# attempt to access with session creator
sign_in_poltergeist(@session_creator)
visit @url
fast_signin(@session_creator, @url)
ensure_success({:show_cta => false})
sign_out_poltergeist
fast_signout
end
it "should show no call to action button if user has not RSVPed and all slots are taken" do
@ -299,10 +274,9 @@ describe "Session Info", :js => true, :type => :feature, :capybara_feature => tr
# approve slot 2 as well to make all slots taken for this session
RsvpRequest.update({:id => @rsvp1.id, :session_id => @music_session.id, :rsvp_responses => [{:request_slot_id => rs2.id, :accept => true}]}, @session_creator)
sign_in_poltergeist(@session_invitee)
visit @url
fast_signin(@session_invitee, @url)
expect {find('#btn-action')}.to raise_error(Capybara::ElementNotFound)
sign_out_poltergeist
fast_signout
end
end

View File

@ -19,8 +19,8 @@ describe MaxMindManager do
it "looks up regions successfully" do
regions = MaxMindManager.regions("US")
regions.length.should == 4
regions.first.should == "AB"
regions.last.should == "DE"
regions.first[:region].should == "AB"
regions.last[:region].should == "DE"
end
it "looks up cities successfully" do

View File

@ -27,13 +27,13 @@ describe MusicSessionManager do
it "updates a session properly" do
active_music_session = @music_session_manager.create(music_session, @user, @connection.client_id, "description", true, false, true, true, @band, [@genre], @tracks, true, 10)
@music_session_manager.update(music_session, "updated description", nil, nil, nil, nil, nil)
@music_session_manager.update(music_session, nil, "updated description", nil, nil, nil, nil, nil, nil)
music_session.reload
music_session.description.should == "updated description"
# Verify that this didnt change
music_session.approval_required.should == false
genre2 = FactoryGirl.create(:genre)
@music_session_manager.update(music_session, nil, [@genre, genre2], nil, nil, nil, nil)
@music_session_manager.update(music_session, nil, nil, @genre, nil, nil, nil, nil, nil)
music_session.reload
music_session.description.should == "updated description"
music_session.genre.should == @genre

View File

@ -182,7 +182,7 @@ describe "Active Music Session API ", :type => :api do
end
it "updated genres" do
put "/api/sessions/#{music_session.id}.json", {:genres => ["jazz"]}.to_json, "CONTENT_TYPE" => 'application/json'
put "/api/sessions/#{music_session.id}.json", {:genre => "jazz"}.to_json, "CONTENT_TYPE" => 'application/json'
last_response.status.should eql(204)
get "/api/sessions/#{music_session.id}.json", "CONTENT_TYPE" => 'application/json'
updated_session = JSON.parse(last_response.body)

View File

@ -17,6 +17,35 @@ def web_config
def external_root_url
"#{external_protocol}#{external_hostname}#{(external_port == 80 || external_port == 443) ? '' : ':' + external_port.to_s}"
end
def aws_bucket
JAMKAZAM_TESTING_BUCKET
end
def aws_access_key_id
'AKIAJESQY24TOT542UHQ'
end
def aws_secret_access_key
'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'
end
def aws_region
'us-east-1'
end
def aws_bucket_public
'jamkazam-testing-public'
end
def aws_cache
'315576000'
end
def max_audio_downloads
10
end
end
klass.new
end

View File

@ -110,6 +110,17 @@ def sign_in_poltergeist(user, options = {})
page.should have_no_selector('.no-websocket-connection') if validate
end
# skip the typical login form, which redirects to /client (sloooow). Just sets the cookie, and puts you where you want to be
def fast_signin(user, url)
page.driver.set_cookie(:remember_token, user.remember_token)
visit url
end
#skip the 'hunt' for Sign Out, and redirect after. Just empty cookie, and go to '/'
def fast_signout
page.driver.set_cookie(:remember_token, '')
visit '/'
end
def sign_out()
if Capybara.javascript_driver == :poltergeist
@ -570,3 +581,8 @@ def garbage length
length.times { output << special_characters.sample }
output.slice(0, length)
end
def nav_profile_history(user)
visit Nav.profile(user)
find('#profile-history-link').trigger(:click)
end