* adding 'select' to dialog-inner so it'll look JKish

This commit is contained in:
Seth Call 2013-12-20 22:24:14 +00:00
commit a68235f280
70 changed files with 3287 additions and 946 deletions

View File

@ -78,4 +78,6 @@ add_user_bio.sql
users_geocoding.sql
recordings_public_launch.sql
notification_band_invite.sql
band_photo_filepicker.sql
bands_geocoding.sql
store_s3_filenames.sql

View File

@ -0,0 +1,4 @@
ALTER TABLE bands ADD COLUMN original_fpfile_photo VARCHAR(8000) DEFAULT NULL;
ALTER TABLE bands ADD COLUMN cropped_fpfile_photo VARCHAR(8000) DEFAULT NULL;
ALTER TABLE bands ADD COLUMN cropped_s3_path_photo VARCHAR(512) DEFAULT NULL;
ALTER TABLE bands ADD COLUMN crop_selection_photo VARCHAR(256) DEFAULT NULL;

View File

@ -0,0 +1,4 @@
ALTER TABLE bands ADD COLUMN lat NUMERIC(15,10);
ALTER TABLE bands ADD COLUMN lng NUMERIC(15,10);
UPDATE bands SET country = 'US' WHERE country = 'USA';

View File

@ -1,12 +1,20 @@
module JamRuby
class Band < ActiveRecord::Base
attr_accessible :name, :website, :biography, :city, :state, :country
attr_accessible :name, :website, :biography, :city, :state,
:country, :original_fpfile_photo, :cropped_fpfile_photo,
:cropped_s3_path_photo, :crop_selection_photo, :photo_url
attr_accessor :updating_photo
self.primary_key = 'id'
before_save :stringify_photo_info , :if => :updating_photo
validate :validate_photo_info
validates :biography, no_profanity: true
before_save :check_lat_lng
# musicians
has_many :band_musicians, :class_name => "JamRuby::BandMusician"
has_many :users, :through => :band_musicians, :class_name => "JamRuby::User"
@ -34,6 +42,9 @@ module JamRuby
has_many :music_sessions, :class_name => "JamRuby::MusicSession", :foreign_key => "band_id"
has_many :music_session_history, :class_name => "JamRuby::MusicSessionHistory", :foreign_key => "band_id", :inverse_of => :band
include Geokit::ActsAsMappable::Glue unless defined?(acts_as_mappable)
acts_as_mappable
def liker_count
return self.likers.size
end
@ -57,6 +68,14 @@ module JamRuby
loc
end
def validate_photo_info
if updating_photo
# we want to mak sure that original_fpfile and cropped_fpfile seems like real fpfile info objects (i.e, json objects from filepicker.io)
errors.add(:original_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.original_fpfile_photo.nil? || self.original_fpfile_photo["key"].nil? || self.original_fpfile_photo["url"].nil?
errors.add(:cropped_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.cropped_fpfile_photo.nil? || self.cropped_fpfile_photo["key"].nil? || self.cropped_fpfile_photo["url"].nil?
end
end
def add_member(user_id, admin)
BandMusician.create(:band_id => self.id, :user_id => user_id, :admin => admin)
end
@ -82,11 +101,11 @@ module JamRuby
if hide_private
recordings = Recording.joins(:band_recordings)
.where(:bands_recordings => {:band_id => "#{band_id}"}, :public => true)
.where(:bands_recordings => {:band_id => "#{band_id}"}, :public => true)
else
recordings = Recording.joins(:band_recordings)
.where(:bands_recordings => {:band_id => "#{band_id}"})
.where(:bands_recordings => {:band_id => "#{band_id}"})
end
return recordings
@ -111,11 +130,10 @@ module JamRuby
# helper method for creating / updating a Band
def self.save(id, name, website, biography, city, state, country, genres, user_id, photo_url, logo_url)
user = User.find(user_id)
# new band
if id.nil?
if id.blank?
# ensure person creating this Band is a Musician
unless user.musician?
@ -125,7 +143,7 @@ module JamRuby
validate_genres(genres, false)
band = Band.new()
# band update
# band update
else
validate_genres(genres, true)
band = Band.find(id)
@ -154,56 +172,115 @@ module JamRuby
# country
band.country = country unless country.nil?
# genres
unless genres.nil?
ActiveRecord::Base.transaction do
# delete all genres for this band first
unless band.id.nil? || band.id.length == 0
band.genres.delete_all
end
# loop through each genre in the array and save to the db
genres.each do |genre_id|
g = Genre.find(genre_id)
band.genres << g
end
end
end
# photo url
band.photo_url = photo_url unless photo_url.nil?
# logo url
band.logo_url = logo_url unless logo_url.nil?
band.updated_at = Time.now.getutc
band.save
# band.updated_at = Time.now.getutc
band.save!
band.reload
# genres
unless genres.nil?
ActiveRecord::Base.transaction do
# delete all genres for this band first
band.genres.delete_all if id.present?
# loop through each genre in the array and save to the db
genres.each { |genre_id| band.genres << Genre.find(genre_id) }
end
end
# add the creator as the admin
if id.nil?
BandMusician.create(:band_id => band.id, :user_id => user_id, :admin => true)
end
BandMusician.create(:band_id => band.id, :user_id => user_id, :admin => true) if id.blank?
return band
end
private
def self.validate_genres(genres, is_nil_ok)
if is_nil_ok && genres.nil?
return
def update_photo(original_fpfile, cropped_fpfile, crop_selection, aws_bucket)
self.updating_photo = true
cropped_s3_path = cropped_fpfile["key"]
return self.update_attributes(
:original_fpfile_photo => original_fpfile,
:cropped_fpfile_photo => cropped_fpfile,
:cropped_s3_path_photo => cropped_s3_path,
:crop_selection_photo => crop_selection,
:photo_url => S3Util.url(aws_bucket, cropped_s3_path, :secure => false)
)
end
def delete_photo(aws_bucket)
Band.transaction do
unless self.cropped_s3_path_photo.nil?
S3Util.delete(aws_bucket, File.dirname(self.cropped_s3_path_photo) + '/cropped.jpg')
S3Util.delete(aws_bucket, self.cropped_s3_path_photo)
end
if genres.nil?
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_MINIMUM_NOT_MET
else
if genres.size < Limits::MIN_GENRES_PER_BAND
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_MINIMUM_NOT_MET
end
return self.update_attributes(
:original_fpfile_photo => nil,
:cropped_fpfile_photo => nil,
:cropped_s3_path_photo => nil,
:crop_selection_photo => nil,
:photo_url => nil
)
end
end
if genres.size > Limits::MAX_GENRES_PER_BAND
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_LIMIT_EXCEEDED
def check_lat_lng
if (city_changed? || state_changed? || country_changed?)
update_lat_lng
end
true
end
def update_lat_lng
if self.city
query = { :city => self.city }
query[:region] = self.state unless self.state.blank?
query[:country] = self.country unless self.country.blank?
if geo = MaxMindGeo.where(query).limit(1).first
if geo.lat && geo.lng && (self.lat != geo.lat || self.lng != geo.lng)
self.lat, self.lng = geo.lat, geo.lng
return true
end
end
end
self.lat, self.lng = nil, nil
false
end
private
def self.validate_genres(genres, is_nil_ok)
if is_nil_ok && genres.nil?
return
end
if genres.nil?
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_MINIMUM_NOT_MET
else
if genres.size < Limits::MIN_GENRES_PER_BAND
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_MINIMUM_NOT_MET
end
if genres.size > Limits::MAX_GENRES_PER_BAND
raise JamRuby::JamArgumentError, ValidationMessages::GENRE_LIMIT_EXCEEDED
end
end
end
def stringify_photo_info
# fpfile comes in as a hash, which is a easy-to-use and validate form. However, we store it as a VARCHAR,
# so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
# later, when serving this data out to the REST API, we currently just leave it as a string and make a JSON capable
# client parse it, because it's very rare when it's needed at all
self.original_fpfile_photo = original_fpfile_photo.to_json if !original_fpfile_photo.nil?
self.cropped_fpfile_photo = cropped_fpfile_photo.to_json if !cropped_fpfile_photo.nil?
self.crop_selection_photo = crop_selection_photo.to_json if !crop_selection_photo.nil?
end
end
end

View File

@ -40,7 +40,35 @@ module JamRuby
end
end
User.find_each { |usr| usr.update_lat_lng }
Band.find_each { |bnd| bnd.update_lat_lng }
end
def self.where_latlng(relation, params, current_user=nil)
if 0 < (distance = params[:distance].to_i)
latlng = []
if location_city = params[:city]
if geo = self.where(:city => params[:city]).limit(1).first
latlng = [geo.lat, geo.lng]
end
elsif current_user
if current_user.lat.nil? || current_user.lng.nil?
if params[:remote_ip] && (geo = self.ip_lookup(params[:remote_ip]))
latlng = [geo.lat, geo.lng] if geo.lat && geo.lng
end
else
latlng = [current_user.lat, current_user.lng]
end
elsif params[:remote_ip] && (geo = self.ip_lookup(params[:remote_ip]))
latlng = [geo.lat, geo.lng] if geo.lat && geo.lng
end
if latlng.present?
relation = relation.where(['lat IS NOT NULL AND lng IS NOT NULL'])
.within(distance, :origin => latlng)
end
end
relation
end
end
end

View File

@ -2,6 +2,7 @@ module JamRuby
# not a active_record model; just a search result
class Search
attr_accessor :bands, :musicians, :fans, :recordings, :friends, :search_type
attr_accessor :bands_filter, :musicians_filter
LIMIT = 10
# performs a site-white search
@ -30,6 +31,8 @@ module JamRuby
@fans = []
@recordings = []
@friends = []
@musicians_filter = []
@bands_filter = []
if search_results.nil?
return
@ -39,7 +42,6 @@ module JamRuby
if result.class == User
if result.musician
@musicians.push(result)
@search_type = PARAM_MUSICIAN
else
@fans.push(result)
end
@ -84,19 +86,23 @@ module JamRuby
attr_accessor :user_counters, :page_num, :page_count
PARAM_MUSICIAN = :srch_m
PARAM_BAND = :srch_b
M_PER_PAGE = 10
B_PER_PAGE = M_PER_PAGE = 10
M_MILES_DEFAULT = 500
B_MILES_DEFAULT = 0
M_ORDER_FOLLOWS = ['Most Followed', :followed]
M_ORDER_PLAYS = ['Most Plays', :plays]
M_ORDER_PLAYING = ['Playing Now', :playing]
M_ORDERINGS = [M_ORDER_FOLLOWS, M_ORDER_PLAYS, M_ORDER_PLAYING]
M_ORDERING_KEYS = M_ORDERINGS.collect { |oo| oo[1] }
ORDERINGS = B_ORDERINGS = M_ORDERINGS = [M_ORDER_FOLLOWS, M_ORDER_PLAYS, M_ORDER_PLAYING]
B_ORDERING_KEYS = M_ORDERING_KEYS = M_ORDERINGS.collect { |oo| oo[1] }
def self.musician_order_param(params)
DISTANCE_OPTS = B_DISTANCE_OPTS = M_DISTANCE_OPTS = [['Any', 0], [1000.to_s, 1000], [500.to_s, 500], [250.to_s, 250], [100.to_s, 100], [50.to_s, 50], [25.to_s, 25]]
def self.order_param(params, keys=M_ORDERING_KEYS)
ordering = params[:orderby]
ordering.blank? ? M_ORDERING_KEYS[0] : M_ORDERING_KEYS.detect { |oo| oo.to_s == ordering }
ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering }
end
def self.musician_search(params={}, current_user=nil)
@ -106,31 +112,10 @@ module JamRuby
.where(['minst.instrument_id = ? AND users.id IS NOT NULL', instrument])
end
location_distance, location_city = params[:distance], params[:city]
distance, latlng = nil, []
if location_distance && location_city
if geo = MaxMindGeo.where(:city => params[:city]).limit(1).first
distance, latlng = location_distance, [geo.lat, geo.lng]
end
elsif current_user
if current_user.lat.nil? || current_user.lng.nil?
if params[:remote_ip] && (geo = MaxMindGeo.ip_lookup(params[:remote_ip]))
latlng = [geo.lat, geo.lng] if geo.lat && geo.lng
end
else
latlng = [current_user.lat, current_user.lng]
end
elsif params[:remote_ip] && (geo = MaxMindGeo.ip_lookup(params[:remote_ip]))
latlng = [geo.lat, geo.lng] if geo.lat && geo.lng
end
if latlng.present?
distance ||= location_distance || M_MILES_DEFAULT
rel = rel.where(['lat IS NOT NULL AND lng IS NOT NULL'])
.within(distance, :origin => latlng)
end
rel = MaxMindGeo.where_latlng(rel, params, current_user)
sel_str = 'users.*'
case ordering = self.musician_order_param(params)
case ordering = self.order_param(params)
when :plays # FIXME: double counting?
sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}"
rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id")
@ -150,9 +135,7 @@ module JamRuby
end
rel = rel.select(sel_str)
perpage = [(params[:per_page] || M_PER_PAGE).to_i, 100].min
page = [params[:page].to_i, 1].max
rel = rel.paginate(:page => page, :per_page => perpage)
rel, page = self.relation_pagination(rel, params)
rel = rel.includes([:instruments, :followings, :friends])
objs = rel.all
@ -161,6 +144,12 @@ module JamRuby
srch.musician_results_for_user(objs, current_user)
end
def self.relation_pagination(rel, params)
perpage = [(params[:per_page] || M_PER_PAGE).to_i, 100].min
page = [params[:page].to_i, 1].max
[rel.paginate(:page => page, :per_page => perpage), page]
end
RESULT_FOLLOW = :follows
RESULT_FRIEND = :friends
@ -171,10 +160,10 @@ module JamRuby
COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION]
def musician_results_for_user(results, user)
@search_type, @musicians = PARAM_MUSICIAN, results
@search_type, @musicians_filter = PARAM_MUSICIAN, results
if user
@user_counters = results.inject({}) { |hh,val| hh[val.id] = []; hh }
mids = "'#{@musicians.map(&:id).join("','")}'"
mids = "'#{@musicians_filter.map(&:id).join("','")}'"
# this gets counts for each search result on friends/follows/records/sessions
results.each do |uu|
@ -258,5 +247,73 @@ module JamRuby
end
end
def self.band_search(params={}, current_user=nil)
rel = Band.scoped
unless (genre = params[:genre]).blank?
rel = Band.joins("RIGHT JOIN bands_genres AS bgenres ON bgenres.band_id = bands.id")
.where(['bgenres.genre_id = ? AND bands.id IS NOT NULL', genre])
end
rel = MaxMindGeo.where_latlng(rel, params, current_user)
sel_str = 'bands.*'
case ordering = self.order_param(params)
when :plays # FIXME: double counting?
sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}"
rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.band_id = bands.id")
.joins("LEFT JOIN recordings AS records ON records.band_id = bands.id")
.group("bands.id")
.order("play_count DESC, bands.created_at DESC")
when :followed
sel_str = "COUNT(follows) AS search_follow_count, #{sel_str}"
rel = rel.joins("LEFT JOIN bands_followers AS follows ON follows.band_id = bands.id")
.group("bands.id")
.order("COUNT(follows) DESC, bands.created_at DESC")
when :playing
rel = rel.joins("LEFT JOIN music_sessions_history AS msh ON msh.band_id = bands.id")
.where('msh.music_session_id IS NOT NULL AND msh.session_removed_at IS NULL')
.order("bands.created_at DESC")
end
rel = rel.select(sel_str)
rel, page = self.relation_pagination(rel, params)
rel = rel.includes([{ :users => :instruments }, :genres ])
objs = rel.all
srch = Search.new
srch.page_num, srch.page_count = page, objs.total_pages
srch.band_results_for_user(objs, current_user)
end
def band_results_for_user(results, user)
@search_type, @bands_filter = PARAM_BAND, results
if user
@user_counters = results.inject({}) { |hh,val| hh[val.id] = []; hh }
mids = "'#{@bands_filter.map(&:id).join("','")}'"
# this gets counts for each search result
results.each do |bb|
counters = { }
counters[COUNT_FOLLOW] = BandFollowing.where(:band_id => bb.id).count
counters[COUNT_RECORD] = Recording.where(:band_id => bb.id).count
counters[COUNT_SESSION] = MusicSession.where(:band_id => bb.id).count
@user_counters[bb.id] << counters
end
# this section determines follow/like/friend status for each search result
# so that action links can be activated or not
rel = Band.select("bands.id AS bid")
rel = rel.joins("LEFT JOIN bands_followers AS follows ON follows.follower_id = '#{user.id}'")
rel = rel.where(["bands.id IN (#{mids}) AND follows.band_id = bands.id"])
rel.all.each { |val| @user_counters[val.bid] << RESULT_FOLLOW }
else
@user_counters = {}
end
self
end
end
end

View File

@ -261,6 +261,10 @@ module JamRuby
return self.band_likes.size
end
def following?(user)
self.followings.exists?(user)
end
def follower_count
return self.followers.size
end
@ -618,6 +622,10 @@ module JamRuby
favorite.save
end
def favorite_count
0 # FIXME: update this with recording likes count when implemented
end
def self.delete_favorite(user_id, recording_id)
JamRuby::UserFavorite.delete_all "(user_id = '#{user_id}' AND recording_id = '#{recording_id}')"
end

View File

@ -0,0 +1,187 @@
require 'spec_helper'
describe 'Band search' do
before(:each) do
@geocode1 = FactoryGirl.create(:geocoder)
@geocode2 = FactoryGirl.create(:geocoder)
@bands = []
@bands << @band1 = FactoryGirl.create(:band)
@bands << @band2 = FactoryGirl.create(:band)
@bands << @band3 = FactoryGirl.create(:band)
@bands << @band4 = FactoryGirl.create(:band)
@bands.each do |bb|
FactoryGirl.create(:band_musician, :band => bb, :user => FactoryGirl.create(:user))
(rand(4)+1).downto(1) do |nn|
FactoryGirl.create(:band_musician, :band => bb, :user => FactoryGirl.create(:user))
end
end
end
context 'default filter settings' do
it "finds all bands" do
# expects all the bands
num = Band.count
results = Search.band_search({ :per_page => num })
expect(results.bands_filter.count).to eq(num)
end
it "finds bands with proper ordering" do
# the ordering should be create_at since no followers exist
expect(BandFollower.count).to eq(0)
results = Search.band_search({ :per_page => Band.count })
results.bands_filter.each_with_index do |uu, idx|
expect(uu.id).to eq(@bands.reverse[idx].id)
end
end
it "sorts bands by followers" do
users = []
4.downto(1) { |nn| users << FactoryGirl.create(:user) }
# establish sorting order
@band4.followers.concat(users[1..-1])
@band3.followers.concat(users[1..3])
@band2.followers.concat(users[0])
@bands.map(&:reload)
expect(@band4.followers.count).to be 3
expect(BandFollower.count).to be 7
# refresh the order to ensure it works right
@band2.followers.concat(users[1..-1])
results = Search.band_search({ :per_page => @bands.size }, users[0])
expect(results.bands_filter[0].id).to eq(@band2.id)
# check the follower count for given entry
expect(results.bands_filter[0].search_follow_count.to_i).not_to eq(0)
# check the follow relationship between current_user and result
expect(results.is_follower?(@band2)).to be true
end
it 'paginates properly' do
# make sure pagination works right
params = { :per_page => 2, :page => 1 }
results = Search.band_search(params)
expect(results.bands_filter.count).to be 2
end
end
def make_session(band)
usr = band.users[0]
session = FactoryGirl.create(:music_session, :creator => usr, :description => "Session", :band => band)
FactoryGirl.create(:connection, :user => usr, :music_session => session)
user = FactoryGirl.create(:user)
session
end
context 'band stat counters' do
it "follow stat shows follower count" do
users = []
2.downto(1) { |nn| users << FactoryGirl.create(:user) }
# establish sorting order
@band1.followers.concat(users)
results = Search.band_search({},@band1)
uu = results.bands_filter.detect { |mm| mm.id == @band1.id }
expect(uu).to_not be_nil
expect(results.follow_count(uu)).to eq(users.count)
end
it "session stat shows session count" do
make_session(@band1)
@band1.reload
results = Search.band_search({},@band1)
uu = results.bands_filter.detect { |mm| mm.id == @band1.id }
expect(uu).to_not be_nil
expect(results.session_count(uu)).to be 1
end
end
context 'band sorting' do
it "by plays" do
make_session(@band2)
make_session(@band2)
make_session(@band2)
make_session(@band1)
# order results by num recordings
results = Search.band_search({ :orderby => 'plays' })
expect(results.bands_filter[0].id).to eq(@band2.id)
expect(results.bands_filter[1].id).to eq(@band1.id)
end
it "by now playing" do
# should get 1 result with 1 active session
session = make_session(@band3)
FactoryGirl.create(:music_session_history, :music_session => session)
results = Search.band_search({ :orderby => 'playing' })
expect(results.bands_filter.count).to be 1
expect(results.bands_filter.first.id).to eq(@band3.id)
# should get 2 results with 2 active sessions
# sort order should be created_at DESC
session = make_session(@band4)
FactoryGirl.create(:music_session_history, :music_session => session)
results = Search.band_search({ :orderby => 'playing' })
expect(results.bands_filter.count).to be 2
expect(results.bands_filter[0].id).to eq(@band4.id)
expect(results.bands_filter[1].id).to eq(@band3.id)
end
end
context 'filter settings' do
it "searches bands for a genre" do
genre = FactoryGirl.create(:genre)
@band1.genres << genre
@band1.reload
ggg = @band1.genres.detect { |gg| gg.id == genre.id }
expect(ggg).to_not be_nil
results = Search.band_search({ :genre => ggg.id })
results.bands_filter.each do |rr|
expect(rr.genres.detect { |gg| gg.id==ggg.id }.id).to eq(genre.id)
end
expect(results.bands_filter.count).to be 1
end
it "finds bands within a given distance of given location" do
num = Band.count
expect(@band1.lat).to_not be_nil
# short distance
results = Search.band_search({ :per_page => num,
:distance => 10,
:city => 'Apex' }, @band1)
expect(results.bands_filter.count).to be num
# long distance
results = Search.band_search({ :per_page => num,
:distance => 1000,
:city => 'Miami',
:state => 'FL' }, @band1)
expect(results.bands_filter.count).to be num
end
it "finds bands within a given distance of bands location" do
expect(@band1.lat).to_not be_nil
# uses the location of @band1
results = Search.band_search({ :distance => 10, :per_page => Band.count }, @band1)
expect(results.bands_filter.count).to be Band.count
end
it "finds no bands within a given distance of location" do
expect(@band1.lat).to_not be_nil
results = Search.band_search({ :distance => 10, :city => 'San Francisco' }, @band1)
expect(results.bands_filter.count).to be 0
end
end
end

View File

@ -0,0 +1,39 @@
require 'spec_helper'
describe Band do
before do
@geocode1 = FactoryGirl.create(:geocoder)
@geocode2 = FactoryGirl.create(:geocoder)
@band = FactoryGirl.create(:band)
end
describe "with profile location data" do
it "should have lat/lng values" do
geo = MaxMindGeo.find_by_city(@band.city)
@band.lat.should == geo.lat
@band.lng.should == geo.lng
end
it "should have updated lat/lng values" do
@band.update_attributes({ :city => @geocode2.city,
:state => @geocode2.region,
:country => @geocode2.country,
})
geo = MaxMindGeo.find_by_city(@band.city)
@band.lat.should == geo.lat
@band.lng.should == geo.lng
end
end
describe "without location data" do
it "should have nil lat/lng values without address" do
@band.update_attributes({ :city => nil,
:state => nil,
:country => nil,
})
@band.lat.should == nil
@band.lng.should == nil
end
end
end

View File

@ -5,7 +5,9 @@ describe User do
let(:user) { FactoryGirl.create(:user) }
before(:each) do
@geocode1 = FactoryGirl.create(:geocoder)
@geocode2 = FactoryGirl.create(:geocoder)
@user = FactoryGirl.create(:user)
@band = Band.save(nil, "Example Band", "www.bands.com", "zomg we rock", "Apex", "NC", "US", ["hip hop"], user.id, nil, nil)
end

View File

@ -18,14 +18,14 @@ describe 'Musician search' do
# expects all the users
num = User.musicians.count
results = Search.musician_search({ :per_page => num })
expect(results.musicians.count).to eq(num)
expect(results.musicians_filter.count).to eq(num)
end
it "finds musicians with proper ordering" do
# the ordering should be create_at since no followers exist
expect(UserFollower.count).to eq(0)
results = Search.musician_search({ :per_page => User.musicians.count })
results.musicians.each_with_index do |uu, idx|
results.musicians_filter.each_with_index do |uu, idx|
expect(uu.id).to eq(@users.reverse[idx].id)
end
end
@ -41,10 +41,10 @@ describe 'Musician search' do
# refresh the order to ensure it works right
@user2.followers.concat([@user3, @user4, @user2])
results = Search.musician_search({ :per_page => @users.size }, @user3)
expect(results.musicians[0].id).to eq(@user2.id)
expect(results.musicians_filter[0].id).to eq(@user2.id)
# check the follower count for given entry
expect(results.musicians[0].search_follow_count.to_i).not_to eq(0)
expect(results.musicians_filter[0].search_follow_count.to_i).not_to eq(0)
# check the follow relationship between current_user and result
expect(results.is_follower?(@user2)).to be true
end
@ -53,7 +53,7 @@ describe 'Musician search' do
# make sure pagination works right
params = { :per_page => 2, :page => 1 }
results = Search.musician_search(params)
expect(results.musicians.count).to be 2
expect(results.musicians_filter.count).to be 2
end
end
@ -96,7 +96,7 @@ describe 'Musician search' do
Friendship.save(@user1.id, @user2.id)
# search on user2
results = Search.musician_search({}, @user2)
friend = results.musicians.detect { |mm| mm.id == @user1.id }
friend = results.musicians_filter.detect { |mm| mm.id == @user1.id }
expect(friend).to_not be_nil
expect(results.friend_count(friend)).to be 1
@user1.reload
@ -115,7 +115,7 @@ describe 'Musician search' do
expect(@user1.recordings.detect { |rr| rr == recording }).to_not be_nil
results = Search.musician_search({},@user1)
uu = results.musicians.detect { |mm| mm.id == @user1.id }
uu = results.musicians_filter.detect { |mm| mm.id == @user1.id }
expect(uu).to_not be_nil
expect(results.record_count(uu)).to be 1
@ -130,28 +130,28 @@ describe 'Musician search' do
make_recording(@user1)
# order results by num recordings
results = Search.musician_search({ :orderby => 'plays' }, @user2)
expect(results.musicians[0].id).to eq(@user1.id)
expect(results.musicians_filter[0].id).to eq(@user1.id)
# add more data and make sure order still correct
make_recording(@user2); make_recording(@user2)
results = Search.musician_search({ :orderby => 'plays' }, @user2)
expect(results.musicians[0].id).to eq(@user2.id)
expect(results.musicians_filter[0].id).to eq(@user2.id)
end
it "by now playing" do
# should get 1 result with 1 active session
make_session(@user3)
results = Search.musician_search({ :orderby => 'playing' }, @user2)
expect(results.musicians.count).to be 1
expect(results.musicians.first.id).to eq(@user3.id)
expect(results.musicians_filter.count).to be 1
expect(results.musicians_filter.first.id).to eq(@user3.id)
# should get 2 results with 2 active sessions
# sort order should be created_at DESC
make_session(@user4)
results = Search.musician_search({ :orderby => 'playing' }, @user2)
expect(results.musicians.count).to be 2
expect(results.musicians[0].id).to eq(@user4.id)
expect(results.musicians[1].id).to eq(@user3.id)
expect(results.musicians_filter.count).to be 2
expect(results.musicians_filter[0].id).to eq(@user4.id)
expect(results.musicians_filter[1].id).to eq(@user3.id)
end
end
@ -166,10 +166,10 @@ describe 'Musician search' do
ii = @user1.instruments.detect { |inst| inst.id == 'tuba' }
expect(ii).to_not be_nil
results = Search.musician_search({ :instrument => ii.id })
results.musicians.each do |rr|
results.musicians_filter.each do |rr|
expect(rr.instruments.detect { |inst| inst.id=='tuba' }.id).to eq(ii.id)
end
expect(results.musicians.count).to be 1
expect(results.musicians_filter.count).to be 1
end
it "finds musicians within a given distance of given location" do
@ -179,26 +179,26 @@ describe 'Musician search' do
results = Search.musician_search({ :per_page => num,
:distance => 10,
:city => 'Apex' }, @user1)
expect(results.musicians.count).to be num
expect(results.musicians_filter.count).to be num
# long distance
results = Search.musician_search({ :per_page => num,
:distance => 1000,
:city => 'Miami',
:state => 'FL' }, @user1)
expect(results.musicians.count).to be num
expect(results.musicians_filter.count).to be num
end
it "finds musicians within a given distance of users location" do
expect(@user1.lat).to_not be_nil
# uses the location of @user1
results = Search.musician_search({ :distance => 10, :per_page => User.musicians.count }, @user1)
expect(results.musicians.count).to be User.musicians.count
expect(results.musicians_filter.count).to be User.musicians.count
end
it "finds no musicians within a given distance of location" do
expect(@user1.lat).to_not be_nil
results = Search.musician_search({ :distance => 10, :city => 'San Francisco' }, @user1)
expect(results.musicians.count).to be 0
expect(results.musicians_filter.count).to be 0
end
end

View File

@ -7,7 +7,6 @@
var self = this;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var userId;
var user = {};
var tmpUploadPath = null;
var userDetail = null;
@ -18,7 +17,6 @@
var userDropdown;
function beforeShow(data) {
userId = data.id;
}
@ -150,7 +148,7 @@
var avatar = $('img.preview_profile_avatar', avatarSpace);
var spinner = $('<div class="spinner spinner-large"></div>')
if(avatar.length == 0) {
if(avatar.length === 0) {
avatarSpace.prepend(spinner);
}
else {

View File

@ -5,6 +5,7 @@
context.JK = context.JK || {};
context.JK.BandProfileScreen = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var bandId;
var isMember = false;
var band = {};
@ -59,7 +60,7 @@
configureBandFollowingButton(true);
}
else {
configureMemberFollowingButton(true);
configureMemberFollowingButton(true, id);
}
},
error: app.ajaxError
@ -350,7 +351,6 @@
}
function bindPendingMembers() {
$("#band-profile-members").append("<br/><br/><h2><b>Pending Band Invitations</b></h2>");
var url = "/api/bands/" + bandId + "/musicians?pending=true";
$.ajax({
type: "GET",
@ -359,7 +359,10 @@
async: false,
processData:false,
success: function(response) {
bindMusicians(response);
if (response && response.length > 0) {
$("#band-profile-members").append("<br/><br/><h2><b>Pending Band Invitations</b></h2>");
bindMusicians(response);
}
},
error: app.ajaxError
});
@ -400,12 +403,34 @@
var following = isFollowingMember(musician.id);
configureMemberFollowingButton(following, musician.id);
configureRemoveMemberButton(musician.id);
// TODO: wire up Friend button click handler
// var friend = isFriend(musician.id);
// configureMemberFriendButton(friend, musician.id);
});
}
function configureRemoveMemberButton(userId) {
var $divMember = $('div[user-id=' + userId + ']', '#band-profile-members');
var $btnRemoveMember = $divMember.find('#btn-remove-member');
if (isMember) {
$btnRemoveMember.show();
$btnRemoveMember.unbind("click");
$btnRemoveMember.click(function() {
rest.removeBandMember(bandId, userId)
.done(function() {
$divMember.remove();
})
.fail(app.ajaxError);
});
}
else {
$btnRemoveMember.hide();
}
}
// TODO: refactor
// checks if person viewing the profile is also a band member
function setIsMember() {
@ -441,12 +466,10 @@
if (isMember) {
$("#btn-follow-band").hide();
$("#btn-edit-band-members").show();
$("#btn-edit-band-profile").show();
}
else {
$("#btn-follow-band").show();
$("#btn-edit-band-members").hide();
$("#btn-edit-band-profile").hide();
}

View File

@ -229,6 +229,7 @@
userNames = [];
userIds = [];
userPhotoUrls = [];
//bandId = "1158c8b6-4c92-47dc-82bf-1e390c4f9b2c";
bandId = $("#hdn-band-id").val();
resetForm();
}
@ -246,9 +247,22 @@
}
else {
loadGenres();
loadCountries();
rest.getResolvedLocation()
.done(function(location) {
loadCountries(location.country, function() {
loadRegions(location.region, function() {
loadCities(location.city);
});
});
});
$("#band-setup-title").html("set up band");
$("#btn-band-setup-save").html("CREATE BAND");
$("#band-change-photo").unbind('click');
$("#band-change-photo").html('Set up band and then add photo.');
}
}
@ -258,10 +272,14 @@
$("#band-website").val(band.website);
$("#band-biography").val(band.biography);
if (band.photo_url) {
$("#band-avatar").attr('src', band.photo_url);
}
loadGenres(band.genres);
loadCountries(band.country, function() {
loadRegions(band.state, function () {
loadRegions(band.state, function() {
loadCities(band.city);
});
});
@ -357,24 +375,26 @@
nilOption.text(nilOptionText);
$region.append(nilOption);
rest.getRegions({'country': selectedCountry}).done(function(response) {
$.each(response["regions"], function(index, region) {
if(!region) return;
var option = $('<option></option>');
option.text(region);
option.attr("value", region);
if (selectedCountry) {
rest.getRegions({'country': selectedCountry}).done(function(response) {
$.each(response["regions"], function(index, region) {
if(!region) return;
var option = $('<option></option>');
option.text(region);
option.attr("value", region);
if (initialRegion === region) {
option.attr("selected", "selected");
if (initialRegion === region) {
option.attr("selected", "selected");
}
$region.append(option);
});
if (onRegionsLoaded) {
onRegionsLoaded();
}
$region.append(option);
});
if (onRegionsLoaded) {
onRegionsLoaded();
}
});
}
}
function loadCities(initialCity) {
@ -387,20 +407,22 @@
nilOption.text(nilOptionText);
$city.append(nilOption);
rest.getCities({'country': selectedCountry, 'region': selectedRegion}) .done(function(response) {
$.each(response["cities"], function(index, city) {
if(!city) return;
var option = $('<option></option>');
option.text(city);
option.attr("value", city);
if (selectedCountry && selectedRegion) {
rest.getCities({'country': selectedCountry, 'region': selectedRegion}) .done(function(response) {
$.each(response["cities"], function(index, city) {
if(!city) return;
var option = $('<option></option>');
option.text(city);
option.attr("value", city);
if (initialCity === city) {
option.attr("selected", "selected");
}
if (initialCity === city) {
option.attr("selected", "selected");
}
$city.append(option);
$city.append(option);
});
});
});
}
}
function friendSelectorCallback(newSelections) {
@ -467,13 +489,21 @@
$('#band-country').on('change', function(evt) {
evt.stopPropagation();
loadRegions('');
loadRegions();
loadCities();
return false;
});
$('#band-region').on('change', function(evt) {
evt.stopPropagation();
loadCities('');
loadCities();
return false;
});
$('#band-change-photo').click(function(evt) {
evt.stopPropagation();
$("#hdn-band-id").val(bandId);
context.location = '#/band/setup/photo';
return false;
});

View File

@ -0,0 +1,431 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.BandSetupPhotoScreen = function(app) {
var self = this;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var bandId;
var band = {};
var tmpUploadPath = null;
var bandDetail = null;
var bandPhoto;
var selection = null;
var targetCropSize = 88;
var updatingBandPhoto = false;
function beforeShow(data) {
bandId = $("#hdn-band-id").val();
logger.debug("bandId=" + bandId);
if (!bandId) {
context.location = '#/home';
}
}
function afterShow(data) {
resetForm();
renderBandPhotoScreen()
}
function resetForm() {
// remove all display errors
$('#band-setup-photo-content-scroller form .error-text').remove()
$('#band-setup-photo-content-scroller form .error').removeClass("error")
}
function populateBandPhoto(bandDetail) {
self.bandDetail = bandDetail;
rest.getBandPhotoFilepickerPolicy({ id:bandId })
.done(function(filepicker_policy) {
var template= context.JK.fillTemplate($('#template-band-setup-photo').html(), {
"fp_apikey" : gon.fp_apikey,
"data-fp-store-path" : createStorePath(bandDetail) + createOriginalFilename(bandDetail),
"fp_policy" : filepicker_policy.policy,
"fp_signature" : filepicker_policy.signature
});
$('#band-setup-photo-content-scroller').html(template);
var currentFpfile = determineCurrentFpfile();
var currentCropSelection = determineCurrentSelection(bandDetail);
renderBandPhoto(currentFpfile, currentCropSelection ? JSON.parse(currentCropSelection) : null);
})
.error(app.ajaxError);
}
// events for main screen
function events() {
// wire up main panel clicks
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-upload', function(evt) { evt.stopPropagation(); handleFilePick(); return false; } );
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-delete', function(evt) { evt.stopPropagation(); handleDeleteBandPhoto(); return false; } );
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-cancel', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } );
$('#band-setup-photo-content-scroller').on('click', '#band-setup-photo-submit', function(evt) { evt.stopPropagation(); handleUpdateBandPhoto(); return false; } );
//$('#band-setup-photo-content-scroller').on('change', 'input[type=filepicker-dragdrop]', function(evt) { evt.stopPropagation(); afterImageUpload(evt.originalEvent.fpfile); return false; } );
}
function handleDeleteBandPhoto() {
if(self.updatingBandPhoto) {
// protect against concurrent update attempts
return;
}
self.updatingBandPhoto = true;
renderBandPhotoSpinner();
rest.deleteBandPhoto({ id: bandId })
.done(function() {
removeBandPhotoSpinner({ delete:true });
deleteBandPhotoSuccess(arguments);
selection = null;
})
.fail(function() {
app.ajaxError(arguments);
$.cookie('original_fpfile_band_photo', null);
self.updatingBandPhoto = false;
})
.always(function() {
})
}
function deleteBandPhotoSuccess(response) {
renderBandPhoto(null, null);
rest.getBand(bandId)
.done(function(bandDetail) {
self.bandDetail = bandDetail;
})
.error(app.ajaxError)
.always(function() {
self.updatingBandPhoto = false;
})
}
function handleFilePick() {
rest.getBandPhotoFilepickerPolicy({ id: bandId })
.done(function(filepickerPolicy) {
renderBandPhotoSpinner();
logger.debug("rendered spinner");
filepicker.setKey(gon.fp_apikey);
filepicker.pickAndStore({
mimetype: 'image/*',
maxSize: 10000*1024,
policy: filepickerPolicy.policy,
signature: filepickerPolicy.signature
},
{ path: createStorePath(self.bandDetail), access: 'public' },
function(fpfiles) {
removeBandPhotoSpinner();
afterImageUpload(fpfiles[0]);
}, function(fperror) {
removeBandPhotoSpinner();
if(fperror.code != 101) { // 101 just means the user closed the dialog
alert("unable to upload file: " + JSON.stringify(fperror))
}
})
})
.fail(app.ajaxError);
}
function renderBandPhotoScreen() {
rest.getBand(bandId)
.done(populateBandPhoto)
.error(app.ajaxError)
}
function navToEditProfile() {
resetForm();
$("#hdn-band-id").val(bandId);
context.location = '#/band/setup';
}
function renderBandPhotoSpinner() {
var bandPhotoSpace = $('#band-setup-photo-content-scroller .band-setup-photo .avatar-space');
// if there is already an image tag, we only obscure it.
var bandPhoto = $('img.preview_profile_avatar', bandPhotoSpace);
var spinner = $('<div class="spinner spinner-large"></div>')
if(bandPhoto.length === 0) {
bandPhotoSpace.prepend(spinner);
}
else {
// in this case, just style the spinner to obscure using opacity, and center it
var jcropHolder = $('.jcrop-holder', bandPhotoSpace);
spinner.width(jcropHolder.width());
spinner.height(jcropHolder.height());
spinner.addClass('op50');
var jcrop = bandPhoto.data('Jcrop');
if(jcrop) {
jcrop.disable();
}
bandPhotoSpace.append(spinner);
}
}
function removeBandPhotoSpinner(options) {
var bandPhotoSpace = $('#band-setup-photo-content-scroller .band-setup-photo .avatar-space');
if(options && options.delete) {
bandPhotoSpace.children().remove();
}
var spinner = $('.spinner-large', bandPhotoSpace);
spinner.remove();
var bandPhoto = $('img.preview_profile_avatar', bandPhotoSpace);
var jcrop = bandPhoto.data('Jcrop')
if(jcrop) {
jcrop.enable();
}
}
function renderBandPhoto(fpfile, storedSelection) {
// clear out
var bandPhotoSpace = $('#band-setup-photo-content-scroller .band-setup-photo .avatar-space');
if(!fpfile) {
renderNoBandPhoto(bandPhotoSpace);
}
else {
rest.getBandPhotoFilepickerPolicy({handle: fpfile.url, id: bandId})
.done(function(filepickerPolicy) {
bandPhotoSpace.children().remove();
renderBandPhotoSpinner();
var photo_url = fpfile.url + '?signature=' + filepickerPolicy.signature + '&policy=' + filepickerPolicy.policy;
bandPhoto = new Image();
$(bandPhoto)
.load(function(e) {
removeBandPhotoSpinner();
bandPhoto = $(this);
bandPhotoSpace.append(bandPhoto);
var width = bandPhoto.naturalWidth();
var height = bandPhoto.naturalHeight();
if(storedSelection) {
var left = storedSelection.x;
var right = storedSelection.x2;
var top = storedSelection.y;
var bottom = storedSelection.y2;
}
else {
if(width < height) {
var left = width * .25;
var right = width * .75;
var top = (height / 2) - (width / 4);
var bottom = (height / 2) + (width / 4);
}
else {
var top = height * .25;
var bottom = height * .75;
var left = (width / 2) - (height / 4);
var right = (width / 2) + (height / 4);
}
}
// jcrop only works well with px values (not percentages)
// so we get container, and work out a decent % ourselves
var container = $('#band-setup-photo-content-scroller');
bandPhoto.Jcrop({
aspectRatio: 1,
boxWidth: container.width() * .75,
boxHeight: container.height() * .75,
// minSelect: [targetCropSize, targetCropSize], unnecessary with scaling involved
setSelect: [ left, top, right, bottom ],
trueSize: [width, height],
onRelease: onSelectRelease,
onSelect: onSelect,
onChange: onChange
});
})
.error(function() {
// default to no avatar look of UI
renderNoBandPhoto(bandPhotoSpace);
})
.attr('src', photo_url)
.attr('alt', 'profile avatar')
.addClass('preview_profile_avatar');
})
.fail(app.ajaxError);
}
}
function afterImageUpload(fpfile) {
$.cookie('original_fpfile_band_photo', JSON.stringify(fpfile));
renderBandPhoto(fpfile, null);
}
function renderNoBandPhoto(bandPhotoSpace) {
// no photo found for band
removeBandPhotoSpinner();
var noAvatarSpace = $('<div></div>');
noAvatarSpace.addClass('no-avatar-space');
noAvatarSpace.text('Please upload a photo');
bandPhotoSpace.append(noAvatarSpace);
}
function handleUpdateBandPhoto(event) {
if(self.updatingBandPhoto) {
// protect against concurrent update attempts
return;
}
if(selection) {
var currentSelection = selection;
self.updatingBandPhoto = true;
renderBandPhotoSpinner();
console.log("Converting...");
// we convert two times; first we crop to the selected region,
// then we scale to 88x88 (targetCropSize X targetCropSize), which is the largest size we use throughout the site.
var fpfile = determineCurrentFpfile();
rest.getBandPhotoFilepickerPolicy({ handle: fpfile.url, convert: true, id: bandId })
.done(function(filepickerPolicy) {
filepicker.setKey(gon.fp_apikey);
filepicker.convert(fpfile, {
crop: [
Math.round(currentSelection.x),
Math.round(currentSelection.y),
Math.round(currentSelection.w),
Math.round(currentSelection.w)],
fit: 'crop',
format: 'jpg',
quality: 90,
policy: filepickerPolicy.policy,
signature: filepickerPolicy.signature
}, { path: createStorePath(self.bandDetail) + 'cropped-' + new Date().getTime() + '.jpg', access: 'public' },
function(cropped) {
logger.debug("converting cropped");
rest.getBandPhotoFilepickerPolicy({handle: cropped.url, convert: true, id: bandId})
.done(function(filepickerPolicy) {
filepicker.convert(cropped, {
height: targetCropSize,
width: targetCropSize,
fit: 'scale',
format: 'jpg',
quality: 75,
policy: filepickerPolicy.policy,
signature: filepickerPolicy.signature
}, { path: createStorePath(self.bandDetail), access: 'public' },
function(scaled) {
logger.debug("converted and scaled final image %o", scaled);
rest.updateBandPhoto({
original_fpfile: determineCurrentFpfile(),
cropped_fpfile: scaled,
crop_selection: currentSelection,
id: bandId
})
.done(updateBandPhotoSuccess)
.fail(app.ajaxError)
.always(function() { removeBandPhotoSpinner(); self.updatingBandPhoto = false;})
},
function(fperror) {
alert("unable to scale selection. error code: " + fperror.code);
removeBandPhotoSpinner();
self.updatingBandPhoto = false;
})
})
.fail(app.ajaxError);
},
function(fperror) {
alert("unable to crop selection. error code: " + fperror.code);
removeBandPhotoSpinner();
self.updatingBandPhoto = false;
}
);
})
.fail(app.ajaxError);
}
else {
app.notify(
{ title: "Upload a Band Photo First",
text: "To update your band photo, first you must upload an image using the UPLOAD button"
},
{ no_cancel: true });
}
}
function updateBandPhotoSuccess(response) {
$.cookie('original_fpfile_band_photo', null);
self.bandDetail = response;
app.notify(
{ title: "Band Photo Changed",
text: "You have updated your band photo successfully."
},
{ no_cancel: true });
}
function onSelectRelease(event) {
}
function onSelect(event) {
selection = event;
}
function onChange(event) {
}
function createStorePath(bandDetail) {
return gon.fp_upload_dir + '/' + bandDetail.id + '/'
}
function createOriginalFilename(bandDetail) {
// get the s3
var fpfile = bandDetail.original_fpfile_photo ? JSON.parse(bandDetail.original_fpfile_photo) : null;
return 'original_band_photo.jpg'
}
// retrieves a file that has not yet been used as an band photo (uploaded, but not cropped)
function getWorkingFpfile() {
return JSON.parse($.cookie('original_fpfile_band_photo'))
}
function determineCurrentFpfile() {
// precedence is as follows:
// * tempOriginal: if set, then the user is working on a new upload
// * storedOriginal: if set, then the user has previously uploaded and cropped a band photo
// * null: neither are set above
var tempOriginal = getWorkingFpfile();
var storedOriginal = self.bandDetail.original_fpfile_photo ? JSON.parse(self.bandDetail.original_fpfile_photo) : null;
return tempOriginal ? tempOriginal : storedOriginal;
}
function determineCurrentSelection(bandDetail) {
// if the cookie is set, don't use the storage selection, just default to null
return $.cookie('original_fpfile_band_photo') == null ? bandDetail.crop_selection_photo : null;
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('band/setup/photo', screenBindings);
events();
}
this.initialize = initialize;
this.beforeShow = beforeShow;
this.afterShow = afterShow;
return this;
};
})(window,jQuery);

View File

@ -613,7 +613,7 @@
// track 2 was removed
if (myTrackCount === 2) {
logger.debug("Deleting track " + myTracks[1].trackId);
client.TrackSetCount(1);
context.jamClient.TrackSetCount(1);
//sessionModel.deleteTrack(sessionId, myTracks[1].trackId);
}
}
@ -830,4 +830,4 @@
return this;
};
})(window,jQuery);
})(window,jQuery);

View File

@ -0,0 +1,229 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.FindBandScreen = function(app) {
var logger = context.JK.logger;
var bands = {};
var bandList;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
var did_show_band_page = false;
var page_num=1, page_count=0;
function loadBands(queryString) {
// squelch nulls and undefines
queryString = !!queryString ? queryString : "";
$.ajax({
type: "GET",
url: "/api/search.json?" + queryString,
async: true,
success: afterLoadBands,
error: app.ajaxError
});
}
function search() {
did_show_band_page = true;
var queryString = 'srch_b=1&page='+page_num+'&';
// order by
var orderby = $('#band_order_by').val();
if (typeof orderby != 'undefined' && orderby.length > 0) {
queryString += "orderby=" + orderby + '&';
}
// genre filter
var genre = $('#band_genre').val();
if (typeof genre != 'undefined' && !(genre === '')) {
queryString += "genre=" + genre + '&';
}
// distance filter
var query_param = $('#band_query_distance').val();
if (query_param !== null && query_param.length > 0) {
var matches = query_param.match(/(\d+)/);
if (0 < matches.length) {
var distance = matches[0];
queryString += "distance=" + distance + '&';
}
}
loadBands(queryString);
}
function refreshDisplay() {
clearResults();
search();
}
function afterLoadBands(mList) {
// display the 'no bands' banner if appropriate
var $noBandsFound = $('#bands-none-found');
bandList = mList;
if(bandList.length == 0) {
$noBandsFound.show();
bands = [];
} else {
$noBandsFound.hide();
bands = bandList['bands'];
if (!(typeof bands === 'undefined')) {
$('#band-filter-city').text(bandList['city']);
if (0 == page_count) {
page_count = bandList['page_count'];
}
renderBands();
}
}
}
/**
* Render a list of bands
*/
function renderBands() {
var ii, len;
var mTemplate = $('#template-find-band-row').html();
var pTemplate = $('#template-band-player-info').html();
var aTemplate = $('#template-band-action-btns').html();
var bVals, bb, renderings='';
var instr_logos, instr;
var players, playerVals, aPlayer;
for (ii=0, len=bands.length; ii < len; ii++) {
bb = bands[ii];
instr_logos = '';
players = '';
playerVals = {};
for (var jj=0, ilen=bb['players'].length; jj<ilen; jj++) {
aPlayer = bb['players'][jj];
var player_instrs = '';
var iter_pinstruments = aPlayer['instruments'].split(',');
for (var kk=0, klen=iter_pinstruments.length; kk<klen; kk++) {
var pinstr = iter_pinstruments[kk];
if (pinstr in instrument_logo_map) {
instr = instrument_logo_map[pinstr];
}
player_instrs += '<img src="' + instr + '" width="24" height="24" />&nbsp;';
}
playerVals = {
player_name: aPlayer.name,
profile_url: '/#/profile/' + aPlayer.user_id,
avatar_url: context.JK.resolveAvatarUrl(aPlayer.photo_url),
player_instruments: player_instrs
};
players += context.JK.fillTemplate(pTemplate, playerVals);
}
var actionVals = {
profile_url: "/#/profile/" + bb.id,
button_follow: bb['is_following'] ? '' : 'button-orange',
button_message: 'button-orange'
};
var band_actions = context.JK.fillTemplate(aTemplate, actionVals);
var bgenres = '';
for (jj=0, ilen=bb['genres'].length; jj<ilen; jj++) {
bgenres += bb['genres'][jj]['description'] + '<br />';
}
bgenres += '<br />';
bVals = {
avatar_url: context.JK.resolveAvatarUrl(bb.photo_url),
profile_url: "/#/profile/" + bb.id,
band_name: bb.name,
band_location: bb.city + ', ' + bb.state,
genres: bgenres,
instruments: instr_logos,
biography: bb['biography'],
follow_count: bb['follow_count'],
recording_count: bb['recording_count'],
session_count: bb['session_count'],
band_id: bb['id'],
band_player_template: players,
band_action_template: band_actions
};
var band_row = context.JK.fillTemplate(mTemplate, bVals);
renderings += band_row;
}
$('#band-filter-results').append(renderings);
$('.search-m-follow').on('click', followBand);
}
function beforeShow(data) {
}
function afterShow(data) {
if (!did_show_band_page) {
refreshDisplay();
}
}
function clearResults() {
bands = {};
$('#band-filter-results').empty();
page_num = 1;
page_count = 0;
}
function followBand(evt) {
// if the band is already followed, remove the button-orange class, and prevent
// the link from working
if (0 == $(this).closest('.button-orange').size()) return false;
$(this).click(function(ee) {ee.preventDefault();});
evt.stopPropagation();
var newFollowing = {};
newFollowing.band_id = $(this).parent().data('band-id');
var url = "/api/users/" + context.JK.currentUserId + "/followings";
$.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
url: url,
data: JSON.stringify(newFollowing),
processData: false,
success: function(response) {
// remove the orange look to indicate it's not selectable
$('div[data-band-id='+newFollowing.band_id+'] .search-m-follow').removeClass('button-orange');
},
error: app.ajaxError
});
}
function events() {
$('#band_query_distance').change(refreshDisplay);
$('#band_genre').change(refreshDisplay);
$('#band_order_by').change(refreshDisplay);
$('#band-filter-results').bind('scroll', function() {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
if (page_num < page_count) {
page_num += 1;
search();
}
}
});
}
/**
* Initialize,
*/
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('bands', screenBindings);
events();
}
this.initialize = initialize;
this.renderBands = renderBands;
this.afterShow = afterShow;
this.clearResults = clearResults;
return this;
};
})(window,jQuery);

View File

@ -29,12 +29,12 @@
var queryString = 'srch_m=1&page='+page_num+'&';
// order by
var orderby = $('.musician-order-by').val();
var orderby = $('#musician_order_by').val();
if (typeof orderby != 'undefined' && orderby.length > 0) {
queryString += "orderby=" + orderby + '&';
}
// instrument filter
var instrument = $('.instrument-list').val();
var instrument = $('#musician_instrument').val();
if (typeof instrument != 'undefined' && !(instrument === '')) {
queryString += "instrument=" + instrument + '&';
}
@ -111,7 +111,7 @@
}
var actionVals = {
profile_url: "/#/profile/" + mm.id,
button_friend: mm['is_friend'] ? '' : 'button-orance',
button_friend: mm['is_friend'] ? '' : 'button-orange',
button_follow: mm['is_following'] ? '' : 'button-orange',
button_message: 'button-orange'
};
@ -197,13 +197,9 @@
}
function events() {
$("#musician_query_distance").keypress(function(evt) {
if (evt.which === 13) {
evt.preventDefault();
refreshDisplay();
}
});
$('#btn-refresh-musicians').on("click", refreshDisplay);
$('#musician_query_distance').change(refreshDisplay);
$('#musician_instrument').change(refreshDisplay);
$('#musician_order_by').change(refreshDisplay);
$('#musician-filter-results').bind('scroll', function() {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
@ -237,4 +233,4 @@
return this;
};
})(window,jQuery);
})(window,jQuery);

View File

@ -115,6 +115,17 @@
});
}
function removeBandMember(bandId, userId) {
var url = "/api/bands/" + bandId + "/musicians/" + userId;
return $.ajax({
type: "DELETE",
dataType: "json",
url: url,
async: false,
processData:false
});
}
function getSession(id) {
var url = "/api/sessions/" + id;
return $.ajax({
@ -173,6 +184,12 @@
});
}
function getResolvedLocation() {
return $.ajax('/api/resolved_location', {
dataType: 'json'
});
}
function getInstruments(options) {
return $.ajax('/api/instruments', {
data : { },
@ -194,6 +211,12 @@
var cropped_fpfile = options['cropped_fpfile'];
var crop_selection = options['crop_selection'];
logger.debug(JSON.stringify({
original_fpfile : original_fpfile,
cropped_fpfile : cropped_fpfile,
crop_selection : crop_selection
}));
var url = "/api/users/" + id + "/avatar";
return $.ajax({
type: "POST",
@ -235,6 +258,60 @@
});
}
function updateBandPhoto(options) {
var id = getId(options);
var original_fpfile = options['original_fpfile'];
var cropped_fpfile = options['cropped_fpfile'];
var crop_selection = options['crop_selection'];
logger.debug(JSON.stringify({
original_fpfile : original_fpfile,
cropped_fpfile : cropped_fpfile,
crop_selection : crop_selection
}));
var url = "/api/bands/" + id + "/photo";
return $.ajax({
type: "POST",
dataType: "json",
url: url,
contentType: 'application/json',
processData:false,
data: JSON.stringify({
original_fpfile : original_fpfile,
cropped_fpfile : cropped_fpfile,
crop_selection : crop_selection
})
});
}
function deleteBandPhoto(options) {
var id = getId(options);
var url = "/api/bands/" + id + "/photo";
return $.ajax({
type: "DELETE",
dataType: "json",
url: url,
contentType: 'application/json',
processData:false
});
}
function getBandPhotoFilepickerPolicy(options) {
var id = getId(options);
var handle = options && options["handle"];
var convert = options && options["convert"]
var url = "/api/bands/" + id + "/filepicker_policy";
return $.ajax(url, {
data : { handle : handle, convert: convert },
dataType : 'json'
});
}
function getFriends(options) {
var friends = [];
var id = getId(options);
@ -455,6 +532,7 @@
this.getRegions = getRegions;
this.getCountries = getCountries;
this.getIsps = getIsps;
this.getResolvedLocation = getResolvedLocation;
this.getInstruments = getInstruments;
this.getGenres = getGenres;
this.updateAvatar = updateAvatar;
@ -481,9 +559,13 @@
this.putTrackSyncChange = putTrackSyncChange;
this.createBand = createBand;
this.updateBand = updateBand;
this.updateBandPhoto = updateBandPhoto;
this.deleteBandPhoto = deleteBandPhoto;
this.getBandPhotoFilepickerPolicy = getBandPhotoFilepickerPolicy;
this.getBand = getBand;
this.createBandInvitation = createBandInvitation;
this.updateBandInvitation = updateBandInvitation;
this.removeBandMember = removeBandMember;
return this;
};

View File

@ -6,7 +6,7 @@
context.JK.ProfileScreen = function(app) {
var logger = context.JK.logger;
var userId;
var user = {};
var user = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
@ -24,6 +24,7 @@
function beforeShow(data) {
userId = data.id;
user = null;
}
function afterShow(data) {
@ -45,9 +46,84 @@
$('.profile-nav a.#profile-about-link').addClass('active');
}
function getUser() {
if (user === null) {
var url = "/api/users/" + userId;
$.ajax({
type: "GET",
dataType: "json",
url: url,
async: false,
processData:false,
success: function(response) {
user = response;
},
error: function(jqXHR, textStatus, errorMessage) {
user = null;
app.ajaxError(jqXHR, textStatus, errorMessage);
}
});
}
return user;
}
function isMusician() {
if (getUser()) {
return user.musician === true;
}
return false;
}
function isCurrentUser() {
return userId === context.JK.currentUserId;
}
function configureUserType() {
if (isMusician()) {
$('#profile-history-link').show();
$('#profile-bands-link').show();
$('#profile-instruments').show();
$('#profile-session-stats').show();
$('#profile-recording-stats').show();
// $('#profile-following-stats').hide();
// $('#profile-favorites-stats').hide();
$('#btn-add-friend').show();
$('.profile-social-left').show();
$('#profile-type-label').text('musician');
$('#profile-location-label').text('Location');
} else {
$('#profile-history-link').hide();
$('#profile-bands-link').hide();
$('#profile-instruments').hide();
$('#profile-session-stats').hide();
$('#profile-recording-stats').hide();
// $('#profile-following-stats').show();
// $('#profile-favorites-stats').show();
$('#btn-add-friend').hide();
$('.profile-social-left').hide();
$('#profile-type-label').text('fan');
$('#profile-location-label').text('Presence');
}
if (isCurrentUser()) {
$('#btn-profile-edit').show();
} else {
$('#btn-profile-edit').hide();
}
}
/****************** MAIN PORTION OF SCREEN *****************/
// events for main screen
function events() {
configureUserType();
// wire up panel clicks
$('#profile-about-link').click(renderAbout);
$('#profile-history-link').click(renderHistory);
@ -56,14 +132,12 @@
$('#profile-favorites-link').click(renderFavorites);
// wire up buttons if you're not viewing your own profile
if (userId != context.JK.currentUserId) {
if (!isCurrentUser()) {
// wire up Add Friend click
var friend = isFriend();
configureFriendButton(friend);
configureFriendButton(isFriend());
// wire up Follow click
var following = isFollowing();
configureFollowingButton(following);
configureFollowingButton(isFollowing());
}
else {
$('#btn-add-friend').hide();
@ -94,27 +168,7 @@
}
function isFriend() {
var alreadyFriend = false;
var url = "/api/users/" + context.JK.currentUserId + "/friends/" + userId;
$.ajax({
type: "GET",
dataType: "json",
url: url,
async: false,
processData: false,
success: function(response) {
if (response.id !== undefined) {
alreadyFriend = true;
}
else {
alreadyFriend = false;
}
},
error: app.ajaxError
});
return alreadyFriend;
return getUser() ? user.is_friend : false;
}
function friendRequestCallback() {
@ -186,27 +240,7 @@
}
function isFollowing() {
var alreadyFollowing = false;
var url = "/api/users/" + context.JK.currentUserId + "/followings/" + userId;
$.ajax({
type: "GET",
dataType: "json",
url: url,
async: false,
processData: false,
success: function(response) {
if (response.id !== undefined) {
alreadyFollowing = true;
}
else {
alreadyFollowing = false;
}
},
error: app.ajaxError
});
return alreadyFollowing;
return getUser() ? user.is_following : false;
}
function configureFollowingButton(following) {
@ -224,6 +258,10 @@
}
}
function configureEditProfileButton() {
$('#btn-follow-user').click(addFollowing);
}
// refreshes the currently active tab
function renderActive() {
if ($('#profile-about-link').hasClass('active')) {
@ -261,21 +299,8 @@
function bindAbout() {
$('#profile-instruments').empty();
var url = "/api/users/" + userId;
$.ajax({
type: "GET",
dataType: "json",
url: url,
async: false,
processData:false,
success: function(response) {
user = response;
},
error: app.ajaxError
});
if (user) {
if (getUser()) {
// name
$('#profile-username').html(user.name);
@ -313,11 +338,18 @@
text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower";
$('#profile-follower-stats').html(user.follower_count + text);
text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session";
$('#profile-session-stats').html(user.session_count + text);
if (isMusician()) {
text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session";
$('#profile-session-stats').html(user.session_count + text);
text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording";
$('#profile-recording-stats').html(user.recording_count + text);
text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording";
$('#profile-recording-stats').html(user.recording_count + text);
} else {
text = " Following";
$('#profile-following-stats').html(user.following_count + text);
text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite";
$('#profile-favorite-stats').html(user.favorite_count + text);
}
$('#profile-biography').html(user.biography);
}
@ -341,33 +373,41 @@
$('.profile-nav a.active').removeClass('active');
$('.profile-nav a.#profile-social-link').addClass('active');
/*if (isMusician()) {
$('.profile-social-left').show();
} else {
$('.profile-social-left').hide();
}*/
bindSocial();
}
function bindSocial() {
// FRIENDS
var url = "/api/users/" + userId + "/friends";
$.ajax({
type: "GET",
dataType: "json",
url: url,
async: false,
processData:false,
success: function(response) {
$.each(response, function(index, val) {
var template = $('#template-profile-social').html();
var friendHtml = context.JK.fillTemplate(template, {
avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
userName: val.name,
location: val.location,
type: "Friends"
});
if (isMusician()) {
// FRIENDS
var url = "/api/users/" + userId + "/friends";
$.ajax({
type: "GET",
dataType: "json",
url: url,
async: false,
processData:false,
success: function(response) {
$.each(response, function(index, val) {
var template = $('#template-profile-social').html();
var friendHtml = context.JK.fillTemplate(template, {
avatar_url: context.JK.resolveAvatarUrl(val.photo_url),
userName: val.name,
location: val.location,
type: "Friends"
});
$('#profile-social-friends').append(friendHtml);
});
},
error: app.ajaxError
});
$('#profile-social-friends').append(friendHtml);
});
},
error: app.ajaxError
});
}
// FOLLOWINGS (USERS)
url = "/api/users/" + userId + "/followings";
@ -482,7 +522,7 @@
async: false,
processData:false,
success: function(response) {
if ( (!response || response.length === 0) && context.JK.currentUserId === userId) {
if ( (!response || response.length === 0) && isCurrentUser()) {
var noBandHtml = $('#template-no-bands').html();
$("#profile-bands").append(noBandHtml);
}
@ -548,7 +588,7 @@
}
function addMoreBandsLink() {
if (context.JK.currentUserId === userId) {
if (isCurrentUser()) {
var moreBandsHtml = $('#template-more-bands').html();
$("#profile-bands").append(moreBandsHtml);
}

View File

@ -17,6 +17,43 @@
font-size:14px;
}
.band-setup-photo {
.avatar-space {
color: $color2;
margin-bottom: 20px;
position:relative;
min-height:300px;
img.preview_profile_avatar {
}
}
.spinner-large {
width:300px;
height:300px;
line-height: 300px;
position:absolute;
top:0;
left:0;
z-index: 2000; // to win over jcrop
}
.no-avatar-space {
border:1px dotted $color2;
color: $color2;
width:300px;
height:300px;
line-height: 300px;
text-align: center;
vertical-align: middle;
background-color:$ColorTextBoxBackground;
}
}
.band-profile-header {
padding:20px;
height:120px;
@ -233,3 +270,22 @@
.band-profile-block-city {
font-size:12px;
}
#band-filter-results {
margin: 0 10px 5px 10px;
overflow: auto;
height: 100%;
width: 100%;
}
.band-list-result {
padding-top: 5px;
padding-right: 5px;
padding-left: 5px;
}
.band-wrapper {
overflow: auto;
height: 480px;
width: 100%;
}

View File

@ -34,5 +34,8 @@ $text: #f3f1ee;
$gradient-diff: 30%; $link: $color8;
$border: hsl(210, 50%, 45%);
$narrow-screen: 1000px; // 990 ? 1000 ?
$short-screen: 600px; // toolbars / chrome for x768

View File

@ -0,0 +1,382 @@
/* This is simply Jeff's content.css file */
@charset "UTF-8";
#content {
background-color: #353535;
border: 1px solid #ed3618;
clear: both;
float: left;
margin-top: 39px;
height: auto;
width: auto;
position:relative;
padding-bottom:3px;
}
.content-head {
height:21px;
padding:4px;
background-color:#ED3618;
}
.content-icon {
margin-right:10px;
float:left;
}
.content-head h1 {
margin: -6px 0px 0px 0px;
padding:0;
float:left;
font-weight:100;
font-size:24px;
}
.content-nav {
float:right;
margin-right:10px;
}
.home-icon {
float:left;
margin-right:20px;
}
.content-nav a.arrow-right {
float:left;
display:block;
margin-top:2px;
margin-right:10px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-left: 7px solid #FFF;
}
.content-nav a.arrow-left {
float:left;
display:block;
margin-top:2px;
margin-right:20px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-right:7px solid #FFF;
}
#content-scroller, .content-scroller {
height:inherit;
position:relative;
display:block;
overflow:auto;
}
.content-wrapper {
padding:10px 30px 10px 36px;
font-size:15px;
color:#ccc;
border-bottom: dotted 1px #444;
overflow-x:hidden;
white-space:nowrap;
}
.create-session-left {
width:50%;
float:left;
}
.create-session-right {
width:45%;
float:right;
font-size:13px;
}
.content-wrapper h2 {
color:#fff;
font-weight:600;
font-size:24px;
}
.content-wrapper select, .content-wrapper textarea, .content-wrapper input[type=text], .content-wrapper input[type=password], div.friendbox, .ftue-inner input[type=text], .ftue-inner input[type=password], .dialog-inner textarea, .dialog-inner input[type=text] {
font-family:"Raleway", arial, sans-serif;
background-color:#c5c5c5;
border:none;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
color:#666;
}
.create-session-description {
padding:5px;
width:100%;
height:80px;
}
.friendbox {
padding:5px;
width:100%;
height:60px;
}
.invite-friend {
margin:0px 4px 4px 4px;
float:left;
display:block;
background-color:#666;
color:#fff;
font-size:12px;
-webkit-border-radius: 7px;
border-radius: 7px;
padding:2px 2px 2px 4px;
}
.content-wrapper div.friendbox input[type=text] {
-webkit-box-shadow: inset 0px 0px 0px 0px #888;
box-shadow: inset 0px 0px 0px 0px #888;
color:#666;
font-style:italic;
}
#genrelist, #musicianlist {
position:relative;
z-index:99;
width: 175px;
-webkit-border-radius: 6px;
border-radius: 6px;
background-color:#C5C5C5;
border: none;
color:#333;
font-weight:400;
padding:0px 0px 0px 8px;
height:20px;
line-height:20px;
overflow:hidden;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
}
#musicianlist, .session-controls #genrelist {
width: 150px;
}
#genrelist a, #musicianlist a {
color:#333;
text-decoration:none;
}
.genre-wrapper, .musician-wrapper {
float:left;
width:175px;
height:127px;
overflow:auto;
}
.musician-wrapper, .session-controls .genre-wrapper {
width:150px;
}
.genrecategory {
font-size:11px;
float:left;
width:135px;
}
.filtercategory, .session-controls .genrecategory {
font-size:11px;
float:left;
width:110px;
}
a.arrow-up {
float:right;
margin-right:5px;
display:block;
margin-top:6px;
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #333;
}
a.arrow-down {
float:right;
margin-right:5px;
display:block;
margin-top:6px;
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #333;
}
.settings-session-description {
padding:10px;
width:300px;
}
#session-controls {
width:100%;
padding:11px 0px 11px 0px;
background-color:#4c4c4c;
min-height:20px;
overflow-x:hidden;
}
#session-controls .searchbox {
float:left;
width:140px;
margin-left: 10px;
-webkit-border-radius: 6px;
border-radius: 6px;
background-color:#C5C5C5;
border: none;
color:#333;
font-weight:400;
padding:0px 0px 0px 8px;
height:20px;
line-height:20px;
overflow:hidden;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
}
#session-controls input[type=text] {
background-color:#c5c5c5;
border:none;
color:#666;
}
.avatar-tiny {
float:left;
padding:1px;
width:24px;
height:24px;
background-color:#ed3618;
-webkit-border-radius:12px;
-moz-border-radius:12px;
border-radius:12px;
}
.ftue-background {
background-image:url(../images/content/bkg_ftue.jpg);
background-repeat:no-repeat;
background-size:cover;
min-height:475px;
min-width:672px;
}
table.generaltable {
background-color: #262626;
border: 1px solid #4D4D4D;
color: #FFFFFF;
font-size: 11px;
margin-top: 6px;
width: 100%;
th {
background-color: #4D4D4D;
border-right: 1px solid #333333;
font-weight: 300;
padding: 6px;
}
td {
border-right: 1px solid #333333;
border-top: 1px solid #333333;
padding: 9px 5px 5px;
vertical-align: top;
white-space: normal;
}
.noborder {
border-right: medium none;
}
}
ul.shortcuts {
border:1px solid #ED3618;
li {
margin:0;
height:20px;
line-height:20px;
padding:2px;
}
.account-home, .band-setup, .audio, .get-help, .download-app, .invite-friends {
border-bottom:1px;
border-style:solid;
border-color:#ED3618;
}
span.arrow-right {
display:inline-block;
width: 0;
height: 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid #FFCC00;
padding-left:5px;
}
ul.shortcuts-submenu {
display:none;
li {
margin:0;
height:20px;
line-height:20px;
padding:2px;
color:#FFCC00;
}
li.google-invite, li.email-invite {
padding-left:9px;
}
}
}
.tagline {
font-size:30px;
margin-top:35px;
color:#ed3718;
font-weight:300;
width:345px;
clear:left;
white-space:normal;
}
.smallbutton {
font-size:10px !important;
padding:2px 8px !important;
}
.whitespace {
white-space:normal;
}
.w0 {width:0% !important}
.w5 {width:5% !important}
.w10 {width:10% !important}
.w15 {width:15% !important}
.w20 {width:20% !important}
.w25 {width:25% !important}
.w30 {width:30% !important}
.w35 {width:35% !important}
.w40 {width:40% !important}
.w45 {width:45% !important}
.w50 {width:50% !important}
.w55 {width:55% !important}
.w60 {width:60% !important}
.w65 {width:65% !important}
.w70 {width:70% !important}
.w75 {width:75% !important}
.w80 {width:80% !important}
.w85 {width:85% !important}
.w90 {width:90% !important}
.w95 {width:95% !important}
.w100 {width:100% !important}

View File

@ -1,5 +1,7 @@
/* This is simply Jeff's content.css file */
@charset "UTF-8";
/* This is Daniel's content.css file */
/* Common styles used in screens */
@import "client/common.css.scss";@charset "UTF-8";
#content {
background-color: #353535;
border: 1px solid #ed3618;
@ -12,58 +14,96 @@
padding-bottom:3px;
}
.content-head {
height:21px;
padding:4px;
background-color:#ED3618;
/* Daniel's tweaks */
.screen, .screen .content {
.content-head {
position: absolute;
height:21px;
padding:4px 0;
width:100%;
background-color:$ColorScreenPrimary;
.content-icon {
margin: -1px 10px 0 4px;
float:left;
}
.content-nav {
float:right;
margin-right:10px;
a {
&.arrow-right,
&.arrow-left {
float:left;
display:block;
margin-top:2px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
}
&.arrow-right {
margin-right:10px;
border-left: 7px solid #FFF;
}
&.arrow-left {
margin-right:20px;
border-right:7px solid #FFF;
}
}
}
h1 {
margin: -3px 0px 0px 0px;
padding:0;
float:left;
font-weight:100;
font-size:24px;
}
}
.content-body {
height:100%;
width:100%;
box-sizing: border-box;
padding-top: 29px;
.content-body-scroller {
height:inherit;
position:relative;
display:block;
overflow:auto;
// padding: 10px 35px;
@media screen and (max-width: $narrow-screen) {
// padding: 10px 20px;
}
&.outer {
overflow: hidden;
> * {
height:inherit;
}
}
}
}
}
.content-icon {
margin-right:10px;
float:left;
.result-list-button-wrapper {
margin-top: 10px;
margin-bottom: 10px;
> a.smallbutton {
margin: 2px;
}
}
.content-head h1 {
margin: -6px 0px 0px 0px;
padding:0;
float:left;
font-weight:100;
font-size:24px;
}
.content-nav {
float:right;
margin-right:10px;
}
.home-icon {
float:left;
margin-right:20px;
}
.content-nav a.arrow-right {
float:left;
display:block;
margin-top:2px;
margin-right:10px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-left: 7px solid #FFF;
}
.content-nav a.arrow-left {
float:left;
display:block;
margin-top:2px;
margin-right:20px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-right:7px solid #FFF;
}
#content-scroller, .content-scroller {
height:inherit;
@ -72,25 +112,14 @@
overflow:auto;
}
.content-wrapper {
padding:10px 30px 10px 36px;
font-size:15px;
color:#ccc;
border-bottom: dotted 1px #444;
overflow-x:hidden;
white-space:nowrap;
}
.create-session-left {
width:50%;
float:left;
}
.create-session-right {
width:45%;
float:right;
font-size:13px;
}
//.content-wrapper {
// padding:10px 30px 10px 36px;
// font-size:15px;
// color:#ccc;
// border-bottom: dotted 1px #444;
// overflow-x:hidden;
// white-space:nowrap;
//}
.content-wrapper h2 {
color:#fff;
@ -98,7 +127,7 @@
font-size:24px;
}
.content-wrapper select, .content-wrapper textarea, .content-wrapper input[type=text], .content-wrapper input[type=password], div.friendbox, .ftue-inner input[type=text], .ftue-inner input[type=password], .dialog-inner textarea, .dialog-inner input[type=text] {
.content-wrapper select, .content-wrapper textarea, .content-wrapper input[type=text], .content-wrapper input[type=password], div.friendbox, .ftue-inner input[type=text], .ftue-inner input[type=password], .dialog-inner textarea, .dialog-inner input[type=text], .dialog-inner select {
font-family:"Raleway", arial, sans-serif;
background-color:#c5c5c5;
border:none;
@ -222,7 +251,7 @@ a.arrow-down {
padding:11px 0px 11px 0px;
background-color:#4c4c4c;
min-height:20px;
overflow-x:hidden;
overflow-x:scroll;
}
#session-controls .searchbox {
@ -379,4 +408,4 @@ ul.shortcuts {
.w85 {width:85% !important}
.w90 {width:90% !important}
.w95 {width:95% !important}
.w100 {width:100% !important}
.w100 {width:100% !important}

View File

@ -1,25 +1,27 @@
.session-left {
width:40%;
float:left;
padding-top:10px;
margin-left:35px;
.session-wrapper {
padding: 10px 35px;
white-space: initial;
> div.session {
width: 50%;
&.right {
font-size: 13px;
}
}
}
#btn-choose-friends {
margin:0;
}
#create-session-genre select, #create-session-band select {
width:145px;
min-width:140px;
}
#find-session-genre select, #find-session-musician select {
width:145px;
}
.session-right {
width:50%;
float:right;
font-size:13px;
padding-top:10px;
margin-right:35px;
}
.session-description {
padding:5px;
@ -32,12 +34,6 @@
line-height:17px;
}
.friendbox {
padding:5px;
height:60px;
width:75%;
}
.terms-checkbox {
float:left;
display:block;
@ -51,19 +47,22 @@
white-space:normal;
}
div.friendbox {
.friendbox {
background-color:#c5c5c5;
border:none;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
color:#333;
}
padding:5px;
height:60px;
div.friendbox input[type=text] {
-webkit-box-shadow: inset 0px 0px 0px 0px #888;
box-shadow: inset 0px 0px 0px 0px #888;
color:#666;
font-style:italic;
input[type=text] {
-webkit-box-shadow: inset 0px 0px 0px 0px #888;
box-shadow: inset 0px 0px 0px 0px #888;
color:#666;
font-style:italic;
}
}
.invitation {

View File

@ -1,18 +1,35 @@
.filter-element {
float:left;
margin-left: 5px;
&.wrapper {
margin-top: 5px;
&.right {
float: right;
> a {
margin-top: 3px;
}
}
}
// @FIXME labeel is overriding from #session-controls.
&.desc {
margin-top: 3px;
padding-top: 3px;
}
}
#musician-filter-results {
margin: 0 10px 5px 10px;
margin: 0 10px 0px 10px;
overflow: auto;
height: 100%;
width: 100%;
// width: 100%;
}
.musician-wrapper {
overflow: auto;
height: 480px;
// overflow: auto;
// height: 480px;
width: 100%;
}
@ -20,8 +37,12 @@
padding-top: 5px;
padding-right: 5px;
padding-left: 5px;
box-sizing:border-box;
}
#session-controls.musician-filter {
padding-top: 6px;
}
.musician-following {
overflow: auto;
}
}

View File

@ -1,8 +1,8 @@
@import "client/common.css.scss";
.profile-header {
padding:20px;
height:120px;
padding:10px 20px;
// height:120px;
}
.profile-header h2 {
@ -252,4 +252,4 @@
border-top:none;
padding:3px;
vertical-align:middle;
}
}

View File

@ -56,20 +56,9 @@
}
.query-distance-params {
float:left;
width:50px;
margin-left: 10px;
-webkit-border-radius: 6px;
border-radius: 6px;
background-color:$ColorTextBoxBackground;
float: left;
// width: 80px;
margin-left: 2px;
border: none;
color:#333;
font-weight:400;
padding:0px 0px 0px 8px;
height:18px;
line-height:18px;
overflow:hidden;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
}

View File

@ -0,0 +1,70 @@
@import "client/common.css.scss";
@charset "UTF-8";
.filter-results {
li {
position: relative;
border-bottom:solid 1px shade($ColorElementPrimary, 20);
clear:both;
}
li strong {
font-weight:bold;
}
li.offline {
background-color: shade($ColorElementPrimary, 20);
color: shade($text, 10);
opacity:0.5;
ms-filter: "alpha(opacity=50)";
}
.avatar-small {
float:left;
padding:1px;
width:36px;
height:36px;
background-color:#ed3618;
margin:10px;
-webkit-border-radius:18px;
-moz-border-radius:18px;
border-radius:18px;
}
.avatar-small img {
width: 36px;
height: 36px;
-webkit-border-radius:18px;
-moz-border-radius:18px;
border-radius:18px;
}
li a {
color:#B3DD15;
}
li a:hover {
color:#FFF;
}
.result-name {
float:left;
font-size:12px;
margin-top:12px;
font-weight:bold;
}
.result-location {
font-size:11px;
color:#D5E2E4;
font-weight:200;
}
.results-wrapper {
width: 300px;
overflow-y:auto;
overflow-x:hidden;
}
}

View File

@ -3,7 +3,8 @@ class ApiBandsController < ApiController
before_filter :api_signed_in_user, :except => [:index, :show, :follower_index]
before_filter :auth_band_member, :only => [:update,
:recording_create, :recording_update, :recording_destroy,
:invitation_index, :invitation_show, :invitation_create, :invitation_destroy]
:invitation_index, :invitation_show, :invitation_create, :invitation_destroy,
:update_photo, :delete_photo, :generate_filepicker_policy]
respond_to :json
@ -65,7 +66,9 @@ class ApiBandsController < ApiController
def musician_destroy
unless params[:id].blank? || params[:user_id].blank?
BandMusician.delete_all "(band_id = '#{params[:id]}' AND user_id = '#{params[:user_id]}')"
end
render :json => {}, :status => 202
end
###################### FOLLOWERS ########################
@ -185,6 +188,56 @@ class ApiBandsController < ApiController
end
end
def update_photo
original_fpfile = params[:original_fpfile]
cropped_fpfile = params[:cropped_fpfile]
crop_selection = params[:crop_selection]
# public bucket to allow images to be available to public
@band.update_photo(original_fpfile, cropped_fpfile, crop_selection, Rails.application.config.aws_bucket_public)
if @band.errors.any?
render :json => { :message => "Unexpected error updating photo."}, :status => :unprocessable_entity
else
render :json => {}, :status => :ok
end
end
def delete_photo
@band.delete_photo(Rails.application.config.aws_bucket_public)
if @band.errors.any?
render :json => { :message => "Unexpected error deleting photo."}, :status => :unprocessable_entity
else
render :json => {}, :status => :ok
end
end
def generate_filepicker_policy
# generates a soon-expiring filepicker policy so that a band can only upload to their own folder in their bucket
handle = params[:handle]
call = 'pick,convert,store'
policy = { :expiry => (DateTime.now + 5.minutes).to_i(),
:call => call
#:path => 'avatars/' + @band.id + '/.*jpg'
}
# if the caller specifies a handle, add it to the hash
unless handle.nil?
start = handle.rindex('/') + 1
policy[:handle] = handle[start..-1]
end
policy = Base64.urlsafe_encode64( policy.to_json )
digest = OpenSSL::Digest::Digest.new('sha256')
signature = OpenSSL::HMAC.hexdigest(digest, Rails.application.config.fp_secret, policy)
render :json => { :signature => signature, :policy => policy }, :status => :ok
end
#############################################################################
protected
# ensures user is a member of the band

View File

@ -34,4 +34,10 @@ class ApiMaxmindRequestsController < ApiController
end
end
# returns location hash (country, region, state) based on requesting IP
def resolved_location
location = MaxMindManager.lookup(request.remote_ip)
render :json => { :country => location[:country], :region => location[:state], :city => location[:city] }, :status => 200
end
end

View File

@ -101,11 +101,11 @@ class ApiMusicSessionsController < ApiController
def participant_delete
client_id = params[:id]
@connection = Connection.find_by_client_id!(client_id)
music_session = MusicSession.find(@connection.music_session_id)
MusicSessionManager.new.participant_delete(current_user, @connection, music_session)
if client_id.present? && client_id != 'undefined'
@connection = Connection.find_by_client_id!(client_id)
music_session = MusicSession.find(@connection.music_session_id)
MusicSessionManager.new.participant_delete(current_user, @connection, music_session)
end
respond_with @connection, responder: ApiResponder
end

View File

@ -7,11 +7,15 @@ class ApiSearchController < ApiController
def index
if 1 == params[Search::PARAM_MUSICIAN].to_i
logger.debug("*** params = #{params.inspect}")
query = params.clone
query[:remote_ip] = request.remote_ip
@search = Search.musician_search(query, current_user)
respond_with @search, responder: ApiResponder, :status => 200
elsif 1 == params[Search::PARAM_BAND].to_i
query = params.clone
query[:remote_ip] = request.remote_ip
@search = Search.band_search(query, current_user)
respond_with @search, responder: ApiResponder, :status => 200
else
@search = Search.search(params[:query], current_user.id)
end

View File

@ -19,7 +19,10 @@ class ApiUsersController < ApiController
end
def show
@user = User.find(params[:id])
@user = User.includes([{:musician_instruments => :instrument},
{:band_musicians => :user},
:bands, :instruments])
.find(params[:id])
respond_with @user, responder: ApiResponder, :status => 200
end

View File

@ -51,11 +51,12 @@ class ArtifactsController < ApiController
end
def determine_url(artifact)
if SampleApp::Application.config.storage_type == :file
# this is basically a dev-time only path of code; we store real artifacts in s3
url = SampleApp::Application.config.jam_admin_root_url + artifact.uri.url
else
url = artifact.uri.url
url = artifact.uri.url.gsub(SampleApp::Application.config.aws_fullhost,SampleApp::Application.config.cloudfront_host)
end
return url

View File

@ -1,6 +1,7 @@
object @band
attributes :id, :name, :city, :state, :country, :location, :website, :biography, :photo_url, :logo_url, :liker_count, :follower_count, :recording_count, :session_count
attributes :id, :name, :city, :state, :country, :location, :website, :biography, :photo_url, :logo_url, :liker_count, :follower_count, :recording_count, :session_count,
:original_fpfile_photo, :cropped_fpfile_photo, :crop_selection_photo
unless @band.users.nil? || @band.users.size == 0
child :users => :musicians do

View File

@ -1,12 +1,26 @@
object @search
unless @search.bands.nil? || @search.bands.size == 0
if @search.bands.present?
child(:bands => :bands) {
attributes :id, :name, :location, :photo_url, :logo_url
}
end
unless @search.musicians.nil? || @search.musicians.size == 0
if @search.musicians.present?
child(:musicians => :musicians) {
attributes :id, :first_name, :last_name, :name, :location, :photo_url
node :is_friend do |musician|
musician.friends?(current_user)
end
child :musician_instruments => :instruments do
attributes :instrument_id, :description, :proficiency_level, :priority
end
}
end
if @search.musicians_filter.present?
node :city do |user|
current_user.try(:location)
@ -16,7 +30,7 @@ unless @search.musicians.nil? || @search.musicians.size == 0
@search.page_count
end
child(:musicians => :musicians) {
child(:musicians_filter => :musicians) {
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :musician, :photo_url, :biography
node :is_friend do |musician|
@ -44,6 +58,36 @@ unless @search.musicians.nil? || @search.musicians.size == 0
}
end
if @search.bands_filter.present?
node :page_count do |foo|
@search.page_count
end
child(:bands_filter => :bands) {
attributes :id, :name, :city, :state, :country, :email, :photo_url, :biography, :logo_url
node :is_following do |band|
@search.is_follower?(band)
end
child :genres => :genres do
attributes :genre_id, :description
end
child :users => :players do |pl|
node :user_id do |uu| uu.id end
node :photo_url do |uu| uu.photo_url end
node :name do |uu| uu.name end
node :instruments do |uu| uu.instruments.map(&:id).join(',') end
end
node :follow_count do |band| @search.follow_count(band) end
node :recording_count do |band| @search.record_count(band) end
node :session_count do |band| @search.session_count(band) end
}
end
unless @search.fans.nil? || @search.fans.size == 0
child(:fans => :fans) {
attributes :id, :first_name, :last_name, :name, :location, :photo_url

View File

@ -1,11 +1,23 @@
object @user
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :band_like_count, :follower_count, :following_count, :band_following_count, :recording_count, :session_count,
:biography
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :band_like_count, :follower_count, :following_count, :band_following_count, :recording_count, :session_count, :biography, :favorite_count
if @user.musician?
node :location do @user.location end
else
node :location do @user.online ? 'Online' : 'Offline' end
end
# give back more info if the user being fetched is yourself
if @user == current_user
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :subscribe_email
elsif current_user
node :is_friend do |uu|
@user.friends?(current_user)
end
node :is_following do |uu|
@user.following?(current_user)
end
end
unless @user.friends.nil? || @user.friends.size == 0

View File

@ -1,111 +1,117 @@
<div class="content">
<!-- Account Summary Dialog -->
<div layout="screen" layout-id="account" class="screen secondary">
<!-- header -->
<div class="content-head">
<div class="content-icon">
<%= image_tag "content/icon_account.png", {:height => 18, :width => 18} %>
<div layout="screen" layout-id="account" class="screen secondary">
<!-- header -->
<div class="content-head">
<div class="content-icon">
<%= image_tag "content/icon_account.png", {:height => 18, :width => 18} %>
</div>
<h1>my account</h1>
<%= render "screen_navigation" %>
</div>
<!-- end header -->
<div class="content-body">
<!-- profile scrolling area -->
<div id="account-content-scroller" class="content-body-scroller account-content-scroller">
</div>
<!-- end content scrolling area -->
</div>
<h1>my account</h1>
<%= render "screen_navigation" %>
</div>
<!-- end header -->
<!-- profile scrolling area -->
<div id="account-content-scroller" class="content-scroller account-content-scroller">
<script type="text/template" id="template-account-main">
<!-- content wrapper -->
<div class="content-wrapper account">
<br />
</div>
<!-- end content scrolling area -->
<div class="account-left">
<h2>identity:</h2>
</div>
<div class="account-mid identity">
<strong>Email:</strong> {email}<br />
<strong>Password:</strong> &bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;
</div>
<div class="right">
<a id="account-edit-identity-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>profile:</h2>
</div>
<div class="account-mid profile">
<div class="left small mr20" align="center">
<a href="#" class="avatar_large"><img src="{photoUrl}" id="profile-avatar" /></a></div>
<strong>Name:</strong> {name}<br />
<strong>Location:</strong> {location}<br />
<strong>Instruments:</strong> {instruments}
</div>
<div class="right">
<a id="account-edit-profile-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>subscriptions:</h2>
</div>
<div class="account-mid subscriptions">
<!--<strong>Studio:</strong> Gold ($49.99/year)<br />
<strong>NaaS:</strong> Yes ($29.99/year)-->
<strong>N/A</strong><br />
<strong>This feature not yet implemented</strong>
</div>
<div class="right">
<!--<a id="account-edit-subscriptions-link" href="#" class="button-orange">UPDATE</a>-->
</div>
<br clear="all" />
<hr />
<% if current_user && current_user.musician? %>
<div class="account-left">
<h2>payments:</h2>
</div>
<div class="account-mid payments">
<!--<strong>Method:</strong> MasterCard &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; 1234<br />
<a href="#">View Payment History</a>&nbsp;&nbsp;<a href="#">Cancel Subscription</a> -->
<strong>N/A</strong></br />
<strong>This feature not yet implemented</strong>
</div>
<div class="right">
<!--<a id="account-edit-payments-link" href="#" class="button-orange">UPDATE</a>-->
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>audio:</h2>
</div>
<div class="account-mid audio">
<strong>Approved Profiles:</strong> <span class="audio-profiles-short">{profiles}</span>
</div>
<div class="right">
<a id="account-edit-audio-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
<% end %>
</div>
<!-- end content wrapper -->
</script>
</div>
<script type="text/template" id="template-account-main">
<!-- content wrapper -->
<div class="content-wrapper account">
<br />
<div class="account-left">
<h2>identity:</h2>
</div>
<div class="account-mid identity">
<strong>Email:</strong> {email}<br />
<strong>Password:</strong> &bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;
</div>
<div class="right">
<a id="account-edit-identity-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>profile:</h2>
</div>
<div class="account-mid profile">
<div class="left small mr20" align="center">
<a href="#" class="avatar_large"><img src="{photoUrl}" id="profile-avatar" /></a></div>
<strong>Name:</strong> {name}<br />
<strong>Location:</strong> {location}<br />
<strong>Instruments:</strong> {instruments}
</div>
<div class="right">
<a id="account-edit-profile-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>subscriptions:</h2>
</div>
<div class="account-mid subscriptions">
<!--<strong>Studio:</strong> Gold ($49.99/year)<br />
<strong>NaaS:</strong> Yes ($29.99/year)-->
<strong>N/A</strong><br />
<strong>This feature not yet implemented</strong>
</div>
<div class="right">
<!--<a id="account-edit-subscriptions-link" href="#" class="button-orange">UPDATE</a>-->
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>payments:</h2>
</div>
<div class="account-mid payments">
<!--<strong>Method:</strong> MasterCard &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; 1234<br />
<a href="#">View Payment History</a>&nbsp;&nbsp;<a href="#">Cancel Subscription</a> -->
<strong>N/A</strong></br />
<strong>This feature not yet implemented</strong>
</div>
<div class="right">
<!--<a id="account-edit-payments-link" href="#" class="button-orange">UPDATE</a>-->
</div>
<br clear="all" />
<hr />
<div class="account-left">
<h2>audio:</h2>
</div>
<div class="account-mid audio">
<strong>Approved Profiles:</strong> <span class="audio-profiles-short">{profiles}</span>
</div>
<div class="right">
<a id="account-edit-audio-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
</div>
<!-- end content wrapper -->
</script>

View File

@ -13,8 +13,10 @@
<!-- end header -->
<!-- profile scrolling area -->
<div id="account-audio-content-scroller" class="content-scroller account-content-scroller">
<div class="content-body">
<div id="account-audio-content-scroller" class="content-body-scroller account-content-scroller">
</div>
</div>
<!-- end content scrolling area -->
</div>

View File

@ -13,8 +13,10 @@
<!-- end header -->
<!-- profile scrolling area -->
<div id="account-identity-content-scroller" class="content-scroller account-content-scroller">
<div class="content-body">
<div id="account-identity-content-scroller" class="content-body-scroller">
</div>
</div>
<!-- end content scrolling area -->
</div>

View File

@ -1,4 +1,4 @@
<!-- Account Summary Dialog -->
<!-- Account Profile Screen -->
<div layout="screen" layout-id="account/profile" class="screen secondary">
<!-- header -->
<div class="content-head">
@ -13,8 +13,10 @@
<!-- end header -->
<!-- profile scrolling area -->
<div id="account-profile-content-scroller" class="content-scroller account-content-scroller">
<div class="content-body">
<div id="account-profile-content-scroller" class="content-body-scroller account-content-scroller">
</div>
</div>
<!-- end content scrolling area -->
</div>

View File

@ -1,4 +1,4 @@
<!-- Account Summary Dialog -->
<!-- Profile Avatar Screen -->
<div layout="screen" layout-id="account/profile/avatar" class="screen secondary">
<!-- header -->
<div class="content-head">
@ -35,7 +35,7 @@
</div>
<!-- end content wrapper -->
<!-- taken from filepickr.io -->
<!-- taken from filepicker.io -->
<script type="text/javascript">
(function(a){if(window.filepicker){return}var b=a.createElement("script");b.type="text/javascript";b.async=!0;b.src=("https:"===a.location.protocol?"https:":"http:")+"//api.filepicker.io/v1/filepicker.js?signature={fp_signature}&policy={fp_policy}";var c=a.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d={};d._queue=[];var e="pick,pickMultiple,pickAndStore,read,write,writeUrl,export,convert,store,storeUrl,remove,stat,setKey,constructWidget,makeDropPane".split(",");var f=function(a,b){return function(){b.push([a,arguments])}};for(var g=0;g<e.length;g++){d[e[g]]=f(e[g],d._queue)}window.filepicker=d})(document);
</script>

View File

@ -19,7 +19,6 @@
<div class="right">
<a id="btn-follow-band" class="button-orange">FOLLOW</a>
<a id="btn-edit-band-profile" class="button-orange">EDIT PROFILE</a>
<a id="btn-edit-band-members" class="button-orange">EDIT MEMBERS</a>
</div>
<br clear="all" /><br />
@ -92,6 +91,7 @@
{biography}<br /><br />
<a class="button-orange smallbutton m0" href="{profile_url}">PROFILE</a>&nbsp;&nbsp;
<a id="btn-follow-member" class="button-orange smallbutton m0">FOLLOW</a>&nbsp;&nbsp;
<a id="btn-remove-member" class="button-orange smallbutton m0">REMOVE MEMBER</a>&nbsp;&nbsp;
<a id="btn-friend-member" style="display:none;" class="button-orange smallbutton m0">CONNECT</a>
</div>
</div>

View File

@ -9,126 +9,127 @@
<%= render "screen_navigation" %>
</div>
<form id="band-setup-form">
<div style="display:block;">
<input id="hdn-band-id" class="hdn-band-id" type="hidden" value="" />
<div class="content-scroller" style="height:350px;">
<div id="band-setup-step-1" class="content-wrapper" style="padding:10px 35px 10px 35px;">
<br />
<table width="105%" cellpadding="0" cellspacing="0">
<tr>
<td valign="top" colspan="2">
<h2>Step 1: General Information</h2>
</td>
<td valign="middle" rowspan="2" width="33%">
<a href="#" class="avatar-profile">
<%= image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 } %>
<div class="content-body">
<div class="content-body-scroller">
<form id="band-setup-form">
<div style="display:block;">
<input id="hdn-band-id" class="hdn-band-id" type="hidden" value="" />
<div id="band-setup-step-1" class="content-wrapper" style="padding:10px 35px 10px 35px;">
<br />
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td valign="top" colspan="2">
<h2>Step 1: General Information</h2>
</td>
<td valign="middle" rowspan="2" width="33%">
<a href="#" class="avatar-profile">
<%= image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 } %>
</a>
<br/><br/>
<a id="band-change-photo" href="#" class="small ml20">Upload Band Photo</a><br clear="all"><br/>
</td>
</tr>
<tr>
<td id="tdBandName" valign="middle" width="33%">Band Name:<br />
<input id="band-name" type="text" maxlength="1024" value="" class="w80"><br />
</td>
<td valign="middle" width="33%">Web Site:<br />
<input id="band-website" type="text" maxlength="4000" value="" class="w80">
</td>
</tr>
<tr>
<td id="tdBandCountry" valign="middle">Country:<br />
<select id="band-country" class="w80">
</select><br /><br />
</td>
<td id="tdBandRegion" valign="middle">State/Region:<br />
<select id="band-region" class="w80">
</select><br /><br />
</td>
<td id="tdBandCity" valign="middle">City:<br />
<select id="band-city" class="w80">
</select><br /><br />
</td>
</tr>
<tr>
<td id="tdBandGenres" valign="top">Genres:<br />
<div class="band-setup-genres w90">
<table id="band-genres" width="100%" cellpadding="10" cellspacing="6"></table>
</div>
</td>
<td id="tdBandBiography" valign="top" colspan="2">Description / Bio:<br />
<textarea id="band-biography" class="band-setup-bio w90" maxlength="4000"></textarea>
</td>
</tr>
</table>
<br clear="all" />
<div class="right">
<a id="btn-band-setup-cancel" class="button-grey">CANCEL</a>&nbsp;&nbsp;
<a id="btn-band-setup-next" class="button-orange">NEXT</a>
</div>
</div>
<div id="band-setup-step-2" class="content-wrapper" style="padding:10px 35px 10px 35px; display:none;">
<br/>
<h2>Step 2: Add Band Members</h2><br/>
<div class="left w70">If your bandmates are already on JamKazam, start typing their names in the box<br/> below, or click the Choose Friends button to select them.</div>
<div class="right" layout-link="select-friends">
<a href="#" id="btn-band-choose-friends" class="button-grey right">CHOOSE FRIENDS</a>
</div>
<br clear="all" />
<br />
<div class="friendbox">
<div id="selected-band-invitees"></div>
<input id="band-invitee-input" type="text" placeholder="Type a friend's name" width="150px" />
</div>
<br/><br/>
If your bandmates are not on JamKazam yet, use any of the options below to invite them to join the service.<br/><br/>
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
<br/><br/>
<a href="#" class="small ml20">Upload Band Photo</a><br clear="all"><br/>
</td>
</tr>
<tr>
<td id="tdBandName" valign="middle" width="33%">Band Name:<br />
<input id="band-name" type="text" maxlength="1024" value="" class="w80"><br />
</td>
<td valign="middle" width="33%">Web Site:<br />
<input id="band-website" type="text" maxlength="4000" value="" class="w80">
</td>
</tr>
<tr>
<td id="tdBandCountry" valign="middle">Country:<br />
<select id="band-country" class="w80">
</select><br /><br />
</td>
<td id="tdBandRegion" valign="middle">State/Region:<br />
<select id="band-region" class="w80">
</select><br /><br />
</td>
<td id="tdBandCity" valign="middle">City:<br />
<select id="band-city" class="w80">
</select><br /><br />
</td>
</tr>
<tr>
<td id="tdBandGenres" valign="top">Genres:<br />
<div class="band-setup-genres w90">
<table id="band-genres" width="100%" cellpadding="10" cellspacing="6"></table>
</div>
</td>
<td id="tdBandBiography" valign="top" colspan="2">Description / Bio:<br />
<textarea id="band-biography" class="band-setup-bio w90" maxlength="4000"></textarea>
</td>
</tr>
</table>
<br clear="all" />
<div class="right">
<a id="btn-band-setup-cancel" class="button-grey">CANCEL</a>&nbsp;&nbsp;
<a id="btn-band-setup-next" class="button-orange">NEXT</a>
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<!--
<div class="left mr20">
<div class="left">
<a id="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Facebook</div>
</div>
-->
<!-- <div class="left mr20">
<div class="left">
<a href="/#/createSession" title="This feature is not yet available.">
<%= image_tag("content/icon_twitter.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Twitter</div>
</div> -->
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
<br clear="all" />
<div class="right">
<a id="btn-band-setup-back" class="button-grey">BACK</a>&nbsp;&nbsp;
<a id="btn-band-setup-save" class="button-orange">CREATE BAND</a>
</div>
<div class="clearall"></div>
</div>
</div>
<div id="band-setup-step-2" class="content-wrapper" style="padding:10px 35px 10px 35px; display:none;">
<br/>
<h2>Step 2: Add Band Members</h2><br/>
<div class="left w70">If your bandmates are already on JamKazam, start typing their names in the box<br/> below, or click the Choose Friends button to select them.</div>
<div class="right" layout-link="select-friends">
<a href="#" id="btn-band-choose-friends" class="button-grey right">CHOOSE FRIENDS</a>
</div>
<br clear="all" />
<br />
<div class="friendbox">
<div id="selected-band-invitees"></div>
<input id="band-invitee-input" type="text" placeholder="Type a friend's name" width="150px" />
</div>
<br/><br/>
If your bandmates are not on JamKazam yet, use any of the options below to invite them to join the service.<br/><br/>
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<!--
<div class="left mr20">
<div class="left">
<a id="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Facebook</div>
</div>
-->
<!-- <div class="left mr20">
<div class="left">
<a href="/#/createSession" title="This feature is not yet available.">
<%= image_tag("content/icon_twitter.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Twitter</div>
</div> -->
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
<br clear="all" />
<div class="right">
<a id="btn-band-setup-back" class="button-grey">BACK</a>&nbsp;&nbsp;
<a id="btn-band-setup-save" class="button-orange">CREATE BAND</a>
</div>
</div>
</div>
</form>
</div>
<div id="band-setup-step-2" style="display:none;">
</div>
</form>
</div>
</div>
<script type="text/template" id="template-band-setup-genres">
@ -139,4 +140,4 @@
<div user-id="{userId}" class="invitation">{userName}
<a><%= image_tag "shared/icon_delete_sm.png", :size => "13x13" %></a>
</div>
</script>
</script>

View File

@ -0,0 +1,41 @@
<!-- Band Photo Setup -->
<div layout="screen" layout-id="band/setup/photo" class="screen secondary">
<!-- header -->
<div class="content-head">
<!-- icon -->
<div class="content-icon">
<%= image_tag "content/icon_bands.png", {:width => 19, :height => 19} %>
</div>
<!-- section head text -->
<h1>band setup</h1>
<%= render "screen_navigation" %>
</div>
<!-- end header -->
<!-- profile scrolling area -->
<div id="band-setup-photo-content-scroller" class="content-scroller">
</div>
<!-- end content scrolling area -->
</div>
<script type="text/template" id="template-band-setup-photo">
<!-- content wrapper -->
<div class="content-wrapper band-setup-photo">
<br />
<div class="avatar-space"></div>
<form id="band-setup-photo-form">
&nbsp;&nbsp; <a href="#" id="band-setup-photo-upload" class="button-orange">UPLOAD</a>
</form>
<br clear="all" />
<div class="right"><a id="band-setup-photo-cancel" href="#" class="button-grey">CANCEL</a>&nbsp;&nbsp;<a id="band-setup-photo-delete" href="#" class="button-orange">DELETE PHOTO</a>&nbsp;&nbsp;<a id="band-setup-photo-submit" href="#" class="button-orange">UPDATE PHOTO</a></div>
</div>
<!-- end content wrapper -->
<!-- taken from filepicker.io -->
<script type="text/javascript">
(function(a){if(window.filepicker){return}var b=a.createElement("script");b.type="text/javascript";b.async=!0;b.src=("https:"===a.location.protocol?"https:":"http:")+"//api.filepicker.io/v1/filepicker.js?signature={fp_signature}&policy={fp_policy}";var c=a.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d={};d._queue=[];var e="pick,pickMultiple,pickAndStore,read,write,writeUrl,export,convert,store,storeUrl,remove,stat,setKey,constructWidget,makeDropPane".split(",");var f=function(a,b){return function(){b.push([a,arguments])}};for(var g=0;g<e.length;g++){d[e[g]]=f(e[g],d._queue)}window.filepicker=d})(document);
</script>

View File

@ -1,13 +1,61 @@
<!-- Band Screen -->
<div layout="screen" layout-id="bands" class="screen secondary">
<div class="content-head">
<%= content_tag(:div, :layout => 'screen', 'layout-id' => 'bands', :class => "screen secondary") do -%>
<%= content_tag(:div, :class => :content) do -%>
<%= content_tag(:div, :class => 'content-head') do -%>
<%= content_tag(:div, image_tag("content/icon_bands.png", {:height => 19, :width => 19}), :class => 'content-icon') %>
<%= content_tag(:h1, 'bands') %>
<%= render "screen_navigation" %>
<% end -%>
<%= content_tag(:div, :class => 'content-body') do -%>
<%= content_tag(:div, :class => 'content-body-scroller') do -%>
<%= form_tag('', :id => 'find-band-form') do -%>
<%= content_tag(:div, render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_BAND}), :class => 'band-filter', :id => 'session-controls') %>
<%= content_tag(:div, :class => 'content-scroller') do -%>
<%= content_tag(:div, content_tag(:div, '', :id => 'band-filter-results', :class => 'filter-results'), :class => 'content-wrapper band-wrapper') %>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<div class="content-icon">
<%= image_tag "content/icon_bands.png", {:height => 19, :width => 19} %>
</div>
<h1>bands</h1>
<%= render "screen_navigation" %>
<!-- Session Row Template -->
<script type="text/template" id="template-find-band-row">
<div class="profile-band-list-result band-list-result">
<div style="float:left">
<!-- avatar -->
<div class="avatar-small"><img src="{avatar_url}" /></div>
<!-- name & location -->
<div style="width:220px;" class="result-name">{band_name}<br />
<span class="result-location">{band_location}</span>
<br /><br />
<div id="result_genres" class="nowrap">{genres}</div>
<br /><br />
{follow_count} <img src="../assets/content/icon_followers.png" width="22" height="12" align="absmiddle" />&nbsp;&nbsp;&nbsp;{recording_count} <img src="../assets/content/icon_recordings.png" width="12" height="13" align="absmiddle" />&nbsp;&nbsp;&nbsp;{session_count} <img src="../assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle" /><br /><br />
</div>
</div>
<p>This feature not yet implemented</p>
</div>
<div class="left ml20 f11 whitespace w35"><br />
{biography}<br />
<br />
<div data-band-id={band_id}>{band_action_template}</div>
</div>
<div class="left ml10 w25 band-players">
<table class="musicians" cellpadding="0" cellspacing="5">{band_player_template}</table>
</div>
<br clear="all" />
</div>
</script>
<script type="text/template" id="template-band-action-btns">
<a href="{profile_url}" class="button-orange smallbutton m0">PROFILE</a><a href="#" class="{button_follow} smallbutton search-m-follow">FOLLOW</a>
</script>
<script type="text/template" id="template-band-player-info">
<tr>
<td width="24"><a href="{profile_url}" class="avatar-tiny"><img src="{avatar_url}" /></a></td>
<td><a href="{profile_url}"><strong>{player_name}</strong></a></td>
<td>{player_instruments}</td>
</tr>
</script>

View File

@ -1,5 +1,5 @@
<!-- Create Session Screen -->
<div layout="screen" layout-id="createSession" class="screen secondary">
<div layout="screen" layout-id="createSession" class="screen secondary create-session">
<div class="content">
<div class="content-head">
<div class="content-icon">
@ -9,163 +9,165 @@
<h1>create session</h1>
<%= render "screen_navigation" %>
<div class="clearall"></div>
</div>
<form id="create-session-form">
<div class="content-scroller">
<div class="content-wrapper">
<!-- left column -->
<div class="session-left">
<div class="content-body">
<div class="content-body-scroller">
<form id="create-session-form">
<div class="content-wrapper">
<!-- left column -->
<div class="session-wrapper">
<div class="session left">
<h2>session info</h2>
<h2>session info</h2>
<br />
<br />
<div id="divGenre" class="left mr20">
<div class="mb5">Genre:</div>
<div id="create-session-genre">
<%= render "genreSelector" %>
</div>
</div>
<div class="left">
<div class="mb5">Band:</div>
<div id="create-session-band">
<select id="band-list">
<option value="">Not a Band Session</option>
</select>
</div>
</div>
<br clear="all" /><br />
<div id="divDescription">
<div class="mb5">Description:</div>
<div>
<textarea rows=4 id="description" name="description" class="session-description"></textarea>
</div>
</div>
<br clear="all" />
<div class="mb5">Musician Access:</div>
<div>
<div>
<select id="musician-access" class="left mr20">
<option selected="selected" value="true">Public</option>
<option value="false">Private</option>
</select>
</div>
<div class="left icheckbuttons">
<input type="radio" name="musician-access-option" id="musician-access-option-false" checked="checked" value="false" class="musician-access-false" /><label for="musician-access-option-false" class="radio-text">Open</label>
<input type="radio" name="musician-access-option" id="musician-access-option-true" value="true" class="musician-access-true" /><label for="musician-access-option-true" class="radio-text">By Approval</label>
</div>
</div>
<br clear="all" /><br />
<div class="mb5">Fan Access:</div>
<div>
<div>
<select id="fan-access" class="left mr20">
<option value="true">Public</option>
<option selected="selected" value="false">Private</option>
</select>
</div>
<div class="left icheckbuttons">
<input type="radio" name="fan-chat-option" id="fan-chat-option-true" value="true" class="fan-chat-option-true" disabled="disabled" /><label for="fan-chat-option-true" class="radio-text">Chat</label>
<input type="radio" name="fan-chat-option" id="fan-chat-option-false" checked="checked" class="fan-chat-option-false" value="false" disabled="disabled" /><label for="fan-chat-option-false" class="radio-text">No Fan Chat</label>
</div>
</div>
</div>
<!-- right column -->
<div class="session-right">
<h2>invite musicians</h2>
<br />
<div style="width:78%;">
<div class="left" style="margin-top:10px;">
Start typing friends' names or:
</div>
<div class="right" layout-link="select-friends">
<a href="#" id="btn-choose-friends" class="button-grey">CHOOSE FRIENDS</a>
</div>
</div>
<div class="clearall"></div>
<br />
<!-- friend invitation box -->
<div class="friendbox">
<div id="selected-friends"></div>
<input id="friend-input" type="text" placeholder="Type a friend's name" />
</div>
<div class="mt35 mb15">
Invite friends and contacts to join you on JamKazam from:
</div>
<div style="width:78%">
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
<div id="divDescription">
<div class="mb5">Description:</div>
<div>
<textarea rows=4 id="description" name="description" class="session-description"></textarea>
</div>
</div>
<div id="divGenre" class="left mr20 mt10">
<div class="mb5">Genre:</div>
<div id="create-session-genre">
<%= render "genreSelector" %>
</div>
</div>
<div class="left mt10">
<div class="mb5">Band:</div>
<div id="create-session-band">
<select id="band-list">
<option value="">Not a Band Session</option>
</select>
</div>
</div>
<br clear="all" /><br />
<div class="mb5">Musician Access:</div>
<div>
<div>
<select id="musician-access" class="left mr20">
<option selected="selected" value="true">Public</option>
<option value="false">Private</option>
</select>
</div>
<div class="left icheckbuttons">
<input type="radio" name="musician-access-option" id="musician-access-option-false" checked="checked" value="false" class="musician-access-false" /><label for="musician-access-option-false" class="radio-text">Open</label>
<input type="radio" name="musician-access-option" id="musician-access-option-true" value="true" class="musician-access-true" /><label for="musician-access-option-true" class="radio-text">By Approval</label>
</div>
</div>
<br clear="all" /><br />
<div class="mb5">Fan Access:</div>
<div>
<div>
<select id="fan-access" class="left mr20">
<option value="true">Public</option>
<option selected="selected" value="false">Private</option>
</select>
</div>
<div class="left icheckbuttons">
<input type="radio" name="fan-chat-option" id="fan-chat-option-true" value="true" class="fan-chat-option-true" disabled="disabled" /><label for="fan-chat-option-true" class="radio-text">Chat</label>
<input type="radio" name="fan-chat-option" id="fan-chat-option-false" checked="checked" class="fan-chat-option-false" value="false" disabled="disabled" /><label for="fan-chat-option-false" class="radio-text">No Fan Chat</label>
</div>
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<!--
<div class="left mr20">
<div class="left">
<a id="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
<!-- right column -->
<div class="session right">
<h2>invite musicians</h2>
<br />
<div>
<div class="right" layout-link="select-friends">
<a href="#" id="btn-choose-friends" class="button-grey">CHOOSE FRIENDS</a>
</div>
<div style="margin-right:140px;">
Start typing friends' names or:
</div>
<div class="clearall"></div>
</div>
<div class="right mt5 ml5">Facebook</div>
</div>
-->
<!-- <div class="left mr20">
<div class="left">
<a href="/#/createSession" title="This feature is not yet available.">
<%= image_tag("content/icon_twitter.png", :size => "24x24", :align => "absmiddle") %>
</a>
<br />
<!-- friend invitation box -->
<div class="friendbox">
<div id="selected-friends"></div>
<input id="friend-input" type="text" placeholder="Type a friend's name" />
</div>
<div class="right mt5 ml5">Twitter</div>
</div> -->
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
<div class="mt35 mb15">
Invite friends and contacts to join you on JamKazam from:
</div>
<div>
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<!--
<div class="left mr20">
<div class="left">
<a id="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Facebook</div>
</div>
-->
<!-- <div class="left mr20">
<div class="left">
<a href="/#/createSession" title="This feature is not yet available.">
<%= image_tag("content/icon_twitter.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Twitter</div>
</div> -->
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
<div class="clearall"></div>
<br clear="all"/>
<br clear="all"/>
<!-- terms -->
<div id="divIntellectualProperty">
<div class="terms-checkbox icheckbuttons">
<input type="checkbox" id="intellectual-property" class="intellectual-property" />
</div>
<div id="divTerms" class="terms ml25">
I agree that intellectual property ownership of any musical works created during this session shall be governed by the terms of the <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/" target="_blank">Creative Commons CC BY-NC-SA license</a> in accordance with the <a rel="external" href="http://www.jamkazam.com/corp/terms" target="_blank">JamKazam Terms of Service</a>.
</div>
</div>
<br clear="all"/>
<br clear="all"/>
<div class="right mt10">
<div>
<a href="#" layout-link="home" class="button-grey">CANCEL</a>
<a href="#" id="btn-create-session" class="button-orange">JAM!</a>
</div>
</div>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
<div class="clearall"></div>
<br clear="all"/>
<br clear="all"/>
<!-- terms -->
<div id="divIntellectualProperty">
<div class="terms-checkbox icheckbuttons">
<input type="checkbox" id="intellectual-property" class="intellectual-property" />
</div>
<div id="divTerms" class="terms ml25">
I agree that intellectual property ownership of any musical works created during this session shall be governed by the terms of the <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/" target="_blank">Creative Commons CC BY-NC-SA license</a> in accordance with the <a rel="external" href="http://www.jamkazam.com/corp/terms" target="_blank">JamKazam Terms of Service</a>.
</div>
</div>
<br clear="all"/>
<br clear="all"/>
<div class="right mt10">
<div>
<a href="#" layout-link="home" class="button-grey">CANCEL</a>
<a href="#" id="btn-create-session" class="button-orange">JAM!</a>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</form>
<div class="clearall"></div>
</div>
</div>
</div>

View File

@ -1,12 +1,18 @@
<!-- Feed Screen -->
<div layout="screen" layout-id="feed" class="screen secondary">
<div class="content-head">
<div class="content">
<div class="content-head">
<div class="content-icon">
<%= image_tag "content/icon_feed.png", {:height => 19, :width => 19} %>
<div class="content-icon">
<%= image_tag "content/icon_feed.png", {:height => 19, :width => 19} %>
</div>
<h1>feed</h1>
<%= render "screen_navigation" %>
</div>
<div class="content-body">
<div class="content-body-scroller">
<p>This feature not yet implemented</p>
</div>
</div>
<h1>feed</h1>
<%= render "screen_navigation" %>
</div>
<p>This feature not yet implemented</p>
</div>

View File

@ -10,43 +10,47 @@
<%= render "screen_navigation" %>
</div>
<form id="find-session-form">
<div class="session-filter">
<div style="min-width:770px;">
<div class="left ml35" style="padding-top:3px;">Filter Session List:</div>
<div class="content-body">
<div class="content-body-scroller">
<form id="find-session-form">
<div class="session-filter">
<div style="min-width:770px;">
<div class="left ml35" style="padding-top:3px;">Filter Session List:</div>
<!-- genre filter -->
<div id="find-session-genre" class="left ml10">
<%= render "genreSelector" %>
</div>
<!-- genre filter -->
<div id="find-session-genre" class="left ml10">
<%= render "genreSelector" %>
</div>
<!-- keyword filter -->
<div class="search-box" style="height:25px;">
<input id="session-keyword-srch" type="text" name="search" placeholder="Search by Keyword" />
<!-- keyword filter -->
<div class="search-box" style="height:25px;">
<input id="session-keyword-srch" type="text" name="search" placeholder="Search by Keyword" />
</div>
<div class="right mr10">
<a id="btn-refresh" href="#/findSession" style="text-decoration:none;" class="button-grey">REFRESH</a>
</div>
</div>
</div>
<div class="right mr10">
<a id="btn-refresh" href="#/findSession" style="text-decoration:none;" class="button-grey">REFRESH</a>
<div class="content-scroller">
<div class="content-wrapper" style="padding-left:35px;padding-top:10px;">
<div id="sessions-invitations">
<%= render :partial => "sessionList", :locals => {:title => "sessions you're invited to", :category => "sessions-invitations"} %>
</div>
<div id="sessions-friends" class="mt35">
<%= render :partial => "sessionList", :locals => {:title => "sessions with friends or bandmates", :category => "sessions-friends"} %>
</div>
<div id="sessions-other" class="mt35">
<%= render :partial => "sessionList", :locals => {:title => "other sessions", :category => "sessions-other"} %>
</div>
</div>
</div>
</form>
<div id="sessions-none-found">
There are currently no public sessions.
</div>
</div>
<div class="content-scroller">
<div class="content-wrapper" style="padding-left:35px;padding-top:10px;">
<div id="sessions-invitations">
<%= render :partial => "sessionList", :locals => {:title => "sessions you're invited to", :category => "sessions-invitations"} %>
</div>
<div id="sessions-friends" class="mt35">
<%= render :partial => "sessionList", :locals => {:title => "sessions with friends or bandmates", :category => "sessions-friends"} %>
</div>
<div id="sessions-other" class="mt35">
<%= render :partial => "sessionList", :locals => {:title => "other sessions", :category => "sessions-other"} %>
</div>
</div>
</div>
</form>
<div id="sessions-none-found">
There are currently no public sessions.
</div>
</div>
</div>
@ -105,4 +109,4 @@
<!-- Musician option template -->
<script type="text/template" id="template-musician-option">
<option value="{value}">{label}</option>
</script>
</script>

View File

@ -1,35 +1,29 @@
<%= content_tag(:div, :style => "min-width:770px;") do -%>
<%= content_tag(:div, :class => 'filter-element') do -%>
<%= content_tag(:div, 'Filter By:', :class => 'filter-element', :style => "padding-top:3px;") %>
<!-- order by filter -->
<%= select_tag(:musician_order_by, options_for_select(Search::M_ORDERINGS), {:class => 'musician-order-by'} ) %>
<% end -%>
<%= content_tag(:div, :class => 'filter-element') do -%>
<!-- instrument filter -->
<%= content_tag(:div, 'Instrument:', :class => 'filter-element') %>
<%= content_tag(:div, :class => 'filter-element', :id => "find-musician-instrument") do -%>
<%= select_tag(:instrument,
options_for_select([['Any', '']].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] })),
{:class => 'instrument-list'} ) %>
<% end -%>
<% end -%>
<!-- distance filter -->
<%= content_tag(:div, :class => 'filter-element') do -%>
<%= content_tag(:div, 'Within', :class => 'filter-element') %>
<%= content_tag(:div, :class => 'query-distance-params', :style => "height:25px;") do -%>
<%= text_field_tag(:query_distance,
Search::M_MILES_DEFAULT.to_s,
:id => :musician_query_distance,
:placeholder => Search::M_MILES_DEFAULT.to_s) %>
<% end -%>
<%= content_tag(:div, :class => 'filter-element') do -%>
miles of <%= content_tag(:span, current_user.current_city(request.remote_ip), :id => 'musician-filter-city') %>
<% end -%>
<% end -%>
<%= content_tag(:div,
link_to('REFRESH', '#',
:id => 'btn-refresh-musicians',
:style => 'text-decoration:none',
:class => 'button-grey'),
:class => 'right mr10') %>
<%= content_tag(:div, :class => 'filter-element wrapper foobar') do -%>
<%= content_tag(:div, 'Filter By:', :class => 'filter-element desc') %>
<!-- order by filter -->
<%= select_tag(:musician_order_by, options_for_select(Search::M_ORDERINGS), {:class => 'musician-order-by'} ) %>
<% end -%>
<%= content_tag(:div, :class => "filter-element wrapper") do -%>
<%= content_tag(:div, :class => 'filter-element wrapper') do -%>
<!-- instrument filter -->
<%= content_tag(:div, 'Instrumente:', :class => 'filter-element') %>
<%= select_tag(:musician_instrument,
options_for_select([['Any', '']].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] }))) %>
<% end -%>
<% end -%>
<!-- distance filter -->
<%= content_tag(:div, :class => 'filter-element wrapper') do -%>
<%= content_tag(:div, 'Within', :class => 'filter-element desc') %>
<%= content_tag(:div, :class => 'query-distance-params') do -%>
<%= select_tag('musician_query_distance', options_for_select(Search::M_DISTANCE_OPTS, Search::M_MILES_DEFAULT)) %>
<% end -%>
<%= content_tag(:div, :class => 'filter-element') do -%>
miles of <%= content_tag(:span, current_user.current_city(request.remote_ip), :id => 'musician-filter-city') %>
<% end -%>
<% end -%>
<%= content_tag(:div,
link_to('REFRESH', '#',
:id => 'btn-refresh-musicians',
:style => 'text-decoration:none',
:class => 'button-grey'),
:class => 'filter-element wrapper right mr10') %>

View File

@ -6,10 +6,14 @@
<%= content_tag(:h1, 'musicians') %>
<%= render "screen_navigation" %>
<% end -%>
<%= form_tag('', :id => 'find-musician-form') do -%>
<%= content_tag(:div, render(:partial => "musician_filter"), :class => 'musician-filter', :id => 'session-controls') %>
<%= content_tag(:div, :class => 'content-scroller') do -%>
<%= content_tag(:div, content_tag(:div, '', :id => 'musician-filter-results'), :class => 'content-wrapper musician-wrapper') %>
<%= content_tag(:div, :class => 'content-body') do -%>
<%= content_tag(:div, :class => 'content-body-scroller') do -%>
<%= form_tag('', :id => 'find-musician-form') do -%>
<%= content_tag(:div, render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_MUSICIAN}), :class => 'musician-filter', :id => 'session-controls') %>
<%= content_tag(:div, :class => 'content-scroller') do -%>
<%= content_tag(:div, content_tag(:div, '', :id => 'musician-filter-results'), :class => 'content-wrapper musician-wrapper') %>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
<% end -%>
@ -32,10 +36,10 @@
</div>
</div>
<div class="left ml35 f11 whitespace w40"><br />
{biography}<br />
{biography}<br />
<br />
<div data-musician-id={musician_id}>
{musician_action_template}
<div class="result-list-button-wrapper" data-musician-id={musician_id}>
{musician_action_template}
</div>
</div>
<div class="left ml10 w20 musician-following">
@ -48,7 +52,13 @@
</script>
<script type="text/template" id="template-musician-action-btns">
<a href="{profile_url}" class="button-orange smallbutton m0">PROFILE</a><% if current_user.musician? %><a href="#" class="{button_friend} smallbutton m0 search-m-friend">CONNECT</a><% end %><a href="#" class="{button_follow} smallbutton search-m-follow">FOLLOW</a><!--<a href="#" class="{button_message} smallbutton search-m-like">MESSAGE</a>-->
<a href="{profile_url}" class="button-orange smallbutton">PROFILE</a>
<% if current_user.musician? %>
<a href="#" class="{button_friend} smallbutton search-m-friend">CONNECT</a>
<% end %>
<a href="#" class="{button_follow} smallbutton search-m-follow">FOLLOW</a>
<!--<a href="#" class="{button_message} smallbutton search-m-like">MESSAGE</a>-->
<div class="clearall"></div>
</script>
<script type="text/template" id="template-musician-follow-info">

View File

@ -5,104 +5,110 @@
<%= image_tag "content/icon_profile.png", :size => "19x19" %>
</div>
<h1>musician profile</h1>
<h1><span id="profile-type-label">musician</span> profile</h1>
<%= render "screen_navigation" %>
</div>
<form id="profile-form">
<div class="profile-header">
<div class="content-body">
<div class="content-body-scroller">
<form id="profile-form">
<div class="profile-header">
<!-- profile name -->
<h2 id="profile-username"></h2>
<!-- profile name -->
<h2 id="profile-username"></h2>
<!-- profile status -->
<div class="profile-status">
</div>
<!-- profile status -->
<div class="profile-status">
</div>
<!-- action buttons -->
<div class="right">
<a id="btn-add-friend" class="button-orange">ADD FRIEND</a>
<a id="btn-follow-user" class="button-orange">FOLLOW</a>
</div>
<br clear="all" /><br />
<!-- action buttons -->
<div class="right">
<a id="btn-add-friend" class="button-orange">ADD FRIEND</a>
<a id="btn-follow-user" class="button-orange">FOLLOW</a>
<%= link_to("EDIT PROFILE", '/client#/account/profile', :id => "btn-profile-edit", :class => "button-orange") %>
</div>
<br clear="all" /><br />
<!-- avatar -->
<div class="profile-photo">
<div class="avatar-profile">
<img id="profile-avatar" width="200" height="200" />
<!-- avatar -->
<div class="profile-photo">
<div class="avatar-profile">
<img id="profile-avatar" width="200" height="200" />
</div>
</div>
<!-- profile navigation -->
<div class="profile-nav">
<a id="profile-about-link" class="active">about</a>
<a id="profile-history-link">history</a>
<a id="profile-bands-link">bands</a>
<a id="profile-social-link">social</a>
<a id="profile-favorites-link" class="last">favorites</a>
</div>
<div class="clearall"></div>
</div>
</div>
<!-- profile navigation -->
<div class="profile-nav">
<a id="profile-about-link" class="active">about</a>
<a id="profile-history-link">history</a>
<a id="profile-bands-link">bands</a>
<a id="profile-social-link">social</a>
<a id="profile-favorites-link" class="last">favorites</a>
</div>
<div class="content-scroller" style="height:350px;">
<div id="profile-about" class="profile-wrapper">
<!-- stats & location -->
<div class="profile-about-left">
<h3>Location:</h3><br />
<span id="profile-location"></span><br /><br /><br />
<h3>Stats:</h3><br />
<span id="profile-friend-stats"></span><br />
<span id="profile-follower-stats"></span><br />
<span id="profile-following-stats"></span><br />
<span id="profile-favorite-stats"></span><br />
<span id="profile-session-stats"></span><br />
<span id="profile-recording-stats"></span><br />
</div>
<div class="profile-about-right">
<p id="profile-biography"></p><br />
<div id="profile-instruments">
</div>
</div>
<br clear="all" />
</div>
<div id="profile-history" class="profile-wrapper">
<br clear="all" />
</div>
<div id="profile-bands" class="profile-wrapper">
<br clear="all" />
</div>
<div id="profile-social" class="profile-wrapper">
<div class="profile-social-left">
<h2>Friends</h2>
<div id="profile-social-friends">
</div>
</div>
<div class="profile-social-mid">
<h2>Following</h2>
<div id="profile-social-followings">
</div>
</div>
<div class="profile-social-right">
<h2>Followers</h2>
<div id="profile-social-followers">
</div>
</div>
<br clear="all" />
</div>
<div id="profile-favorites" class="profile-wrapper">
<br clear="all" />
</div>
</div>
</form>
</div>
<br clear="all" />
<div class="content-scroller" style="height:350px;">
<div id="profile-about" class="profile-wrapper">
<!-- stats & location -->
<div class="profile-about-left">
<h3>Location:</h3><br />
<span id="profile-location"></span><br /><br /><br />
<h3>Stats:</h3><br />
<span id="profile-friend-stats"></span><br />
<span id="profile-follower-stats"></span><br />
<span id="profile-session-stats"></span><br />
<span id="profile-recording-stats"></span><br />
</div>
<div class="profile-about-right">
<p id="profile-biography"></p><br />
<div id="profile-instruments">
</div>
</div>
<br clear="all" />
</div>
<div id="profile-history" class="profile-wrapper">
<br clear="all" />
</div>
<div id="profile-bands" class="profile-wrapper">
<br clear="all" />
</div>
<div id="profile-social" class="profile-wrapper">
<div class="profile-social-left">
<h2>Friends</h2>
<div id="profile-social-friends">
</div>
</div>
<div class="profile-social-mid">
<h2>Following</h2>
<div id="profile-social-followings">
</div>
</div>
<div class="profile-social-right">
<h2>Followers</h2>
<div id="profile-social-followers">
</div>
</div>
<br clear="all" />
</div>
<div id="profile-favorites" class="profile-wrapper">
<br clear="all" />
</div>
</div>
</form>
</div>
</div>
<script type="text/template" id="template-no-bands">
<div class="when-empty">
Not In Any Bands:<br/>
<a href="#/band/setup">Set Up Your Band</a>
<a id="band-setup-link" href="#/band/setup">Set Up Your Band</a>
</div>
</script>
<script type="text/template" id="template-more-bands">
<div class="when-empty">
<a href="#/band/setup">Set Up Another Band</a>
<a id="band-setup-link" href="#/band/setup">Set Up Another Band</a>
</div>
</script>
@ -149,4 +155,4 @@
<div class="profile-block-name">{userName}</div>
<div class="profile-block-city">{location}</div>
</div>
</script>
</script>

View File

@ -5,7 +5,6 @@
<%= image_tag "shared/icon_session.png", {:height => 19, :width => 19} %>
</div>
<h1>session</h1>
<%= render "screen_navigation" %>
</div>
<!-- session controls -->

View File

@ -0,0 +1,30 @@
<% filter_label = defined?(search_type) && search_type == Search::PARAM_BAND ? :band : :musician %>
<%= content_tag(:div, :class => "filter-element wrapper") do -%>
<%= content_tag(:div, 'Filter By:', :class => 'filter-element desc') %>
<!-- order by filter -->
<%= select_tag("#{filter_label}_order_by", options_for_select(Search::ORDERINGS), {:class => "#{filter_label}-order-by"} ) %>
<% end -%>
<%= content_tag(:div, :class => 'filter-element wrapper') do -%>
<% if :musician == filter_label %>
<!-- instrument filter -->
<%= content_tag(:div, 'Instrument:', :class => 'filter-element desc') %>
<%= select_tag("#{filter_label}_instrument",
options_for_select([['Any', '']].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] }))) %>
<% elsif :band == filter_label %>
<!-- genre filter -->
<%= content_tag(:div, 'Genre:', :class => 'filter-element desc') %>
<%= select_tag("#{filter_label}_genre",
options_for_select([['Any', '']].concat(JamRuby::Genre.all.collect { |ii| [ii.description, ii.id] }))) %>
<% end %>
<% end -%>
<!-- distance filter -->
<%= content_tag(:div, :class => 'filter-element wrapper') do -%>
<%= content_tag(:div, 'Within', :class => 'filter-element desc') %>
<%= content_tag(:div, :class => 'query-distance-params') do -%>
<% default_distance = :musician == filter_label ? Search::M_MILES_DEFAULT : Search::B_MILES_DEFAULT %>
<%= select_tag("#{filter_label}_query_distance", options_for_select(Search::DISTANCE_OPTS, default_distance)) %>
<% end -%>
<%= content_tag(:div, :class => 'filter-element desc') do -%>
miles of <%= content_tag(:span, current_user.current_city(request.remote_ip), :id => "#{filter_label}-filter-city") %>
<% end -%>
<% end -%>

View File

@ -22,6 +22,7 @@
<%= render "profile" %>
<%= render "bandProfile" %>
<%= render "band_setup" %>
<%= render "band_setup_photo" %>
<%= render "feed" %>
<%= render "bands" %>
<%= render "musicians" %>
@ -141,6 +142,9 @@
var bandSetupScreen = new JK.BandSetupScreen(JK.app);
bandSetupScreen.initialize(invitationDialog, friendSelectorDialog);
var bandSetupPhotoScreen = new JK.BandSetupPhotoScreen(JK.app);
bandSetupPhotoScreen.initialize();
var findSessionScreen = new JK.FindSessionScreen(JK.app);
var sessionLatency = null;
if ("jamClient" in window) {
@ -151,6 +155,9 @@
var findMusicianScreen = new JK.FindMusicianScreen(JK.app);
findMusicianScreen.initialize();
var findBandScreen = new JK.FindBandScreen(JK.app);
findBandScreen.initialize();
var sessionScreen = new JK.SessionScreen(JK.app);
sessionScreen.initialize();
var sessionSettingsDialog = new JK.SessionSettingsDialog(JK.app, sessionScreen);

View File

@ -2,15 +2,15 @@
<% provide(:purpose, 'news') %>
<h1>News</h1>
<p>August 27, 2013 -- <span class="white">Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</span>&nbsp;&nbsp;<a href="http://www.jeffkoke.com/jamkazam/web_corp_news.html#">READ MORE <20></a></p>
<p>August 27, 2013 -- <span class="white">Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</span>&nbsp;&nbsp;<a href="/corp/news">READ MORE »</a></p>
<p>August 15, 2013 -- <span class="white">Aliquam et nisl vel ligula consectetuer suscipit. Morbi euismod enim eget neque. Donec sagittis massa. Vestibulum quis augue sit amet ipsum laoreet pretium. Nulla facilisi.&nbsp;&nbsp;<a href="http://www.jeffkoke.com/jamkazam/web_corp_news.html#">READ MORE <20></a></span></p>
<p>August 15, 2013 -- <span class="white">Aliquam et nisl vel ligula consectetuer suscipit. Morbi euismod enim eget neque. Donec sagittis massa. Vestibulum quis augue sit amet ipsum laoreet pretium. Nulla facilisi.&nbsp;&nbsp;<a href="/corp/news">READ MORE »</a></span></p>
<p>August 2, 2013 -- <span class="white">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. </span>&nbsp;&nbsp;<a href="http://www.jeffkoke.com/jamkazam/web_corp_news.html#">READ MORE <20></a></p>
<p>August 2, 2013 -- <span class="white">Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. </span>&nbsp;&nbsp;<a href="/corp/news">READ MORE »</a></p>
<p>July 12, 2013 -- <span class="white">Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</span>&nbsp;&nbsp;<a href="http://www.jeffkoke.com/jamkazam/web_corp_news.html#">READ MORE <20></a></p>
<p>July 12, 2013 -- <span class="white">Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</span>&nbsp;&nbsp;<a href="/corp/news">READ MORE »</a></p>
<p>July 7, 2013 -- <span class="white">Aliquam et nisl vel ligula consectetuer suscipit. Morbi euismod enim eget neque. Donec sagittis massa. Vestibulum quis augue sit amet ipsum laoreet pretium. Nulla facilisi.</span>&nbsp;&nbsp;<a href="http://www.jeffkoke.com/jamkazam/web_corp_news.html#">READ MORE <20></a></p>
<p>July 7, 2013 -- <span class="white">Aliquam et nisl vel ligula consectetuer suscipit. Morbi euismod enim eget neque. Donec sagittis massa. Vestibulum quis augue sit amet ipsum laoreet pretium. Nulla facilisi.</span>&nbsp;&nbsp;<a href="/corp/news">READ MORE »</a></p>

View File

@ -459,7 +459,7 @@
discover Content that infringes any or violates any of your other rights, which you believe is defamatory,
pornographic, obscene, racist or otherwise liable to cause widespread offence, or which constitutes impersonation,
harassment, spam or otherwise violates these Terms of Service or applicable law, please
report this to us at <a rel="external" href="mailto:legal@jamkazazm.com">legal@jamkazam.com</a>.
report this to us at <a rel="external" href="mailto:legal@jamkazam.com">legal@jamkazam.com</a>.
</p>

View File

@ -18,8 +18,10 @@
<li class="profile"><%= link_to "Profile", '/client#/account/profile' %></li>
<!--<li class="subscriptions"><%= link_to "Subscriptions", '/client#/account/subscriptions' %></li> -->
<!-- <li class="payments"><%= link_to "Payments", '/client#/account/payments' %></li> -->
<li class="audio"><%= link_to "Audio Gear", '/client#/account/audio' %></li>
<li class="band-setup"><%= link_to "Band Setup", '/client#/band/setup' %></li>
<% if current_user && current_user.musician? %>
<li class="audio"><%= link_to "Audio Gear", '/client#/account/audio' %></li>
<li class="band-setup"><%= link_to "Band Setup", '/client#/band/setup' %></li>
<% end %>
<li class="invite-friends"><span class='menuheader'><span class="arrow-right"></span><%= link_to "Invite Friends", '#' %></span>
<ul class="shortcuts-submenu">
<li class="google-invite"><%= link_to "Google", '#' %></li>
@ -37,4 +39,4 @@
<a class="signin" href="/signin">Sign In</a>
<% end %>
</div>
</div>

View File

@ -122,6 +122,10 @@ if defined?(Bundler)
config.aws_bucket = 'jamkazam-dev'
config.aws_bucket_public = 'jamkazam-dev-public'
config.aws_cache = '315576000'
config.aws_fullhost = "#{config.aws_bucket_public}.s3.amazonaws.com"
# cloudfront host
config.cloudfront_host = "d34f55ppvvtgi3.cloudfront.net"
# facebook keys
config.facebook_key = '468555793186398'

View File

@ -45,6 +45,9 @@ SampleApp::Application.configure do
TEST_CONNECT_STATES = false
# Overloaded value to match production for using cloudfront in dev mode
config.cloudfront_host = "d48bcgsnmsm6a.cloudfront.net"
# this is totally awful and silly; the reason this exists is so that if you upload an artifact
# through jam-admin, then jam-web can point users at it. I think 99% of devs won't even see or care about this config, and 0% of users
config.jam_admin_root_url = 'http://192.168.1.152:3333'

View File

@ -76,6 +76,10 @@ SampleApp::Application.configure do
config.aws_bucket = 'jamkazam'
config.aws_bucket_public = 'jamkazam-public'
config.aws_fullhost = "#{config.aws_bucket_public}.s3.amazonaws.com"
# Dev cloudfront hostname
config.cloudfront_host = "d34f55ppvvtgi3.cloudfront.net"
# filepicker app configured to use S3 bucket jamkazam
config.filepicker_rails.api_key = "AhUoVoBZSLirP3esyCl7Zz"

View File

@ -204,6 +204,11 @@ SampleApp::Application.routes.draw do
match '/bands' => 'api_bands#create', :via => :post
match '/bands/:id' => 'api_bands#update', :via => :post
# photo
match '/bands/:id/photo' => 'api_bands#update_photo', :via => :post
match '/bands/:id/photo' => 'api_bands#delete_photo', :via => :delete
match '/bands/:id/filepicker_policy' => 'api_bands#generate_filepicker_policy', :via => :get
# band members
match '/bands/:id/musicians' => 'api_bands#musician_index', :via => :get
match '/bands/:id/musicians' => 'api_bands#musician_create', :via => :post
@ -259,6 +264,7 @@ SampleApp::Application.routes.draw do
match '/regions' => 'api_maxmind_requests#regions', :via => :get
match '/cities' => 'api_maxmind_requests#cities', :via => :get
match '/isps' => 'api_maxmind_requests#isps', :via => :get
match '/resolved_location' => 'api_maxmind_requests#resolved_location', :via => :get
# Recordings

View File

@ -27,5 +27,160 @@ namespace :db do
DatabaseCleaner.start
DatabaseCleaner.clean
end
task populate_friends: :environment do
make_friends
end
task populate_bands: :environment do
make_bands
end
task populate_band_members: :environment do
make_band_members
end
task populate_band_genres: :environment do
make_band_genres
end
desc "Fill database with music session sample data"
task populate_music_sessions: :environment do
make_users(10) if 14 > User.count
make_bands if 0==Band.count
make_music_sessions_history
make_music_sessions_user_history
end
end
def make_music_sessions_history
users = User.all.map(&:id)
bands = Band.all.map(&:id)
genres = Genre.all.map(&:description)
50.times do |nn|
obj = MusicSessionHistory.new
obj.music_session_id = rand(100000000).to_s
obj.description = Faker::Lorem.paragraph
obj.user_id = users[rand(users.count)]
obj.band_id = bands[rand(bands.count)]
obj.created_at = Time.now - rand(1.month.seconds)
obj.session_removed_at = obj.created_at + (rand(3)+1).hour
obj.genres = genres.shuffle[0..rand(4)].join(' | ')
obj.save!
end
end
def make_music_sessions_user_history
users = User.all.map(&:id)
hists = MusicSessionHistory.all
hists.each do |msh|
(rand(9)+1).times do |nn|
obj = MusicSessionUserHistory.new
obj.music_session_id = msh.music_session_id
obj.user_id = users[rand(users.count)]
obj.created_at = msh.created_at
obj.session_removed_at = obj.created_at + (rand(3)+1).hour
obj.client_id = rand(100000000).to_s
obj.save!
end
end
end
def make_band_members
Band.find_each do |bb|
User.order('RANDOM()').limit(4).each do |uu|
BandMusician.create!({:user_id => uu.id, :band_id => bb.id})
end
end
end
def make_band_genres
Band.find_each do |bb|
next if bb.genres.present?
Genre.order('RANDOM()').limit(rand(3)+1).each do |gg|
bb.genres << gg
end
end
end
def make_bands
10.times do |nn|
name = Faker::Name.name
website = Faker::Internet.url
biography = Faker::Lorem.sentence
city = 'Austin' # Faker::Address.city
state = 'TX' # Faker::Address.state_abbr
country = 'US'
bb = Band.new(
name: name,
website: website,
biography: biography,
city: city,
state: state,
country: country,
)
begin
bb.save!
rescue
puts $!.to_s + ' ' + bb.errors.inspect
end
end
end
def make_users(num=99)
admin = User.create!( first_name: Faker::Name.name,
last_name: Faker::Name.name,
email: "example@railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
terms_of_service: true)
admin.toggle!(:admin)
num.times do |n|
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(first_name: Faker::Name.name,
last_name: Faker::Name.name,
terms_of_service: true,
email: email,
password: password,
password_confirmation: password)
end
end
def make_microposts
users = User.all(limit: 6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
end
def make_relationships
users = User.all
user = users.first
followed_users = users[2..50]
followers = users[3..40]
followed_users.each { |followed| user.followings << followed }
followers.each { |follower| follower.follow!(user) }
end
def make_followings
users = User.all
users.each do |uu|
users[0..rand(users.count)].shuffle.each do |uuu|
uuu.followings << uu unless 0 < UserFollowing.where(:user_id => uu.id, :follower_id => uuu.id).count
uu.followings << uuu unless 0 < UserFollowing.where(:user_id => uuu.id, :follower_id => uu.id).count if rand(3)==0
end
end
end
def make_friends
users = User.all
users[6..-1].each do |uu|
users[0..5].shuffle.each do |uuu|
Friendship.save(uu.id, uuu.id)
end
end
end

View File

@ -0,0 +1,33 @@
require 'spec_helper'
describe "Bands", :js => true, :type => :feature, :capybara_feature => true do
subject { page }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 15
end
let(:user) { FactoryGirl.create(:user) }
let(:finder) { FactoryGirl.create(:user) }
before(:each) do
UserMailer.deliveries.clear
end
it "band setup is accessible through profile page" do
sign_in_poltergeist(user)
wait_until_curtain_gone
find('div.homecard.profile').trigger(:click)
wait_for_ajax
find('#profile-bands-link').trigger(:click)
wait_for_ajax
find('#band-setup-link').trigger(:click)
expect(page).to have_selector('#band-setup-title')
end
end

View File

@ -21,14 +21,17 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr
it "can't see a private session until it is made public", :slow => true do
create_session(user, description = "Public or private, I cant decide!")
set_session_as_private(user)
in_client(user) do
set_session_as_private
end
in_client(finder) do
sign_in_poltergeist finder
visit "/client#/findSession"
expect(page).to have_selector('#sessions-none-found') # verify private session is not found
end
set_session_as_public(user)
in_client(user) do
set_session_as_public
end
join_session(finder, description) # verify the public session is able to be joined
end
@ -57,11 +60,38 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr
in_client(finder) { expect(page).to_not have_selector('div.track-label', text: user.name) }
end
#it "can see all tracks with four users", :slow => true do
# others = Array.new
# 3.times { others.push FactoryGirl.create(:user) }
# create_join_session(user, others)
# assert_all_tracks_seen(others.push user)
#end
many = 4
it "can see all tracks with #{many} users in a session", :slow => true do
others = Array.new
(many-1).times { others.push FactoryGirl.create(:user) }
create_join_session(user, others)
assert_all_tracks_seen(others.push user)
#in_client(others[0]) {page.save_screenshot('tmp/partys_all_here_now.png')}
end
it "a user can change the genre and the change will be seen by another participant" do
pending "...it doesn't work this way, but i will reuse this pattern"
create_join_session(user, [finder])
in_client(user) do
@new_genre = change_session_genre
end
in_client(finder) do
expect(get_session_genre).to include(@new_genre)
end
end
it "a user can change the genre and the Find Session screen will be updated" do
create_session(user)
in_client(finder) { sign_in_poltergeist finder }
2.times do
in_client(user) do
@new_genre = change_session_genre #randomizes it
end
in_client(finder) do
find_session_contains?(@new_genre)
end
end
end
end

View File

@ -24,14 +24,12 @@ describe "Musician Search", :js => true, :type => :feature, :capybara_feature =>
end
it "shows submits query" do
find("a#btn-refresh-musicians").trigger(:click)
expect(page).to have_selector('#musician-filter-results .musician-list-result')
end
it "shows blank result set" do
expect(page).to have_selector('#instrument')
find('#instrument').find(:xpath, 'option[2]').select_option
find("a#btn-refresh-musicians").trigger(:click)
expect(page).to have_selector('#musician_instrument')
find('#musician_instrument').find(:xpath, 'option[2]').select_option
expect(page).to_not have_selector('#musician-filter-results .musician-list-result')
end

View File

@ -1,6 +1,6 @@
require 'spec_helper'
describe "Find Session", :js => true, :type => :feature, :capybara_feature => true, :slow => true do
describe "Session Recordings", :js => true, :type => :feature, :capybara_feature => true, :slow => true do
subject { page }

View File

@ -29,7 +29,7 @@ describe "Musician Search API", :type => :api do
it "default search" do
get_query
good_response
expect(json['musicians'].count).to be [Search::M_PER_PAGE, User.musicians_geocoded.count].min
expect(json['musicians'].count).to be [Search::M_PER_PAGE, User.musicians.count].min
end
context 'location filtering' do

View File

@ -125,7 +125,7 @@ end
# this code assumes that there are no music sessions in the database. it should fail on the
# find('.join-link') call if > 1 session exists because capybara will complain of multile matches
# find('.join-link') call if > 1 session exists because capybara will complain of multiple matches
def join_session(joiner, unique_session_desc)
in_client(joiner) do
@ -141,6 +141,7 @@ def join_session(joiner, unique_session_desc)
end
end
def create_join_session(creator, joiners=[])
creator, unique_session_desc = create_session(creator)
@ -151,26 +152,61 @@ def create_join_session(creator, joiners=[])
end
def set_session_as_private(session_owner)
in_client(session_owner) do
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
def set_session_as_private()
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
select('Private', :from => 'session-settings-musician-access')
find('#session-settings-dialog-submit').trigger(:click)
end
end
end
def set_session_as_public(session_owner)
in_client(session_owner) do
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
select('Public', :from => 'session-settings-musician-access')
find('#session-settings-dialog-submit').trigger(:click)
end
def set_session_as_public()
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
select('Public', :from => 'session-settings-musician-access')
find('#session-settings-dialog-submit').trigger(:click)
end
end
def get_options(selector)
return find(selector).all('option').collect(&:text).uniq
end
def selected_genres
return page.evaluate_script("JK.GenreSelectorHelper.getSelectedGenres('#session-settings-genre')")
end
def change_session_genre #randomly just change it
here = 'select.genre-list'
#wait_for_ajax
find('#session-settings-button').trigger(:click)
within('#session-settings-dialog') do
wait_for_ajax
@new_genre = get_options(here).-(["Select Genre"]).-(selected_genres).sample.to_s
select(@new_genre, :from => 'genres')
wait_for_ajax
find('#session-settings-dialog-submit').trigger(:click)
end
return @new_genre
end
def get_session_genre
here = 'select.genre-list'
find('#session-settings-button').trigger(:click)
wait_for_ajax
@current_genres = selected_genres
find('#session-settings-dialog-submit').trigger(:click)
return @current_genres.join(" ")
end
def find_session_contains?(text)
visit "/client#/findSession"
wait_for_ajax
within('#find-session-form') do
expect(page).to have_text(text)
end
end
def assert_all_tracks_seen(users=[])
users.each do |user|
in_client(user) do