diff --git a/admin/spec/factories.rb b/admin/spec/factories.rb index d6888a6bc..ad1c0bf69 100644 --- a/admin/spec/factories.rb +++ b/admin/spec/factories.rb @@ -19,7 +19,7 @@ FactoryGirl.define do end before(:create) do |user| - user.musician_instruments << FactoryGirl.build(:musician_instrument, user: user) + user.musician_instruments << FactoryGirl.build(:musician_instrument, player: user) end factory :single_user_session do diff --git a/db/manifest b/db/manifest index cf1750345..a3bddb189 100755 --- a/db/manifest +++ b/db/manifest @@ -276,6 +276,12 @@ jam_track_duration.sql sales.sql show_whats_next_count.sql recurly_adjustments.sql +alter_type_columns.sql +user_presences_rename.sql +add_genre_type.sql +add_description_to_perf_samples.sql +alter_genre_player_unique_constraint.sql +musician_search.sql signup_hints.sql packaging_notices.sql first_played_jamtrack_at.sql @@ -286,5 +292,6 @@ signing.sql optimized_redeemption.sql optimized_redemption_warn_mode.sql affiliate_partners2.sql +enhance_band_profile.sql broadcast_notifications.sql -broadcast_notifications_fk.sql \ No newline at end of file +broadcast_notifications_fk.sql diff --git a/db/up/add_description_to_perf_samples.sql b/db/up/add_description_to_perf_samples.sql new file mode 100644 index 000000000..2c1ee462e --- /dev/null +++ b/db/up/add_description_to_perf_samples.sql @@ -0,0 +1 @@ +alter table performance_samples add column description varchar(256) NULL; \ No newline at end of file diff --git a/db/up/add_genre_type.sql b/db/up/add_genre_type.sql new file mode 100644 index 000000000..9c8312ada --- /dev/null +++ b/db/up/add_genre_type.sql @@ -0,0 +1 @@ +alter table genre_players add column genre_type varchar(20) default 'profile'; \ No newline at end of file diff --git a/db/up/alter_genre_player_unique_constraint.sql b/db/up/alter_genre_player_unique_constraint.sql new file mode 100644 index 000000000..c0159b59b --- /dev/null +++ b/db/up/alter_genre_player_unique_constraint.sql @@ -0,0 +1,2 @@ +ALTER TABLE genre_players DROP CONSTRAINT genre_player_uniqkey; +ALTER TABLE genre_players ADD CONSTRAINT genre_player_uniqkey UNIQUE (player_id, player_type, genre_id, genre_type); \ No newline at end of file diff --git a/db/up/alter_type_columns.sql b/db/up/alter_type_columns.sql new file mode 100644 index 000000000..690edec52 --- /dev/null +++ b/db/up/alter_type_columns.sql @@ -0,0 +1,2 @@ +ALTER TABLE user_presences RENAME COLUMN type to service_type; +ALTER TABLE performance_samples RENAME COLUMN type to service_type; \ No newline at end of file diff --git a/db/up/enhance_band_profile.sql b/db/up/enhance_band_profile.sql new file mode 100644 index 000000000..ed8fc29dd --- /dev/null +++ b/db/up/enhance_band_profile.sql @@ -0,0 +1,25 @@ +ALTER TABLE bands ADD COLUMN band_type VARCHAR(16) DEFAULT ''; +ALTER TABLE bands ADD COLUMN band_status VARCHAR(16) DEFAULT ''; +ALTER TABLE bands ADD COLUMN concert_count SMALLINT DEFAULT 0; +ALTER TABLE bands ADD COLUMN add_new_members BOOLEAN DEFAULT FALSE; +ALTER TABLE bands ADD COLUMN play_commitment SMALLINT DEFAULT 0; +ALTER TABLE bands ADD COLUMN touring_option BOOLEAN DEFAULT FALSE; +ALTER TABLE bands ADD COLUMN paid_gigs BOOLEAN DEFAULT FALSE; +ALTER TABLE bands ADD COLUMN free_gigs BOOLEAN DEFAULT FALSE; +ALTER TABLE bands ADD COLUMN hourly_rate INT DEFAULT 0; +ALTER TABLE bands ADD COLUMN gig_minimum INT DEFAULT 0; + +ALTER TABLE musicians_instruments ALTER COLUMN user_id DROP NOT NULL; +ALTER TABLE musicians_instruments RENAME COLUMN user_id TO player_id; +ALTER TABLE musicians_instruments ADD COLUMN player_type VARCHAR(32) DEFAULT 'user'; +ALTER TABLE musicians_instruments DROP CONSTRAINT musicians_instruments_user_id_fkey; + +ALTER TABLE performance_samples ALTER COLUMN user_id DROP NOT NULL; +ALTER TABLE performance_samples RENAME COLUMN user_id TO player_id; +ALTER TABLE performance_samples ADD COLUMN player_type VARCHAR(32) DEFAULT 'user'; +ALTER TABLE performance_samples DROP CONSTRAINT performance_samples_user_id_fkey; + +ALTER TABLE online_presences ALTER COLUMN user_id DROP NOT NULL; +ALTER TABLE online_presences RENAME COLUMN user_id TO player_id; +ALTER TABLE online_presences ADD COLUMN player_type VARCHAR(32) DEFAULT 'user'; +ALTER TABLE online_presences DROP CONSTRAINT online_presences_user_id_fkey; \ No newline at end of file diff --git a/db/up/musician_search.sql b/db/up/musician_search.sql new file mode 100644 index 000000000..380ddb0a6 --- /dev/null +++ b/db/up/musician_search.sql @@ -0,0 +1,10 @@ +CREATE TABLE json_stores ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + foreign_key1_id VARCHAR(64), + data_blob JSON NOT NULL DEFAULT '{}', + type VARCHAR(128) +); + +CREATE INDEX user_idx ON json_stores(user_id); +CREATE INDEX foreign_key1_idx ON json_stores(foreign_key1_id); diff --git a/db/up/user_presences_rename.sql b/db/up/user_presences_rename.sql new file mode 100644 index 000000000..92cb4e4ca --- /dev/null +++ b/db/up/user_presences_rename.sql @@ -0,0 +1,10 @@ +drop table user_presences; + +CREATE TABLE online_presences ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + service_type VARCHAR(100) NOT NULL, + username VARCHAR(100) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/ruby/Gemfile b/ruby/Gemfile index 621108eea..d46e44e82 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -24,7 +24,7 @@ gem 'uuidtools', '2.1.2' gem 'bcrypt-ruby', '3.0.1' gem 'ruby-protocol-buffers', '1.2.2' gem 'eventmachine', '1.0.3' -gem 'amqp', '1.0.2' +gem 'amqp', '1.0.2' gem 'will_paginate' gem 'actionmailer', '3.2.13' gem 'sendgrid', '1.2.0' @@ -62,6 +62,7 @@ group :test do gem 'resque_spec' #, :path => "/home/jam/src/resque_spec/" gem 'timecop' gem 'rspec-prof' + gem 'time_difference' gem 'byebug' end diff --git a/ruby/jt_metadata.json b/ruby/jt_metadata.json new file mode 100644 index 000000000..cc85875b4 --- /dev/null +++ b/ruby/jt_metadata.json @@ -0,0 +1 @@ +{"container_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/jam-track-35.jkz", "version": "0", "coverart": null, "rsa_priv_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/skey.pem", "tracks": [{"name": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/7452fa4a-0c55-4cb2-948e-221475d7299c.ogg", "trackName": "track_00"}], "rsa_pub_file": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/d20150519-97259-1h1tbhj/pkey.pem", "jamktrack_info": "/var/folders/fk/0ckzmddd4tq28kxbb09vckbr0000gn/T/tmpGdncJS"} \ No newline at end of file diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 8c1a51a58..f132f7b10 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -230,6 +230,10 @@ require "jam_ruby/models/broadcast_notification_view" require "jam_ruby/jam_tracks_manager" require "jam_ruby/jam_track_importer" require "jam_ruby/jmep_manager" +require "jam_ruby/models/performance_sample" +require "jam_ruby/models/online_presence" +require "jam_ruby/models/json_store" +require "jam_ruby/models/musician_search" include Jampb diff --git a/ruby/lib/jam_ruby/constants/limits.rb b/ruby/lib/jam_ruby/constants/limits.rb index bbbe44fd7..ab4403b76 100644 --- a/ruby/lib/jam_ruby/constants/limits.rb +++ b/ruby/lib/jam_ruby/constants/limits.rb @@ -14,7 +14,7 @@ module Limits # instruments MIN_INSTRUMENTS_PER_MUSICIAN = 1 - MAX_INSTRUMENTS_PER_MUSICIAN = 5 + MAX_INSTRUMENTS_PER_MUSICIAN = 1000 # users USERS_CAN_INVITE = true diff --git a/ruby/lib/jam_ruby/models/band.rb b/ruby/lib/jam_ruby/models/band.rb index f64bc47d2..c98f4a532 100644 --- a/ruby/lib/jam_ruby/models/band.rb +++ b/ruby/lib/jam_ruby/models/band.rb @@ -5,9 +5,11 @@ module JamRuby attr_accessible :name, :website, :biography, :city, :state, :country, :original_fpfile_photo, :cropped_fpfile_photo, :cropped_large_fpfile_photo, - :cropped_s3_path_photo, :cropped_large_s3_path_photo, :crop_selection_photo, :photo_url, :large_photo_url + :cropped_s3_path_photo, :cropped_large_s3_path_photo, :crop_selection_photo, :photo_url, :large_photo_url, + :band_type, :band_status, :concert_count, :add_new_members, :play_commitment, :touring_option, :paid_gigs, + :free_gigs, :hourly_rate, :gig_minimum - attr_accessor :updating_photo, :skip_location_validation + attr_accessor :updating_photo, :skip_location_validation, :skip_genre_validation self.primary_key = 'id' @@ -19,12 +21,20 @@ module JamRuby validates :city, presence: true, :unless => :skip_location_validation validate :validate_photo_info - validate :require_at_least_one_genre + validate :require_at_least_one_genre, :unless => :skip_genre_validation validate :limit_max_genres before_save :check_lat_lng before_save :check_website_url + # instruments + has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument", :foreign_key=> 'player_id' + has_many :instruments, :through => :musician_instruments, :class_name => "JamRuby::Instrument", :foreign_key=> 'player_id' + + has_many :online_presences, :class_name => "JamRuby::OnlinePresence", :foreign_key=> 'player_id' + has_many :performance_samples, :class_name => "JamRuby::PerformanceSample", :foreign_key=> 'player_id' + + # musicians has_many :band_musicians, :class_name => "JamRuby::BandMusician", dependent: :destroy has_many :users, :through => :band_musicians, :class_name => "JamRuby::User" @@ -169,7 +179,7 @@ module JamRuby band = id.blank? ? Band.new : Band.find(id) # ensure user updating Band details is a Band member - unless band.new_record? || band.users.exists?(user) + unless band.new_record? || band.users.exists?(user) raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR end @@ -189,12 +199,33 @@ module JamRuby band.genres = genres end + band.skip_genre_validation = true unless params[:validate_genres] + + unless band.new_record? + OnlinePresence.delete_all(["player_id = ?", band.id]) + PerformanceSample.delete_all(["player_id = ?", band.id]) + end + online_presences = params[:online_presences] + if online_presences.present? + online_presences.each do |op| + new_presence = OnlinePresence.create(band, op, false) + band.online_presences << new_presence + end + end + + performance_samples = params[:performance_samples] + if performance_samples.present? + performance_samples.each do |ps| + band.performance_samples << PerformanceSample.create(band, ps, false) + end + end + band end # helper method for creating / updating a Band - def self.save(user, params) + def self.save(user, params) band = build_band(user, params) if band.save diff --git a/ruby/lib/jam_ruby/models/claimed_recording.rb b/ruby/lib/jam_ruby/models/claimed_recording.rb index 18dd11898..462ce6b58 100644 --- a/ruby/lib/jam_ruby/models/claimed_recording.rb +++ b/ruby/lib/jam_ruby/models/claimed_recording.rb @@ -12,6 +12,7 @@ module JamRuby has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession" has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "claimed_recording_id", :dependent => :destroy has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => "claimed_recording_id", :dependent => :destroy + has_many :performance_samples, :class_name => "JamRuby::PerformanceSample", :foreign_key => "claimed_recording_id", :dependent => :destroy has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id', :dependent => :destroy validates :name, no_profanity: true, length: {minimum: 3, maximum: 64}, presence: true diff --git a/ruby/lib/jam_ruby/models/genre_player.rb b/ruby/lib/jam_ruby/models/genre_player.rb index 76bdb4ccf..2d125ea9e 100644 --- a/ruby/lib/jam_ruby/models/genre_player.rb +++ b/ruby/lib/jam_ruby/models/genre_player.rb @@ -1,6 +1,13 @@ module JamRuby class GenrePlayer < ActiveRecord::Base + PROFILE = 'profile' + VIRTUAL_BAND = 'virtual_band' + TRADITIONAL_BAND = 'traditional_band' + PAID_SESSION = 'paid_sessions' + FREE_SESSION = 'free_sessions' + COWRITING = 'cowriting' + self.table_name = "genre_players" belongs_to :player, polymorphic: true diff --git a/ruby/lib/jam_ruby/models/instrument.rb b/ruby/lib/jam_ruby/models/instrument.rb index e7f0185f5..1a3fa8df7 100644 --- a/ruby/lib/jam_ruby/models/instrument.rb +++ b/ruby/lib/jam_ruby/models/instrument.rb @@ -36,7 +36,7 @@ module JamRuby # users has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument" - has_many :users, :through => :musician_instruments, :class_name => "JamRuby::User" + has_many :players, :through => :musician_instruments has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :instrument has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :inverse_of => :instrument diff --git a/ruby/lib/jam_ruby/models/json_store.rb b/ruby/lib/jam_ruby/models/json_store.rb new file mode 100644 index 000000000..ca6aeb087 --- /dev/null +++ b/ruby/lib/jam_ruby/models/json_store.rb @@ -0,0 +1,33 @@ +module JamRuby + class JsonStore < ActiveRecord::Base + self.table_name = 'json_stores' + + serialize :data_blob, JSON + + before_create do + self.data_blob ||= {} + end + + after_save do + @json = nil + end + + attr_accessible :user_id + + belongs_to :user, class_name: "JamRuby::User" + + def json + @json ||= self.data_blob + end + + def update_json_value(key, val) + self.json[key] = val + self.update_attribute(:data_blob, self.json) + end + + def json_value(key) + self.json[key] + end + + end +end diff --git a/ruby/lib/jam_ruby/models/musician_instrument.rb b/ruby/lib/jam_ruby/models/musician_instrument.rb index 376b99a3c..445070da6 100644 --- a/ruby/lib/jam_ruby/models/musician_instrument.rb +++ b/ruby/lib/jam_ruby/models/musician_instrument.rb @@ -10,7 +10,7 @@ module JamRuby # proficiency is 1 = Beginner, 2 = Intermediate, 3 = Expert - belongs_to :user, :class_name => "JamRuby::User" + belongs_to :player, polymorphic: true belongs_to :instrument, :class_name => "JamRuby::Instrument" def description diff --git a/ruby/lib/jam_ruby/models/musician_search.rb b/ruby/lib/jam_ruby/models/musician_search.rb new file mode 100644 index 000000000..22d3d6221 --- /dev/null +++ b/ruby/lib/jam_ruby/models/musician_search.rb @@ -0,0 +1,405 @@ +module JamRuby + class MusicianSearch < JsonStore + + attr_accessor :page_count, :results, :user_counters, :page_number + + ANY_VAL_STR = 'any' + ANY_VAL_INT = -1 + + PER_PAGE = 10 + PG_SMALLINT_MAX = 32767 + + KEY_GIGS = 'concert_gigs' + KEY_STUDIOS = 'studio_sessions' + KEY_AGES = 'ages' + KEY_SKILL = 'skill_level' + KEY_GENRES = 'genres' + KEY_INSTRUMENTS = 'instruments' + KEY_INTERESTS = 'interests' + KEY_SORT_ORDER = 'sort_order' + + SORT_VALS = %W{ latency distance } + SORT_ORDERS = { + SORT_VALS[0] => 'Latency to Me', + SORT_VALS[1] => 'Distance to Me' + } + + SKILL_VALS = [ANY_VAL_INT, 1, 2] + SKILL_LEVELS = { + SKILL_VALS[0] => 'Any', + SKILL_VALS[1] => 'Amateur', + SKILL_VALS[2] => 'Pro', + } + + GIG_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4] + GIG_LABELS = { + GIG_COUNTS[0] => 'Any', + GIG_COUNTS[1] => 'under 10', + GIG_COUNTS[2] => '10 to 50', + GIG_COUNTS[3] => '50 to 100', + GIG_COUNTS[4] => 'over 100' + } + + STUDIO_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4] + STUDIOS_LABELS = { + STUDIO_COUNTS[0] => 'Any', + STUDIO_COUNTS[1] => 'under 10', + STUDIO_COUNTS[2] => '10 to 50', + STUDIO_COUNTS[3] => '50 to 100', + STUDIO_COUNTS[4] => 'over 100' + } + + AGE_COUNTS = [10, 20, 30, 40, 50] + AGES = { + AGE_COUNTS[0] => 'Teens', + AGE_COUNTS[1] => "20's", + AGE_COUNTS[2] => "30's", + AGE_COUNTS[3] => "40's", + AGE_COUNTS[4] => "50+" + } + + INTEREST_VALS = [ANY_VAL_STR, + GenrePlayer::VIRTUAL_BAND, + GenrePlayer::TRADITIONAL_BAND, + GenrePlayer::PAID_SESSION, + GenrePlayer::FREE_SESSION, + GenrePlayer::COWRITING, + ] + INTERESTS = { + INTEREST_VALS[0] => 'Any', + INTEREST_VALS[1] => 'Virtual Band', + INTEREST_VALS[2] => 'Traditional Band', + INTEREST_VALS[3] => 'Paid Sessions', + INTEREST_VALS[4] => 'Free Sessions', + INTEREST_VALS[5] => 'Co-Writing' + } + + INSTRUMENT_PROFICIENCY = { + 1 => 'Beginner', + 2 => 'Intermediate', + 3 => 'Expert', + } + + JSON_SCHEMA = { + KEY_SORT_ORDER => SORT_VALS[0], + KEY_INSTRUMENTS => [], + KEY_INTERESTS => INTEREST_VALS[0], + KEY_GENRES => [], + KEY_GIGS => GIG_COUNTS[0].to_s, + KEY_STUDIOS => STUDIO_COUNTS[0].to_s, + KEY_SKILL => SKILL_VALS[0].to_s, + KEY_AGES => [] + } + JSON_SCHEMA_KEYS = JSON_SCHEMA.keys + MULTI_VALUE_KEYS = JSON_SCHEMA.collect { |kk,vv| vv.is_a?(Array) ? kk : nil }.compact + SINGLE_VALUE_KEYS = JSON_SCHEMA.keys - MULTI_VALUE_KEYS + + SEARCH_FILTER_META = { + per_page: PER_PAGE, + filter_keys: { + keys: JSON_SCHEMA_KEYS, + multi: MULTI_VALUE_KEYS, + single: SINGLE_VALUE_KEYS, + }, + sort_order: { keys: SORT_VALS, map: SORT_ORDERS }, + interests: { keys: INTEREST_VALS, map: INTERESTS }, + ages: { keys: AGE_COUNTS, map: AGES } + } + + def self.user_search_filter(user) + unless ms = user.musician_search + ms = self.create_search(user) + end + ms + end + + def self.search_filter_json(user) + self.user_search_filter(user).json + end + + def self.create_search(user) + ms = self.new + ms.user = user + ms.data_blob = JSON_SCHEMA + ms.save! + ms + end + + def _genres(rel) + gids = json[KEY_GENRES] + unless gids.blank? + gidsql = gids.join("','") + gpsql = "SELECT player_id FROM genre_players WHERE (player_type = 'JamRuby::User' AND genre_id IN ('#{gidsql}'))" + rel = rel.where("users.id IN (#{gpsql})") + end + rel + end + + def _instruments(rel) + unless (instruments = json['instruments']).blank? + instsql = "SELECT player_id FROM musicians_instruments WHERE ((" + instsql += instruments.collect do |inst| + "instrument_id = '#{inst['instrument_id']}' AND proficiency_level = #{inst['proficiency_level']}" + end.join(") OR (") + instsql += "))" + rel = rel.where("users.id IN (#{instsql})") + end + rel + end + + def _ages(rel) + unless (vals = json[KEY_AGES]).blank? + return rel if vals.detect { |vv| ANY_VAL_INT == vv } + arels = [] + vals.each do |val| + today = Date.today + case val.to_i + when 10 + arels << User.where("birth_date >= ? AND birth_date < ?", + today - 20.years, today - 10.years) + when 20 + arels << User.where("birth_date >= ? AND birth_date < ?", + today - 30.years, today - 20.years) + when 30 + arels << User.where("birth_date >= ? AND birth_date < ?", + today - 40.years, today - 50.years) + when 40 + arels << User.where("birth_date >= ? AND birth_date < ?", + today - 50.years, today - 40.years) + when 50 + arels << User.where("birth_date <= ?", today - 50.years) + end + end + rel = rel.where("birth_date IS NOT NULL") + sql = "(#{arels.map(&:where_values).flatten.join(') OR (')})" + rel = rel.where(sql) + end + rel + end + + def _studios(rel) + ss = json[KEY_STUDIOS].to_i + rel = rel.where('studio_session_count = ?', ss) if 0 <= ss + rel + end + + def _gigs(rel) + gg = json[KEY_GIGS].to_i + rel = rel.where('concert_count = ?',gg) if 0 <= gg + rel + end + + def _skills(rel) + if 0 < (val = json[KEY_SKILL].to_i) + rel = rel.where(['skill_level = ?', val]) + end + rel + end + + def _interests(rel) + val = json[KEY_INTERESTS] + if val.present? && ANY_VAL_STR != val + rel = rel.where("#{val} = ?", true) + end + rel + end + + def _sort_order(rel) + val = json[KEY_SORT_ORDER] + if SORT_VALS[1] == val + locidispid = self.user.last_jam_locidispid || 0 + my_locid = locidispid / 1000000 + rel = rel.joins("LEFT JOIN geoiplocations AS my_geo ON my_geo.locid = #{my_locid}") + rel = rel.joins("LEFT JOIN geoiplocations AS other_geo ON users.last_jam_locidispid/1000000 = other_geo.locid") + rel = rel.group("users.id, my_geo.geog, other_geo.geog") + rel = rel.order('st_distance(my_geo.geog, other_geo.geog)') + else + rel = rel.joins("LEFT JOIN current_scores ON current_scores.a_userid = users.id AND current_scores.b_userid = '#{self.user.id}'") + rel = rel.order('current_scores.full_score ASC') + end + rel + end + + def do_search(params={}) + rel = User.musicians.where('users.id <> ?', self.user.id) + rel = self._genres(rel) + rel = self._ages(rel) + rel = self._studios(rel) + rel = self._gigs(rel) + rel = self._skills(rel) + rel = self._instruments(rel) + rel = self._interests(rel) + rel = self._sort_order(rel) + rel + end + + def search_results_page(filter=nil, page=1) + if filter + self.data_blob = filter + self.save + else + filter = self.data_blob + end + + rel = do_search(filter) + + @page_number = [page.to_i, 1].max + rel = rel.paginate(:page => @page_number, :per_page => PER_PAGE) + + rel = rel.includes([:instruments, :followings, :friends]) + @page_count = rel.total_pages + + musician_results(rel.all) + end + + def reset_filter + self.data_blob = JSON_SCHEMA + self.save + end + + def reset_search_results + reset_filter + search_results_page + end + + RESULT_FOLLOW = :follows + RESULT_FRIEND = :friends + + COUNT_FRIEND = :count_friend + COUNT_FOLLOW = :count_follow + COUNT_RECORD = :count_record + COUNT_SESSION = :count_session + COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION] + + def musician_results(_results) + @results = _results + @user_counters = {} and return self unless user + + @user_counters = @results.inject({}) { |hh,val| hh[val.id] = []; hh } + mids = "'#{@results.map(&:id).join("','")}'" + + # this gets counts for each search result on friends/follows/records/sessions + @results.each do |uu| + counters = { } + counters[COUNT_FRIEND] = Friendship.where(:user_id => uu.id).count + counters[COUNT_FOLLOW] = Follow.where(:followable_id => uu.id).count + counters[COUNT_RECORD] = ClaimedRecording.where(:user_id => uu.id).count + counters[COUNT_SESSION] = MusicSession.where(:user_id => uu.id).count + @user_counters[uu.id] << counters + end + + # this section determines follow/like/friend status for each search result + # so that action links can be activated or not + rel = User.select("users.id AS uid") + rel = rel.joins("LEFT JOIN follows ON follows.user_id = '#{user.id}'") + rel = rel.where(["users.id IN (#{mids}) AND follows.followable_id = users.id"]) + rel.all.each { |val| @user_counters[val.uid] << RESULT_FOLLOW } + + rel = User.select("users.id AS uid") + rel = rel.joins("LEFT JOIN friendships AS friends ON friends.friend_id = '#{user.id}'") + rel = rel.where(["users.id IN (#{mids}) AND friends.user_id = users.id"]) + rel.all.each { |val| @user_counters[val.uid] << RESULT_FRIEND } + + self + end + + private + + def _count(musician, key) + if mm = @user_counters[musician.id] + return mm.detect { |ii| ii.is_a?(Hash) }[key] + end if @user_counters + 0 + end + + public + + def follow_count(musician) + _count(musician, COUNT_FOLLOW) + end + + def friend_count(musician) + _count(musician, COUNT_FRIEND) + end + + def record_count(musician) + _count(musician, COUNT_RECORD) + end + + def session_count(musician) + _count(musician, COUNT_SESSION) + end + + def is_friend?(musician) + if mm = @user_counters[musician.id] + return mm.include?(RESULT_FRIEND) + end if @user_counters + false + end + + def is_follower?(musician) + if mm = @user_counters[musician.id] + return mm.include?(RESULT_FOLLOW) + end if @user_counters + false + end + + def search_type + self.class.to_s + end + + def is_blank? + self.data_blob == JSON_SCHEMA + end + + def description + if self.is_blank? + return 'Click search button to look for musicians with similar interests, skill levels, etc.' + end + jj = self.json + str = 'Current Search: ' + str += "Sort = #{SORT_ORDERS[json_value(MusicianSearch::KEY_SORT_ORDER)]}" + if (val = jj[KEY_INTERESTS]) != INTEREST_VALS[0] + str += "; Interest = #{INTERESTS[val]}" + end + if (val = jj[KEY_SKILL].to_i) != SKILL_VALS[0] + str += "; Skill = #{SKILL_LEVELS[val]}" + end + if (val = jj[KEY_STUDIOS].to_i) != STUDIO_COUNTS[0] + str += "; Studio Sessions = #{STUDIOS_LABELS[val]}" + end + if (val = jj[KEY_GIGS].to_i) != GIG_COUNTS[0] + str += "; Concert Gigs = #{GIG_LABELS[val]}" + end + val = jj[KEY_AGES].map(&:to_i) + val.sort! + if !val.blank? + str += "; Ages = " + val.each_with_index do |vv, idx| + str += "#{AGES[vv]}" + str += ', ' unless idx==(val.length-1) + end + end + if 0 < (val = jj[KEY_GENRES]).length + str += "; Genres = " + genres = Genre.where(["id IN (?)", val]).order('description').pluck(:description) + genres.each_with_index do |gg, idx| + str += "#{gg}" + str += ', ' unless idx==(genres.length-1) + end + end + if 0 < (val = jj[KEY_INSTRUMENTS]).length + str += "; Instruments = " + instr_ids = val.collect { |vv| vv['instrument_id'] } + instrs = Instrument.where(["id IN (?)", instr_ids]).order(:description) + instrs.each_with_index do |ii, idx| + proficiency = val.detect { |vv| vv['instrument_id'] == ii.id }['proficiency_level'] + str += "#{ii.description} (#{INSTRUMENT_PROFICIENCY[proficiency.to_i]})" + str += ', ' unless idx==(instrs.length-1) + end + end + str + end + + end +end diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index 33ac67145..db4018ab8 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -57,7 +57,7 @@ module JamRuby # remove all notifications related to this session if it's not found if session.nil? - Notification.delete_all "(session_id = '#{session_id}')" + Notification.delete_all "(session_id = '#{self.session_id}')" end end diff --git a/ruby/lib/jam_ruby/models/online_presence.rb b/ruby/lib/jam_ruby/models/online_presence.rb new file mode 100644 index 000000000..ed4f498bd --- /dev/null +++ b/ruby/lib/jam_ruby/models/online_presence.rb @@ -0,0 +1,70 @@ +module JamRuby + class OnlinePresence < ActiveRecord::Base + + PERMISSION_MSG = "You do not have permission to perform this operation." + + attr_accessible :player_id, :service_type, :username + belongs_to :player, polymorphic: true + + validates :service_type, presence:true, length: {maximum: 100} + validates :username, presence:true, length: {maximum: 100} + + validate :username_service_type_unique + + def username_service_type_unique + match = OnlinePresence.exists?(:username => self.username, :service_type => self.service_type) + raise ConflictError, "Username #{self.username} is already associated with a #{self.service_type} account" if match + end + + def self.index(options = {}) + raise StateError, "The user is not specified." if options[:id].blank? + OnlinePresence.where("player_id = ?", options[:id]) + end + + # Create with target_player (target_player is either user or band): + def self.create(target_player, options = {}, save = true) + auth_player(target_player, options) + raise StateError, "Missing required information" if options[:service_type].blank? || options[:username].blank? + + up = OnlinePresence.new({ + :player_id => target_player.id, + :service_type => options[:service_type], + :username => options[:username] + }) + + up.save! if save + up + end + + def self.update(target_player, options = {}) + auth_player(target_player, options) + raise StateError, "Missing required information" if options[:service_type].blank? || options[:username].blank? || options[:id].blank? + + up = OnlinePresence.find(options[:id]) + up.service_type = options[:service_type] + up.username = options[:username] + up.save! + end + + def self.delete(target_player, options = {}) + id = options[:id] + raise StateError, "Missing required information" if id.blank? + online_presence = OnlinePresence.find(id) + + if online_presence.player_id != target_player.id + raise JamPermissionError, PERMISSION_MSG + end + + unless online_presence.nil? + OnlinePresence.destroy(id) + end + end + + private + def self.auth_player(target_player, options={}) + if target_player.nil? || options[:player_id] != target_player.id + raise JamPermissionError, PERMISSION_MSG + end + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/performance_sample.rb b/ruby/lib/jam_ruby/models/performance_sample.rb new file mode 100644 index 000000000..38ed618bb --- /dev/null +++ b/ruby/lib/jam_ruby/models/performance_sample.rb @@ -0,0 +1,73 @@ +module JamRuby + class PerformanceSample < ActiveRecord::Base + + PERMISSION_MSG = "You do not have permission to perform this operation." + + attr_accessible :player_id, :service_type, :claimed_recording_id, :service_id, :url, :description + + belongs_to :player, polymorphic: true + belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id" + + validates :service_type, presence:true, length: {maximum: 100} + + # JamKazam validators + validate :claimed_recording_id_present, :if => lambda { |p| p.service_type == "jamkazam" } + validate :user_type_recording_unique, :if => lambda { |p| p.service_type == "jamkazam" } + + # Non-JamKazam validators + validate :service_id_present, :if => lambda { |p| p.service_type != "jamkazam" } + validate :user_type_service_unique, :if => lambda { |p| p.service_type != "jamkazam" } + + def claimed_recording_id_present + raise StateError, "Claimed recording is required for JamKazam performance samples" if self.claimed_recording_id.blank? + end + + def user_type_recording_unique + match = PerformanceSample.exists?(:player_id => self.player_id, :claimed_recording_id => self.claimed_recording_id, :service_type => self.service_type) + raise ConflictError, "You already have this JamKazam recording listed as a sample" if match + end + + def service_id_present + raise StateError, "Service ID is required for non-JamKazam performance samples" if self.service_id.blank? + end + + def user_type_service_unique + match = PerformanceSample.exists?(:player_id => self.player_id, :service_id => self.service_id, :service_type => self.service_type) + raise ConflictError, "You already have this #{self.service_type} sample listed (#{self.service_id})." if match + end + + def self.index(options = {}) + raise JamArgumentError, "The user is not specified." if options[:id].blank? + PerformanceSample.where("player_id = ?", options[:id]) + end + + # Create with target_player (target_player is either user or band): + def self.create(target_player, options = {}, save = true) + auth_user(target_player, options) + raise StateError, "Missing required information" if options[:service_type].blank? + + ps = PerformanceSample.new({ + :player_id => target_player.id, + :service_type => options[:service_type], + :claimed_recording_id => options[:claimed_recording_id], + :service_id => options[:service_id], + :url => options[:url], + :description => options[:description] + }) + + ps.save! if save + ps + end + + def self.delete(target_player, options = {}) + raise JamPermissionError, "You do not have permission to perform this operation" if target_player.nil? || options[:player_id] != target_player.id + raise StateError, "The performance sample ID is missing." if options[:id].blank? + PerformanceSample.destroy(options[:id]) + end + + private + def self.auth_user(target_player, options={}) + raise JamPermissionError, PERMISSION_MSG if target_player.nil? || options[:player_id] != target_player.id + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/search.rb b/ruby/lib/jam_ruby/models/search.rb index ae0ba396e..d1a4d13ea 100644 --- a/ruby/lib/jam_ruby/models/search.rb +++ b/ruby/lib/jam_ruby/models/search.rb @@ -2,7 +2,7 @@ module JamRuby # not a active_record model; just a search result container class Search - attr_accessor :results, :search_type + attr_accessor :results, :search_type, :query attr_accessor :user_counters, :page_num, :page_count LIMIT = 10 @@ -10,82 +10,6 @@ module JamRuby SEARCH_TEXT_TYPES = [:musicians, :bands, :fans] SEARCH_TEXT_TYPE_ID = :search_text_type - def self.band_search(txt, user = nil) - self.text_search({ SEARCH_TEXT_TYPE_ID => :bands, :query => txt }, user) - end - - def self.fan_search(txt, user = nil) - self.text_search({ SEARCH_TEXT_TYPE_ID => :fans, :query => txt }, user) - end - - def self.musician_search(txt, user = nil) - self.text_search({ SEARCH_TEXT_TYPE_ID => :musicians, :query => txt }, user) - end - - def self.session_invite_search(query, user) - srch = Search.new - srch.search_type = :session_invite - like_str = "%#{query.downcase}%" - rel = User - .musicians - .where(["users.id IN (SELECT friend_id FROM friendships WHERE user_id = '#{user.id}')"]) - .where(["first_name ILIKE ? OR last_name ILIKE ?", like_str, like_str]) - .limit(10) - .order([:last_name, :first_name]) - srch.results = rel.all - srch - end - - def self.text_search(params, user = nil) - srch = Search.new - unless (params.blank? || params[:query].blank? || 2 > params[:query].length) - srch.text_search(params, user) - end - srch - end - - def text_search(params, user = nil) - tsquery = Search.create_tsquery(params[:query]) - return [] if tsquery.blank? - - rel = case params[SEARCH_TEXT_TYPE_ID].to_s - when 'bands' - @search_type = :bands - Band.scoped - when 'fans' - @search_type = :fans - User.fans - else - @search_type = :musicians - User.musicians - end - @results = rel.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery).limit(10) - @results - end - - def initialize(search_results=nil) - @results = [] - self - end - - def self.create_tsquery(query) - return nil if query.blank? - - search_terms = query.split - return nil if search_terms.length == 0 - - args = nil - search_terms.each do |search_term| - if args == nil - args = search_term - else - args = args + " & " + search_term - end - end - args = args + ":*" - args - end - PARAM_SESSION_INVITE = :srch_sessinv PARAM_MUSICIAN = :srch_m PARAM_BAND = :srch_b @@ -133,164 +57,322 @@ module JamRuby DATE_OPTS = [['Today', 'today'], ['This Week', 'week'], ['This Month', 'month'], ['All Time', 'all']] - def self.order_param(params, keys=M_ORDERING_KEYS) - ordering = params[:orderby] - ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering } + + def initialize(search_results=nil) + @results = [] + self + end + + def is_blank? + !!@query && @query.empty? end - # produce a list of musicians (users where musician is true) - # params: - # instrument - instrument to search for or blank - # score_limit - a range specification for score, see M_SCORE_OPTS above. - # handled by relation_pagination: - # page - page number to fetch (origin 1) - # per_page - number of entries per page - # handled by order_param: - # orderby - what sort of search, also defines order (followed, plays, playing) - # previously handled by where_latlng: - # distance - defunct! - # city - defunct! - # remote_ip - defunct! - def self.musician_filter(params={}, user=nil) + def text_search(params, user = nil) + @query = params[:query] + tsquery = Search.create_tsquery(params[:query]) + return [] if tsquery.blank? - rel = User.musicians # not musicians_geocoded on purpose; we allow 'unknowns' to surface in the search page - rel = rel.select('users.*') - rel = rel.group('users.id') + rel = case params[SEARCH_TEXT_TYPE_ID].to_s + when 'bands' + @search_type = :bands + Band.scoped + when 'fans' + @search_type = :fans + User.fans + else + @search_type = :musicians + User.musicians + end + @results = rel.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery).limit(10) + @results + end - unless (instrument = params[:instrument]).blank? - rel = rel.joins("inner JOIN musicians_instruments AS minst ON minst.user_id = users.id") - .where(['minst.instrument_id = ?', instrument]) + class << self + def band_search(txt, user = nil) + self.text_search({ SEARCH_TEXT_TYPE_ID => :bands, :query => txt }, user) end - # to find appropriate musicians we need to join users with scores to get to those with no scores or bad scores - # weeded out - - # filter on scores using selections from params - # see M_SCORE_OPTS - score_limit = ANY_SCORE - l = params[:score_limit] - unless l.nil? - score_limit = l + def fan_search(txt, user = nil) + self.text_search({ SEARCH_TEXT_TYPE_ID => :fans, :query => txt }, user) end - locidispid = user.nil? ? 0 : (user.last_jam_locidispid || 0) - - # user can override their location with these 3 values - country = params[:country] - region = params[:region] - city = params[:city] - - my_locid = nil # this is used for distance searches only - - if country && region && city - geoiplocation = GeoIpLocations.where(countrycode: country, region: region, city: city).first - my_locid = geoiplocation.locid + def musician_search(txt, user = nil) + self.text_search({ SEARCH_TEXT_TYPE_ID => :musicians, :query => txt }, user) end - unless my_locid - my_locid = locidispid/1000000 # if the user didn't specify a location to search on, user their account locidispid + def session_invite_search(query, user) + srch = Search.new + srch.search_type = :session_invite + like_str = "%#{query.downcase}%" + rel = User + .musicians + .where(["users.id IN (SELECT friend_id FROM friendships WHERE user_id = '#{user.id}')"]) + .where(["first_name ILIKE ? OR last_name ILIKE ?", like_str, like_str]) + .limit(10) + .order([:last_name, :first_name]) + srch.results = rel.all + srch end - if !locidispid.nil? && !user.nil? - # score_join of left allows for null scores, whereas score_join of inner requires a score however good or bad - # this is ANY_SCORE: - score_join = 'left outer' # or 'inner' - score_min = nil - score_max = nil - # these score_min, score_max come from here (doubled): https://jamkazam.atlassian.net/browse/VRFS-1962 - case score_limit - when GOOD_SCORE - score_join = 'inner' - score_min = nil - score_max = 40 - when MODERATE_SCORE - score_join = 'inner' - score_min = 40 - score_max = 70 - when POOR_SCORE - score_join = 'inner' - score_min = 80 - score_max = 100 - when UNACCEPTABLE_SCORE - score_join = 'inner' - score_min = 100 - score_max = nil - when SCORED_SCORE - score_join = 'inner' - score_min = nil - score_max = nil - when TEST_SCORE - score_join = 'inner' - score_min = nil - score_max = 60 - when ANY_SCORE - # the default of ANY setup above applies + def text_search(params, user = nil) + srch = Search.new + unless (params.blank? || params[:query].blank? || 2 > params[:query].length) + srch.text_search(params, user) + end + srch + end + + def create_tsquery(query) + return nil if query.blank? + + search_terms = query.split + return nil if search_terms.length == 0 + + args = nil + search_terms.each do |search_term| + if args == nil + args = search_term else - # the default of ANY setup above applies + args = args + " & " + search_term + end + end + args = args + ":*" + args + end + def order_param(params, keys=M_ORDERING_KEYS) + ordering = params[:orderby] + ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering } + end + + # produce a list of musicians (users where musician is true) + # params: + # instrument - instrument to search for or blank + # score_limit - a range specification for score, see M_SCORE_OPTS above. + # handled by relation_pagination: + # page - page number to fetch (origin 1) + # per_page - number of entries per page + # handled by order_param: + # orderby - what sort of search, also defines order (followed, plays, playing) + # previously handled by where_latlng: + # distance - defunct! + # city - defunct! + # remote_ip - defunct! + def musician_filter(params={}, user=nil) + + rel = User.musicians # not musicians_geocoded on purpose; we allow 'unknowns' to surface in the search page + rel = rel.select('users.*') + rel = rel.group('users.id') + + unless (instrument = params[:instrument]).blank? + rel = rel.joins("inner JOIN musicians_instruments AS minst ON minst.player_id = users.id") + .where(['minst.instrument_id = ?', instrument]) end - rel = rel.joins("LEFT JOIN current_scores ON current_scores.a_userid = users.id AND current_scores.b_userid = '#{user.id}'") + # to find appropriate musicians we need to join users with scores to get to those with no scores or bad scores + # weeded out - rel = rel.joins('LEFT JOIN regions ON regions.countrycode = users.country AND regions.region = users.state') + # filter on scores using selections from params + # see M_SCORE_OPTS + score_limit = ANY_SCORE + l = params[:score_limit] + unless l.nil? + score_limit = l + end - rel = rel.where(['current_scores.full_score > ?', score_min]) unless score_min.nil? - rel = rel.where(['current_scores.full_score <= ?', score_max]) unless score_max.nil? + locidispid = user.nil? ? 0 : (user.last_jam_locidispid || 0) - rel = rel.select('current_scores.full_score, current_scores.score, regions.regionname') - rel = rel.group('current_scores.full_score, current_scores.score, regions.regionname') + # user can override their location with these 3 values + country = params[:country] + region = params[:region] + city = params[:city] + + my_locid = nil # this is used for distance searches only + + if country && region && city + geoiplocation = GeoIpLocations.where(countrycode: country, region: region, city: city).first + my_locid = geoiplocation.locid + end + + unless my_locid + my_locid = locidispid/1000000 # if the user didn't specify a location to search on, user their account locidispid + end + + if !locidispid.nil? && !user.nil? + # score_join of left allows for null scores, whereas score_join of inner requires a score however good or bad + # this is ANY_SCORE: + score_join = 'left outer' # or 'inner' + score_min = nil + score_max = nil + # these score_min, score_max come from here (doubled): https://jamkazam.atlassian.net/browse/VRFS-1962 + case score_limit + when GOOD_SCORE + score_join = 'inner' + score_min = nil + score_max = 40 + when MODERATE_SCORE + score_join = 'inner' + score_min = 40 + score_max = 70 + when POOR_SCORE + score_join = 'inner' + score_min = 80 + score_max = 100 + when UNACCEPTABLE_SCORE + score_join = 'inner' + score_min = 100 + score_max = nil + when SCORED_SCORE + score_join = 'inner' + score_min = nil + score_max = nil + when TEST_SCORE + score_join = 'inner' + score_min = nil + score_max = 60 + when ANY_SCORE + # the default of ANY setup above applies + else + # the default of ANY setup above applies + end + + rel = rel.joins("LEFT JOIN current_scores ON current_scores.a_userid = users.id AND current_scores.b_userid = '#{user.id}'") + + rel = rel.joins('LEFT JOIN regions ON regions.countrycode = users.country AND regions.region = users.state') + + rel = rel.where(['current_scores.full_score > ?', score_min]) unless score_min.nil? + rel = rel.where(['current_scores.full_score <= ?', score_max]) unless score_max.nil? + + rel = rel.select('current_scores.full_score, current_scores.score, regions.regionname') + rel = rel.group('current_scores.full_score, current_scores.score, regions.regionname') + end + + ordering = self.order_param(params) + case ordering + when :latency + # nothing to do. the sort added below 'current_scores.score ASC NULLS LAST' handles this + when :distance + # convert miles to meters for PostGIS functions + miles = params[:distance].blank? ? 500 : params[:distance].to_i + meters = miles * 1609.34 + rel = rel.joins("INNER JOIN geoiplocations AS my_geo ON #{my_locid} = my_geo.locid") + rel = rel.joins("INNER JOIN geoiplocations AS other_geo ON users.last_jam_locidispid/1000000 = other_geo.locid") + rel = rel.where("users.last_jam_locidispid/1000000 IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = #{my_locid}), #{meters}))") + rel = rel.group("my_geo.geog, other_geo.geog") + rel = rel.order('st_distance(my_geo.geog, other_geo.geog)') + when :plays # FIXME: double counting? + # sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}" + rel = rel.select('COUNT(records.id)+COUNT(sessions.id) AS search_play_count') + rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id") + rel = rel.joins("LEFT JOIN recordings AS records ON records.owner_id = users.id") + rel = rel.order("search_play_count DESC") + when :followed + rel = rel.joins('left outer join follows on follows.followable_id = users.id') + rel = rel.select('count(follows.user_id) as search_follow_count') + rel = rel.order('search_follow_count DESC') + when :playing + rel = rel.joins("inner JOIN connections ON connections.user_id = users.id") + rel = rel.where(['connections.aasm_state != ?', 'expired']) + end + + if !locidispid.nil? && !user.nil? + rel = rel.order('current_scores.full_score ASC NULLS LAST') + end + + rel = rel.order('users.created_at DESC') + + rel, page = self.relation_pagination(rel, params) + rel = rel.includes([:instruments, :followings, :friends]) + + # XXX: DOES THIS MEAN ALL MATCHING USERS ARE RETURNED? + objs = rel.all + + srch = Search.new + srch.search_type = :musicians_filter + srch.page_num, srch.page_count = page, objs.total_pages + srch.musician_results_for_user(objs, user) end - ordering = self.order_param(params) - case ordering - when :latency - # nothing to do. the sort added below 'current_scores.score ASC NULLS LAST' handles this - when :distance - # convert miles to meters for PostGIS functions - miles = params[:distance].blank? ? 500 : params[:distance].to_i - meters = miles * 1609.34 - rel = rel.joins("INNER JOIN geoiplocations AS my_geo ON #{my_locid} = my_geo.locid") - rel = rel.joins("INNER JOIN geoiplocations AS other_geo ON users.last_jam_locidispid/1000000 = other_geo.locid") - rel = rel.where("users.last_jam_locidispid/1000000 IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = #{my_locid}), #{meters}))") - rel = rel.group("my_geo.geog, other_geo.geog") - rel = rel.order('st_distance(my_geo.geog, other_geo.geog)') + def 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 + + def new_musicians(usr, since_date) + # this attempts to find interesting musicians to tell another musician about where interesting + # is "has a good score and was created recently" + # we're sort of depending upon usr being a musicians_geocoded as well... + # this appears to only be called from EmailBatchNewMusician#deliver_batch_sets! which is + # an offline process and thus uses the last jam location as "home base" + + locidispid = usr.last_jam_locidispid + score_limit = 70 + limit = 50 + + rel = User.musicians_geocoded + .where(['users.created_at >= ? AND users.id != ?', since_date, usr.id]) + .joins('inner join current_scores on users.id = current_scores.a_userid') + .where(['current_scores.b_userid = ?', usr.id]) + .where(['current_scores.full_score <= ?', score_limit]) + .order('current_scores.full_score') # best scores first + .order('users.created_at DESC') # then most recent + .limit(limit) + + objs = rel.all.to_a + + if block_given? + yield(objs) if 0 < objs.count + else + return objs + end + end + + def band_filter(params={}, current_user=nil) + rel = Band.scoped + + unless (genre = params[:genre]).blank? + rel = Band.joins("RIGHT JOIN genre_players AS bgenres ON bgenres.player_id = bands.id AND bgenres.player_type = 'JamRuby::Band'") + .where(['bgenres.genre_id = ? AND bands.id IS NOT NULL', genre]) + end + + rel = GeoIpLocations.where_latlng(rel, params, current_user) + + sel_str = 'bands.*' + case ordering = self.order_param(params, B_ORDERING_KEYS) when :plays # FIXME: double counting? - # sel_str = "COUNT(records)+COUNT(sessions) AS play_count, #{sel_str}" - rel = rel.select('COUNT(records.id)+COUNT(sessions.id) AS search_play_count') - rel = rel.joins("LEFT JOIN music_sessions AS sessions ON sessions.user_id = users.id") - rel = rel.joins("LEFT JOIN recordings AS records ON records.owner_id = users.id") - rel = rel.order("search_play_count DESC") + sel_str = "COUNT(records)+COUNT(msh) AS play_count, #{sel_str}" + rel = rel.joins("LEFT JOIN music_sessions AS msh ON msh.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 - rel = rel.joins('left outer join follows on follows.followable_id = users.id') - rel = rel.select('count(follows.user_id) as search_follow_count') - rel = rel.order('search_follow_count DESC') + sel_str = "COUNT(follows) AS search_follow_count, #{sel_str}" + rel = rel.joins("LEFT JOIN follows ON follows.followable_id = bands.id") + .group("bands.id") + .order("COUNT(follows) DESC, bands.created_at DESC") when :playing - rel = rel.joins("inner JOIN connections ON connections.user_id = users.id") - rel = rel.where(['connections.aasm_state != ?', 'expired']) + rel = rel.joins("LEFT JOIN music_sessions 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.search_type = :band_filter + srch.page_num, srch.page_count = page, objs.total_pages + if 1 == page && current_user.bands.present? + current_user.bands.order('created_at DESC').each { |bb| objs.unshift(bb) } + end if current_user && current_user.is_a?(User) + srch.band_results_for_user(objs, current_user) end - if !locidispid.nil? && !user.nil? - rel = rel.order('current_scores.full_score ASC NULLS LAST') - end - - rel = rel.order('users.created_at DESC') - - rel, page = self.relation_pagination(rel, params) - rel = rel.includes([:instruments, :followings, :friends]) - - # XXX: DOES THIS MEAN ALL MATCHING USERS ARE RETURNED? - objs = rel.all - - srch = Search.new - srch.search_type = :musicians_filter - srch.page_num, srch.page_count = page, objs.total_pages - srch.musician_results_for_user(objs, 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 @@ -399,77 +481,7 @@ module JamRuby false end - def self.new_musicians(usr, since_date) - # this attempts to find interesting musicians to tell another musician about where interesting - # is "has a good score and was created recently" - # we're sort of depending upon usr being a musicians_geocoded as well... - # this appears to only be called from EmailBatchNewMusician#deliver_batch_sets! which is - # an offline process and thus uses the last jam location as "home base" - - locidispid = usr.last_jam_locidispid - score_limit = 70 - limit = 50 - - rel = User.musicians_geocoded - .where(['users.created_at >= ? AND users.id != ?', since_date, usr.id]) - .joins('inner join current_scores on users.id = current_scores.a_userid') - .where(['current_scores.b_userid = ?', usr.id]) - .where(['current_scores.full_score <= ?', score_limit]) - .order('current_scores.full_score') # best scores first - .order('users.created_at DESC') # then most recent - .limit(limit) - - objs = rel.all.to_a - if block_given? - yield(objs) if 0 < objs.count - else - return objs - end - end - - def self.band_filter(params={}, current_user=nil) - rel = Band.scoped - - unless (genre = params[:genre]).blank? - rel = Band.joins("RIGHT JOIN genre_players AS bgenres ON bgenres.player_id = bands.id AND bgenres.player_type = 'JamRuby::Band'") - .where(['bgenres.genre_id = ? AND bands.id IS NOT NULL', genre]) - end - - rel = GeoIpLocations.where_latlng(rel, params, current_user) - - sel_str = 'bands.*' - case ordering = self.order_param(params, B_ORDERING_KEYS) - when :plays # FIXME: double counting? - sel_str = "COUNT(records)+COUNT(msh) AS play_count, #{sel_str}" - rel = rel.joins("LEFT JOIN music_sessions AS msh ON msh.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 follows ON follows.followable_id = bands.id") - .group("bands.id") - .order("COUNT(follows) DESC, bands.created_at DESC") - when :playing - rel = rel.joins("LEFT JOIN music_sessions 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.search_type = :band_filter - srch.page_num, srch.page_count = page, objs.total_pages - if 1 == page && current_user.bands.present? - current_user.bands.order('created_at DESC').each { |bb| objs.unshift(bb) } - end if current_user && current_user.is_a?(User) - srch.band_results_for_user(objs, current_user) - end def band_results_for_user(_results, user) @results = _results diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index f91973626..ecd0a3501 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -53,7 +53,7 @@ module JamRuby has_many :received_friend_requests, :class_name => "JamRuby::FriendRequest", :foreign_key => 'friend_id' # instruments - has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument" + has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument", :foreign_key=> 'player_id' has_many :instruments, :through => :musician_instruments, :class_name => "JamRuby::Instrument" # bands @@ -165,6 +165,11 @@ module JamRuby # This causes the authenticate method to be generated (among other stuff) #has_secure_password + has_many :online_presences, :class_name => "JamRuby::OnlinePresence", :foreign_key=> 'player_id' + has_many :performance_samples, :class_name => "JamRuby::PerformanceSample", :foreign_key=> 'player_id' + + has_one :musician_search, :class_name => 'JamRuby::MusicianSearch' + before_save :create_remember_token, :if => :should_validate_password? before_save :stringify_avatar_info , :if => :updating_avatar @@ -359,7 +364,7 @@ module JamRuby def age now = Time.now.utc.to_date - self.birth_date.nil? ? "unspecified" : now.year - self.birth_date.year - (self.birth_date.to_date.change(:year => now.year) > now ? 1 : 0) + self.birth_date.nil? ? "" : now.year - self.birth_date.year - (self.birth_date.to_date.change(:year => now.year) > now ? 1 : 0) end def session_count @@ -651,13 +656,45 @@ module JamRuby return recordings end - def update_genres(gids) + def update_genres(gids, genre_type) unless self.new_record? - GenrePlayer.delete_all(["player_id = ? AND player_type = ?", - self.id, self.class.name]) + GenrePlayer.delete_all(["player_id = ? AND player_type = ? AND genre_type = ?", + self.id, self.class.name, genre_type]) end + gids.each do |gid| - self.genres << Genre.find_by_id(gid) + genre_player = GenrePlayer.new + genre_player.player_id = self.id + genre_player.player_type = self.class.name + genre_player.genre_id = gid + genre_player.genre_type = genre_type + self.genre_players << genre_player + end + end + + def update_online_presences(online_presences) + unless self.new_record? + OnlinePresence.delete_all(["player_id = ?", self.id]) + end + + unless online_presences.nil? + online_presences.each do |op| + new_presence = OnlinePresence.create(self, op, false) + self.online_presences << new_presence + end + end + end + + def update_performance_samples(performance_samples) + unless self.new_record? + PerformanceSample.delete_all(["player_id = ?", self.id]) + end + + unless performance_samples.nil? + performance_samples.each do |ps| + new_sample = PerformanceSample.create(self, ps, false) + self.performance_samples << new_sample + end end end @@ -665,14 +702,14 @@ module JamRuby def update_instruments(instruments) # delete all instruments for this user first unless self.new_record? - MusicianInstrument.delete_all(["user_id = ?", self.id]) + MusicianInstrument.delete_all(["player_id = ?", self.id]) end # loop through each instrument in the array and save to the db instruments.each do |musician_instrument_param| instrument = Instrument.find(musician_instrument_param[:instrument_id]) musician_instrument = MusicianInstrument.new - musician_instrument.user = self + musician_instrument.player = self musician_instrument.instrument = instrument musician_instrument.proficiency_level = musician_instrument_param[:proficiency_level] musician_instrument.priority = musician_instrument_param[:priority] @@ -999,7 +1036,7 @@ module JamRuby instruments.each do |musician_instrument_param| instrument = Instrument.find(musician_instrument_param[:instrument_id]) musician_instrument = MusicianInstrument.new - musician_instrument.user = user + musician_instrument.player = user musician_instrument.instrument = instrument musician_instrument.proficiency_level = musician_instrument_param[:proficiency_level] musician_instrument.priority = musician_instrument_param[:priority] @@ -1150,13 +1187,13 @@ module JamRuby end unless user.new_record? - MusicianInstrument.delete_all(["user_id = ?", user.id]) + MusicianInstrument.delete_all(["player_id = ?", user.id]) end instruments.each do |musician_instrument_param| instrument = Instrument.find(musician_instrument_param[:instrument_id]) musician_instrument = MusicianInstrument.new - musician_instrument.user = user + musician_instrument.player = user musician_instrument.instrument = instrument musician_instrument.proficiency_level = musician_instrument_param[:proficiency_level] musician_instrument.priority = musician_instrument_param[:priority] diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 4538efbd6..90cd48c44 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -25,10 +25,10 @@ FactoryGirl.define do before(:create) do |user, evaluator| if evaluator.specific_instruments evaluator.specific_instruments.each do |instrument| - user.musician_instruments << FactoryGirl.build(:musician_instrument, user: user, instrument: instrument) + user.musician_instruments << FactoryGirl.build(:musician_instrument, player: user, instrument: instrument) end else - user.musician_instruments << FactoryGirl.build(:musician_instrument, user: user) + user.musician_instruments << FactoryGirl.build(:musician_instrument, player: user) end end @@ -193,7 +193,7 @@ FactoryGirl.define do end factory :invitation, :class => JamRuby::Invitation do - + end factory :friendship, :class => JamRuby::Friendship do @@ -218,7 +218,7 @@ FactoryGirl.define do band.genres << Genre.first } end - + factory :genre, :class => JamRuby::Genre do description { |n| "Genre #{n}" } end @@ -239,7 +239,7 @@ FactoryGirl.define do end factory :video_source, :class => JamRuby::VideoSource do - #client_video_source_id "test_source_id" + #client_video_source_id "test_source_id" sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"} end @@ -269,7 +269,7 @@ FactoryGirl.define do association :recording, factory: :recording end - factory :recorded_video, :class => JamRuby::RecordedVideo do + factory :recorded_video, :class => JamRuby::RecordedVideo do sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"} fully_uploaded true length 1 @@ -832,4 +832,5 @@ FactoryGirl.define do factory :affiliate_legalese, class: 'JamRuby::AffiliateLegalese' do legalese Faker::Lorem.paragraphs(6).join("\n\n") end + end diff --git a/ruby/spec/jam_ruby/connection_manager_spec.rb b/ruby/spec/jam_ruby/connection_manager_spec.rb index 6fce625cb..502039c1f 100644 --- a/ruby/spec/jam_ruby/connection_manager_spec.rb +++ b/ruby/spec/jam_ruby/connection_manager_spec.rb @@ -46,7 +46,7 @@ describe ConnectionManager, no_transaction: true do client_id = "client_id1" user_id = create_user("test", "user1", "user1@jamkazam.com") user = User.find(user_id) - user.musician_instruments << FactoryGirl.build(:musician_instrument, user: user) + user.musician_instruments << FactoryGirl.build(:musician_instrument, player: user) user.save! user = nil diff --git a/ruby/spec/jam_ruby/models/band_filter_search_spec.rb b/ruby/spec/jam_ruby/models/band_filter_search_spec.rb index 305cb83f0..d2114f13c 100644 --- a/ruby/spec/jam_ruby/models/band_filter_search_spec.rb +++ b/ruby/spec/jam_ruby/models/band_filter_search_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe 'Band search' do before(:each) do + Band.delete_all @bands = [] @bands << @band1 = FactoryGirl.create(:band) @bands << @band2 = FactoryGirl.create(:band) @@ -31,6 +32,8 @@ describe 'Band search' do # the ordering should be create_at since no followers exist expect(Follow.count).to eq(0) results = Search.band_filter({ :per_page => Band.count }) + + rbands = @bands.reverse results.results.each_with_index do |uu, idx| expect(uu.id).to eq(@bands.reverse[idx].id) end diff --git a/ruby/spec/jam_ruby/models/band_spec.rb b/ruby/spec/jam_ruby/models/band_spec.rb index a74ec1bb6..706f16691 100644 --- a/ruby/spec/jam_ruby/models/band_spec.rb +++ b/ruby/spec/jam_ruby/models/band_spec.rb @@ -19,6 +19,21 @@ describe Band do } } + describe 'with instruments' do + it 'builds with instruments' do + band.musician_instruments << FactoryGirl.build(:musician_instrument, player: band) + band.musician_instruments.should have(1).items + band.instruments.should have(1).items + end + + it 'creates with instruments' do + FactoryGirl.create(:musician_instrument, player: band) + band.reload + band.musician_instruments.should have(1).items + band.instruments.should have(1).items + end + end + describe 'website update' do it 'should have http prefix on website url' do band.website = 'example.com' diff --git a/ruby/spec/jam_ruby/models/email_batch_spec_scheduled_session.rb b/ruby/spec/jam_ruby/models/email_batch_spec_scheduled_session.rb index e93133a61..805799df0 100644 --- a/ruby/spec/jam_ruby/models/email_batch_spec_scheduled_session.rb +++ b/ruby/spec/jam_ruby/models/email_batch_spec_scheduled_session.rb @@ -299,7 +299,7 @@ describe EmailBatchScheduledSessions do 4.downto(1) do |nn| uu = FactoryGirl.create(:user, :last_jam_locidispid => 6, :last_jam_addr => 6) uu.musician_instruments << FactoryGirl.build(:musician_instrument, - user: uu, + player: uu, instrument: instruments.sample, proficiency_level: 2) end diff --git a/ruby/spec/jam_ruby/models/musician_search_spec.rb b/ruby/spec/jam_ruby/models/musician_search_spec.rb index 117222d82..da97f5eaa 100644 --- a/ruby/spec/jam_ruby/models/musician_search_spec.rb +++ b/ruby/spec/jam_ruby/models/musician_search_spec.rb @@ -1,79 +1,206 @@ require 'spec_helper' +require 'time_difference' -describe 'Musician search' do - before(:all) do - User.delete_all +describe 'Musician Search Model' do + + let!(:searcher) { FactoryGirl.create(:austin_user) } + let!(:search) { MusicianSearch.create_search(searcher) } + + let!(:austin_user) { FactoryGirl.create(:austin_user) } + let!(:dallas_user) { FactoryGirl.create(:dallas_user) } + let!(:miami_user) { FactoryGirl.create(:miami_user) } + let!(:seattle_user) { FactoryGirl.create(:seattle_user) } + + let!(:user_types) { [:austin_user, :dallas_user, :miami_user, :seattle_user] } + + describe "creates search obj" do + before(:all) do + User.delete_all + end + + it "associates to user" do + expect(search.user).to eq(searcher) + searcher.reload + expect(searcher.musician_search).to eq(search) + end + + it "sets json" do + search.update_json_value(MusicianSearch::KEY_GIGS, MusicianSearch::GIG_COUNTS[1]) + expect(search.json[MusicianSearch::KEY_GIGS]).to eq(MusicianSearch::GIG_COUNTS[1]) + end + end + + describe "filtering criteria" do + before(:all) do + User.delete_all + end + # it "filters musicians" do + # expect(search.do_search(per_page: User.musicians.count).count).to eq(User.musicians.count) + # end + + describe "filters by age" do + before(:all) do + @users = [] + today = Date.today + MusicianSearch::AGE_COUNTS.each_with_index do |age, idx| + dd = today - age.years - 1.day + @users << FactoryGirl.create(:austin_user, :birth_date => dd) + @users << FactoryGirl.create(:dallas_user, :birth_date => dd) + @users << FactoryGirl.create(:miami_user, :birth_date => dd) + @users << FactoryGirl.create(:seattle_user, :birth_date => dd) + end + end + + it "filters by one age" do + age = MusicianSearch::AGE_COUNTS[0] + search.update_json_value(MusicianSearch::KEY_AGES, [age]) + today = Date.today.to_time + search.do_search.all.each do |uu| + diff = TimeDifference.between(uu.birth_date.to_time, today).in_years + expect(diff).to be >= age + expect(diff).to be < MusicianSearch::AGE_COUNTS[1] + end + end + + it "filters by multiple ages" do + ages = MusicianSearch::AGE_COUNTS[0..2] + search.update_json_value(MusicianSearch::KEY_AGES, ages[0..1]) + today = Date.today.to_time + search.do_search.all.each do |uu| + diff = TimeDifference.between(uu.birth_date.to_time, today).in_years + expect(diff).to be >= ages.first + expect(diff).to be < ages.last + end + end + + it "skips filtering by ages" do + search.update_json_value(MusicianSearch::KEY_AGES, [MusicianSearch::ANY_VAL_INT]) + search.do_search.to_sql =~ /(birth_date)/ + expect($1).to eq(nil) + end + end + + describe "filtering by gig" do + before(:all) do + user_types.each do |utype| + FactoryGirl.create(utype, :concert_count => MusicianSearch::GIG_COUNTS[1]) + end + end + + it "ignores gig count if any selected" do + search.update_json_value(MusicianSearch::KEY_GIGS, MusicianSearch::GIG_COUNTS[0]) + expect(search.do_search.count).to eq(User.musicians.count - 1) # searcher is musician + end + + it "filters by gig count" do + search.update_json_value(MusicianSearch::KEY_GIGS, MusicianSearch::GIG_COUNTS[1]) + expect(search.do_search.count).to eq(user_types.count) + end + end + + describe "filtering by studio" do + before(:all) do + user_types.each do |utype| + FactoryGirl.create(utype, :studio_session_count => MusicianSearch::STUDIO_COUNTS[1]) + end + end + + it "ignores studio count if any selected" do + search.update_json_value(MusicianSearch::KEY_STUDIOS, MusicianSearch::STUDIO_COUNTS[0]) + expect(search.do_search.count).to eq(User.musicians.count - 1) + end + + it "filters by studio count" do + search.update_json_value(MusicianSearch::KEY_STUDIOS, MusicianSearch::STUDIO_COUNTS[1]) + expect(search.do_search.count).to eq(user_types.count) + end + end + + describe "filters skills" do + before(:all) do + MusicianSearch::SKILL_VALS.each do |val| + user_types.each { |utype| FactoryGirl.create(utype, :skill_level => val) } + end + end + + it "get expected number per skill" do + search.update_json_value(MusicianSearch::KEY_SKILL, MusicianSearch::SKILL_VALS[1]) + expect(search.do_search.count).to eq(user_types.count) + end + end + + describe "filters interests" do + before(:all) do + MusicianSearch::INTEREST_VALS[1..-1].each do |val| + user_types.each { |utype| FactoryGirl.create(utype, val => true) } + end + end + + it "get expected number per interest" do + search.update_json_value(MusicianSearch::KEY_INTERESTS, MusicianSearch::INTEREST_VALS[1]) + expect(search.do_search.count).to eq(user_types.count) + end + end + + describe "filters genres" do + before(:all) do + user_types.each do |utype| + uu = FactoryGirl.create(utype) + uu.update_genres([Genre.first.id, Genre.last.id], GenrePlayer::PROFILE) + end + end + + it "gets expected number of users" do + search.update_json_value(MusicianSearch::KEY_GENRES, [Genre.first.id, Genre.last.id]) + expect(search.do_search.count).to eq(user_types.count) + end + end + + describe "filters instruments" do + before(:all) do + instruments = Instrument.first(user_types.count).collect do |inst| + { instrument_id: inst.id, proficiency_level: 2, priority: 1 } + end + user_types[0..2].each do |utype| + uu = FactoryGirl.create(utype) + uu.update_instruments(instruments) + end + end + + it "gets expected number of users" do + instjson = [{ instrument_id: Instrument.first.id, proficiency_level: 2 }, + { instrument_id: Instrument.first(2)[1].id, proficiency_level: 2 } + ] + search.update_json_value(MusicianSearch::KEY_INSTRUMENTS, instjson) + expect(search.do_search.count).to eq(3) + end + end end - # need a data set with actual distances - describe "test set A" do + describe "sort order by distance" do + before(:all) do + User.delete_all + end - before(:each) do + before(:each) do create_phony_database end - let!(:austin_user) { FactoryGirl.create(:austin_user) } - let!(:dallas_user) { FactoryGirl.create(:dallas_user) } - let!(:miami_user) { FactoryGirl.create(:miami_user) } - let!(:seattle_user) { FactoryGirl.create(:seattle_user) } - - describe "search on distance" do - - it "finds self when very local search" do - Search.musician_filter({distance: 1, orderby: 'distance'}, austin_user).results.should == [austin_user] # just to see that distance is 0 to self - Search.musician_filter({distance: 1, orderby: 'distance'}, dallas_user).results.should == [dallas_user] # just to see that distance is 0 to self - Search.musician_filter({distance: 1, orderby: 'distance'}, miami_user).results.should == [miami_user] # just to see that distance is 0 to self - Search.musician_filter({distance: 1, orderby: 'distance'}, seattle_user).results.should == [seattle_user] # just to see that distance is 0 to self - end - - it "finds dallas when in range of austin" do - expected_results = [austin_user, dallas_user] - - Search.musician_filter({distance: 500, orderby: 'distance'}, austin_user).results.should == expected_results - Search.musician_filter({distance: 100, orderby: 'distance'}, austin_user).results.should == [austin_user] - end - - it "finds miami when in range of austin" do - expected_results = [austin_user, dallas_user, miami_user] - - Search.musician_filter({distance: 1500, orderby: 'distance'}, austin_user).results.should == expected_results - Search.musician_filter({distance: 300, orderby: 'distance'}, austin_user).results.should == [austin_user, dallas_user] - Search.musician_filter({distance: 100, orderby: 'distance'}, austin_user).results.should == [austin_user] - end - - it "finds seattle when in range of austin" do - expected_results = [austin_user, dallas_user, miami_user, seattle_user] - - Search.musician_filter({distance: 2000, orderby: 'distance'}, austin_user).results.should == expected_results - Search.musician_filter({distance: 1500, orderby: 'distance'}, austin_user).results.should == [austin_user, dallas_user, miami_user] - Search.musician_filter({distance: 300, orderby: 'distance'}, austin_user).results.should == [austin_user, dallas_user] - Search.musician_filter({distance: 100, orderby: 'distance'}, austin_user).results.should == [austin_user] - end - - it "finds austin & dallas by user-specified location when in range" do - Search.musician_filter({distance: 500, orderby: 'distance', city: 'Austin', region: 'TX', country: 'US'}, austin_user).results.should == [austin_user, dallas_user] - end - - it "finds dallas & austin by user-specified location when in range" do - Search.musician_filter({distance: 500, orderby: 'distance', city: 'Dallas', region: 'TX', country: 'US'}, austin_user).results.should == [dallas_user, austin_user] - end - - it "finds miami user-specified location when in range" do - Search.musician_filter({distance: 300, orderby: 'distance', city: 'Tampa', region: 'FL', country: 'US'}, austin_user).results.should == [miami_user] - end - - it "finds all users with user-specified location when in range" do - Search.musician_filter({distance: 2500, orderby: 'distance', city: 'Tampa', region: 'FL', country: 'US'}, austin_user).results.should == [miami_user, dallas_user, austin_user, seattle_user] - end + it "sorts by distance" do + musician_search = MusicianSearch.create_search(searcher) + musician_search.update_json_value(MusicianSearch::KEY_SORT_ORDER, MusicianSearch::SORT_VALS[1]) + results = musician_search.do_search + expect(results[0].id).to eq(austin_user.id) + expect(results[1].id).to eq(dallas_user.id) + expect(results[2].id).to eq(miami_user.id) + expect(results[3].id).to eq(seattle_user.id) end end - describe "test set B" do - + describe "sort order by latency" do before(:each) do - # @geocode1 = FactoryGirl.create(:geocoder) - # @geocode2 = FactoryGirl.create(:geocoder) + User.delete_all t = Time.now - 10.minute @user1 = FactoryGirl.create(:user, created_at: t+1.minute, last_jam_locidispid: 1) @@ -92,13 +219,6 @@ describe 'Musician search' do @musicians << @user5 @musicians << @user6 - @geomusicians = [] - @geomusicians << @user1 - @geomusicians << @user2 - @geomusicians << @user3 - @geomusicians << @user4 - @geomusicians << @user5 - # from these scores: # user1 has scores other users in user1 location, and with user2, user3, user4 # user2 has scores with users in user3 and user4 location @@ -112,318 +232,83 @@ describe 'Musician search' do Score.createx(2, 'b', 2, 4, 'd', 4, 70) end - - context 'default filter settings' do - - it "finds all musicians" do - # expects all the musicians (geocoded) - results = Search.musician_filter({score_limit: Search::TEST_SCORE}) - results.search_type.should == :musicians_filter - results.results.count.should == @musicians.length - results.results.should eq @musicians.reverse - end - - it "finds all musicians page 1" do - # expects all the musicians - results = Search.musician_filter({page: 1, score_limit: Search::TEST_SCORE}) - results.search_type.should == :musicians_filter - results.results.count.should == @musicians.length - results.results.should eq @musicians.reverse - end - - it "finds all musicians page 2" do - # expects no musicians (all fit on page 1) - results = Search.musician_filter({page: 2, score_limit: Search::TEST_SCORE}) - results.search_type.should == :musicians_filter - results.results.count.should == 0 - end - - it "finds all musicians page 1 per_page 3" do - # expects three of the musicians - results = Search.musician_filter({per_page: 3, score_limit: Search::TEST_SCORE}) - results.search_type.should == :musicians_filter - results.results.count.should == 3 - results.results.should eq @musicians.reverse.slice(0, 3) - end - - it "finds all musicians page 2 per_page 3" do - # expects two of the musicians - results = Search.musician_filter({page: 2, per_page: 3, score_limit: Search::TEST_SCORE}) - results.search_type.should == :musicians_filter - results.results.count.should == 3 - results.results.should eq @musicians.reverse.slice(3, 3) - end - - it "finds all musicians page 3 per_page 3" do - # expects two of the musicians - results = Search.musician_filter({page: 3, per_page: 3, score_limit: Search::TEST_SCORE}) - results.search_type.should == :musicians_filter - results.results.count.should == 0 - end - - it "sorts musicians by followers" do - # establish sorting order - - # @user4 - f1 = Follow.new - f1.user = @user2 - f1.followable = @user4 - f1.save - - f2 = Follow.new - f2.user = @user3 - f2.followable = @user4 - f2.save - - f3 = Follow.new - f3.user = @user4 - f3.followable = @user4 - f3.save - - # @user3 - f4 = Follow.new - f4.user = @user3 - f4.followable = @user3 - f4.save - - f5 = Follow.new - f5.user = @user4 - f5.followable = @user3 - f5.save - - # @user2 - f6 = Follow.new - f6.user = @user1 - f6.followable = @user2 - f6.save - - # @user4.followers.concat([@user2, @user3, @user4]) - # @user3.followers.concat([@user3, @user4]) - # @user2.followers.concat([@user1]) - expect(@user4.followers.count).to be 3 - expect(Follow.count).to be 6 - - # refresh the order to ensure it works right - f1 = Follow.new - f1.user = @user3 - f1.followable = @user2 - f1.save - - f2 = Follow.new - f2.user = @user4 - f2.followable = @user2 - f2.save - - f3 = Follow.new - f3.user = @user2 - f3.followable = @user2 - f3.save - - # @user2.followers.concat([@user3, @user4, @user2]) - results = Search.musician_filter({:per_page => @musicians.size, score_limit: Search::TEST_SCORE, orderby: 'followed'}, @user3) - expect(results.results[0].id).to eq(@user2.id) - - # check the follower count for given entry - expect(results.results[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 - - it 'paginates properly' do - # make sure pagination works right - params = {:per_page => 2, :page => 1, score_limit: Search::TEST_SCORE} - results = Search.musician_filter(params) - expect(results.results.count).to be 2 - end - + it "sorts by latency" do + search.update_json_value(MusicianSearch::KEY_SORT_ORDER, MusicianSearch::SORT_VALS[0]) + results = search.do_search + expect(results[0].id).to eq(@user1.id) + expect(results[1].id).to eq(@user2.id) + expect(results[2].id).to eq(@user3.id) + expect(results[3].id).to eq(@user4.id) end - def make_recording(usr) - connection = FactoryGirl.create(:connection, :user => usr, locidispid: usr.last_jam_locidispid) - instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') - track = FactoryGirl.create(:track, :connection => connection, :instrument => instrument) - music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true) - # music_session.connections << connection - # music_session.save - connection.join_the_session(music_session, true, nil, usr, 10) - recording = Recording.start(music_session, usr) - recording.stop - recording.reload - genre = FactoryGirl.create(:genre) - recording.claim(usr, "name", "description", genre, true) - recording.reload - recording - end - - def make_session(usr) - connection = FactoryGirl.create(:connection, :user => usr, locidispid: usr.last_jam_locidispid) - music_session = FactoryGirl.create(:active_music_session, :creator => usr, :musician_access => true) - # music_session.connections << connection - # music_session.save - connection.join_the_session(music_session, true, nil, usr, 10) - end - - context 'musician stat counters' do - - it "displays musicians top followings" do - f1 = Follow.new - f1.user = @user4 - f1.followable = @user4 - f1.save - - f2 = Follow.new - f2.user = @user4 - f2.followable = @user3 - f2.save - - f3 = Follow.new - f3.user = @user4 - f3.followable = @user2 - f3.save - - # @user4.followers.concat([@user4]) - # @user3.followers.concat([@user4]) - # @user2.followers.concat([@user4]) - expect(@user4.top_followings.count).to eq 3 - expect(@user4.top_followings.map(&:id)).to match_array((@musicians - [@user1, @user5, @user6]).map(&:id)) - end - - it "friends stat shows friend count" do - # create friendship record - Friendship.save(@user1.id, @user2.id) - # search on user2 - results = Search.musician_filter({score_limit: Search::TEST_SCORE}, @user2) - friend = results.results.detect { |mm| mm.id == @user1.id } - expect(friend).to_not be_nil - expect(results.friend_count(friend)).to be 1 - @user1.reload - expect(friend.friends?(@user2)).to be true - expect(results.is_friend?(@user1)).to be true - end - - it "recording stat shows recording count" do - Recording.delete_all - - recording = make_recording(@user1) - expect(recording.users.length).to be 1 - expect(recording.users.first).to eq(@user1) - @user1.reload - expect(@user1.recordings.length).to be 1 - expect(@user1.recordings.first).to eq(recording) - expect(recording.claimed_recordings.length).to be 1 - expect(@user1.recordings.detect { |rr| rr == recording }).to_not be_nil - - results = Search.musician_filter({score_limit: Search::TEST_SCORE}, @user1) - # puts "====================== results #{results.inspect}" - uu = results.results.detect { |mm| mm.id == @user1.id } - expect(uu).to_not be_nil - - expect(results.record_count(uu)).to be 1 - expect(results.session_count(uu)).to be 1 - end - - end - - context 'musician sorting' do - - it "by plays" do - Recording.delete_all - - make_recording(@user1) - # order results by num recordings - results = Search.musician_filter({orderby: 'plays', score_limit: Search::TEST_SCORE}, @user2) - # puts "========= results #{results.inspect}" - expect(results.results.length).to eq(2) - expect(results.results[0].id).to eq(@user1.id) - expect(results.results[1].id).to eq(@user3.id) - - # add more data and make sure order still correct - make_recording(@user3) - make_recording(@user3) - results = Search.musician_filter({:orderby => 'plays', score_limit: Search::TEST_SCORE}, @user2) - expect(results.results.length).to eq(2) - expect(results.results[0].id).to eq(@user3.id) - expect(results.results[1].id).to eq(@user1.id) - end - - it "by now playing" do - pending "these tests worked, so leaving them in, but we don't currently have 'Now Playing' in the find musicians screen" - # should get 1 result with 1 active session - make_session(@user1) - results = Search.musician_filter({:orderby => 'playing', score_limit: Search::TEST_SCORE}, @user2) - expect(results.results.count).to be 1 - expect(results.results.first.id).to eq(@user1.id) - - # should get 2 results with 2 active sessions - # sort order should be created_at DESC - make_session(@user3) - results = Search.musician_filter({:orderby => 'playing', score_limit: Search::TEST_SCORE}, @user2) - expect(results.results.count).to be 2 - expect(results.results[0].id).to eq(@user3.id) - expect(results.results[1].id).to eq(@user1.id) - end - - end - - context 'filter settings' do - it "searches musicians for an instrument" do - minst = FactoryGirl.create(:musician_instrument, { - :user => @user1, - :instrument => Instrument.find('tuba')}) - @user1.musician_instruments << minst - @user1.reload - ii = @user1.instruments.detect { |inst| inst.id == 'tuba' } - expect(ii).to_not be_nil - results = Search.musician_filter({:instrument => ii.id, score_limit: Search::TEST_SCORE}, @user2) - results.results.each do |rr| - expect(rr.instruments.detect { |inst| inst.id=='tuba' }.id).to eq(ii.id) - end - expect(results.results.count).to be 1 - end - end - - context 'new users' do - - it "find three for user1" do - # user2..4 are scored against user1 - ms = Search.new_musicians(@user1, Time.now - 1.week) - ms.should_not be_nil - ms.length.should == 3 - ms.should eq [@user2, @user3, @user4] - end - - it "find two for user2" do - # user1,3,4 are scored against user1, but user4 is bad - ms = Search.new_musicians(@user2, Time.now - 1.week) - ms.should_not be_nil - ms.length.should == 2 - ms.should eq [@user3, @user1] - end - - it "find two for user3" do - # user1..2 are scored against user3 - ms = Search.new_musicians(@user3, Time.now - 1.week) - ms.should_not be_nil - ms.length.should == 2 - ms.should eq [@user2, @user1] - end - - it "find one for user4" do - # user1..2 are scored against user4, but user2 is bad - ms = Search.new_musicians(@user4, Time.now - 1.week) - ms.should_not be_nil - ms.length.should == 1 - ms.should eq [@user1] - end - - it "find none for user5" do - # user1..4 are not scored against user5 - ms = Search.new_musicians(@user5, Time.now - 1.week) - ms.should_not be_nil - ms.length.should == 0 - end - - end end + + describe "produces accurate query description" do + before(:all) do + User.delete_all + end + + it 'has default description' do + expect(search.description).to match(/^Click search button to look for musicians/) + end + it 'has correct sort order description' do + search.update_json_value(MusicianSearch::KEY_SORT_ORDER, MusicianSearch::SORT_VALS[1]) + str = MusicianSearch::SORT_ORDERS[search.json_value(MusicianSearch::KEY_SORT_ORDER)] + expect(search.description).to match(/^Current Search: Sort = #{str}$/) + end + + it 'has correct description for single-valued selections' do + selections = [{ + key: MusicianSearch::KEY_INTERESTS, + value: MusicianSearch::INTEREST_VALS[1], + lookup: MusicianSearch::INTERESTS, + description: 'Interest' + }, + { + key: MusicianSearch::KEY_SKILL, + value: MusicianSearch::SKILL_VALS[1], + lookup: MusicianSearch::SKILL_LEVELS, + description: 'Skill' + }, + { + key: MusicianSearch::KEY_STUDIOS, + value: MusicianSearch::STUDIO_COUNTS[1], + lookup: MusicianSearch::STUDIOS_LABELS, + description: 'Studio Sessions' + }, + { + key: MusicianSearch::KEY_GIGS, + value: MusicianSearch::GIG_COUNTS[1], + lookup: MusicianSearch::GIG_LABELS, + description: 'Concert Gigs' + }] + selections.each do |hash| + search.update_json_value(hash[:key], hash[:value]) + json_val = search.json_value(hash[:key]) + expect(search.description).to match(/; #{hash[:description]} = #{hash[:lookup][json_val]}/) + end + end + + it 'has correct description for genres' do + search.update_json_value(MusicianSearch::KEY_GENRES, [Genre.first.id, Genre.last.id]) + expect(search.description).to match(/; Genres = #{Genre.first.description}, #{Genre.last.description}/) + end + + it 'has correct description for ages' do + search.update_json_value(MusicianSearch::KEY_AGES, [MusicianSearch::AGE_COUNTS[0],MusicianSearch::AGE_COUNTS[1]]) + expect(search.description).to match(/; Ages = #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[0]]}, #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[1]]}/) + end + + it 'has correct description for instruments' do + instrs = Instrument.limit(2).order(:description) + instjson = [{ instrument_id: instrs[0].id, proficiency_level: 2 }, + { instrument_id: instrs[1].id, proficiency_level: 1 } + ] + search.update_json_value(MusicianSearch::KEY_INSTRUMENTS, instjson) + instr_descrip = "#{instrs[0].description} (#{MusicianSearch::INSTRUMENT_PROFICIENCY[2]}), #{instrs[1].description} (#{MusicianSearch::INSTRUMENT_PROFICIENCY[1]})" + expect(search.description).to match(/; Instruments = #{Regexp.escape(instr_descrip)}/) + end + + end end diff --git a/ruby/spec/jam_ruby/models/online_presence_spec.rb b/ruby/spec/jam_ruby/models/online_presence_spec.rb new file mode 100644 index 000000000..883917f25 --- /dev/null +++ b/ruby/spec/jam_ruby/models/online_presence_spec.rb @@ -0,0 +1,173 @@ +require 'spec_helper' + +describe OnlinePresence do + + shared_examples_for :online_presence_specs do + describe "index" do + + before(:all) do + OnlinePresence.delete_all + + player1_presence1 = OnlinePresence.new({:player_id => player1.id, :username => "myonlineusername", :service_type => "facebook"}) + player1_presence1.save! + + player1_presence2 = OnlinePresence.new({:player_id => player1.id, :username => "myonlineusername", :service_type => "twitter"}) + player1_presence2.save! + + player2_presence1 = OnlinePresence.new({:player_id => player2.id, :username => "myonlineusername", :service_type => "soundcloud"}) + player2_presence1.save! + end + + context "when request is valid" do + it "should return all records for user" do + presence = OnlinePresence.index({:id => player1.id}) + presence.count.should == 2 + + presence = OnlinePresence.index({:id => player2.id}) + presence.count.should == 1 + end + end + + context "when request is invalid" do + it "should raise error when options are missing" do + lambda{OnlinePresence.index}.should raise_error(StateError) + end + + it "should raise error when user id is missing" do + lambda{OnlinePresence.index({:id => ""})}.should raise_error(StateError) + end + end + end + + describe "create" do + + before(:all) do + OnlinePresence.delete_all + end + + context "when request is valid" do + it "should save successfully" do + OnlinePresence.create(player1, {:player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer1"}) + + # make sure we can save a second OnlinePresence for same user and type + OnlinePresence.create(player1, {:player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer2"}) + + OnlinePresence.index({:id => player1.id}).count.should == 2 + end + end + + context "when request is not valid" do + it "should raise JamPermissionError if requester id does not match id in request" do + lambda{OnlinePresence.create(player1, {:player_id => player2.id, :service_type => "soundcloud", :username => "soundcloudplayer2"})}.should raise_error(JamPermissionError) + end + + it "should raise error if service type is missing" do + lambda{OnlinePresence.create(player1, {:player_id => player1.id, :username => "soundcloudplayer2"})}.should raise_error(StateError) + end + + it "should raise error if username is missing" do + lambda{OnlinePresence.create(player1, {:player_id => player1.id, :service_type => "soundcloud"})}.should raise_error(StateError) + end + + it "should not allow duplicates of the same username / service type combination" do + OnlinePresence.create(player1, {:player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer1"}) + OnlinePresence.index({:id => player1.id}).count.should == 1 + + lambda{OnlinePresence.create(player1, {:player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer1"})}.should raise_error(ConflictError) + OnlinePresence.index({:id => player1.id}).count.should == 1 + end + end + end + + describe "update" do + + before(:all) do + OnlinePresence.delete_all + @online_presence = OnlinePresence.new(:player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer1") + @online_presence.save! + end + + context "when request is valid" do + it "should save successfully" do + + up_list = OnlinePresence.index({:id => player1.id}) + up_list.count.should == 1 + up_list.first.service_type.should == "soundcloud" + up_list.first.username.should == "soundcloudplayer1" + + OnlinePresence.update(player1, {:id => @online_presence.id, :player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer2"}) + + up_list = OnlinePresence.index({:id => player1.id}) + up_list.count.should == 1 + up_list.first.service_type.should == "soundcloud" + up_list.first.username.should == "soundcloudplayer2" + end + end + + context "when request is not valid" do + it "should raise JamPermissionError if requester id does not match id in request" do + lambda{OnlinePresence.update(player1, {:player_id => player2.id, :id => @online_presence.id, :service_type => "soundcloud", :username => "soundcloudplayer2"})}.should raise_error(JamPermissionError) + end + + it "should raise error if type is missing" do + lambda{OnlinePresence.update(player1, {:player_id => player1.id, :id => @online_presence.id, :username => "soundcloudplayer2"})}.should raise_error(StateError) + end + + it "should raise error if username is missing" do + lambda{OnlinePresence.update(player1, {:player_id => player1.id, :id => @online_presence.id, :service_type => "soundcloud"})}.should raise_error(StateError) + end + + it "should raise error if player presence id is missing" do + lambda{OnlinePresence.update(player1, {:player_id => player1.id, :username => "soundcloudplayer2", :service_type => "soundcloud"})}.should raise_error(StateError) + end + end + end + + describe "destroy" do + + before(:all) do + OnlinePresence.delete_all + @online_presence = OnlinePresence.new(:player_id => player1.id, :service_type => "soundcloud", :username => "soundcloudplayer1") + @online_presence.save! + end + + context "when request is valid" do + it "should destroy successfully" do + up_list = OnlinePresence.index({:id => player1.id}) + up_list.count.should == 1 + up_list.first.service_type.should == "soundcloud" + up_list.first.username.should == "soundcloudplayer1" + + OnlinePresence.delete(player1, {:player_id => player1.id, :id => @online_presence.id}) + + up_list = OnlinePresence.index({:id => player1.id}) + up_list.count.should == 0 + end + end + + context "when request is not valid" do + it "should raise JamPermissionError if requester id does not match id in request" do + lambda{OnlinePresence.delete(player2, {:player_id => player1.id, :id => @online_presence.id})}.should raise_error(JamPermissionError) + end + + it "should raise error if player presence id is missing" do + lambda{OnlinePresence.delete(player1, {:player_id => player1.id})}.should raise_error(StateError) + end + end + end + end # shared + + describe "with a user" do + it_should_behave_like :online_presence_specs do + let(:player1) { FactoryGirl.create(:user) } + let(:player2) { FactoryGirl.create(:user) } + end + end + + describe "with a band" do + it_should_behave_like :online_presence_specs do + let(:player1) { FactoryGirl.create(:band) } + let(:player2) { FactoryGirl.create(:band) } + end + end +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/performance_sample_spec.rb b/ruby/spec/jam_ruby/models/performance_sample_spec.rb new file mode 100644 index 000000000..5cf592a2b --- /dev/null +++ b/ruby/spec/jam_ruby/models/performance_sample_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +describe PerformanceSample do + + shared_examples_for :performance_sample_specs do + let(:claimed_recording) { FactoryGirl.create(:claimed_recording) } + + describe "index" do + + before(:all) do + PerformanceSample.delete_all + + @player1_sample1 = PerformanceSample.new(:player_id => player1.id, :service_type => "jamkazam", :claimed_recording_id => claimed_recording.id) + @player1_sample1.save! + + @player1_sample2 = PerformanceSample.new(:player_id => player1.id, :service_type => "youtube", :service_id => "12345") + @player1_sample2.save! + + @player2_sample1 = PerformanceSample.new(:player_id => player2.id, :service_type => "soundcloud", :service_id => "67890") + @player2_sample1.save! + end + + context "when request is valid" do + it "should return all records for user" do + sample = PerformanceSample.index({:id => player1.id}) + sample.count.should == 2 + + sample = PerformanceSample.index({:id => player2.id}) + sample.count.should == 1 + end + end + + context "when request is invalid" do + it "should raise error when options are missing" do + lambda{PerformanceSample.index}.should raise_error(JamArgumentError) + end + + it "should raise error when user id is missing" do + lambda{PerformanceSample.index({:id => ""})}.should raise_error(JamArgumentError) + end + end + end + + describe "create" do + + before(:all) do + PerformanceSample.delete_all + end + + context "when request is valid" do + it "should save successfully" do + PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "youtube", :service_id => "12345"}) + + # make sure we can save a second PerformanceSample for same user and type + PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "youtube", :service_id => "67890"}) + end + end + + context "when request is not valid" do + it "should raise JamPermissionError if requester id does not match id in request" do + lambda{PerformanceSample.create(player1, {:player_id => player2.id, :service_type => "soundcloud", :service_id => "12345"})}.should raise_error(JamPermissionError) + end + + it "should raise error if service type is missing" do + lambda{PerformanceSample.create(player1, {:player_id => player1.id, :service_id => "12345"})}.should raise_error(StateError) + end + + it "should raise error if service id is missing for non-JamKazam sample" do + lambda{PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "youtube"})}.should raise_error(StateError) + end + + it "should raise error if recording id is missing for JamKazam sample" do + lambda{PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "jamkazam"})}.should raise_error(StateError) + end + + it "should not allow duplicate type/service id combination for non-JamKazam sample" do + PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "youtube", :service_id => "12345"}) + lambda{PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "youtube", :service_id => "12345"})}.should raise_error(ConflictError) + end + + it "should not allow duplicate type/recording id combination for JamKazam sample" do + PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "jamkazam", :claimed_recording_id => claimed_recording.id}) + lambda{PerformanceSample.create(player1, {:player_id => player1.id, :service_type => "jamkazam", :claimed_recording_id => claimed_recording.id})}.should raise_error(ConflictError) + end + end + end + + + describe "destroy" do + + before(:all) do + PerformanceSample.delete_all + @user_sample = PerformanceSample.new(:player_id => player1.id, :service_type => "soundcloud", :service_id => "12345") + @user_sample.save! + end + + context "when request is valid" do + it "should destroy successfully" do + ps_list = PerformanceSample.index({:id => player1.id}) + ps_list.count.should == 1 + ps_list.first.service_type.should == "soundcloud" + ps_list.first.service_id.should == "12345" + + PerformanceSample.delete(player1, {:player_id => player1.id, :id => @user_sample.id}) + + ps_list = PerformanceSample.index({:id => player1.id}) + ps_list.count.should == 0 + end + end + + context "when request is not valid" do + it "should raise JamPermissionError if requester id does not match id in request" do + lambda{PerformanceSample.delete(player2, {:player_id => player1.id, :id => @user_sample.id})}.should raise_error(JamPermissionError) + end + + it "should raise error if user sample id is missing" do + lambda{PerformanceSample.delete(player1, {:player_id => player1.id})}.should raise_error(StateError) + end + end + end + end + + describe "with a user" do + it_should_behave_like :performance_sample_specs do + let(:player1) { FactoryGirl.create(:user) } + let(:player2) { FactoryGirl.create(:user) } + end + end + + describe "with a band" do + it_should_behave_like :performance_sample_specs do + let(:player1) { FactoryGirl.create(:band) } + let(:player2) { FactoryGirl.create(:band) } + end + end +end \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 1d6b0f40f..0fb434442 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -9,7 +9,7 @@ describe User do User.delete_all @user = User.new(first_name: "Example", last_name: "User", email: "user@example.com", password: "foobar", password_confirmation: "foobar", city: "Apex", state: "NC", country: "US", terms_of_service: true, musician: true) - @user.musician_instruments << FactoryGirl.build(:musician_instrument, user: @user) + @user.musician_instruments << FactoryGirl.build(:musician_instrument, player: @user) @recurly = RecurlyClient.new end @@ -701,13 +701,16 @@ describe User do describe "age" do let(:user) {FactoryGirl.create(:user)} - + it "should calculate age based on birth_date" do user.birth_date = Time.now - 10.years user.age.should == 10 user.birth_date = Time.now - 10.years + 3.months user.age.should == 9 + + user.birth_date = nil + user.age.should == "" end end diff --git a/ruby/spec/mailers/render_emails_spec.rb b/ruby/spec/mailers/render_emails_spec.rb index 4439671e6..dfc84cef3 100644 --- a/ruby/spec/mailers/render_emails_spec.rb +++ b/ruby/spec/mailers/render_emails_spec.rb @@ -98,13 +98,13 @@ describe "RenderMailers", :slow => true do BatchMailer.deliveries.clear scheduled_batch.reset! - drummer.musician_instruments << FactoryGirl.build(:musician_instrument, user: drummer, instrument: drums, proficiency_level: 2) - drummer.musician_instruments << FactoryGirl.build(:musician_instrument, user: drummer, instrument: guitar, proficiency_level: 2) + drummer.musician_instruments << FactoryGirl.build(:musician_instrument, player: drummer, instrument: drums, proficiency_level: 2) + drummer.musician_instruments << FactoryGirl.build(:musician_instrument, player: drummer, instrument: guitar, proficiency_level: 2) - guitarist.musician_instruments << FactoryGirl.build(:musician_instrument, user: guitarist, instrument: guitar, proficiency_level: 2) - guitarist.musician_instruments << FactoryGirl.build(:musician_instrument, user: guitarist, instrument: bass, proficiency_level: 2) + guitarist.musician_instruments << FactoryGirl.build(:musician_instrument, player: guitarist, instrument: guitar, proficiency_level: 2) + guitarist.musician_instruments << FactoryGirl.build(:musician_instrument, player: guitarist, instrument: bass, proficiency_level: 2) - vocalist.musician_instruments << FactoryGirl.build(:musician_instrument, user: vocalist, instrument: vocals, proficiency_level: 2) + vocalist.musician_instruments << FactoryGirl.build(:musician_instrument, player: vocalist, instrument: vocals, proficiency_level: 2) FactoryGirl.create(:rsvp_slot, :instrument => drums, :music_session => session1) FactoryGirl.create(:rsvp_slot, :instrument => guitar, :music_session => session1) diff --git a/web/Gemfile b/web/Gemfile index d7975e309..00e74d2a6 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -53,6 +53,7 @@ gem 'sendgrid', '1.2.0' gem 'filepicker-rails', '0.1.0' gem 'aws-sdk', '~> 1' gem 'aasm', '3.0.16' +gem 'carmen' gem 'carrierwave', '0.9.0' gem 'carrierwave_direct' gem 'fog' @@ -105,8 +106,10 @@ group :development, :test do gem 'execjs', '1.4.0' gem 'factory_girl_rails', '4.1.0' # in dev because in use by rake task gem 'database_cleaner', '1.3.0' #in dev because in use by rake task -# gem 'teaspoon' -# gem 'teaspoon-jasmine' + + # gem 'teaspoon' + # gem 'teaspoon-jasmine' + # gem 'puma' end group :unix do gem 'therubyracer' #, '0.11.0beta8' diff --git a/web/app/assets/images/content/bandcamp-logo.png b/web/app/assets/images/content/bandcamp-logo.png new file mode 100644 index 000000000..03eec06be Binary files /dev/null and b/web/app/assets/images/content/bandcamp-logo.png differ diff --git a/web/app/assets/images/content/facebook-logo.png b/web/app/assets/images/content/facebook-logo.png new file mode 100644 index 000000000..bb99485bc Binary files /dev/null and b/web/app/assets/images/content/facebook-logo.png differ diff --git a/web/app/assets/images/content/fandalism-logo.png b/web/app/assets/images/content/fandalism-logo.png new file mode 100644 index 000000000..9cf95bee1 Binary files /dev/null and b/web/app/assets/images/content/fandalism-logo.png differ diff --git a/web/app/assets/images/content/reverbnation-logo.png b/web/app/assets/images/content/reverbnation-logo.png new file mode 100644 index 000000000..152d59982 Binary files /dev/null and b/web/app/assets/images/content/reverbnation-logo.png differ diff --git a/web/app/assets/images/content/soundcloud-logo.png b/web/app/assets/images/content/soundcloud-logo.png new file mode 100644 index 000000000..2dee5398a Binary files /dev/null and b/web/app/assets/images/content/soundcloud-logo.png differ diff --git a/web/app/assets/images/content/twitter-logo.png b/web/app/assets/images/content/twitter-logo.png new file mode 100644 index 000000000..c2c63ca0c Binary files /dev/null and b/web/app/assets/images/content/twitter-logo.png differ diff --git a/web/app/assets/images/content/website-logo.png b/web/app/assets/images/content/website-logo.png new file mode 100644 index 000000000..fa0552330 Binary files /dev/null and b/web/app/assets/images/content/website-logo.png differ diff --git a/web/app/assets/images/content/youtube-logo.png b/web/app/assets/images/content/youtube-logo.png new file mode 100644 index 000000000..d5ca8f9c5 Binary files /dev/null and b/web/app/assets/images/content/youtube-logo.png differ diff --git a/web/app/assets/javascripts/accounts_profile.js b/web/app/assets/javascripts/accounts_profile.js index 79b8389fe..9938d4cf0 100644 --- a/web/app/assets/javascripts/accounts_profile.js +++ b/web/app/assets/javascripts/accounts_profile.js @@ -1,595 +1,509 @@ (function(context,$) { - "use strict"; + "use strict"; - context.JK = context.JK || {}; - context.JK.AccountProfileScreen = function(app) { - var $document = $(document); - var logger = context.JK.logger; - var EVENTS = context.JK.EVENTS; - var api = context.JK.Rest(); - var userId; - var user = {}; - var selectLocation = null; - var recentUserDetail = null; - var loadingCitiesData = false; - var loadingRegionsData = false; - var loadingCountriesData = false; - var nilOptionStr = ''; - var nilOptionText = 'n/a'; + context.JK = context.JK || {}; + context.JK.AccountProfileScreen = function(app) { + var $document = $(document); + var logger = context.JK.logger; + var EVENTS = context.JK.EVENTS; + var api = context.JK.Rest(); + var userId; + var user = {}; + var selectLocation = null; + var recentUserDetail = null; + var loadingCitiesData = false; + var loadingRegionsData = false; + var loadingCountriesData = false; + var nilOptionStr = ''; + var nilOptionText = 'n/a'; + var $screen = $('#account-profile-basics'); + var $avatar = $screen.find('#avatar'); + var $country = $screen.find('#country'); + var $region = $screen.find('#region'); + var $city = $screen.find('#city'); + var $firstName = $screen.find('#first-name'); + var $lastName = $screen.find('#last-name'); + var $gender = $screen.find('#gender'); + var $biography = $screen.find('#biography'); + var $subscribe = $screen.find('#subscribe'); - function beforeShow(data) { - userId = data.id; + var $btnCancel = $screen.find('.account-edit-profile-cancel'); + var $btnSubmit = $screen.find('.account-edit-profile-submit'); + + function beforeShow(data) { + userId = data.id; + } + + function afterShow(data) { + resetForm(); + renderAccountProfile(); + } + + function resetForm() { + // remove all display errors + $('#account-profile-content-scroller form .error-text').remove() + $('#account-profile-content-scroller form .error').removeClass("error") + } + + function populateAccountProfile(userDetail) { + + $avatar.attr('src', context.JK.resolveAvatarUrl(userDetail.photo_url)); + $country.val(userDetail.country); + $region.val(userDetail.state); + $city.val(userDetail.city); + $firstName.val(userDetail.first_name); + $lastName.val(userDetail.last_name); + $gender.val(userDetail.gender); + $biography.val(userDetail.biography); + + if (userDetail.subscribe_email) { + $subscribe.attr('checked', 'checked'); + } + + var content_root = $('#account-profile-content-scroller'); + + // set birth_date + if(userDetail.birth_date) { + var birthDateFields = userDetail.birth_date.split('-') + var birthDateYear = birthDateFields[0]; + var birthDateMonth = birthDateFields[1]; + var birthDateDay = birthDateFields[2]; + + $('select#user_birth_date_1i', content_root).val(parseInt(birthDateYear)); + $('select#user_birth_date_2i', content_root).val(parseInt(birthDateMonth)); + $('select#user_birth_date_3i', content_root).val(parseInt(birthDateDay)); + } + + context.JK.dropdown($('select', content_root)); + } + + function populateAccountProfileLocation(userDetail, regions, cities) { + populateRegions(regions, userDetail.state); + populateCities(cities, userDetail.city); + } + + function populateCountries(countries, userCountry) { + + // countries has the format ["US", ...] + var foundCountry = false; + var countrySelect = getCountryElement(); + countrySelect.children().remove(); + + var nilOption = $(nilOptionStr); + nilOption.text(nilOptionText); + countrySelect.append(nilOption); + + $.each(countries, function(index, country) { + if(!country) return; + + var option = $(nilOptionStr); + option.text(country); + option.attr("value", country); + + if(country == userCountry) { + foundCountry = true; } - function afterShow(data) { - resetForm(); - renderAccountProfile(); + countrySelect.append(option); + }); + + if(!foundCountry) { + // in this case, the user has a country that is not in the database + // this can happen in a development/test scenario, but let's assume it can + // happen in production too. + var option = $(nilOptionStr); + option.text(userCountry); + option.attr("value", userCountry); + countrySelect.append(option); + } + + countrySelect.val(userCountry); + countrySelect.attr("disabled", null) + + context.JK.dropdown(countrySelect); + } + + + function populateCountriesx(countriesx, userCountry) { + + // countriesx has the format [{countrycode: "US", countryname: "United States"}, ...] + + var foundCountry = false; + var countrySelect = getCountryElement(); + countrySelect.children().remove(); + + var nilOption = $(nilOptionStr); + nilOption.text(nilOptionText); + countrySelect.append(nilOption); + + $.each(countriesx, function(index, countryx) { + if (!countryx.countrycode) return; + + var option = $(nilOptionStr); + option.text(countryx.countryname); + option.attr("value", countryx.countrycode); + + if(countryx.countrycode == userCountry) { + foundCountry = true; } - function resetForm() { - // remove all display errors - $('#account-profile-content-scroller form .error-text').remove() - $('#account-profile-content-scroller form .error').removeClass("error") - } + countrySelect.append(option); + }); - function populateAccountProfile(userDetail, instruments) { - var template = context.JK.fillTemplate($('#template-account-profile').html(), { - country: userDetail.country, - region: userDetail.state, - city: userDetail.city, - first_name: userDetail.first_name, - last_name: userDetail.last_name, - photoUrl: context.JK.resolveAvatarUrl(userDetail.photo_url), - user_instruments: userDetail.instruments, - birth_date : userDetail.birth_date, - gender: userDetail.gender, - subscribe_email: userDetail.subscribe_email ? "checked=checked" : "" - }); + if(!foundCountry) { + // in this case, the user has a country that is not in the database + // this can happen in a development/test scenario, but let's assume it can + // happen in production too. + var option = $(nilOptionStr); + option.text(userCountry); + option.attr("value", userCountry); + countrySelect.append(option); + } - var content_root = $('#account-profile-content-scroller') - content_root.html(template); + countrySelect.val(userCountry); + countrySelect.attr("disabled", null); - // now use javascript to fix up values too hard to do with templating - // set gender - $('select[name=gender]', content_root).val(userDetail.gender) + context.JK.dropdown(countrySelect); + } - // set birth_date - if(userDetail.birth_date) { - var birthDateFields = userDetail.birth_date.split('-') - var birthDateYear = birthDateFields[0]; - var birthDateMonth = birthDateFields[1]; - var birthDateDay = birthDateFields[2]; + function populateRegions(regions, userRegion) { + var regionSelect = getRegionElement() + regionSelect.children().remove() - $('select#user_birth_date_1i', content_root).val(parseInt(birthDateYear)); - $('select#user_birth_date_2i', content_root).val(parseInt(birthDateMonth)); - $('select#user_birth_date_3i', content_root).val(parseInt(birthDateDay)); - } + var nilOption = $(nilOptionStr); + nilOption.text(nilOptionText); + regionSelect.append(nilOption); - loadGenres(userDetail.genres); + $.each(regions, function(index, region) { + if(!region) return; - // update instruments - $.each(instruments, function(index, instrument) { - var template = context.JK.fillTemplate($('#account-profile-instrument').html(), { - checked : isUserInstrument(instrument, userDetail.instruments) ? "checked=\"checked\"" :"", - description : instrument.description, - id : instrument.id - }) - $('.instrument_selector', content_root).append(template) - }) - // and fill in the proficiency for the instruments that the user can play - if(userDetail.instruments) { - $.each(userDetail.instruments, function(index, userInstrument) { - $('tr[data-instrument-id="' + userInstrument.instrument_id + '"] select.proficiency_selector', content_root).val(userInstrument.proficiency_level) - }) - } + var option = $(nilOptionStr); + option.text(region['name']); + option.attr("value", region['region']); - context.JK.dropdown($('select', content_root)); - } + regionSelect.append(option); + }) - function isUserInstrument(instrument, userInstruments) { - var isUserInstrument = false; - if(!userInstruments) return false; + regionSelect.val(userRegion) + regionSelect.attr("disabled", null) - $.each(userInstruments, function(index, userInstrument) { - if(instrument.id == userInstrument.instrument_id) { - isUserInstrument = true; - return false; - } - }) - return isUserInstrument; - } + context.JK.dropdown(regionSelect); + } - function loadGenres(selectedGenres) { - $("#user-genres").empty(); + function populateCities(cities, userCity) { + var citySelect = getCityElement(); + citySelect.children().remove(); - rest.getGenres().done(function (genres) { - $.each(genres, function (index, genre) { - var genreTemplate = $('#template-user-setup-genres').html(); - var selected = ''; - if (selectedGenres) { - var genreMatch = $.grep(selectedGenres, function (n, i) { - return n.id === genre.id; - }); - if (genreMatch.length > 0) { - selected = "checked"; - } - } - var genreHtml = context.JK.fillTemplate(genreTemplate, { - id: genre.id, - description: genre.description, - checked: selected - }); + var nilOption = $(nilOptionStr); + nilOption.text(nilOptionText); + citySelect.append(nilOption); - $('#user-genres').append(genreHtml); + $.each(cities, function(index, city) { + if(!city) return; + + var option = $(nilOptionStr); + option.text(city); + option.attr("value", city); + + citySelect.append(option); + }); + + citySelect.val(userCity); + citySelect.attr("disabled", null); + + context.JK.dropdown(citySelect); + } + + /****************** MAIN PORTION OF SCREEN *****************/ + // events for main screen + function events() { + $btnCancel.click(function(evt) { + evt.stopPropagation(); + navToAccount(); + return false; + }); + + $('#account-profile-content-scroller').on('click', '#account-change-avatar', function(evt) { evt.stopPropagation(); navToAvatar(); return false; } ); + + enableSubmits(); + } + + function renderAccountProfile() { + $.when(api.getUserProfile()) + .done(function(userDetail) { + recentUserDetail = userDetail; + populateAccountProfile(userDetail); + + selectLocation = new context.JK.SelectLocation(getCountryElement(), getRegionElement(), getCityElement(), app); + selectLocation.load(userDetail.country, userDetail.state, userDetail.city) }); - }); - } - function resetGenres() { - $('input[type=checkbox]:checked', '#user-genres').each(function (i) { - $(this).removeAttr("checked"); - }); - var $tdGenres = $("#tdBandGenres"); + + context.JK.dropdown($('select')); } - function getSelectedGenres() { - var genres = []; - $('input[type=checkbox]:checked', '#user-genres').each(function (i) { - var genre = $(this).val(); - genres.push(genre); - }); - return genres; + function navToAccount() { + resetForm(); + window.location = '/client#/profile/' + context.JK.currentUserId; } - function populateAccountProfileLocation(userDetail, regions, cities) { - populateRegions(regions, userDetail.state); - populateCities(cities, userDetail.city); + function navToAvatar() { + resetForm(); + window.location = '/client#/account/profile/avatar'; + } + + function handleUpdateProfile() { + disableSubmits(); + resetForm(); + + var country = getCountryElement().val(); + var region = getRegionElement().val(); + var city = getCityElement().val(); + var firstName = getFirstNameElement().val(); + var lastName = getLastNameElement().val(); + var gender = getGenderElement().val(); + var subscribeEmail = getSubscribeEmail().is(':checked'); + var birthDate = getBirthDate(); + var biography = $biography.val(); + + api.updateUser({ + country: country, + state: region, + city: city, + first_name: firstName, + last_name: lastName, + gender: gender, + birth_date: birthDate, + biography: biography, + subscribe_email: subscribeEmail + }) + .done(postUpdateProfileSuccess) + .fail(postUpdateProfileFailure) + .always(enableSubmits) + } + + function enableSubmits() { + $btnSubmit.click(function(evt) { + evt.stopPropagation(); + handleUpdateProfile(); + return false; + }); + $btnSubmit.removeClass("disabled"); + $('#account-profile-content-scroller').on('submit', '#account-edit-email-form', function(evt) { evt.stopPropagation(); handleUpdateProfile(); return false; } ); + $("#account-edit-email-form").removeClass("disabled"); + } + + function disableSubmits() { + $("#account-edit-email-form").addClass("disabled") + $("#account-edit-email-form").off("click") + $btnSubmit.addClass("disabled") + $btnSubmit.off("click") + } + + function postUpdateProfileSuccess(response) { + $document.triggerHandler(EVENTS.USER_UPDATED, response); + context.location = "/client#/account/profile/experience"; + } + + function postUpdateProfileFailure(xhr, textStatus, errorMessage) { + + var errors = JSON.parse(xhr.responseText) + + if(xhr.status == 422) { + var first_name = context.JK.format_errors("first_name", errors); + var last_name = context.JK.format_errors("last_name", errors); + var country = context.JK.format_errors("country", errors); + var state = context.JK.format_errors("state", errors); + var city = context.JK.format_errors("city", errors); + var birth_date = context.JK.format_errors("birth_date", errors); + var gender = context.JK.format_errors("birth_date", errors); + var subscribeEmail = context.JK.format_errors("subscribe_email", errors); + var biography = context.JK.format_errors("biography", errors); + + if(first_name != null) { + getFirstNameElement().closest('div.field').addClass('error').end().after(first_name); } - - function populateCountries(countries, userCountry) { - - // countries has the format ["US", ...] - - var foundCountry = false; - var countrySelect = getCountryElement(); - countrySelect.children().remove(); - - var nilOption = $(nilOptionStr); - nilOption.text(nilOptionText); - countrySelect.append(nilOption); - - $.each(countries, function(index, country) { - if(!country) return; - - var option = $(nilOptionStr); - option.text(country); - option.attr("value", country); - - if(country == userCountry) { - foundCountry = true; - } - - countrySelect.append(option); - }); - - if(!foundCountry) { - // in this case, the user has a country that is not in the database - // this can happen in a development/test scenario, but let's assume it can - // happen in production too. - var option = $(nilOptionStr); - option.text(userCountry); - option.attr("value", userCountry); - countrySelect.append(option); - } - - countrySelect.val(userCountry); - countrySelect.attr("disabled", null) - - context.JK.dropdown(countrySelect); + if(last_name != null) { + getLastNameElement().closest('div.field').addClass('error').end().after(last_name); } - - function populateCountriesx(countriesx, userCountry) { - - // countriesx has the format [{countrycode: "US", countryname: "United States"}, ...] - - var foundCountry = false; - var countrySelect = getCountryElement(); - countrySelect.children().remove(); - - var nilOption = $(nilOptionStr); - nilOption.text(nilOptionText); - countrySelect.append(nilOption); - - $.each(countriesx, function(index, countryx) { - if(!countryx.countrycode) return; - - var option = $(nilOptionStr); - option.text(countryx.countryname); - option.attr("value", countryx.countrycode); - - if(countryx.countrycode == userCountry) { - foundCountry = true; - } - - countrySelect.append(option); - }); - - if(!foundCountry) { - // in this case, the user has a country that is not in the database - // this can happen in a development/test scenario, but let's assume it can - // happen in production too. - var option = $(nilOptionStr); - option.text(userCountry); - option.attr("value", userCountry); - countrySelect.append(option); - } - - countrySelect.val(userCountry); - countrySelect.attr("disabled", null) - - context.JK.dropdown(countrySelect); + if(country != null) { + getCountryElement().closest('div.field').addClass('error').end().after(country); } - - function populateRegions(regions, userRegion) { - var regionSelect = getRegionElement() - regionSelect.children().remove() - - var nilOption = $(nilOptionStr); - nilOption.text(nilOptionText); - regionSelect.append(nilOption); - - $.each(regions, function(index, region) { - if(!region) return; - - var option = $(nilOptionStr) - option.text(region['name']) - option.attr("value", region['region']) - - regionSelect.append(option) - }) - - regionSelect.val(userRegion) - regionSelect.attr("disabled", null) - - context.JK.dropdown(regionSelect); + if(state != null) { + getRegionElement().closest('div.field').addClass('error').end().after(state); } - function populateCities(cities, userCity) { - var citySelect = getCityElement(); - citySelect.children().remove(); - - var nilOption = $(nilOptionStr); - nilOption.text(nilOptionText); - citySelect.append(nilOption); - - $.each(cities, function(index, city) { - if(!city) return; - - var option = $(nilOptionStr) - option.text(city) - option.attr("value", city) - - citySelect.append(option) - }) - - citySelect.val(userCity) - citySelect.attr("disabled", null) - - context.JK.dropdown(citySelect); + if(city != null) { + getCityElement().closest('div.field').addClass('error').end().after(city); } - /****************** MAIN PORTION OF SCREEN *****************/ - // events for main screen - function events() { - $('#account-profile-content-scroller').on('click', '#account-edit-profile-cancel', function(evt) { evt.stopPropagation(); navToAccount(); return false; } ); - $('#account-profile-content-scroller').on('click', '#account-edit-profile-submit', function(evt) { evt.stopPropagation(); handleUpdateProfile(); return false; } ); - $('#account-profile-content-scroller').on('submit', '#account-edit-email-form', function(evt) { evt.stopPropagation(); handleUpdateProfile(); return false; } ); - $('#account-profile-content-scroller').on('click', '#account-change-avatar', function(evt) { evt.stopPropagation(); navToAvatar(); return false; } ); - } - function renderAccountProfile() { - - $.when( api.getUserDetail(), - api.getInstruments()) - .done(function(userDetailResponse, instrumentsResponse) { - var userDetail = userDetailResponse[0]; - recentUserDetail = userDetail // store userDetail for later - // show page; which can be done quickly at this point - populateAccountProfile(userDetail, - instrumentsResponse[0]); - - selectLocation = new context.JK.SelectLocation(getCountryElement(), getRegionElement(), getCityElement(), app); - selectLocation.load(userDetail.country, userDetail.state, userDetail.city) - }) - context.JK.dropdown($('select')); + if(birth_date != null) { + getYearElement().closest('div.field').addClass('error').end().after(birth_date); } - function navToAccount() { - resetForm(); - window.location = '/client#/account'; + if(subscribeEmail != null) { + getSubscribeEmail().closest('div.field').addClass('error').end().after(subscribeEmail); } - function navToAvatar() { - resetForm(); - window.location = '/client#/account/profile/avatar'; + if(gender != null) { + getGenderElement().closest('div.field').addClass('error').end().after(gender); } + } + else { + app.ajaxError(xhr, textStatus, errorMessage) + } + } - function handleUpdateProfile() { + function handleCountryChanged() { + var selectedCountry = getCountryElement().val() + var selectedRegion = getRegionElement().val() + var cityElement = getCityElement(); - resetForm(); + updateRegionList(selectedCountry, getRegionElement()); + updateCityList(selectedCountry, null, cityElement); + } - var country = getCountryElement().val(); - var region = getRegionElement().val(); - var city = getCityElement().val(); - var firstName = getFirstNameElement().val(); - var lastName = getLastNameElement().val(); - var gender = getGenderElement().val(); - var subscribeEmail = getSubscribeEmail().is(':checked'); - var birthDate = getBirthDate(); - var instruments = getInstrumentsValue(); - var genres = getSelectedGenres(); + function updateRegionList(selectedCountry, regionElement) { + // only update region + if (selectedCountry) { + // set city disabled while updating + regionElement.attr('disabled', true); + loadingRegionsData = true; - api.updateUser({ - country: country, - state: region, - city: city, - first_name: firstName, - last_name: lastName, - gender: gender, - birth_date: birthDate, - instruments: instruments, - genres: genres, - subscribe_email: subscribeEmail - }) - .done(postUpdateProfileSuccess) - .fail(postUpdateProfileFailure) - } + regionElement.children().remove() + regionElement.append($(nilOptionStr).text('loading...')) - function postUpdateProfileSuccess(response) { - app.notify( - { title: "Profile Changed", - text: "You have updated your profile successfully." - }, - null, - true); - - $document.triggerHandler(EVENTS.USER_UPDATED, response); + api.getRegions({ country: selectedCountry }) + .done(getRegionsDone) + .error(function(err) { + regionElement.children().remove() + regionElement.append($(nilOptionStr).text(nilOptionText)) + }) + .always(function () { + loadingRegionsData = false; + }); + } + else { + regionElement.children().remove() + regionElement.append($(nilOptionStr).text(nilOptionText)) + } + } - } + function updateCityList(selectedCountry, selectedRegion, cityElement) { + logger.debug("updating city list: selectedCountry %o, selectedRegion %o", selectedCountry, selectedRegion); - function postUpdateProfileFailure(xhr, textStatus, errorMessage) { + // only update cities + if (selectedCountry && selectedRegion) { + // set city disabled while updating + cityElement.attr('disabled', true); + loadingCitiesData = true; - var errors = JSON.parse(xhr.responseText) + cityElement.children().remove(); + cityElement.append($(nilOptionStr).text('loading...')); - if(xhr.status == 422) { + api.getCities({ country: selectedCountry, region: selectedRegion }) + .done(getCitiesDone) + .error(function(err) { + cityElement.children().remove(); + cityElement.append($(nilOptionStr).text(nilOptionText)); + }) + .always(function () { + loadingCitiesData = false; + }); + } + else { + cityElement.children().remove(); + cityElement.append($(nilOptionStr).text(nilOptionText)); + context.JK.dropdown(cityElement); + } + } - var first_name = context.JK.format_errors("first_name", errors); - var last_name = context.JK.format_errors("last_name", errors); - var country = context.JK.format_errors("country", errors); - var state = context.JK.format_errors("state", errors); - var city = context.JK.format_errors("city", errors); - var birth_date = context.JK.format_errors("birth_date", errors); - var gender = context.JK.format_errors("birth_date", errors); - var subscribeEmail = context.JK.format_errors("subscribe_email", errors); - var instruments = context.JK.format_errors("musician_instruments", errors) + function handleRegionChanged() { + var selectedCountry = getCountryElement().val() + var selectedRegion = getRegionElement().val() + var cityElement = getCityElement(); - if(first_name != null) { - getFirstNameElement().closest('div.field').addClass('error').end().after(first_name); - } + updateCityList(selectedCountry, selectedRegion, cityElement); + } - if(last_name != null) { - getLastNameElement().closest('div.field').addClass('error').end().after(last_name); - } + function getCitiesDone(data) { + populateCities(data['cities'], recentUserDetail.city); + } - if(country != null) { - getCountryElement().closest('div.field').addClass('error').end().after(country); - } + function getRegionsDone(data) { + populateRegions(data['regions'], recentUserDetail.state); + updateCityList(getCountryElement().val(), getRegionElement().val(), getCityElement()); + } - if(state != null) { - getRegionElement().closest('div.field').addClass('error').end().after(state); - } + function getCountryElement() { + return $('#account-profile-content-scroller select[name=country]'); + } - if(city != null) { - getCityElement().closest('div.field').addClass('error').end().after(city); - } + function getRegionElement() { + return $('#account-profile-content-scroller select[name=region]'); + } - if(birth_date != null) { - getYearElement().closest('div.field').addClass('error').end().after(birth_date); - } + function getCityElement() { + return $('#account-profile-content-scroller select[name=city]'); + } - if(subscribeEmail != null) { - getSubscribeEmail().closest('div.field').addClass('error').end().after(subscribeEmail); - } + function getFirstNameElement() { + return $('#account-profile-content-scroller input[name=first_name]'); + } - if(gender != null) { - getGenderElement().closest('div.field').addClass('error').end().after(gender); - } + function getLastNameElement() { + return $('#account-profile-content-scroller input[name=last_name]'); + } - if(instruments != null) { - getInstrumentsElement().closest('div.field').addClass('error').append(instruments); - } - } - else { - app.ajaxError(xhr, textStatus, errorMessage) - } - } + function getGenderElement() { + return $('#account-profile-content-scroller select[name=gender]'); + } - function handleCountryChanged() { - var selectedCountry = getCountryElement().val() - var selectedRegion = getRegionElement().val() - var cityElement = getCityElement(); + function getMonthElement() { + return $('#account-profile-content-scroller select#user_birth_date_2i'); + } - updateRegionList(selectedCountry, getRegionElement()); - updateCityList(selectedCountry, null, cityElement); - } + function getDayElement() { + return $('#account-profile-content-scroller select#user_birth_date_3i'); + } - function updateRegionList(selectedCountry, regionElement) { - // only update region - if (selectedCountry) { - // set city disabled while updating - regionElement.attr('disabled', true); - loadingRegionsData = true; + function getYearElement() { + return $('#account-profile-content-scroller select#user_birth_date_1i'); + } - regionElement.children().remove() - regionElement.append($(nilOptionStr).text('loading...')) + function getSubscribeEmail() { + return $('#account-profile-content-scroller input[name=subscribe_email]'); + } - api.getRegions({ country: selectedCountry }) - .done(getRegionsDone) - .error(function(err) { - regionElement.children().remove() - regionElement.append($(nilOptionStr).text(nilOptionText)) - }) - .always(function () { - loadingRegionsData = false; - }) - } - else { - regionElement.children().remove() - regionElement.append($(nilOptionStr).text(nilOptionText)) - } - } + function getBirthDate() { + var month = getMonthElement().val() + var day = getDayElement().val() + var year = getYearElement().val() - function updateCityList(selectedCountry, selectedRegion, cityElement) { - logger.debug("updating city list: selectedCountry %o, selectedRegion %o", selectedCountry, selectedRegion); + if(month != null && month.length > 0 && day != null && day.length > 0 && year != null && year.length > 0) { + return month + "-" + day + "-" + year; + } + else { + return null; + } + } - // only update cities - if (selectedCountry && selectedRegion) { - // set city disabled while updating - cityElement.attr('disabled', true); - loadingCitiesData = true; + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('account/profile', screenBindings); + events(); + } - cityElement.children().remove() - cityElement.append($(nilOptionStr).text('loading...')) - - api.getCities({ country: selectedCountry, region: selectedRegion }) - .done(getCitiesDone) - .error(function(err) { - cityElement.children().remove() - cityElement.append($(nilOptionStr).text(nilOptionText)) - }) - .always(function () { - loadingCitiesData = false; - }) - } - else { - cityElement.children().remove(); - cityElement.append($(nilOptionStr).text(nilOptionText)); - context.JK.dropdown(cityElement); - } - } - - function handleRegionChanged() { - var selectedCountry = getCountryElement().val() - var selectedRegion = getRegionElement().val() - var cityElement = getCityElement(); - - updateCityList(selectedCountry, selectedRegion, cityElement); - } - - function getCitiesDone(data) { - populateCities(data['cities'], recentUserDetail.city); - } - - function getRegionsDone(data) { - populateRegions(data['regions'], recentUserDetail.state); - updateCityList(getCountryElement().val(), getRegionElement().val(), getCityElement()); - } - - function getCountryElement() { - return $('#account-profile-content-scroller select[name=country]'); - } - - function getRegionElement() { - return $('#account-profile-content-scroller select[name=region]'); - } - - function getCityElement() { - return $('#account-profile-content-scroller select[name=city]'); - } - - function getFirstNameElement() { - return $('#account-profile-content-scroller input[name=first_name]'); - } - - function getLastNameElement() { - return $('#account-profile-content-scroller input[name=last_name]'); - } - - function getGenderElement() { - return $('#account-profile-content-scroller select[name=gender]'); - } - - function getMonthElement() { - return $('#account-profile-content-scroller select#user_birth_date_2i'); - } - - function getDayElement() { - return $('#account-profile-content-scroller select#user_birth_date_3i'); - } - - function getYearElement() { - return $('#account-profile-content-scroller select#user_birth_date_1i'); - } - - function getSubscribeEmail() { - return $('#account-profile-content-scroller input[name=subscribe_email]'); - } - - function getInstrumentsElement() { - return $('#account-profile-content-scroller .instrument_selector'); - } - - - function getBirthDate() { - var month = getMonthElement().val() - var day = getDayElement().val() - var year = getYearElement().val() - - if(month != null && month.length > 0 && day != null && day.length > 0 && year != null && year.length > 0) { - return month + "-" + day + "-" + year; - } - else { - return null; - } - } - - // looks in instrument_selector parent element, and gathers up all - // selected elements, and the proficiency level declared - function getInstrumentsValue() { - var instrumentsParentElement = getInstrumentsElement(); - - var instruments = [] - $('input[type=checkbox]:checked', instrumentsParentElement).each(function(i) { - - var instrumentElement = $(this).closest('tr'); - // traverse up to common parent of this instrument, and pick out proficiency selector - var proficiency = $('select.proficiency_selector', instrumentElement).val() - - instruments.push({ - instrument_id: instrumentElement.attr('data-instrument-id'), - proficiency_level: proficiency, - priority : i - }) - }); - - return instruments; - } - - function initialize() { - var screenBindings = { - 'beforeShow': beforeShow, - 'afterShow': afterShow - }; - app.bindScreen('account/profile', screenBindings); - events(); - } - - this.initialize = initialize; - this.beforeShow = beforeShow; - this.afterShow = afterShow; - return this; - }; + this.initialize = initialize; + this.beforeShow = beforeShow; + this.afterShow = afterShow; + return this; + }; })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/accounts_profile_avatar.js b/web/app/assets/javascripts/accounts_profile_avatar.js index c89039d97..63d1aca1f 100644 --- a/web/app/assets/javascripts/accounts_profile_avatar.js +++ b/web/app/assets/javascripts/accounts_profile_avatar.js @@ -59,11 +59,21 @@ // wire up main panel clicks $('#account-profile-avatar-content-scroller').on('click', '#account-edit-avatar-upload', function(evt) { evt.stopPropagation(); handleFilePick(); return false; } ); $('#account-profile-avatar-content-scroller').on('click', '#account-edit-avatar-delete', function(evt) { evt.stopPropagation(); handleDeleteAvatar(); return false; } ); - $('#account-profile-avatar-content-scroller').on('click', '#account-edit-avatar-cancel', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } ); - $('#account-profile-avatar-content-scroller').on('click', '#account-edit-avatar-submit', function(evt) { evt.stopPropagation(); handleUpdateAvatar(); return false; } ); + $('#account-profile-avatar-content-scroller').on('click', '#account-edit-avatar-cancel', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } ); + enableSubmits() //$('#account-profile-avatar-content-scroller').on('change', 'input[type=filepicker-dragdrop]', function(evt) { evt.stopPropagation(); afterImageUpload(evt.originalEvent.fpfile); return false; } ); } + function enableSubmits() { + $('#account-profile-avatar-content-scroller').on('click', '#account-edit-avatar-submit', function(evt) { evt.stopPropagation(); handleUpdateAvatar(); return false; } ); + $("#account-edit-avatar-submit").removeClass("disabled") + } + + function disableSubmits() { + $("#account-edit-avatar-submit").addClass("disabled") + $("#account-edit-avatar-submit").off("click") + } + function handleDeleteAvatar() { if(self.updatingAvatar) { @@ -277,7 +287,7 @@ } function handleUpdateAvatar(event) { - + disableSubmits() if(self.updatingAvatar) { // protect against concurrent update attempts return; @@ -373,10 +383,11 @@ }, null, true); - } + } + enableSubmits() } - function updateAvatarSuccess(response) { + function updateAvatarSuccess(response) { $.cookie('original_fpfile', null); self.userDetail = response; diff --git a/web/app/assets/javascripts/accounts_profile_experience.js b/web/app/assets/javascripts/accounts_profile_experience.js new file mode 100644 index 000000000..de57e8695 --- /dev/null +++ b/web/app/assets/javascripts/accounts_profile_experience.js @@ -0,0 +1,236 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.AccountProfileExperience = function(app) { + var $document = $(document); + var logger = context.JK.logger; + var EVENTS = context.JK.EVENTS; + var api = context.JK.Rest(); + var $screen = $('#account-profile-experience'); + var profileUtils = context.JK.ProfileUtils; + var $btnCancel = $screen.find('.account-edit-profile-cancel'); + var $btnBack = $screen.find('.account-edit-profile-back'); + var $btnSubmit = $screen.find('.account-edit-profile-submit'); + + var $instrumentSelector = $screen.find('.instrument_selector'); + var $userGenres = $screen.find('#user-genres'); + + function beforeShow(data) { + } + + function afterShow(data) { + resetForm(); + renderExperience(); + } + + function resetForm() { + $screen.find('form .error-text').remove(); + $screen.find('form .error').removeClass("error"); + } + + function populateAccountProfile(userDetail, instruments) { + + loadGenres(profileUtils.profileGenres(userDetail.genres)); + + $instrumentSelector.empty(); + $.each(instruments, function(index, instrument) { + var template = context.JK.fillTemplate($('#account-profile-instrument').html(), { + checked : isUserInstrument(instrument, userDetail.instruments) ? "checked=\"checked\"" :"", + description : instrument.description, + id : instrument.id + }); + $instrumentSelector.append(template); + }); + + // and fill in the proficiency for the instruments that the user can play + if(userDetail.instruments) { + $.each(userDetail.instruments, function(index, userInstrument) { + $('tr[data-instrument-id="' + userInstrument.instrument_id + '"] select.proficiency_selector', $screen).val(userInstrument.proficiency_level); + }); + } + + $screen.find('select[name=skill_level]').val(userDetail.skill_level); + $screen.find('select[name=concert_count]').val(userDetail.concert_count); + $screen.find('select[name=studio_session_count]').val(userDetail.studio_session_count); + + context.JK.dropdown($('select', $screen)); + } + + function isUserInstrument(instrument, userInstruments) { + var isUserInstrument = false; + if(!userInstruments) return false; + + $.each(userInstruments, function(index, userInstrument) { + if(instrument.id == userInstrument.instrument_id) { + isUserInstrument = true; + return false; + } + }); + return isUserInstrument; + } + + function loadGenres(selectedGenres) { + $userGenres.empty(); + + rest.getGenres().done(function (genres) { + $.each(genres, function (index, genre) { + var genreTemplate = $('#template-user-setup-genres').html(); + var selected = ''; + if (selectedGenres) { + var genreMatch = $.grep(selectedGenres, function (n, i) { + return n.genre_id === genre.id; + }); + if (genreMatch.length > 0) { + selected = "checked"; + } + } + var genreHtml = context.JK.fillTemplate(genreTemplate, { + id: genre.id, + description: genre.description, + checked: selected + }); + $userGenres.append(genreHtml); + }); + }); + } + + function resetGenres() { + $('input[type=checkbox]:checked', $userGenres).each(function (i) { + $(this).removeAttr("checked"); + }); + var $tdGenres = $("#tdBandGenres"); + } + + function getSelectedGenres() { + var genres = []; + $('input[type=checkbox]:checked', $userGenres).each(function (i) { + var genre = $(this).val(); + genres.push(genre); + }); + return genres; + } + + function events() { + $btnCancel.click(function(evt) { + evt.stopPropagation(); + navigateTo('/client#/profile/' + context.JK.currentUserId); + return false; + }); + + $btnBack.click(function(evt) { + evt.stopPropagation(); + navigateTo('/client#/account/profile/'); + return false; + }); + + enableSubmits() + } + + function enableSubmits() { + $btnSubmit.on("click", function(evt) { + evt.stopPropagation(); + handleUpdateProfile(); + return false; + }); + + $btnSubmit.removeClass("disabled") + } + + function disableSubmits() { + $btnSubmit.addClass("disabled") + $btnSubmit.off("click") + } + + function renderExperience() { + $.when(api.getUserProfile(), api.getInstruments()) + .done(function(userDetailResponse, instrumentsResponse) { + var userDetail = userDetailResponse[0]; + populateAccountProfile(userDetail, instrumentsResponse[0]); + }); + + context.JK.dropdown($('select')); + } + + function navigateTo(targetLocation) { + resetForm(); + context.location = targetLocation; + } + + function handleUpdateProfile() { + disableSubmits() + resetForm(); + + var instruments = getSelectedInstruments(); + var genres = getSelectedGenres(); + + api.updateUser({ + instruments: instruments, + genres: genres, + skill_level: $screen.find('select[name=skill_level]').val(), + concert_count: $screen.find('select[name=concert_count]').val(), + studio_session_count: $screen.find('select[name=studio_session_count]').val() + }) + .done(postUpdateProfileSuccess) + .fail(postUpdateProfileFailure) + .always(enableSubmits) + } + + function postUpdateProfileSuccess(response) { + $document.triggerHandler(EVENTS.USER_UPDATED, response); + context.location = "/client#/account/profile/interests"; + } + + function postUpdateProfileFailure(xhr, textStatus, errorMessage) { + + var errors = JSON.parse(xhr.responseText) + + if(xhr.status == 422) { + var instruments = context.JK.format_errors("musician_instruments", errors); + + if(instruments != null) { + $instrumentSelector.closest('div.field').addClass('error').append(instruments); + } + } + else { + app.ajaxError(xhr, textStatus, errorMessage) + } + } + + function getSelectedInstruments() { + var instruments = []; + $('input[type=checkbox]:checked', $instrumentSelector).each(function(i) { + + var instrumentElement = $(this).closest('tr'); + // traverse up to common parent of this instrument, and pick out proficiency selector + var proficiency = $('select.proficiency_selector', instrumentElement).val(); + + instruments.push({ + instrument_id: instrumentElement.attr('data-instrument-id'), + proficiency_level: proficiency, + priority : i + }); + }); + + return instruments; + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + + app.bindScreen('account/profile/experience', screenBindings); + + events(); + } + + this.initialize = initialize; + this.beforeShow = beforeShow; + this.afterShow = afterShow; + return this; + }; + +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/accounts_profile_interests.js b/web/app/assets/javascripts/accounts_profile_interests.js new file mode 100644 index 000000000..6a4a65e2c --- /dev/null +++ b/web/app/assets/javascripts/accounts_profile_interests.js @@ -0,0 +1,298 @@ +(function(context,$) { + + "use strict" + + context.JK = context.JK || {} + context.JK.AccountProfileInterests = function(app) { + var $document = $(document) + var logger = context.JK.logger + var EVENTS = context.JK.EVENTS + var api = context.JK.Rest() + var ui = new context.JK.UIHelper(JK.app) + var user = {} + var profileUtils = context.JK.ProfileUtils + var masterGenreList = [] + + var NONE_SPECIFIED = 'None specified' + var GENRE_LIST_DELIMITER = ', ' + + var $screen = $('#account-profile-interests') + + var SELECT_GENRE_SELECTOR = '.select-genre' + var GENRE_LIST_SELECTOR = '.genre-list' + + // virtual bands + var $virtualBandYes = $screen.find('#virtual-band-yes') + var $virtualBandNo = $screen.find('#virtual-band-no') + var $virtualBandGenres = $screen.find('#virtual-band-genres') + var $btnVirtualBandGenreSelect = $virtualBandGenres.find(SELECT_GENRE_SELECTOR) + var $virtualBandGenreList = $virtualBandGenres.find(GENRE_LIST_SELECTOR) + var $virtualBandCommitment = $screen.find('#virtual-band-commitment') + + // traditional bands + var $traditionalBandYes = $screen.find('#traditional-band-yes') + var $traditionalBandNo = $screen.find('#traditional-band-no') + var $traditionalBandGenres = $screen.find('#traditional-band-genres') + var $btnTraditionalBandGenreSelect = $traditionalBandGenres.find(SELECT_GENRE_SELECTOR) + var $traditionalBandGenreList = $traditionalBandGenres.find(GENRE_LIST_SELECTOR) + var $traditionalBandCommitment = $screen.find('#traditional-band-commitment') + var $traditionalTouringOption = $screen.find('#traditional-band-touring') + + // paid sessions + var $paidSessionsYes = $screen.find('#paid-sessions-yes') + var $paidSessionsNo = $screen.find('#paid-sessions-no') + var $paidSessionsGenres = $screen.find('#paid-sessions-genres') + var $btnPaidSessionsGenreSelect = $paidSessionsGenres.find(SELECT_GENRE_SELECTOR) + var $paidSessionsGenreList = $paidSessionsGenres.find(GENRE_LIST_SELECTOR) + var $hourlyRate = $screen.find('#hourly-rate') + var $dailyRate = $screen.find('#daily-rate') + + // free sessions + var $freeSessionsYes = $screen.find('#free-sessions-yes') + var $freeSessionsNo = $screen.find('#free-sessions-no') + var $freeSessionsGenres = $screen.find('#free-sessions-genres') + var $btnFreeSessionsGenreSelect = $freeSessionsGenres.find(SELECT_GENRE_SELECTOR) + var $freeSessionsGenreList = $freeSessionsGenres.find(GENRE_LIST_SELECTOR) + + // cowriting + var $cowritingYes = $screen.find('#cowriting-yes') + var $cowritingNo = $screen.find('#cowriting-no') + var $cowritingGenres = $screen.find('#cowriting-genres') + var $btnCowritingGenreSelect = $cowritingGenres.find(SELECT_GENRE_SELECTOR) + var $cowritingGenreList = $cowritingGenres.find(GENRE_LIST_SELECTOR) + var $cowritingPurpose = $screen.find('#cowriting-purpose') + + var $btnCancel = $screen.find('.account-edit-profile-cancel') + var $btnBack = $screen.find('.account-edit-profile-back') + var $btnSubmit = $screen.find('.account-edit-profile-submit') + + function beforeShow(data) { + } + + function afterShow(data) { + renderInterests() + } + + function resetForm() { + $screen.find('form .error-text').remove() + $screen.find('form .error').removeClass("error") + } + + function populateAccountProfile(userDetail) { + + // Column 1 - options + if (userDetail) { + + if (userDetail.virtual_band) { + $virtualBandYes.iCheck('check').attr('checked', 'checked') + } + else { + $virtualBandNo.iCheck('check').attr('checked', 'checked') + } + + if (userDetail.traditional_band) { + $traditionalBandYes.iCheck('check').attr('checked', 'checked') + } + else { + $traditionalBandNo.iCheck('check').attr('checked', 'checked') + } + + if (userDetail.paid_sessions) { + $paidSessionsYes.iCheck('check').attr('checked', 'checked') + } + else { + $paidSessionsNo.iCheck('check').attr('checked', 'checked') + } + + if (userDetail.free_sessions) { + $freeSessionsYes.iCheck('check').attr('checked', 'checked') + } + else { + $freeSessionsNo.iCheck('check').attr('checked', 'checked') + } + + if (userDetail.cowriting) { + $cowritingYes.iCheck('check').attr('checked', 'checked') + } + else { + $cowritingNo.iCheck('check').attr('checked', 'checked') + } + } + + // Column 2 - genres + var genres = profileUtils.virtualBandGenreList(userDetail.genres) + $virtualBandGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED) + + genres = profileUtils.traditionalBandGenreList(userDetail.genres) + $traditionalBandGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED) + + genres = profileUtils.paidSessionGenreList(userDetail.genres) + $paidSessionsGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED) + + genres = profileUtils.freeSessionGenreList(userDetail.genres) + $freeSessionsGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED) + + genres = profileUtils.cowritingGenreList(userDetail.genres) + $cowritingGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED) + + // Column 3 - misc (play commitment, rates, cowriting purpose) + $virtualBandCommitment.val(userDetail.virtual_band_commitment) + context.JK.dropdown($virtualBandCommitment) + + $traditionalBandCommitment.val(userDetail.traditional_band_commitment) + context.JK.dropdown($traditionalBandCommitment) + + $traditionalTouringOption.val(userDetail.traditional_band_touring ? '1' : '0') + context.JK.dropdown($traditionalTouringOption) + + $hourlyRate.val(userDetail.paid_sessions_hourly_rate) + $dailyRate.val(userDetail.paid_sessions_daily_rate) + + $cowritingPurpose.val(userDetail.cowriting_purpose) + context.JK.dropdown($cowritingPurpose) + } + + function bindGenreSelector(type, $btnSelect, $genreList) { + $btnSelect.unbind('click').bind('click', function(e) { + e.preventDefault() + var genreText = $genreList.html() + var genres = [] + if (genres !== NONE_SPECIFIED) { + genres = genreText.split(GENRE_LIST_DELIMITER) + } + + ui.launchGenreSelectorDialog(type, genres, function(selectedGenres) { + $genreList.html(selectedGenres && selectedGenres.length > 0 ? selectedGenres.join(GENRE_LIST_DELIMITER) : NONE_SPECIFIED) + }) + + return false + }) + } + + function events() { + + bindGenreSelector('virtual bands', $btnVirtualBandGenreSelect, $virtualBandGenreList) + bindGenreSelector('traditional bands', $btnTraditionalBandGenreSelect, $traditionalBandGenreList) + bindGenreSelector('paid sessions', $btnPaidSessionsGenreSelect, $paidSessionsGenreList) + bindGenreSelector('free sessions', $btnFreeSessionsGenreSelect, $freeSessionsGenreList) + bindGenreSelector('co-writing', $btnCowritingGenreSelect, $cowritingGenreList) + + $btnCancel.click(function(e) { + e.stopPropagation() + navigateTo('/client#/profile/' + context.JK.currentUserId) + return false + }) + + $btnBack.click(function(e) { + e.stopPropagation() + navigateTo('/client#/account/profile/experience') + return false + }) + + enableSubmits() + + context.JK.dropdown($virtualBandCommitment) + context.JK.dropdown($traditionalBandCommitment) + context.JK.dropdown($cowritingPurpose) + } + + function enableSubmits() { + $btnSubmit.on("click", function(e) { + e.stopPropagation() + handleUpdateProfile() + return false + }) + + $btnSubmit.removeClass("disabled") + } + + function disableSubmits() { + $btnSubmit.addClass("disabled") + $btnSubmit.off("click") + } + + function renderInterests() { + $.when(api.getUserProfile()) + .done(function(userDetail) { + populateAccountProfile(userDetail) + }) + } + + function navigateTo(targetLocation) { + context.location = targetLocation + } + + function handleUpdateProfile() { + disableSubmits() + resetForm() + + api.updateUser({ + virtual_band: $screen.find('input[name=virtual_band]:checked').val(), + virtual_band_genres: $virtualBandGenreList.html() === NONE_SPECIFIED ? [] : $virtualBandGenreList.html().split(GENRE_LIST_DELIMITER), + virtual_band_commitment: $virtualBandCommitment.val(), + + traditional_band: $screen.find('input[name=traditional_band]:checked').val(), + traditional_band_genres: $traditionalBandGenreList.html() === NONE_SPECIFIED ? [] : $traditionalBandGenreList.html().split(GENRE_LIST_DELIMITER), + traditional_band_commitment: $traditionalBandCommitment.val(), + traditional_band_touring: $traditionalTouringOption.val(), + + paid_sessions: $screen.find('input[name=paid_sessions]:checked').val(), + paid_session_genres: $paidSessionsGenreList.html() === NONE_SPECIFIED ? [] : $paidSessionsGenreList.html().split(GENRE_LIST_DELIMITER), + paid_sessions_hourly_rate: $hourlyRate.val(), + paid_sessions_daily_rate: $dailyRate.val(), + + free_sessions: $screen.find('input[name=free_sessions]:checked').val(), + free_session_genre: $freeSessionsGenreList.html() === NONE_SPECIFIED ? [] : $freeSessionsGenreList.html().split(GENRE_LIST_DELIMITER), + + cowriting: $screen.find('input[name=cowriting]:checked').val(), + cowriting_genres: $cowritingGenreList.html() === NONE_SPECIFIED ? [] : $cowritingGenreList.html().split(GENRE_LIST_DELIMITER), + cowriting_purpose: $cowritingPurpose.val() + }) + .done(postUpdateProfileSuccess) + .fail(postUpdateProfileFailure) + .always(enableSubmits) + } + + function postUpdateProfileSuccess(response) { + $document.triggerHandler(EVENTS.USER_UPDATED, response) + context.location = "/client#/account/profile/samples" + } + + function postUpdateProfileFailure(xhr, textStatus, errorMessage) { + + var errors = JSON.parse(xhr.responseText) + + if(xhr.status == 422) { + + } + else { + app.ajaxError(xhr, textStatus, errorMessage) + } + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + } + + app.bindScreen('account/profile/interests', screenBindings) + + events() + + $screen.find('.interest-options').iCheck({ + checkboxClass: 'icheckbox_minimal', + radioClass: 'iradio_minimal', + inheritClass: true + }) + + profileUtils.initializeHelpBubbles($screen) + } + + this.initialize = initialize + this.beforeShow = beforeShow + this.afterShow = afterShow + return this + } + +})(window,jQuery) \ No newline at end of file diff --git a/web/app/assets/javascripts/accounts_profile_samples.js b/web/app/assets/javascripts/accounts_profile_samples.js new file mode 100644 index 000000000..6cce9de0e --- /dev/null +++ b/web/app/assets/javascripts/accounts_profile_samples.js @@ -0,0 +1,487 @@ +(function(context,$) { + + "use strict"; + + context.JK = context.JK || {}; + + // TODO: Add a target type, which can be band or user -- call the + // appropriate API methods. + context.JK.AccountProfileSamples = function(app, parent, loadFn, updateFn) { + var $document = $(document) + + // used to initialize RecordingSourceValidator in site_validator.js.coffee + var jamkazamRecordingSources = []; + var soundCloudRecordingSources = []; + var youTubeRecordingSources = []; + + var logger = context.JK.logger; + var EVENTS = context.JK.EVENTS; + var api = context.JK.Rest(); + var ui = new context.JK.UIHelper(JK.app); + var target = {}; + var profileUtils = context.JK.ProfileUtils; + var $screen = $('.profile-online-sample-controls', parent); + // online presences + var $website = $screen.find('.website'); + var $soundCloudUsername = $screen.find('.soundcloud-username'); + var $reverbNationUsername = $screen.find('.reverbnation-username'); + var $bandCampUsername = $screen.find('.bandcamp-username'); + var $fandalismUsername = $screen.find('.fandalism-username'); + var $youTubeUsername = $screen.find('.youtube-username'); + var $facebookUsername = $screen.find('.facebook-username'); + var $twitterUsername = $screen.find('.twitter-username'); + + // performance samples + var $jamkazamSampleList = $screen.find(".sample-list[source-type='jamkazam']") + var $soundCloudSampleList = $screen.find(".sample-list[source-type='soundcloud']") + var $youTubeSampleList = $screen.find(".sample-list[source-type='youtube']") + + // buttons + var $btnAddJkRecording = $screen.find('.btn-add-jk-recording') + var $btnCancel = parent.find('.account-edit-profile-cancel') + var $btnBack = parent.find('.account-edit-profile-back') + var $btnSubmit = parent.find('.account-edit-profile-submit') + + + var urlValidator=null + var soundCloudValidator=null + var reverbNationValidator=null + var bandCampValidator=null + var fandalismValidator=null + var youTubeValidator=null + var facebookValidator=null + var twitterValidator=null + var soundCloudRecordingValidator=null + var youTubeRecordingValidator=null + + function beforeShow(data) { + } + + function afterShow(data) { + $.when(loadFn()) + .done(function(targetPlayer) { + if (targetPlayer && targetPlayer.keys && targetPlayer.keys.length > 0) { + renderPlayer(targetPlayer) + } + }) + } + + function renderPlayer(targetPlayer) { + renderPresence(targetPlayer); + renderSamples(targetPlayer); + } + + function renderPresence(targetPlayer) { + $website.val(targetPlayer.website); + + // SoundCloud + var presences = profileUtils.soundCloudPresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $soundCloudUsername.val(presences[0].username); + } + + // ReverbNation + presences = profileUtils.reverbNationPresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $reverbNationUsername.val(presences[0].username); + } + + // Bandcamp + presences = profileUtils.bandCampPresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $bandCampUsername.val(presences[0].username); + } + + // Fandalism + presences = profileUtils.fandalismPresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $fandalismUsername.val(presences[0].username); + } + + // YouTube + presences = profileUtils.youTubePresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $youTubeUsername.val(presences[0].username); + } + + // Facebook + presences = profileUtils.facebookPresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $facebookUsername.val(presences[0].username); + } + + // Twitter + presences = profileUtils.twitterPresences(targetPlayer.online_presences); + if (presences && presences.length > 0) { + $twitterUsername.val(presences[0].username); + } + } + + function renderSamples(targetPlayer) { + // JamKazam recordings + var samples = profileUtils.jamkazamSamples(targetPlayer.performance_samples); + loadSamples(samples, 'jamkazam', $jamkazamSampleList, jamkazamRecordingSources); + + // SoundCloud recordings + samples = profileUtils.soundCloudSamples(targetPlayer.performance_samples); + loadSamples(samples, 'soundcloud', $soundCloudSampleList, soundCloudRecordingSources); + + // YouTube videos + samples = profileUtils.youTubeSamples(targetPlayer.performance_samples); + loadSamples(samples, 'youtube', $youTubeSampleList, youTubeRecordingSources); + } + + function loadSamples(samples, type, $sampleList, recordingSources) { + $sampleList.find(":not(.empty)").remove(); + + if (type === 'jamkazam') { + $.each(samples, function(index, val) { + recordingSources.push({ + 'claimed_recording_id': val.claimed_recording.id, + 'description': val.claimed_recording.name + }); + + buildJamkazamEntry(val.claimed_recording.id, val.claimed_recording.name); + }); + } else { + if (samples && samples.length > 0) { + $.each(samples, function(index, val) { + + recordingSources.push({ + 'url': val.url, + 'recording_id': val.service_id, + 'recording_title': val.description + }); + + // TODO: this code is repeated in HTML file + var recordingIdAttr = ' data-recording-id="' + val.service_id + '" '; + var recordingUrlAttr = ' data-recording-url="' + val.url + '" '; + var recordingTitleAttr = ' data-recording-title="' + val.description + '"'; + var title = formatTitle(val.description); + $sampleList.append('
' + title + '
'); + $sampleList.append('
X
'); + }); + } + } + } + + function buildJamkazamEntry(recordingId, recordingName) { + var title = formatTitle(recordingName); + + var recordingIdAttr = ' data-recording-id="' + recordingId + '" '; + $jamkazamSampleList.append('
' + title + '
'); + $jamkazamSampleList.append('
X
'); + } + + function events() { + + // buttons + $btnAddJkRecording.click(function(evt) { + evt.preventDefault(); + + // retrieve recordings and pass to modal dialog + api.getClaimedRecordings() + .done(function(response) { + ui.launchRecordingSelectorDialog(response, jamkazamRecordingSources, function(selectedRecordings) { + $jamkazamSampleList.empty(); + + jamkazamRecordingSources = []; + + // update the list with the selected recordings + $.each(selectedRecordings, function(index, val) { + jamkazamRecordingSources.push({ + 'claimed_recording_id': val.id, + 'description': val.name + }); + + buildJamkazamEntry(val.id, val.name); + }); + }); + }); + + return false; + }); + + $btnCancel.click(function(evt) { + evt.stopPropagation(); + navigateTo('/client#/profile/' + context.JK.currentUserId); + return false; + }); + + $btnBack.click(function(evt) { + evt.stopPropagation(); + navigateTo('/client#/account/profile/interests'); + return false; + }); + + enableSubmits(); + + $screen.find(".sample-list").off("click").on("click", ".close-button", function(e) { + removeRow($(this).data("recording-id"), $(this).data("recording-type")) + }) + } + + function enableSubmits() { + $btnSubmit.off("click").on("click", function(e) { + e.stopPropagation(); + handleUpdateProfile(); + return false; + }) + + $btnSubmit.removeClass("disabled") + } + + function disableSubmits() { + $btnSubmit.addClass("disabled") + $btnSubmit.off("click") + } + + function validate() { + var errors = $screen.find('.site_validator.error'); + return !(errors && errors.length > 0); + } + + function navigateTo(targetLocation) { + context.location = targetLocation; + } + + function addOnlinePresence(presenceArray, username, type) { + if ($.trim(username).length > 0) { + presenceArray.push({ + 'player_id': context.JK.currentUserId, + 'service_type': type, + 'username': username + }); + } + } + + function addPerformanceSamples(sampleArray, $samplesSelector, type) { + var rows = $samplesSelector.find('.recording-row'); + + // loop over rows, extracting service id, description, and url + rows.each(function(index) { + var id = $(this).attr('data-recording-id'); + + if (type === 'jamkazam') { + sampleArray.push({ + 'player_id': context.JK.currentUserId, + 'service_type': type, + 'claimed_recording_id': id, + }); + } else { + var url = $(this).attr('data-recording-url'); + var title = $(this).attr('data-recording-title'); + + sampleArray.push({ + 'player_id': context.JK.currentUserId, + 'service_type': type, + 'service_id': id, + 'url': url, + 'description': title + }); + } + }); + } + + function handleUpdateProfile() { + disableSubmits() + + var player = buildPlayer() + updateFn({ + website: player.website, + online_presences: player.online_presences, + performance_samples: player.performance_samples + }) + .done(postUpdateProfileSuccess) + .fail(postUpdateProfileFailure) + .always(enableSubmits); + } + + function buildPlayer() { + // extract online presences + var op = []; + var presenceTypes = profileUtils.ONLINE_PRESENCE_TYPES; + addOnlinePresence(op, $soundCloudUsername.val(), presenceTypes.SOUNDCLOUD.description); + addOnlinePresence(op, $reverbNationUsername.val(), presenceTypes.REVERBNATION.description); + addOnlinePresence(op, $bandCampUsername.val(), presenceTypes.BANDCAMP.description); + addOnlinePresence(op, $fandalismUsername.val(), presenceTypes.FANDALISM.description); + addOnlinePresence(op, $youTubeUsername.val(), presenceTypes.YOUTUBE.description); + addOnlinePresence(op, $facebookUsername.val(), presenceTypes.FACEBOOK.description); + addOnlinePresence(op, $twitterUsername.val(), presenceTypes.TWITTER.description); + + // extract performance samples + var ps = []; + var performanceSampleTypes = profileUtils.SAMPLE_TYPES; + addPerformanceSamples(ps, $jamkazamSampleList, performanceSampleTypes.JAMKAZAM.description); + addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD.description); + addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE.description); + + return { + website: $website.val(), + online_presences: op, + performance_samples: ps + } + } + + function postUpdateProfileSuccess(response) { + $document.triggerHandler(EVENTS.USER_UPDATED, response); + context.location = "/client#/profile/" + context.JK.currentUserId; + } + + function postUpdateProfileFailure(xhr, textStatus, errorMessage) { + + var errors = JSON.parse(xhr.responseText) + + if(xhr.status == 422) { + + } else { + app.ajaxError(xhr, textStatus, errorMessage) + } + } + + function removeRow(recordingId, type) { + $('div[data-recording-id=' + recordingId + ']').remove(); + var sampleList = $('.sample-list[source-type="' + type + '"]') + var rowCnt = sampleList.find('.recording-row').length + if (0==parseInt(rowCnt)) { + sampleList.find(".empty").removeClass("hidden") + } + + if (type === 'soundcloud') { + soundCloudRecordingValidator.removeRecordingId(recordingId); + } else if (type === 'youtube') { + youTubeRecordingValidator.removeRecordingId(recordingId); + } + } + + function formatTitle(title) { + return title && title.length > 30 ? title.substring(0, 30) + "..." : title; + } + + // This function is a bit of a mess. It was pulled + // from the html.erb file verbatim, and could use a + // refactor: + function initializeValidators() { + var initialized = false; + //$document.on('JAMKAZAM_READY', function(e, data) { + JK.JamServer.get$Server().on(JK.EVENTS.CONNECTION_UP, function() { + if(initialized) { + return; + } + initialized = true; + + //var $screen = $('#account-profile-samples'); + var $btnAddSoundCloudRecording = $screen.find('.btn-add-soundcloud-recording'); + var $btnAddYouTubeVideo = $screen.find('.btn-add-youtube-video'); + // var $soundCloudSampleList = $screen.find('.samples.soundcloud'); + // var $youTubeSampleList = $screen.find('.samples.youtube'); + + + setTimeout(function() { + urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback, parent) + urlValidator.init() + + soundCloudValidator = new JK.SiteValidator('soundcloud', userNameSuccessCallback, userNameFailCallback, parent) + soundCloudValidator.init() + + reverbNationValidator = new JK.SiteValidator('reverbnation', userNameSuccessCallback, userNameFailCallback, parent) + reverbNationValidator.init() + + bandCampValidator = new JK.SiteValidator('bandcamp', userNameSuccessCallback, userNameFailCallback, parent) + bandCampValidator.init() + + fandalismValidator = new JK.SiteValidator('fandalism', userNameSuccessCallback, userNameFailCallback, parent) + fandalismValidator.init() + + youTubeValidator = new JK.SiteValidator('youtube', userNameSuccessCallback, userNameFailCallback, parent) + youTubeValidator.init() + + facebookValidator = new JK.SiteValidator('facebook', userNameSuccessCallback, userNameFailCallback, parent) + facebookValidator.init() + + twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback, parent) + twitterValidator.init() + + soundCloudRecordingValidator = new JK.RecordingSourceValidator('rec_soundcloud', soundCloudSuccessCallback, siteFailCallback, parent) + youTubeRecordingValidator = new JK.RecordingSourceValidator('rec_youtube', youTubeSuccessCallback, siteFailCallback, parent) + + soundCloudRecordingValidator.init(soundCloudRecordingSources) + youTubeRecordingValidator.init(youTubeRecordingSources) + }, 1) + + function userNameSuccessCallback($inputDiv) { + $inputDiv.removeClass('error'); + $inputDiv.find('.error-text').remove(); + } + + function userNameFailCallback($inputDiv) { + $inputDiv.addClass('error'); + $inputDiv.find('.error-text').remove(); + $inputDiv.append("Invalid username").show(); + } + + function soundCloudSuccessCallback($inputDiv) { + siteSuccessCallback($inputDiv, soundCloudRecordingValidator, $soundCloudSampleList, 'soundcloud'); + } + + function youTubeSuccessCallback($inputDiv) { + siteSuccessCallback($inputDiv, youTubeRecordingValidator, $youTubeSampleList, 'youtube'); + } + + function siteSuccessCallback($inputDiv, recordingSiteValidator, sampleList, type) { + sampleList.find(".empty").addClass("hidden") + $inputDiv.removeClass('error'); + $inputDiv.find('.error-text').remove(); + + var recordingSources = recordingSiteValidator.recordingSources(); + if (recordingSources && recordingSources.length > 0) { + var addedRecording = recordingSources[recordingSources.length-1]; + + // TODO: this code is repeated in elsewhere in this JS file: + var recordingIdAttr = ' data-recording-id="' + addedRecording.recording_id + '" '; + var recordingUrlAttr = ' data-recording-url="' + addedRecording.url + '" '; + var recordingTitleAttr = ' data-recording-title="' + addedRecording.recording_title + '"'; + var title = formatTitle(addedRecording.recording_title); + sampleList.append('
' + title + '
'); + sampleList.append('
X
'); + } + + $inputDiv.find('input').val(''); + } + + function siteFailCallback($inputDiv) { + $inputDiv.addClass('error'); + $inputDiv.find('.error-text').remove(); + $inputDiv.append("Invalid URL").show(); + } + }); + //}); + + + + } // end initializeValidators. + + function resetForm() { + $("input", $screen).val("") + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + + app.bindScreen('account/profile/samples', screenBindings); + initializeValidators(); + events(); + } + + this.initialize = initialize; + this.beforeShow = beforeShow; + this.afterShow = afterShow; + this.buildPlayer = buildPlayer; + this.renderPlayer = renderPlayer + this.resetForm = resetForm; + return this; + }; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/bandProfile.js b/web/app/assets/javascripts/bandProfile.js index c140d2ea2..b7501373b 100644 --- a/web/app/assets/javascripts/bandProfile.js +++ b/web/app/assets/javascripts/bandProfile.js @@ -4,526 +4,623 @@ 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 isAdmin = false; - var band = {}; - var instrument_logo_map = context.JK.getInstrumentIconMap24(); + var NOT_SPECIFIED_TEXT = 'Not specified'; + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var bandId; + var isMember = false; + var isAdmin = false; + var band = {}; + var instrument_logo_map = context.JK.getInstrumentIconMap24(); + var profileUtils = context.JK.ProfileUtils; + var feed = new context.JK.Feed(app) + var $root = $("#band-profile") + var $history = $root.find("#band-profile-history") - function beforeShow(data) { - bandId = data.id; + + + + function beforeShow(data) { + bandId = data.id; + feed.setBand(bandId); + } + + function beforeHide(data) { + feed.setBand(null); + } + + + function afterShow(data) { + // hide until we know if 'isMember' + $("#btn-follow-band").hide(); + $("#btn-edit-band-profile").hide(); + $("#btn-edit-band-bio").hide(); + $("#btn-edit-band-info").hide(); + $("#btn-edit-band-members").hide(); + $("#btn-edit-band-delete").hide(); + + resetForm(); + events(); + determineMembership() + .done(function() { + renderActive(); + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to talk to server") + }) + } + + function resetForm() { + $('#band-profile-instruments').empty(); + + $('#band-profile-about').show(); + $('#band-profile-history').hide(); + $('#band-profile-members').hide(); + $('#band-profile-social').hide(); + + $('.band-profile-nav a.active').removeClass('active'); + $('.band-profile-nav a#band-profile-about-link').addClass('active'); + } + + /****************** MAIN PORTION OF SCREEN *****************/ + + function addFollowing(isBand, id) { + var newFollowing = {}; + + if (!isBand) { + newFollowing.user_id = id; + } + else { + newFollowing.band_id = id; } - function afterShow(data) { - - // hide until we know if 'isMember' - $("#btn-follow-band").hide(); - $("#btn-edit-band-profile").hide(); - $("#btn-edit-band-info").hide(); - $("#btn-edit-band-members").hide(); - $("#btn-edit-band-delete").hide(); - - resetForm(); - events(); - determineMembership() - .done(function() { - renderActive(); - }) - .fail(function(jqXHR) { - app.notifyServerError(jqXHR, "Unable to talk to server") - }) - } - - function resetForm() { - $('#band-profile-instruments').empty(); - - $('#band-profile-about').show(); - $('#band-profile-history').hide(); - $('#band-profile-members').hide(); - $('#band-profile-social').hide(); - - $('.band-profile-nav a.active').removeClass('active'); - $('.band-profile-nav a#band-profile-about-link').addClass('active'); - } - - /****************** MAIN PORTION OF SCREEN *****************/ - - function addFollowing(isBand, id) { - var newFollowing = {}; - - if (!isBand) { - newFollowing.user_id = id; - } - else { - newFollowing.band_id = id; - } - - rest.addFollowing(newFollowing) - .done(function() { - if (isBand) { - var newCount = parseInt($("#band-profile-follower-stats").text()) + 1; - var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower"; - $('#band-profile-follower-stats').html(newCount + text); - configureBandFollowingButton(true); - } - else { - configureMemberFollowingButton(true, id); - } - renderActive(); - }) - .fail(app.ajaxError); - } - - function removeFollowing(isBand, id) { - rest.removeFollowing(id) - .done(function() { - if (isBand) { - var newCount = parseInt($("#band-profile-follower-stats").text()) - 1; - var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower"; - $('#band-profile-follower-stats').html(newCount + text); - configureBandFollowingButton(false); - } - else { - configureMemberFollowingButton(false, id); - } - renderActive(); - }) - .fail(app.ajaxError); - } - - function configureBandFollowingButton(following) { - $('#btn-follow-band').unbind("click"); - - if (following) { - $('#btn-follow-band').text('UNFOLLOW'); - $('#btn-follow-band').click(function() { - removeFollowing(true, bandId); - return false; - }); + rest.addFollowing(newFollowing) + .done(function() { + if (isBand) { + var newCount = parseInt($("#band-profile-follower-stats").text()) + 1; + var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower"; + $('#band-profile-follower-stats').html(newCount + text); + configureBandFollowingButton(true); } else { - $('#btn-follow-band').text('FOLLOW'); - $('#btn-follow-band').click(function() { - addFollowing(true, bandId); - return false; - }); + configureMemberFollowingButton(true, id); } - } + renderActive(); + }) + .fail(app.ajaxError); + } - function configureMemberFollowingButton(following, userId) { - - var $btnFollowMember = $('div[user-id=' + userId + ']', '#band-profile-members').find('#btn-follow-member'); - - if (context.JK.currentUserId === userId) { - $btnFollowMember.hide(); + function removeFollowing(isBand, id) { + rest.removeFollowing(id) + .done(function() { + if (isBand) { + var newCount = parseInt($("#band-profile-follower-stats").text()) - 1; + var text = newCount > 1 || newCount == 0 ? " Followers" : " Follower"; + $('#band-profile-follower-stats').html(newCount + text); + configureBandFollowingButton(false); } else { - $btnFollowMember.unbind("click"); - - if (following) { - $btnFollowMember.text('UNFOLLOW'); - $btnFollowMember.click(function() { - removeFollowing(false, userId); - return false; - }); - } - else { - $btnFollowMember.text('FOLLOW'); - $btnFollowMember.click(function() { - addFollowing(false, userId); - return false; - }); - } + configureMemberFollowingButton(false, id); } - } + renderActive(); + }) + .fail(app.ajaxError); + } - // refreshes the currently active tab - function renderActive() { + function configureBandFollowingButton(following) { + $('#btn-follow-band').unbind("click"); - if (isMember) { - $("#btn-follow-band").hide(); - $("#btn-edit-band-profile").show(); - $("#btn-edit-band-info").show(); - $("#btn-edit-band-members").show(); - if (isAdmin) - $("#btn-edit-band-delete").show(); - } - else { - $("#btn-follow-band").show(); - $("#btn-edit-band-profile").hide(); - $("#btn-edit-band-info").hide(); - $("#btn-edit-band-members").hide(); - $("#btn-edit-band-delete").hide(); - } - - if ($('#band-profile-about-link').hasClass('active')) { - renderAbout(); - } - else if ($('#band-profile-history-link').hasClass('active')) { - renderHistory(); - } - else if ($('#band-profile-members-link').hasClass('active')) { - renderMembers(); - } - else if ($('#band-profile-social-link').hasClass('active')) { - renderSocial(); - } - } - - /****************** ABOUT TAB *****************/ - function renderAbout() { - - $('#band-profile-about').show(); - $('#band-profile-history').hide(); - $('#band-profile-members').hide(); - $('#band-profile-social').hide(); - - $('.band-profile-nav a.active').removeClass('active'); - $('.band-profile-nav a#band-profile-about-link').addClass('active'); - - bindAbout(); - } - - function bindAbout() { - - rest.getBand(bandId) - .done(function(response) { - band = response; - if (band) { - // name - $('#band-profile-name').text(band.name); - - // avatar - $('#band-profile-avatar').attr('src', context.JK.resolveAvatarUrl(band.photo_url)); - - // location - $('#band-profile-location').html(band.location); - - // stats - var text = band.follower_count > 1 || band.follower_count == 0 ? " Followers" : " Follower"; - $('#band-profile-follower-stats').html(band.follower_count + text); - - text = band.session_count > 1 || band.session_count == 0 ? " Sessions" : " Session"; - $('#band-profile-session-stats').html(band.session_count + text); - - text = band.recording_count > 1 || band.recording_count == 0 ? " Recordings" : " Recording"; - $('#band-profile-recording-stats').html(band.recording_count + text); - - $('#band-profile-biography').text(band.biography); - - if (band.website) { - $('#band-profile-website').attr('href', band.website); - $('#band-profile-website').text(band.website); - } - - // wire up Follow click - configureBandFollowingButton(band.is_following); - } - else { - logger.debug("No band found with bandId = " + bandId); - } - }) - .fail(function(xhr) { - if(xhr.status >= 500) { - context.JK.fetchUserNetworkOrServerFailure(); - } - else if(xhr.status == 404) { - context.JK.entityNotFound("Band"); - } - else { - context.JK.app.ajaxError(arguments); - } - }); - } - - /****************** SOCIAL TAB *****************/ - function renderSocial() { - $('#band-profile-social-followers').empty(); - - $('#band-profile-about').hide(); - $('#band-profile-history').hide(); - $('#band-profile-members').hide(); - $('#band-profile-social').show(); - - $('.band-profile-nav a.active').removeClass('active'); - $('.band-profile-nav a#band-profile-social-link').addClass('active'); - - bindSocial(); - } - - function bindSocial() { - - rest.getBandFollowers(bandId) - .done(function(response) { - $.each(response, function(index, val) { - // var template = $('#template-profile-social').html(); - var template = $('#template-band-profile-social').html(); - var followerHtml = context.JK.fillTemplate(template, { - userId: val.id, - hoverAction: val.musician ? "musician" : "fan", - avatar_url: context.JK.resolveAvatarUrl(val.photo_url), - userName: val.name, - location: val.location - }); - - $('#band-profile-social-followers').append(followerHtml); - - if (index === response.length-1) { - context.JK.bindHoverEvents(); - } - }) - }) - .fail(function(xhr) { - if(xhr.status >= 500) { - context.JK.fetchUserNetworkOrServerFailure(); - } - else { - context.JK.app.ajaxError(arguments); - } - }); - } - - /****************** HISTORY TAB *****************/ - function renderHistory() { - $('#band-profile-about').hide(); - $('#band-profile-history').show(); - $('#band-profile-members').hide(); - $('#band-profile-social').hide(); - - $('.band-profile-nav a.active').removeClass('active'); - $('.band-profile-nav a#band-profile-history-link').addClass('active'); - - bindHistory(); - } - - function bindHistory() { - - } - - /****************** BANDS TAB *****************/ - function renderMembers() { - $('#band-profile-members').empty(); - - $('#band-profile-about').hide(); - $('#band-profile-history').hide(); - $('#band-profile-members').show(); - $('#band-profile-social').hide(); - - $('.band-profile-nav a.active').removeClass('active'); - $('.band-profile-nav a#band-profile-members-link').addClass('active'); - - bindMembers(); - } - - function bindMembers() { - rest.getBandMembers(bandId, false) - .done(function(response) { - bindMusicians(response, false); - if (isMember) { - bindPendingMembers(); - } - }) - .fail(function(xhr) { - if(xhr.status >= 500) { - context.JK.fetchUserNetworkOrServerFailure(); - } - else { - context.JK.app.ajaxError(arguments); - } - }); - } - - function bindPendingMembers() { - rest.getBandMembers(bandId, true) - .done(function(response) { - if (response && response.length > 0) { - $("#band-profile-members").append("

Pending Band Invitations

"); - bindMusicians(response, true); - } - }) - .fail(function(xhr) { - if(xhr.status >= 500) { - context.JK.fetchUserNetworkOrServerFailure(); - } - else { - context.JK.app.ajaxError(arguments); - } - }); - } - - function bindMusicians(musicians, isPending) { - $.each(musicians, function(index, musician) { - var instrumentLogoHtml = ''; - if ("instruments" in musician && musician.instruments != null) { - for (var j=0; j < musician.instruments.length; j++) { - var instrument = musician.instruments[j]; - var inst = '../assets/content/icon_instrument_default24.png'; - if (instrument.instrument_id in instrument_logo_map) { - inst = instrument_logo_map[instrument.instrument_id].asset; - } - instrumentLogoHtml += ' '; - } - } - var bandAdmin = musician.band_admin; - var template = $('#template-band-profile-members').html(); - var memberHtml = context.JK.fillTemplate(template, { - userId: musician.id, - band_admin: bandAdmin, - is_pending: isPending, - invitation_id: isPending ? musician.invitation_id : '', - profile_url: "/client#/profile/" + musician.id, - avatar_url: context.JK.resolveAvatarUrl(musician.photo_url), - name: musician.name, - location: musician.location, - biography: musician.biography, - friend_count: musician.friend_count, - follower_count: musician.follower_count, - recording_count: musician.recording_count, - session_count: musician.session_count, - instruments: instrumentLogoHtml - }); - - $('#band-profile-members').append(memberHtml); - - // wire up Follow button click handler - configureMemberFollowingButton(musician.is_following, musician.id); - configureRemoveMemberButton(musician.id, isPending, bandAdmin); - - // TODO: wire up Friend button click handler - // var friend = isFriend(musician.id); - // configureMemberFriendButton(friend, musician.id); - }); - if (isPending) { - $('div[pending-member=true] .btn-reinvite-member').each(function() { - var btn = $(this); - btn.show(); - btn.unbind('click'); - btn.click(function() { - var inviteid = $(this).closest('.band-profile-members').attr('invitation-id'); - rest.resendBandInvitation(bandId, inviteid) - .done(function (response) { - app.notifyAlert('Band Invitation', 'Your invitation has been re-sent'); - }).fail(app.ajaxError); - }); - }); - } - } - - function configureRemoveMemberButton(userId, isPending, bandAdmin) { - var $divMember = $('div[user-id=' + userId + ']', '#band-profile-members'); - var $btnRemoveMember = $divMember.find('.btn-remove-member'); - if (isMember && !isPending && !bandAdmin) { - $btnRemoveMember.show(); - $btnRemoveMember.unbind("click"); - $btnRemoveMember.click(function() { - var confirmMemberDeleteTxt; - if (userId == context.JK.currentUserId) - confirmMemberDeleteTxt = 'Are you sure you want to delete yourself from the band?'; - else - confirmMemberDeleteTxt = 'Are you sure you want to delete this member from the band?'; - var confirmDialog = new context.JK.ConfirmDialog(app, "DELETE", confirmMemberDeleteTxt, "Edit Band Membership", - function() { - app.layout.closeDialog('confirm'); - rest.removeBandMember(bandId, userId) - .done(function() { - $divMember.remove(); - if (userId == context.JK.currentUserId) { - $('#btn-edit-band-profile').hide(); - $('#btn-edit-band-info').hide(); - $('#btn-edit-band-members').hide(); - $('.btn-remove-member').each(function(idx) { $(this).hide(); }); - $('.btn-reinvite-member').each(function(idx) { $(this).hide(); }); - } - }) - .fail(app.ajaxError); - }); - confirmDialog.initialize(); - context.JK.app.layout.showDialog('confirm'); + if (following) { + $('#btn-follow-band').text('UNFOLLOW'); + $('#btn-follow-band').click(function() { + removeFollowing(true, bandId); return false; - }); + }); + } else { + $('#btn-follow-band').text('FOLLOW'); + $('#btn-follow-band').click(function() { + addFollowing(true, bandId); + return false; + }); + } + } + + function configureMemberFollowingButton(following, userId) { + + var $btnFollowMember = $('div[user-id=' + userId + ']', '#band-profile-members').find('#btn-follow-member'); + + if (context.JK.currentUserId === userId) { + $btnFollowMember.hide(); + } else { + $btnFollowMember.unbind("click"); + + if (following) { + $btnFollowMember.text('UNFOLLOW'); + $btnFollowMember.click(function() { + removeFollowing(false, userId); + return false; + }); + } + else { + $btnFollowMember.text('FOLLOW'); + $btnFollowMember.click(function() { + addFollowing(false, userId); + return false; + }); + } + } + } + + // refreshes the currently active tab + function renderActive() { + + if (isMember) { + $("#btn-follow-band").hide(); + $("#btn-edit-band-profile").show(); + $("#btn-edit-band-bio").show(); + $("#btn-edit-band-info").show(); + $("#btn-edit-band-members").show(); + if (isAdmin) { + $("#btn-edit-band-delete").show(); + } + } else { + $("#btn-follow-band").show(); + $("#btn-edit-band-bio").hide(); + $("#btn-edit-band-profile").hide(); + $("#btn-edit-band-info").hide(); + $("#btn-edit-band-members").hide(); + $("#btn-edit-band-delete").hide(); + } + + if ($('#band-profile-about-link').hasClass('active')) { + renderAbout(); + } else if ($('#band-profile-history-link').hasClass('active')) { + renderHistory(); + } else if ($('#band-profile-members-link').hasClass('active')) { + renderMembers(); + } else if ($('#band-profile-social-link').hasClass('active')) { + renderSocial(); + } + } + + /****************** ABOUT TAB *****************/ + function renderAbout() { + + $('#band-profile-about').show(); + $('#band-profile-history').hide(); + $('#band-profile-members').hide(); + $('#band-profile-social').hide(); + + $('.band-profile-nav a.active').removeClass('active'); + $('.band-profile-nav a#band-profile-about-link').addClass('active'); + + + + bindAbout(); + } + + function bindAbout() { + + rest.getBand(bandId) + .done(function(response) { + band = response; + if (band) { + // name + $('#band-profile-name').text(band.name); + + // avatar + $('#band-profile-avatar').attr('src', context.JK.resolveAvatarUrl(band.photo_url)); + + // location + $('#band-profile-location').html(band.location); + + // stats + var text = band.follower_count > 1 || band.follower_count == 0 ? " Followers" : " Follower"; + $('#band-profile-follower-stats').html(band.follower_count + text); + + text = band.session_count > 1 || band.session_count == 0 ? " Sessions" : " Session"; + $('#band-profile-session-stats').html(band.session_count + text); + + text = band.recording_count > 1 || band.recording_count == 0 ? " Recordings" : " Recording"; + $('#band-profile-recording-stats').html(band.recording_count + text); + + $('#band-profile-biography').text(band.biography); + + renderMusicalExperience() + profileUtils.renderPerformanceSamples(band, $root, isAdmin) + profileUtils.renderOnlinePresence(band, $root, isAdmin) + renderCurrentInterests() + + // wire up Follow click + configureBandFollowingButton(band.is_following); + } + else { + logger.debug("No band found with bandId = " + bandId); + } + }) + .fail(function(xhr) { + if(xhr.status >= 500) { + context.JK.fetchUserNetworkOrServerFailure(); + } + else if(xhr.status == 404) { + context.JK.entityNotFound("Band"); + } + else { + context.JK.app.ajaxError(arguments); + } + }); + } + + function renderMusicalExperience() { + var genres = buildGenreList(band.genres) + var gigs = (band.concert_count > 0) ? 'Has played ' + profileUtils.gigMap[band.concert_count] + ' live concert gigs' : NOT_SPECIFIED_TEXT; + var bandType ; + if (!band.band_type || typeof(band.band_type)=="undefined" || band.band_type==="") { + bandType = "Not specified"; + } else if (band.band_type.toLowerCase()==="physical") { + bandType = "Physical"; + } else if (band.band_type.toLowerCase()==="virtual") { + bandType = "Virtual"; + } else { + bandType = "Not specified"; + } + + var bandStatus ; + if (!band.band_status || typeof(band.band_status)=="undefined" || band.band_status==="") { + bandStatus = "Not specified"; + } else if (band.band_status.toLowerCase()==="amateur") { + bandStatus = "Amateur Band"; + } else if (band.band_status.toLowerCase()==="professional") { + bandStatus = "Professional Band"; + } else { + bandStatus = "Not specified"; + } + + $root.find(".experience-genres").html(genres) + $root.find(".experience-gigs").html(gigs) + $root.find(".experience-status").html(bandStatus) + $root.find(".experience-type").html(bandType) + + } + + function renderCurrentInterests() { + if (band.add_new_members) { + $root.find(".new-member-details").html(profileUtils.renderBandInstruments(band)) + $root.find(".interests-new-members").removeClass("hidden") + } else { + $root.find(".interests-new-members").addClass("hidden") + } + + if (band.paid_gigs) { + $root.find(".paid-gig-rate").html(band.hourly_rate) + $root.find(".paid-gig-minimum").html(band.gig_minimum) + $root.find(".interests-paid-gigs").removeClass("hidden") + } else { + $root.find(".interests-paid-gigs").addClass("hidden") + } + + if (band.free_gigs) { + $root.find(".interests-free-gigs").removeClass("hidden") + } else { + $root.find(".interests-free-gigs").addClass("hidden") + } + + } + + + /****************** SOCIAL TAB *****************/ + function renderSocial() { + $('#band-profile-social-followers').empty(); + + $('#band-profile-about').hide(); + $('#band-profile-history').hide(); + $('#band-profile-members').hide(); + $('#band-profile-social').show(); + + $('.band-profile-nav a.active').removeClass('active'); + $('.band-profile-nav a#band-profile-social-link').addClass('active'); + + bindSocial(); + } + + function bindSocial() { + + rest.getBandFollowers(bandId) + .done(function(response) { + $.each(response, function(index, val) { + // var template = $('#template-profile-social').html(); + var template = $('#template-band-profile-social').html(); + var followerHtml = context.JK.fillTemplate(template, { + userId: val.id, + hoverAction: val.musician ? "musician" : "fan", + avatar_url: context.JK.resolveAvatarUrl(val.photo_url), + userName: val.name, + location: val.location + }); + + $('#band-profile-social-followers').append(followerHtml); + + if (index === response.length-1) { + context.JK.bindHoverEvents(); + } + }) + }) + .fail(function(xhr) { + if(xhr.status >= 500) { + context.JK.fetchUserNetworkOrServerFailure(); } else { - $btnRemoveMember.hide(); + context.JK.app.ajaxError(arguments); } + }); + } + + /****************** HISTORY TAB *****************/ + function renderHistory() { + $('#band-profile-about').hide(); + $('#band-profile-history').show(); + $('#band-profile-members').hide(); + $('#band-profile-social').hide(); + + $('.band-profile-nav a.active').removeClass('active'); + $('.band-profile-nav a#band-profile-history-link').addClass('active'); + + bindHistory(); + } + + function bindHistory() { + feed.refresh(); + } + + function buildGenreList(genres) { + var list = ''; + + for (var i=0; i < genres.length; i++) { + list = list.concat(genres[i].id); + if (i !== genres.length - 1) { + list = list.concat(', '); + } } - // checks if person viewing the profile is also a band member - function determineMembership() { - var url = "/api/bands/" + bandId + "/musicians"; - return $.ajax({ - type: "GET", - dataType: "json", - url: url, - processData:false, - error: app.ajaxError - }) - .done(function(response) { - isAdmin = isMember = false; - $.each(response, function(index, val) { - if (val.id === context.JK.currentUserId) { - isMember = true; - if (val.band_admin) { - isAdmin = true; - } + return list; + } + + /****************** BANDS TAB *****************/ + function renderMembers() { + $('#band-profile-members').empty(); + + $('#band-profile-about').hide(); + $('#band-profile-history').hide(); + $('#band-profile-members').show(); + $('#band-profile-social').hide(); + + $('.band-profile-nav a.active').removeClass('active'); + $('.band-profile-nav a#band-profile-members-link').addClass('active'); + + bindMembers(); + } + + function bindMembers() { + rest.getBandMembers(bandId, false) + .done(function(response) { + bindMusicians(response, false); + if (isMember) { + bindPendingMembers(); + } + }) + .fail(function(xhr) { + if(xhr.status >= 500) { + context.JK.fetchUserNetworkOrServerFailure(); + } + else { + context.JK.app.ajaxError(arguments); + } + }); + } + + function bindPendingMembers() { + rest.getBandMembers(bandId, true) + .done(function(response) { + if (response && response.length > 0) { + $("#band-profile-members").append("

Pending Band Invitations

"); + bindMusicians(response, true); + } + }) + .fail(function(xhr) { + if(xhr.status >= 500) { + context.JK.fetchUserNetworkOrServerFailure(); + } + else { + context.JK.app.ajaxError(arguments); + } + }); + } + + function bindMusicians(musicians, isPending) { + $.each(musicians, function(index, musician) { + var instrumentLogoHtml = ''; + if ("instruments" in musician && musician.instruments != null) { + for (var j=0; j < musician.instruments.length; j++) { + var instrument = musician.instruments[j]; + var inst = '../assets/content/icon_instrument_default24.png'; + if (instrument.instrument_id in instrument_logo_map) { + inst = instrument_logo_map[instrument.instrument_id].asset; + } + instrumentLogoHtml += ' '; } - }); - }) + } + var bandAdmin = musician.band_admin; + var template = $('#template-band-profile-members').html(); + var memberHtml = context.JK.fillTemplate(template, { + userId: musician.id, + band_admin: bandAdmin, + is_pending: isPending, + invitation_id: isPending ? musician.invitation_id : '', + profile_url: "/client#/profile/" + musician.id, + avatar_url: context.JK.resolveAvatarUrl(musician.photo_url), + name: musician.name, + location: musician.location, + biography: musician.biography, + friend_count: musician.friend_count, + follower_count: musician.follower_count, + recording_count: musician.recording_count, + session_count: musician.session_count, + instruments: instrumentLogoHtml + }); + + $('#band-profile-members').append(memberHtml); + + // wire up Follow button click handler + configureMemberFollowingButton(musician.is_following, musician.id); + configureRemoveMemberButton(musician.id, isPending, bandAdmin); + + // TODO: wire up Friend button click handler + // var friend = isFriend(musician.id); + // configureMemberFriendButton(friend, musician.id); + }); + if (isPending) { + $('div[pending-member=true] .btn-reinvite-member').each(function() { + var btn = $(this); + btn.show(); + btn.unbind('click'); + btn.click(function() { + var inviteid = $(this).closest('.band-profile-members').attr('invitation-id'); + rest.resendBandInvitation(bandId, inviteid) + .done(function (response) { + app.notifyAlert('Band Invitation', 'Your invitation has been re-sent'); + }).fail(app.ajaxError); + }); + }); } + } - // events for main screen - function events() { - // wire up panel clicks - $('#band-profile-about-link').unbind('click').click(renderAbout); - $('#band-profile-history-link').unbind('click').click(renderHistory); - $('#band-profile-members-link').unbind('click').click(renderMembers); - $('#band-profile-social-link').unbind('click').click(renderSocial); + function configureRemoveMemberButton(userId, isPending, bandAdmin) { + var $divMember = $('div[user-id=' + userId + ']', '#band-profile-members'); + var $btnRemoveMember = $divMember.find('.btn-remove-member'); + if (isMember && !isPending && !bandAdmin) { + $btnRemoveMember.show(); + $btnRemoveMember.unbind("click"); + $btnRemoveMember.click(function() { + var confirmMemberDeleteTxt; + if (userId == context.JK.currentUserId) + confirmMemberDeleteTxt = 'Are you sure you want to delete yourself from the band?'; + else + confirmMemberDeleteTxt = 'Are you sure you want to delete this member from the band?'; + var confirmDialog = new context.JK.ConfirmDialog(app, "DELETE", confirmMemberDeleteTxt, "Edit Band Membership", + function() { + app.layout.closeDialog('confirm'); + rest.removeBandMember(bandId, userId) + .done(function() { + $divMember.remove(); + if (userId == context.JK.currentUserId) { + $('#btn-edit-band-profile').hide(); + $('#btn-edit-band-bio').hide(); + $('#btn-edit-band-info').hide(); + $('#btn-edit-band-members').hide(); + $('.btn-remove-member').each(function(idx) { $(this).hide(); }); + $('.btn-reinvite-member').each(function(idx) { $(this).hide(); }); + } + }) + .fail(app.ajaxError); + }); + confirmDialog.initialize(); + context.JK.app.layout.showDialog('confirm'); + return false; + }); + } else { + $btnRemoveMember.hide(); + } + } - $("#btn-edit-band-profile").unbind('click').click(function() { - context.location = "/client#/band/setup/" + bandId + '/step1'; - return false; - }); - $("#btn-edit-band-info").unbind('click').click(function() { - context.location = "/client#/band/setup/" + bandId + '/step1'; - return false; - }); - $("#btn-edit-band-members").unbind('click').click(function() { - context.location = "/client#/band/setup/" + bandId + '/step2'; - return false; - }); - $("#btn-edit-band-delete").unbind('click').click(function() { - var confirmDialog = new context.JK.ConfirmDialog(app, - "DELETE", - "Are you sure you want to delete this band? This is a permanent action which cannot be undone.", - "Delete Band", - function() { - app.layout.closeDialog('confirm'); - rest.deleteBand(bandId) - .done(function() { - context.location = "/client#/profile/"+context.JK.currentUserId; - }) - .fail(app.ajaxError); - }); - confirmDialog.initialize(); - context.JK.app.layout.showDialog('confirm'); + // checks if person viewing the profile is also a band member + function determineMembership() { + var url = "/api/bands/" + bandId + "/musicians"; + return $.ajax({ + type: "GET", + dataType: "json", + url: url, + processData:false, + error: app.ajaxError + }) + .done(function(response) { + isAdmin = isMember = false; + $.each(response, function(index, val) { + if (val.id === context.JK.currentUserId) { + isMember = true; + if (val.band_admin) { + isAdmin = true; + } + } + }); + }) + } + + // events for main screen + function events() { + // wire up panel clicks + $('#band-profile-about-link').unbind('click').click(renderAbout); + $('#band-profile-history-link').unbind('click').click(renderHistory); + $('#band-profile-members-link').unbind('click').click(renderMembers); + $('#band-profile-social-link').unbind('click').click(renderSocial); + + $("#btn-edit-band-profile").unbind('click').click(function() { + context.location = "/client#/band/setup/" + bandId + '/step0'; return false; - }); - } + }); + $("#btn-edit-band-info").unbind('click').click(function() { + context.location = "/client#/band/setup/" + bandId + '/step1'; + return false; + }); + $("#btn-edit-band-bio").unbind('click').click(function() { + context.location = "/client#/band/setup/" + bandId + '/step0'; + return false; + }); + $("#btn-edit-band-members").unbind('click').click(function() { + context.location = "/client#/band/setup/" + bandId + '/step2'; + return false; + }); + $("#btn-edit-band-delete").unbind('click').click(function() { + var confirmDialog = new context.JK.ConfirmDialog(app, + "DELETE", + "Are you sure you want to delete this band? This is a permanent action which cannot be undone.", + "Delete Band", + function() { + app.layout.closeDialog('confirm'); + rest.deleteBand(bandId) + .done(function() { + context.location = "/client#/profile/"+context.JK.currentUserId; + }) + .fail(app.ajaxError); + }); + confirmDialog.initialize(); + context.JK.app.layout.showDialog('confirm'); + return false; + }); + } - function initialize() { - var screenBindings = { - 'beforeShow': beforeShow, - 'afterShow': afterShow - }; - app.bindScreen('bandProfile', screenBindings); - } + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('bandProfile', screenBindings); + + var $feedScroller = $history.find(".content-body-scroller") + var $feedContent = $feedScroller.find(".feed-content") + var $noMoreFeeds = $feedScroller.find('.end-of-list') + var $refresh = $history.find('.btn-refresh-entries'); + var $sortFeedBy = $history.find('#feed_order_by'); + var $includeDate = $history.find('#feed_date'); + var $includeType = $history.find('#feed_show'); - this.initialize = initialize; - this.beforeShow = beforeShow; - this.afterShow = afterShow; - return this; - }; + feed.initialize($history, $feedScroller, $feedContent, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType, {time_range: 'all'}) + } + + this.initialize = initialize; + this.beforeShow = beforeShow; + this.afterShow = afterShow; + return this; + } })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/band_setup.js b/web/app/assets/javascripts/band_setup.js index 44324bc78..4e09a8e47 100644 --- a/web/app/assets/javascripts/band_setup.js +++ b/web/app/assets/javascripts/band_setup.js @@ -9,24 +9,107 @@ // accounts_profiles.js context.JK.BandSetupScreen = function (app) { - var logger = context.JK.logger; - var rest = context.JK.Rest(); - var inviteMusiciansUtil = null; - var invitationDialog = null; - var autoComplete = null; - var userNames = []; - var userIds = []; - var userPhotoUrls = []; - var selectedFriendIds = {}; - var nilOptionStr = ''; - var nilOptionText = 'n/a'; - var bandId = ''; - var friendInput=null; - var step1, step2; - var isSaving = false; + var NONE_SPECIFIED = 'None specified' + var GENRE_STEP = 1 + var SAMPLE_STEP = 3 + var STEPS_COUNT = 5 + var currentStep = 0 + var ui = new context.JK.UIHelper(JK.app) + var logger = context.JK.logger + var profileUtils = context.JK.ProfileUtils + var rest = context.JK.Rest() + var inviteMusiciansUtil = null + var invitationDialog = null + var autoComplete = null + var userNames = [] + var userIds = [] + var userPhotoUrls = [] + var selectedFriendIds = {} + var nilOptionStr = '' + var nilOptionText = 'n/a' + var bandId = '' + var friendInput=null + var bandType=null + var bandStatus=null + var concertCount=null - function is_new_record() { - return bandId.length == 0; + + var $screen=$("#band-setup") + var $samples = $screen.find(".account-profile-samples") + var $selectedInstruments=[] + + var accountProfileSamples = new JK.AccountProfileSamples(app, $screen, loadBandCallback, rest.updateBand) + accountProfileSamples.initialize() + + function navBack() { + if (currentStep>0) { + saveBand(function() { + currentStep-- + renderCurrentPage() + }) + } + } + + function navCancel() { + resetForm() + window.history.go(-1) + return false + } + + function navNext() { + if (currentStep 0) ? instrumentText : NONE_SPECIFIED) + } + function showProfile(band_id) { context.location = "/client#/bandProfile/" + band_id; } - function saveBand() { - if (isSaving) return; - isSaving = true; + function saveInvitations(response) { + if (0 < $('#selected-friends-band .invitation').length) { + createBandInvitations(response.id, function () { + showProfile(response.id); + }); + } + } + function saveBand(saveBandSuccessFn) { + + unbindNavButtons() + removeErrors() var band = buildBand() - if (is_new_record()) { - rest.createBand(band) - .done(function (response) { - isSaving = false; - if (0 < $('#selected-friends-band .invitation').length) { - createBandInvitations(response.id, function () { - showProfile(response.id); - }); - } else - showProfile(response.id); - }) - .fail(function (jqXHR) { - isSaving = false; - app.notifyServerError(jqXHR, "Unable to create band") - }); - ; - } - else { - band.id = bandId; - if (!step1 && !step2){ - rest.updateBand(band) - .done(function (response) { - isSaving = false; - createBandInvitations(band.id, function () { - showProfile(band.id); - }); - }).fail(function (jqXHR) { - isSaving = false; - app.notifyServerError(jqXHR, "Unable to create band") - }); - } else { - if (step1) { - rest.updateBand(band) - .done(function (response) { - isSaving = false; - app.notifyAlert('Band Information', 'Your changes have been saved'); - }).fail(function (jqXHR) { - isSaving = false; - app.notifyServerError(jqXHR, "Unable to update band") - }); - } else if (step2) { - isSaving = false; - if (0 < $('#selected-friends-band .invitation').length) { - createBandInvitations(bandId, function () { - app.notifyAlert('Band Members', 'Your invitations have been sent'); - showProfile(bandId); - }); - } else - showProfile(bandId); + var saveBandFn = (isNewBand()) ? rest.createBand : rest.updateBand + saveBandFn(band) + .done(function (response) { + bandId = response.id + saveInvitations(response) + if(saveBandSuccessFn) { + saveBandSuccessFn(band) } - } - } + }) + .fail(function (jqXHR) { + if(jqXHR.status == 422) { + renderErrors(JSON.parse(jqXHR.responseText)) + } else { + app.notifyServerError(jqXHR, "Unable to create band") + } + }) + .always(function (jqXHR) { + bindNavButtons() + }) } function createBandInvitations(bandId, onComplete) { @@ -219,14 +352,13 @@ function beforeShow(data) { inviteMusiciansUtil.clearSelections(); bandId = data.id == 'new' ? '' : data.id; - - step1 = step2 = false; - if ('step2'==data['d']) { - step2 = true; - delete data['d']; - } else if ('step1'==data['d']){ - step1 = true; + currentStep=0 + if (data['d']) { + var stepNum = data['d'].substring(4) + if(stepNum) { + currentStep=stepNum delete data['d']; + } } resetForm(); } @@ -234,61 +366,68 @@ function afterShow(data) { inviteMusiciansUtil.loadFriends(); - if (!is_new_record()) { - $("#band-setup-title").html("edit band"); - $("#btn-band-setup-save").html("SAVE CHANGES"); + if (!isNewBand()) { $("#band-change-photo").html('Upload band photo.'); - $('#tdBandPhoto').css('visibility', 'visible'); + $('.band-photo').removeClass("hidden") // retrieve and initialize band profile data points loadBandDetails(); - - if (step2) { - $("#band-setup-step-2").show(); - $("#band-setup-step-1").hide(); - $('.band-setup-text-step2').each(function(idx) { $(this).hide(); }); - $('#btn-band-setup-back').text('CANCEL'); - $('#btn-band-setup-save').text('SEND INVITATIONS'); - - } else if (step1) { - $("#band-setup-step-1").show(); - $("#band-setup-step-2").hide(); - $('.band-setup-text-step1').each(function(idx) { $(this).hide(); }); - $('#btn-band-setup-next').text('SAVE'); - } - if (! step1 && ! step2) { - $('#btn-band-setup-next').text('NEXT'); - $('#btn-band-setup-back').text('CANCEL'); - $('#btn-band-setup-save').text('CREATE BAND'); - $('.band-setup-text-step1').each(function(idx) { $(this).show(); }); - $('.band-setup-text-step2').each(function(idx) { $(this).show(); }); - } - } - else { + } else { loadGenres(); - rest.getResolvedLocation() - .done(function (location) { - loadCountries(location.country, function () { - loadRegions(location.region, function () { - loadCities(location.city); - }); + // Load geo settings: + 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"); - $('#tdBandPhoto').css('visibility', 'hidden'); // can't upload photo when going through initial setup + $('.band-photo').addClass("hidden") } + renderCurrentPage() } + function loadDesiredExperience() { + + } + + function loadBandDetails() { rest.getBand(bandId).done(function (band) { $("#band-name").val(band.name); $("#band-website").val(band.website); $("#band-biography").val(band.biography); + bandType.val(band.band_type) + bandStatus.val(band.band_status) + concertCount.val(band.concert_count) + + if (band.add_new_members){ + $("#new-member-no").iCheck('check').attr('checked', 'checked') + } else { + $("#new-member-yes").iCheck('check').attr('checked', 'checked') + } + + if (band.paid_gigs) { + $("#paid-gigs-no").iCheck('check').attr('checked', 'checked') + } else { + $("#paid-gigs-yes").iCheck('check').attr('checked', 'checked') + } + + if (band.free_gigs) { + $("#free-gigs-no").iCheck('check').attr('checked', 'checked') + } else { + $("#free-gigs-yes").iCheck('check').attr('checked', 'checked') + } + + $('#touring-option').val(band.touring_option ? 'yes' : 'no') + $("#play-commitment").val(band.play_commitment) + $("#hourly-rate").val(band.hourly_rate) + $("#gig-minimum").val(band.gig_minimum) + + // Initialize avatar if (band.photo_url) { $("#band-avatar").attr('src', band.photo_url); } @@ -301,7 +440,20 @@ }); }); - // TODO: initialize avatar + renderOptionalControls(); + + $.each(band.instruments, function(index, instrument) { + var h = {} + h.id = instrument.instrument_id + h.level = instrument.proficiency_level + h.approve = true + $selectedInstruments.push(h) + }) + + renderDesiredExperienceLabel($selectedInstruments) + + accountProfileSamples.renderPlayer(band) + }); } @@ -432,7 +584,7 @@ } else { context.JK.dropdown($city); } - } + } function addInvitation(value, data) { if ($('#selected-band-invitees div[user-id=' + data + ']').length === 0) { @@ -441,8 +593,7 @@ $('#selected-band-invitees').append(invitationHtml); $('#band-invitee-input').select(); selectedFriendIds[data] = true; - } - else { + } else { $('#band-invitee-input').select(); context.alert('Invitation already exists for this musician.'); } @@ -453,58 +604,55 @@ context.location = '/client#/band/setup/photo/' + bandId; return false; } - + function removeInvitation(evt) { delete selectedFriendIds[$(evt.currentTarget).parent().attr('user-id')]; $(evt.currentTarget).closest('.invitation').remove(); } + function bindNavButtons() { + $('#btn-band-setup-back').on("click", function (e) { + e.stopPropagation() + navBack() + return false + }) + + $('#btn-band-setup-cancel').on("click", function (e) { + e.stopPropagation() + navCancel() + return false + }) + + $('#btn-band-setup-next').on("click", function (e) { + e.stopPropagation() + navNext() + return false + }) + + $('#btn-band-setup-back').removeClass("disabled") + $('#btn-band-setup-cancel').removeClass("disabled") + $('#btn-band-setup-next').removeClass("disabled") + } + + function unbindNavButtons() { + $('#btn-band-setup-back').off("click") + $('#btn-band-setup-cancel').off("click") + $('#btn-band-setup-next').off("click") + $('#btn-band-setup-back').addClass("disabled") + $('#btn-band-setup-cancel').addClass("disabled") + $('#btn-band-setup-next').addClass("disabled") + } + function events() { $('#selected-band-invitees').on("click", ".invitation a", removeInvitation); + bindNavButtons(); + // friend input focus $('#band-invitee-input').focus(function () { $(this).val(''); }); - $('#btn-band-setup-cancel').click(function () { - resetForm(); - window.history.go(-1); - return false; - }); - - $('#btn-band-setup-next').click(function () { - validateGeneralInfo() - .done(function (response) { - if (!step1 && !step2) { - $("#band-setup-step-2").show(); - $("#band-setup-step-1").hide(); - } else if (step1) { - saveBand(); - } - }) - .fail(function (jqXHR) { - if(jqXHR.status == 422) { - renderErrors(JSON.parse(jqXHR.responseText)) - } - else { - app.notifyServerError(jqXHR, "Unable to validate band") - } - }); - }); - - $('#btn-band-setup-back').click(function () { - if (!step2) { - $("#band-setup-step-1").show(); - $("#band-setup-step-2").hide(); - } else { - showProfile(bandId); - return false; - } - }); - - $('#btn-band-setup-save').click(saveBand); - $('#band-country').on('change', function (evt) { evt.stopPropagation(); loadRegions(); @@ -519,7 +667,7 @@ }); $('#band-change-photo').click(navigateToBandPhoto); - $('#band-setup .avatar-profile').click(navigateToBandPhoto); + $('#band-setup .band-avatar-profile').click(navigateToBandPhoto); $('div[layout-id="band/setup"] .btn-email-invitation').click(function () { invitationDialog.showEmailDialog(); @@ -533,27 +681,57 @@ invitationDialog.showFacebookDialog(); }); + $('a#choose-desired-experience').on("click", chooseExperience) + + $('#band-setup').on('ifToggled', 'input[type="radio"].dependent-master', renderOptionalControls); + $(friendInput).focus(function() { $(this).val(''); }) } + function chooseExperience(e) { + e.stopPropagation() + ui.launchInstrumentSelectorDialog("new member(s)", $selectedInstruments, function(selectedInstruments) { + $selectedInstruments = selectedInstruments + renderDesiredExperienceLabel($selectedInstruments) + return false + }) + return false + } + + function loadBandCallback() { + return (isNewBand()) ? {} : rest.getBand(bandId) + } + function initialize(invitationDialogInstance, friendSelectorDialog) { - inviteMusiciansUtil = new JK.InviteMusiciansUtil(app); - inviteMusiciansUtil.initialize(friendSelectorDialog); - friendInput = inviteMusiciansUtil.inviteBandCreate('#band-setup-invite-musicians', "
If your bandmates are already on JamKazam, start typing their names in the box below, or click the Choose Friends button to select them.
"); - invitationDialog = invitationDialogInstance; - events(); + inviteMusiciansUtil = new JK.InviteMusiciansUtil(app) + inviteMusiciansUtil.initialize(friendSelectorDialog) + + friendInput = inviteMusiciansUtil.inviteBandCreate('#band-setup-invite-musicians', "
If your bandmates are already on JamKazam, start typing their names in the box below, or click the Choose Friends button to select them.
") + invitationDialog = invitationDialogInstance + events() var screenBindings = { 'beforeShow': beforeShow, 'afterShow': afterShow - }; + } - app.bindScreen('band/setup', screenBindings); + bandType=$("#band-type") + bandStatus=$("#band-status") + concertCount=$("#concert-count") + + app.bindScreen('band/setup', screenBindings) + + $screen.find('input[type=radio]').iCheck({ + checkboxClass: 'icheckbox_minimal', + radioClass: 'iradio_minimal', + inheritClass: true + }) + + profileUtils.initializeHelpBubbles() } this.initialize = initialize; this.afterShow = afterShow; return this; }; - })(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/genreSelectorDialog.js b/web/app/assets/javascripts/dialog/genreSelectorDialog.js new file mode 100644 index 000000000..3a9bf4c4f --- /dev/null +++ b/web/app/assets/javascripts/dialog/genreSelectorDialog.js @@ -0,0 +1,83 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.GenreSelectorDialog = function(app, type, genres, callback) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var $dialog = null; + var dialogId = 'genre-selector-dialog'; + var $screen = $('#' + dialogId); + var $btnSelect = $screen.find(".btn-select-genres"); + var $instructions = $screen.find('.instructions'); + var $genres = $screen.find('.genres'); + + function beforeShow(data) { + } + + function afterShow(data) { + var genreList = context.JK.genres; + + $genres.empty(); + + if (genreList) { + $.each(genreList, function(index, val) { + $genres.append('
  • '); + var checked = ''; + if (genres && $.inArray(val.id, genres) > -1) { + checked = 'checked'; + } + + $genres.append('' + val.description); + $genres.append('
  • '); + }); + } + } + + function afterHide() { + $btnSelect.unbind("click") + } + + function showDialog() { + return app.layout.showDialog(dialogId); + } + + function events() { + $btnSelect.unbind("click").bind("click", function(evt) { + evt.preventDefault(); + var selectedGenres = []; + $genres.find('input[type=checkbox]:checked').each(function(index) { + selectedGenres.push($(this).val()); + }); + + if (callback) { + callback(selectedGenres); + } + + app.layout.closeDialog(dialogId); + + return false; + + }); + } + + function initialize() { + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterShow' : afterShow, + 'afterHide': afterHide + }; + + app.bindDialog(dialogId, dialogBindings); + + $instructions.html('Select one or more genres for ' + type + ':'); + + events(); + } + + this.initialize = initialize; + this.showDialog = showDialog; + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/instrumentSelectorDialog.js b/web/app/assets/javascripts/dialog/instrumentSelectorDialog.js new file mode 100644 index 000000000..3346eef65 --- /dev/null +++ b/web/app/assets/javascripts/dialog/instrumentSelectorDialog.js @@ -0,0 +1,82 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.InstrumentSelectorDialog = function(app, type, instruments, callback) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var $dialog = null; + var dialogId = 'instrument-selector-dialog'; + var $screen = $('#' + dialogId); + var $btnSelect = $screen.find(".btn-select-instruments"); + var $instructions = $screen.find('.instructions'); + var $instruments = $screen.find('.instruments'); + var $instrumentSelectorContainer = $screen.find('.instrument-selector-container') + var instrumentSelector = new JK.InstrumentSelector(app, $instrumentSelectorContainer); + var $callback = callback + var selectedInstruments = instruments + function beforeShow(data) { + instrumentSelector.initialize(false) + instrumentSelector.render($instrumentSelectorContainer) + instrumentSelector.setSelectedInstruments(selectedInstruments) + } + + function afterShow(data) { + // var instrumentList = context.JK.instruments; + + // $instruments.empty(); + + // if (instrumentList) { + // $.each(instrumentList, function(index, val) { + // $instruments.append('
  • '); + // var checked = ''; + // if (instruments && $.inArray(val.id, selectedInstruments) > -1) { + // checked = 'checked'; + // } + + // $instruments.append('' + val.description); + // $instruments.append('
  • '); + // }); + // } + } + + function afterHide() { + $btnSelect.unbind("click") + } + + function showDialog() { + return app.layout.showDialog(dialogId); + } + + function events() { + $btnSelect.unbind("click").bind("click", function(evt) { + evt.preventDefault(); + + selectedInstruments = instrumentSelector.getSelectedInstruments() + $callback(selectedInstruments) + app.layout.closeDialog(dialogId); + return false; + + }); + } + + function initialize() { + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterShow' : afterShow, + 'afterHide': afterHide + }; + + app.bindDialog(dialogId, dialogBindings); + + $instructions.html('Select the instruments and expertise you need for ' + type + ':'); + + events(); + } + + this.initialize = initialize; + this.showDialog = showDialog; + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/recordingSelectorDialog.js b/web/app/assets/javascripts/dialog/recordingSelectorDialog.js new file mode 100644 index 000000000..cc9e5dcf9 --- /dev/null +++ b/web/app/assets/javascripts/dialog/recordingSelectorDialog.js @@ -0,0 +1,230 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.RecordingSelectorDialog = function(app, recordings, selectedRecordings, selectCallback) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var recordingUtils = context.JK.RecordingUtils; + var $dialog = null; + var dialogId = 'recording-selector-dialog'; + var $screen = $('#' + dialogId); + var $btnSelect = $screen.find(".btn-select-recordings"); + var $instructions = $screen.find('#instructions'); + var $recordings = $screen.find('.recordings'); + var feedHelper = new context.JK.Feed(app); + + function beforeShow(data) { + } + + function afterShow(data) { + + $recordings.empty(); + + $.each(recordings, function(index, val) { + bindRecordingItem(val); + }); + + // hide the avatars + $screen.find('.avatar-small.ib').hide(); + } + + /********* THE FOLLOWING BLOCK IS REPEATED IN feedHelper.js **********/ + function startRecordingPlay($feedItem) { + var img = $('.play-icon', $feedItem); + var $controls = $feedItem.find('.recording-controls'); + img.attr('src', '/assets/content/icon_pausebutton.png'); + $controls.trigger('play.listenRecording'); + $feedItem.data('playing', true); + } + + function stopRecordingPlay($feedItem) { + var img = $('.play-icon', $feedItem); + var $controls = $feedItem.find('.recording-controls'); + img.attr('src', '/assets/content/icon_playbutton.png'); + $controls.trigger('pause.listenRecording'); + $feedItem.data('playing', false); + } + + function toggleRecordingPlay() { + + var $playLink = $(this); + var $feedItem = $playLink.closest('.feed-entry'); + var playing = $feedItem.data('playing'); + + if(playing) { + stopRecordingPlay($feedItem); + } + else { + startRecordingPlay($feedItem); + } + return false; + } + + function toggleRecordingDetails() { + var $detailsLink = $(this); + var $feedItem = $detailsLink.closest('.feed-entry'); + var $musicians = $feedItem.find('.musician-detail'); + var $description = $feedItem.find('.description'); + var $name = $feedItem.find('.name'); + var toggledOpen = $detailsLink.data('toggledOpen'); + + if(toggledOpen) { + toggleClose($feedItem, $name, $description, $musicians) + } + else { + toggleOpen($feedItem, $name, $description, $musicians) + } + + toggledOpen = !toggledOpen; + $detailsLink.data('toggledOpen', toggledOpen); + + return false; + } + + function stateChangeRecording(e, data) { + var $controls = data.element; + var $feedItem = $controls.closest('.feed-entry'); + + var $sliderBar = $('.recording-position', $feedItem); + var $statusBar = $('.recording-status', $feedItem); + var $currentTime = $('.recording-current', $feedItem); + var $status = $('.status-text', $feedItem); + var $playButton = $('.play-button', $feedItem); + + if(data.isEnd) stopRecordingPlay($feedItem); + if(data.isError) { + $sliderBar.hide(); + $playButton.hide(); + $currentTime.hide(); + $statusBar.show(); + $status.text(data.displayText); + } + } + + function toggleOpen($feedItem, $name, $description, $musicians) { + $description.trigger('destroy.dot'); + $description.data('original-height', $description.css('height')).css('height', 'auto'); + $name.trigger('destroy.dot'); + $name.data('original-height', $name.css('height')).css('height', 'auto'); + $musicians.show(); + $feedItem.animate({'max-height': '1000px'}); + } + + function toggleClose($feedItem, $name, $description, $musicians, immediate) { + $feedItem.css('height', $feedItem.height() + 'px') + $feedItem.animate({'height': $feedItem.data('original-max-height')}, immediate ? 0 : 400).promise().done(function() { + $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height')); + + $musicians.hide(); + $description.css('height', $description.data('original-height')); + $description.dotdotdot(); + $name.css('height', $name.data('original-height')); + $name.dotdotdot(); + }); + } + /**********************************************************/ + + function bindRecordingItem(claimedRecording) { + claimedRecording.recording.mix_info = recordingUtils.createMixInfo({state: claimedRecording.recording.mix_state}); + var options = { + feed_item: claimedRecording.recording, + candidate_claimed_recording: claimedRecording, + mix_class: claimedRecording['has_mix?'] ? 'has-mix' : 'no-mix', + }; + + var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'})); + var $controls = $feedItem.find('.recording-controls'); + + var $titleText = $feedItem.find('.title .title-text'); + + // if this item will be discarded, tack on a * to the RECORDING NAME + var discardTime = claimedRecording.recording['when_will_be_discarded?']; + if(discardTime) { + context.JK.helpBubble($titleText, 'recording-discarded-soon', {discardTime: discardTime}, {}); + $titleText.text($titleText.text() + '*'); + } + + $controls.data('mix-state', claimedRecording.recording.mix_info); // for recordingUtils helper methods + $controls.data('server-info', claimedRecording.recording.mix); // for recordingUtils helper methods + $controls.data('view-context', 'feed'); + + $('.timeago', $feedItem).timeago(); + context.JK.prettyPrintElements($('.duration', $feedItem)); + context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem)); + $('.details', $feedItem).click(toggleRecordingDetails); + $('.details-arrow', $feedItem).click(toggleRecordingDetails); + $('.play-button', $feedItem).click(toggleRecordingPlay); + + var checked = ''; + + var match = $.grep(selectedRecordings, function(obj, index) { + return obj.claimed_recording_id === claimedRecording.id; + }); + + if (match && match.length > 0) { + checked = 'checked'; + } + + // put the item on the page + $recordings.append("
    "); + $recordings.append($feedItem); + + // these routines need the item to have height to work (must be after renderFeed) + $controls.listenRecording({recordingId: claimedRecording.recording.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'}); + $controls.bind('statechange.listenRecording', stateChangeRecording); + $('.dotdotdot', $feedItem).dotdotdot(); + $feedItem.data('original-max-height', $feedItem.css('height')); + context.JK.bindHoverEvents($feedItem); + context.JK.bindProfileClickEvents($feedItem); + } + + function afterHide() { + } + + function showDialog() { + return app.layout.showDialog(dialogId); + } + + function events() { + $btnSelect.click(function(evt) { + evt.preventDefault(); + var preSelectedRecordings = []; + $recordings.find('input[type=checkbox]:checked').each(function(index) { + preSelectedRecordings.push({ + "id": $(this).attr('data-recording-id'), + "name": $(this).attr('data-recording-title') + }); + }); + + if (selectCallback) { + selectCallback(preSelectedRecordings); + } + + app.layout.closeDialog(dialogId); + + return false; + + }); + } + + function initialize() { + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterShow' : afterShow, + 'afterHide': afterHide + }; + + app.bindDialog(dialogId, dialogBindings); + + $instructions.html('Select one or more recordings and click ADD to add JamKazam recordings to your performance samples.'); + + events(); + } + + this.initialize = initialize; + this.showDialog = showDialog; + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/soundCloudPlayerDialog.js.coffee b/web/app/assets/javascripts/dialog/soundCloudPlayerDialog.js.coffee new file mode 100644 index 000000000..29d2670bf --- /dev/null +++ b/web/app/assets/javascripts/dialog/soundCloudPlayerDialog.js.coffee @@ -0,0 +1,40 @@ +$ = jQuery +context = window +context.JK ||= {} + +context.JK.SoundCloudPlayerDialog = class SoundCloudPlayerDialog + constructor: (@app) -> + @rest = context.JK.Rest() + @client = context.jamClient + @logger = context.JK.logger + @screen = null + @dialogId = 'sound-cloud-player-dialog' + @dialog = null + @player = null + + initialize:(@url, @caption) => + dialogBindings = { + 'beforeShow' : @beforeShow, + 'afterShow' : @afterShow + } + + @dialog = $('[layout-id="' + @dialogId + '"]') + @app.bindDialog(@dialogId, dialogBindings) + @player = @dialog.find(".sound-cloud-player") + @dialog.find(".caption").text("'#{@caption}'") + @player.addClass("hidden") + + beforeShow:() => + @player.addClass("hidden") + @player.attr("src", "") + u = encodeURIComponent(@url) + src = "https://w.soundcloud.com/player/?url=#{u}&auto_play=true&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true&loop=true" + @player.attr("src", src) + + afterShow:() => + @player.removeClass("hidden") + + showDialog:() => + @app.layout.showDialog(@dialogId) + + \ No newline at end of file diff --git a/web/app/assets/javascripts/feedHelper.js b/web/app/assets/javascripts/feedHelper.js index 9ae0b54db..9faf87f33 100644 --- a/web/app/assets/javascripts/feedHelper.js +++ b/web/app/assets/javascripts/feedHelper.js @@ -11,6 +11,7 @@ var ui = new context.JK.UIHelper(JK.app); var recordingUtils = context.JK.RecordingUtils; var userId = null; + var bandId = null; var currentFeedPage = 0; var feedBatchSize = 10; var $screen = null; @@ -35,6 +36,10 @@ query.user = userId; } + if(bandId) { + query.band = bandId; + } + return query; } @@ -83,6 +88,10 @@ userId = _userId; } + function setBand(_bandId) { + bandId = _bandId; + } + function refresh() { clearResults(); populate(); @@ -99,9 +108,9 @@ function populate() { if (isLoading || didLoadAllFeeds) return; - + setLoading(true); - + rest.getFeeds(buildQuery()) .done(function(response) { handleFeedResponse(response); @@ -242,7 +251,7 @@ return candidate; } else { - return recording.claimed_recordings[0] + return recording.claimed_recordings[0]; } } @@ -566,7 +575,6 @@ var winheight = $(scrollerID).height(); var docheight = $('#'+screenID()+'-feed-entry-list').height(); var scrollTrigger = 0.90; - //console.log("feed scroll: wintop="+wintop+" docheight="+docheight+" winheight="+winheight+" ratio="+(wintop / (docheight - winheight))); if ((wintop / (docheight - winheight)) > scrollTrigger) { populate(); } @@ -606,6 +614,7 @@ this.initialize = initialize; this.refresh = refresh; this.setUser = setUser; + this.setBand = setBand; return this; } diff --git a/web/app/assets/javascripts/instrumentSelector.js b/web/app/assets/javascripts/instrumentSelector.js index 57d6d6e51..51e740b75 100644 --- a/web/app/assets/javascripts/instrumentSelector.js +++ b/web/app/assets/javascripts/instrumentSelector.js @@ -4,18 +4,19 @@ context.JK = context.JK || {}; context.JK.InstrumentSelectorDeferred = null; - context.JK.InstrumentSelector = (function(app) { + context.JK.InstrumentSelector = (function(app, parentSelector) { var logger = context.JK.logger; var rest = new context.JK.Rest(); var _instruments = []; // will be list of structs: [ {label:xxx, value:yyy}, {...}, ... ] var _rsvp = false; - var _parentSelector = null; + if (typeof(_parentSelector)=="undefined") {_parentSelector=null} + var _parentSelector = parentSelector; var deferredInstruments = null; var self = this; function reset() { - $('input[type=checkbox]', _parentSelector).attr('checked', ''); + $('input[type="checkbox"]', _parentSelector).attr('checked', ''); if (_rsvp) { $('select.rsvp_count option', _parentSelector).eq(0).prop('selected', true); $('select.rsvp_level option', _parentSelector).eq(0).prop('selected', true); @@ -88,7 +89,7 @@ var $selectedVal = $('input[type="checkbox"]:checked', _parentSelector); $.each($selectedVal, function (index, value) { var id = $(value).attr('session-instrument-id'); - var name = $('label[for="' + $(value).attr('id') + '"]', _parentSelector).text(); + var name = $('label[for="' + $(value).attr('id') + '"]', _parentSelector).text().trim(); if (_rsvp) { var count = $('select[session-instrument-id="' + id + '"].rsvp-count', _parentSelector).val(); var rsvp_level = $('select[session-instrument-id="' + id + '"].rsvp-level', _parentSelector).val(); @@ -99,16 +100,16 @@ selectedInstruments.push({id: id, name: name, level: level}); } }); + return selectedInstruments; } function setSelectedInstruments(instrumentList) { if (!instrumentList) { return; - } - - $.each(instrumentList, function (index, value) { - $('input[type=checkbox][id="' + value.id + '"]') + } + $.each(instrumentList, function (index, value) { + $('input[type="checkbox"][session-instrument-id="' + value.id + '"]') .attr('checked', 'checked') .iCheck({ checkboxClass: 'icheckbox_minimal', @@ -116,11 +117,11 @@ inheritClass: true }); if (_rsvp) { - $('select[session-instrument-id="' + value.value + '"].rsvp-count', _parentSelector).val(value.count); - $('select[session-instrument-id="' + value.value + '"].rsvp-level', _parentSelector).val(value.level); + $('select[session-instrument-id="' + value.id + '"].rsvp-count', _parentSelector).val(value.count); + $('select[session-instrument-id="' + value.id + '"].rsvp-level', _parentSelector).val(value.level); } else { - $('select[session-instrument-id="' + value.value + '"]').val(value.level); + $('select[session-instrument-id="' + value.id + '"]').val(value.level); } }); } diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 82773d503..a05a24827 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -188,7 +188,7 @@ if (includePending) { includeFlag = 'true'; } - + return $.ajax({ type: "GET", dataType: "json", @@ -505,6 +505,20 @@ return detail; } + function getUserProfile(options) { + var id = getId(options); + var profile = null; + if (id != null && typeof(id) != 'undefined') { + profile = $.ajax({ + type: "GET", + dataType: "json", + url: "/api/users/" + id + "/profile", + processData: false + }); + } + return profile; + } + function createAffiliatePartner(options) { return $.ajax({ type: "POST", @@ -1273,7 +1287,7 @@ function openBackingTrack(options) { var musicSessionId = options["id"]; delete options["id"]; - + return $.ajax({ type: "POST", dataType: "json", @@ -1336,7 +1350,7 @@ function openMetronome(options) { var musicSessionId = options["id"]; delete options["id"]; - + return $.ajax({ type: "POST", dataType: "json", @@ -1345,11 +1359,11 @@ data: JSON.stringify(options) }) } - + function closeMetronome(options) { var musicSessionId = options["id"]; delete options["id"]; - + return $.ajax({ type: "POST", dataType: "json", @@ -1358,7 +1372,7 @@ data: JSON.stringify(options) }) } - + function discardRecording(options) { var recordingId = options["id"]; @@ -1597,7 +1611,7 @@ url: '/api/recurly/payment_history', dataType: "json", contentType: 'application/json' - }); + }); } function getSalesHistory(options) { @@ -1693,7 +1707,7 @@ function placeOrder() { return $.ajax({ - type: "POST", + type: "POST", url: '/api/recurly/place_order', dataType: "json", contentType: 'application/json' @@ -1707,6 +1721,19 @@ }); } + function getMusicianSearchFilter(query) { + var qarg = query === undefined ? '' : query; + return $.get("/api/search/musicians.json?"+qarg); + } + + function postMusicianSearchFilter(query) { + return $.ajax({ + type: "POST", + url: "/api/search/musicians.json", + data: query + }); + } + function getMount(options) { var id = getId(options); return $.ajax({ @@ -1807,6 +1834,7 @@ this.cancelSession = cancelSession; this.updateScheduledSession = updateScheduledSession; this.getUserDetail = getUserDetail; + this.getUserProfile = getUserProfile; this.getAffiliatePartnerData = getAffiliatePartnerData; this.postAffiliatePartnerData = postAffiliatePartnerData; this.createAffiliatePartner = createAffiliatePartner; @@ -1889,7 +1917,7 @@ this.openJamTrack = openJamTrack this.openBackingTrack = openBackingTrack this.closeBackingTrack = closeBackingTrack - this.closeMetronome = closeMetronome; + this.closeMetronome = closeMetronome; this.closeJamTrack = closeJamTrack; this.openMetronome = openMetronome; this.closeMetronome = closeMetronome; @@ -1947,6 +1975,8 @@ this.validateUrlSite = validateUrlSite; this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent; this.addRecordingTimeline = addRecordingTimeline; + this.getMusicianSearchFilter = getMusicianSearchFilter; + this.postMusicianSearchFilter = postMusicianSearchFilter; this.playJamTrack = playJamTrack; this.createSignupHint = createSignupHint; this.createAlert = createAlert; @@ -1954,5 +1984,4 @@ this.portOverCarts = portOverCarts; return this; }; - })(window,jQuery); diff --git a/web/app/assets/javascripts/musician_search_filter.js.coffee b/web/app/assets/javascripts/musician_search_filter.js.coffee new file mode 100644 index 000000000..4187d6acd --- /dev/null +++ b/web/app/assets/javascripts/musician_search_filter.js.coffee @@ -0,0 +1,417 @@ +$ = jQuery +context = window +context.JK ||= {}; + +context.JK.MusicianSearchFilter = class MusicianSearchFilter + + constructor: () -> + @rest = context.JK.Rest() + @logger = context.JK.logger + @searchFilter = null + @profileUtils = context.JK.ProfileUtils + @helpBubble = context.JK.HelpBubbleHelper + @searchResults = null + @isSearching = false + @pageNumber = 1 + @instrument_logo_map = context.JK.getInstrumentIconMap24() + + init: (app) => + @app = app + @screenBindings = { 'afterShow': this.afterShow, 'afterHide': this.afterHide } + @app.bindScreen('musicians', @screenBindings) + + @screen = $('#musicians-screen') + @resultsListContainer = @screen.find('#musician-search-filter-results-list') + @spinner = @screen.find('.paginate-wait') + + this.registerResultsPagination() + + @screen.find('#btn-musician-search-builder').on 'click', => + this.showBuilder() + + @screen.find('#btn-musician-search-reset').on 'click', => + this.resetFilter() + + afterShow: () => + @screen.find('#musician-search-filter-results').show() + @screen.find('#musician-search-filter-builder').hide() + this.getUserFilterResults() + + showBuilder: () => + @screen.find('#musician-search-filter-results').hide() + @screen.find('#musician-search-filter-builder').show() + @resultsListContainer.empty() + + afterHide: () => + @resultsListContainer.empty() + + renderSearchFilter: () => + $.when(this.rest.getMusicianSearchFilter()).done (sFilter) => + this.loadSearchFilter(sFilter) + + loadSearchFilter: (sFilter) => + @searchFilter = JSON.parse(sFilter) + args = + interests: @searchFilter.data_blob.interests + skill_level: @searchFilter.data_blob.skill_level + studio_sessions: @searchFilter.data_blob.studio_sessions + concert_gigs: @searchFilter.data_blob.concert_gigs + + template = context.JK.fillTemplate(@screen.find('#template-musician-search-filter').html(), args) + + content_root = @screen.find('#musician-search-filter-builder') + content_root.html template + + @screen.find('#btn-perform-musician-search').on 'click', => + this.performSearch() + + @screen.find('#btn-musician-search-cancel').on 'click', => + this.cancelFilter() + + this._populateSkill() + this._populateStudio() + this._populateGigs() + this._populateInterests() + this._populateAges() + this._populateGenres() + this._populateInstruments() + this._populateSortOrder() + + + + _populateSelectWithKeys: (struct, selection, keys, element) => + element.children().remove() + $.each keys, (idx, value) => + label = struct[value] + blankOption = $ '' + blankOption.text label + blankOption.attr 'value', value + blankOption.attr 'selected', '' if value == selection + element.append(blankOption) + context.JK.dropdown(element) + + _populateSelectIdentifier: (identifier) => + elem = $ '#musician-search-filter-builder select[name='+identifier+']' + struct = gon.musician_search_meta[identifier]['map'] + keys = gon.musician_search_meta[identifier]['keys'] + this._populateSelectWithKeys(struct, @searchFilter[identifier], keys, elem) + + _populateSelectWithInt: (sourceStruct, selection, element) => + struct = + '-1': 'Any' + $.extend(struct, sourceStruct) + this._populateSelectWithKeys(struct, selection, Object.keys(struct).sort(), element) + + _populateSortOrder: () => + this._populateSelectIdentifier('sort_order') + + _populateInterests: () => + this._populateSelectIdentifier('interests') + + _populateStudio: () => + elem = $ '#musician-search-filter-builder select[name=studio_sessions]' + this._populateSelectWithInt(@profileUtils.studioMap, @searchFilter.data_blob.studio_sessions.toString(), elem) + + _populateGigs: () => + elem = $ '#musician-search-filter-builder select[name=concert_gigs]' + this._populateSelectWithInt(@profileUtils.gigMap, @searchFilter.data_blob.concert_gigs.toString(), elem) + + _populateSkill: () => + elem = $ '#musician-search-filter-builder select[name=skill_level]' + this._populateSelectWithInt(@profileUtils.skillLevelMap, @searchFilter.data_blob.skill_level.toString(), elem) + + _populateAges: () => + @screen.find('#search-filter-ages').empty() + ages_map = gon.musician_search_meta['ages']['map'] + $.each gon.musician_search_meta['ages']['keys'], (index, key) => + ageTemplate = @screen.find('#template-search-filter-setup-ages').html() + selected = '' + ageLabel = ages_map[key] + if 0 < @searchFilter.data_blob.ages.length + key_val = key.toString() + ageMatch = $.grep(@searchFilter.data_blob.ages, (n, i) -> + n == key_val) + selected = 'checked' if ageMatch.length > 0 + ageHtml = context.JK.fillTemplate(ageTemplate, + id: key + description: ageLabel + checked: selected) + @screen.find('#search-filter-ages').append ageHtml + + _populateGenres: () => + @screen.find('#search-filter-genres').empty() + @rest.getGenres().done (genres) => + genreTemplate = @screen.find('#template-search-filter-setup-genres').html() + selected = '' + $.each genres, (index, genre) => + if 0 < @searchFilter.data_blob.genres.length + genreMatch = $.grep(@searchFilter.data_blob.genres, (n, i) -> + n == genre.id) + else + genreMatch = [] + selected = 'checked' if genreMatch.length > 0 + genreHtml = context.JK.fillTemplate(genreTemplate, + id: genre.id + description: genre.description + checked: selected) + @screen.find('#search-filter-genres').append genreHtml + + _populateInstruments: () => + @screen.find('#search-filter-instruments').empty() + @rest.getInstruments().done (instruments) => + $.each instruments, (index, instrument) => + instrumentTemplate = @screen.find('#template-search-filter-setup-instrument').html() + selected = '' + proficiency = '1' + if 0 < @searchFilter.data_blob.instruments.length + instMatch = $.grep(@searchFilter.data_blob.instruments, (inst, i) -> + yn = inst.instrument_id == instrument.id + proficiency = inst.proficiency_level if yn + yn) + selected = 'checked' if instMatch.length > 0 + instrumentHtml = context.JK.fillTemplate(instrumentTemplate, + id: instrument.id + description: instrument.description + checked: selected) + @screen.find('#search-filter-instruments').append instrumentHtml + profsel = '#search-filter-instruments tr[data-instrument-id="'+instrument.id+'"] select' + jprofsel = @screen.find(profsel) + jprofsel.val(proficiency) + context.JK.dropdown(jprofsel) + return true + + _builderSelectValue: (identifier) => + elem = $ '#musician-search-filter-builder select[name='+identifier+']' + elem.val() + + _builderSelectMultiValue: (identifier) => + vals = [] + elem = $ '#search-filter-'+identifier+' input[type=checkbox]:checked' + if 'instruments' == identifier + elem.each (idx) -> + row = $(this).parent().parent() + instrument = + instrument_id: row.data('instrument-id') + proficiency_level: row.find('select').val() + vals.push instrument + else + elem.each (idx) -> + vals.push $(this).val() + vals + + willSearch: (reload) => + return false if @isSearching + @isSearching = true + if reload + @pageNumber = 1 + @screen.find('#musician-search-filter-spinner').show() + @resultsListContainer.empty() + @screen.find('#musician-search-filter-builder').hide() + @screen.find('#musician-search-filter-results').show() + true + + didSearch: (response) => + this.loadSearchFilter(response.filter_json) + @searchResults = response + @screen.find('#musician-search-filter-spinner').hide() + this.renderMusicians() + @screen.find('.paginate-wait').hide() + @isSearching = false + + resetFilter: () => + if this.willSearch(true) + @rest.postMusicianSearchFilter({ filter: 'reset' }).done(this.didSearch) + + cancelFilter: () => + this.resetFilter() + + getUserFilterResults: () => + if this.willSearch(true) + @rest.getMusicianSearchFilter('results=true').done(this.didSearch) + + performSearch: () => + if this.willSearch(true) + $.each gon.musician_search_meta.filter_keys.single, (index, key) => + @searchFilter[key] = this._builderSelectValue(key) + $.each gon.musician_search_meta.filter_keys.multi, (index, key) => + @searchFilter[key] = this._builderSelectMultiValue(key) + @rest.postMusicianSearchFilter({ filter: JSON.stringify(@searchFilter), page: @pageNumber }).done(this.didSearch) + + renderResultsHeader: () => + @screen.find('#musician-search-filter-description').html(@searchResults.description) + if @searchResults.is_blank_filter + @screen.find('#btn-musician-search-reset').hide() + else + @screen.find('#btn-musician-search-reset').show() + + renderMusicians: () => + this.renderResultsHeader() if @pageNumber == 1 + musicians = @searchResults.musicians + len = musicians.length + if 0 == len + @screen.find('#musician-search-filter-results-list-blank').show() + @screen.find('#musician-search-filter-results-list-blank').html('No results found') + return + else + @screen.find('#musician-search-filter-results-list-blank').hide() + + ii = 0 + mTemplate = @screen.find('#template-search-musician-row').html() + aTemplate = @screen.find('#template-search-musician-action-btns').html() + mVals = undefined + musician = undefined + renderings = '' + instr_logos = undefined + follows = undefined + followVals = undefined + aFollow = undefined + myAudioLatency = @searchResults.my_audio_latency + while ii < len + musician = musicians[ii] + if context.JK.currentUserId == musician.id + ii++ + continue + instr_logos = '' + jj = 0 + ilen = musician['instruments'].length + while jj < ilen + instr_id = musician['instruments'][jj].instrument_id + if instr_img = @instrument_logo_map[instr_id] + instr_logos += '' + jj++ + actionVals = + profile_url: '/client#/profile/' + musician.id + friend_class: 'button-' + (if musician['is_friend'] then 'grey' else 'orange') + friend_caption: (if musician.is_friend then 'DIS' else '') + 'CONNECT' + follow_class: 'button-' + (if musician['is_following'] then 'grey' else 'orange') + follow_caption: (if musician.is_following then 'UN' else '') + 'FOLLOW' + message_class: 'button-orange' + message_caption: 'MESSAGE' + button_message: 'button-orange' + musician_actions = context.JK.fillTemplate(aTemplate, actionVals) + latencyBadge = context._.template($("#template-account-session-latency").html(), $.extend(sessionUtils.createLatency(musician), musician), variable: 'data') + mVals = + avatar_url: context.JK.resolveAvatarUrl(musician.photo_url) + profile_url: '/client#/profile/' + musician.id + musician_name: musician.name + musician_location: this._formatLocation(musician) + instruments: instr_logos + biography: musician['biography'] + follow_count: musician['follow_count'] + friend_count: musician['friend_count'] + recording_count: musician['recording_count'] + session_count: musician['session_count'] + musician_id: musician['id'] + musician_action_template: musician_actions + latency_badge: latencyBadge + musician_first_name: musician['first_name'] + $rendering = $(context.JK.fillTemplate(mTemplate, mVals)) + $offsetParent = @resultsListContainer.closest('.content') + data = entity_type: 'musician' + options = + positions: [ + 'top' + 'bottom' + 'right' + 'left' + ] + offsetParent: $offsetParent + scoreOptions = offsetParent: $offsetParent + context.JK.helpBubble($('.follower-count', $rendering), 'follower-count', data, options); + context.JK.helpBubble($('.friend-count', $rendering), 'friend-count', data, options); + context.JK.helpBubble($('.recording-count', $rendering), 'recording-count', data, options); + context.JK.helpBubble($('.session-count', $rendering), 'session-count', data, options); + @helpBubble.scoreBreakdown $('.latency', $rendering), false, musician['full_score'], myAudioLatency, musician['audio_latency'], musician['score'], scoreOptions + @resultsListContainer.append $rendering + $rendering.find('.biography').dotdotdot() + ii++ + + this._bindMessageMusician() + this._bindFriendMusician() + this._bindFollowMusician() + + context.JK.bindHoverEvents() + return + + _bindMessageMusician: () => + objThis = this + @screen.find('.search-m-message').on 'click', (evt) -> + userId = $(this).parent().data('musician-id') + objThis.app.layout.showDialog 'text-message', d1: userId + + + _bindFriendMusician: () => + objThis = this + @screen.find('.search-m-friend').on 'click', (evt) -> + # if the musician is already a friend, remove the button-orange class, and prevent the link from working + if 0 == $(this).closest('.button-orange').size() + return false + $(this).click (ee) -> + ee.preventDefault() + return + evt.stopPropagation() + uid = $(this).parent().data('musician-id') + objThis.rest.sendFriendRequest objThis.app, uid, this.friendRequestCallback + + _bindFollowMusician: () => + objThis = this + @screen.find('.search-m-follow').on 'click', (evt) -> + # if the musician 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 (ee) -> + ee.preventDefault() + return + evt.stopPropagation() + newFollowing = {} + newFollowing.user_id = $(this).parent().data('musician-id') + url = '/api/users/' + context.JK.currentUserId + '/followings' + $.ajax + type: 'POST' + dataType: 'json' + contentType: 'application/json' + url: url + data: JSON.stringify(newFollowing) + processData: false + success: (response) -> + # remove the orange look to indicate it's not selectable + # @FIXME -- this will need to be tweaked when we allow unfollowing + objThis.screen.find('div[data-musician-id=' + newFollowing.user_id + '] .search-m-follow').removeClass('button-orange').addClass 'button-grey' + return + error: objThis.app.ajaxError + + _formatLocation: (musician) -> + if musician.city and musician.state + musician.city + ', ' + musician.state + else if musician.city + musician.city + else if musician.regionname + musician.regionname + else + 'Location Unavailable' + + friendRequestCallback: (user_id)=> + # TODO: + + paginate: () => + if @pageNumber < @searchResults.page_count && this.willSearch(false) + @screen.find('.paginate-wait').show() + @pageNumber += 1 + @rest.postMusicianSearchFilter({ filter: JSON.stringify(@searchFilter), page: @pageNumber }).done(this.didSearch) + return true + false + + registerResultsPagination: () => + _resultsListContainer = @resultsListContainer + _headerHeight = @screen.find('#musician-search-filter-results-header').height() + _paginator = this.paginate + + @screen.find('.content-body-scroller').scroll -> + if _resultsListContainer.is(':visible') + jthis = $(this) + wintop = jthis.scrollTop() + winheight = jthis.innerHeight() + docheight = jthis[0].scrollHeight - _headerHeight + scrollTrigger = 0.98; + if ((wintop / (docheight - winheight)) >= scrollTrigger) + _paginator() diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js index 49543492d..fe616b7d7 100644 --- a/web/app/assets/javascripts/profile.js +++ b/web/app/assets/javascripts/profile.js @@ -11,24 +11,107 @@ var rest = context.JK.Rest(); var decrementedFriendCountOnce = false; var sentFriendRequest = false; - var profileScreen = null; var textMessageDialog = null; var feed = null; + var player = null; + var profileUtils = context.JK.ProfileUtils; + + var NOT_SPECIFIED_TEXT = 'Not specified'; + + var $screen = $('#user-profile'); + + var $biography = $screen.find('#biography'); + + // musical experience + var $instruments = $screen.find('#instruments'); + var $musicianStatus = $screen.find('#musician-status'); + var $genres = $screen.find('#genres'); + var $concertCount = $screen.find('#concert-count'); + var $studioCount = $screen.find('#studio-count'); + + // performance samples + var $noSamples = $screen.find('.no-samples'); + var $jamkazamSamples = $screen.find('.jamkazam-samples'); + var $soundCloudSamples = $screen.find('.soundcloud-samples'); + var $youTubeSamples = $screen.find('.youtube-samples'); + + // online presence + var $noOnlinePresence = $screen.find('.no-online-presence'); + var $userWebsite = $screen.find('.user-website'); + var $soundCloudPresence = $screen.find('.soundcloud-presence'); + var $reverbNationPresence = $screen.find('.reverbnation-presence'); + var $bandCampPresence = $screen.find('.bandcamp-presence'); + var $fandalismPresence = $screen.find('.fandalism-presence'); + var $youTubePresence = $screen.find('.youtube-presence'); + var $facebookPresence = $screen.find('.facebook-presence'); + var $twitterPresence = $screen.find('.twitter-presence'); + + // current interests + var $noInterests = $screen.find('#no-interests'); + var $paidGigSection = $screen.find('#paid-gigs'); + var $paidGigDetails = $screen.find('#paid-gig-details'); + + var $freeGigSection = $screen.find('#free-gigs'); + var $freeGigDetails = $screen.find('#free-gig-details'); + + var $cowritingSection = $screen.find('#cowriting'); + var $cowritingDetails = $screen.find('#cowriting-details'); + + var $traditionalBandSection = $screen.find('#traditional-band'); + var $traditionalBandDetails = $screen.find('#traditional-band-details'); + + var $virtualBandSection = $screen.find('#virtual-band'); + var $virtualBandDetails = $screen.find('#virtual-band-details'); + + // tabs + var $aboutLink = $screen.find('#about-link'); + var $aboutContent = $screen.find('#about-content'); + + var $historyLink = $screen.find('#history-link'); + var $historyContent = $screen.find('#history-content'); + + var $bandsLink = $screen.find('#bands-link'); + var $bandsContent = $screen.find('#bands-content'); + + var $socialLink = $screen.find('#social-link'); + var $socialContent = $screen.find('#social-content'); + + var $favoritesLink = $screen.find('#favorites-link'); + var $favoritesContent = $screen.find('#favorites-content'); + + // stats + var $friendStats = $screen.find('#friend-stats'); + var $followerStats = $screen.find('#follower-stats'); + var $sessionStats = $screen.find('#session-stats'); + var $recordingStats = $screen.find('#recording-stats'); + var $followingStats = $screen.find('#following-stats'); + var $favoriteStats = $screen.find('#favorite-stats'); + + // miscellaneous + var $userName = $screen.find('#username'); + var $avatar = $screen.find('#avatar'); + var $typeLabel = $screen.find('#type-label'); + var $location = $screen.find('#location'); + var $age = $screen.find('#age'); + + // buttons + var $btnEdit = $screen.find('#btn-edit'); + var $btnAddFriend = $screen.find('#btn-add-friend'); + var $btnFollowUser = $screen.find('#btn-follow-user'); + var $btnMessageUser = $screen.find('#btn-message-user'); + var $btnEditBio = $screen.find(".add-bio"); + var $btnAddRecordings = $screen.find('.add-recordings'); + var $btnAddSites = $screen.find('.add-sites'); + var $btnAddInterests = $screen.find('.add-interests'); + + // social + var $socialLeft = $screen.find('.profile-social-left'); + var $socialFriends = $screen.find('#social-friends'); + var $socialFollowings = $screen.find('#social-followings'); + var $socialFollowers = $screen.find('#social-followers'); var instrument_logo_map = context.JK.getInstrumentIconMap24(); - var proficiencyDescriptionMap = { - "1": "BEGINNER", - "2": "INTERMEDIATE", - "3": "EXPERT" - }; - - var proficiencyCssMap = { - "1": "proficiency-beginner", - "2": "proficiency-intermediate", - "3": "proficiency-expert" - }; - function beforeShow(data) { userId = data.id; feed.setUser(userId); @@ -37,6 +120,7 @@ function afterShow(data) { initUser(); resetForm(); + renderAllStats(); } function beforeHide(data) { @@ -44,27 +128,28 @@ } function resetForm() { - $('#profile-instruments').empty(); + $instruments.empty(); - $('#profile-about').show(); - $('#profile-history').hide(); - $('#profile-bands').hide(); - $('#profile-social').hide(); - $('#profile-favorites').hide(); + $aboutContent.show(); + $historyContent.hide(); + $bandsContent.hide(); + $socialContent.hide(); + $favoritesContent.hide(); $('.profile-nav a.active').removeClass('active'); - $('.profile-nav a#profile-about-link').addClass('active'); + $aboutLink.addClass('active'); } function initUser() { user = null; decrementedFriendCountOnce = false; sentFriendRequest = false; - userDefer = rest.getUserDetail({id: userId}) + userDefer = rest.getUserProfile({id: userId}) .done(function (response) { user = response; configureUserType(); renderActive(); + renderAllStats(); }) .fail(function (jqXHR) { if (jqXHR.status >= 500) { @@ -89,43 +174,41 @@ 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(); - $('.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(); - $('.profile-social-left').hide(); - $('#profile-type-label').text('fan'); - $('#profile-location-label').text('Presence'); + $historyLink.show(); + $bandsLink.show(); + $instruments.show(); + $sessionStats.show(); + $recordingStats.show(); + // $followingStats.hide(); + // $favoriteStats.hide(); + $socialLeft.show(); + $typeLabel.text('musician'); + $location.text('Location'); + } else { + $historyLink.hide(); + $bandsLink.hide(); + $instruments.hide(); + $sessionStats.hide(); + $recordingStats.hide(); + // $followingStats.show(); + // $favoriteStats.show(); + $socialLeft.hide(); + $typeLabel.text('fan'); + $location.text('Presence'); } if (isCurrentUser()) { - $('#btn-profile-edit').show(); - $('#btn-add-friend').hide(); - $('#btn-follow-user').hide(); - $('#btn-message-user').hide(); - } - else { + $btnEdit.show(); + $btnAddFriend.hide(); + $btnFollowUser.hide(); + $btnMessageUser.hide(); + } else { configureFriendFollowersControls(); - $('#btn-profile-edit').hide(); - $('#btn-add-friend').show(); - $('#btn-follow-user').show(); - $('#btn-message-user').show(); + $btnEdit.hide(); + $btnAddFriend.show(); + $btnFollowUser.show(); + $btnMessageUser.show(); } } @@ -141,33 +224,48 @@ // events for main screen function events() { // wire up panel clicks -- these need to check deferred because they can't be hidden when in an invalid state - $('#profile-about-link').click(function () { + $aboutLink.click(function () { renderTabDeferred(renderAbout) }); - $('#profile-history-link').click(function () { + + $historyLink.click(function () { renderTabDeferred(renderHistory) }); - $('#profile-bands-link').click(function () { + + $bandsLink.click(function () { renderTabDeferred(renderBands) }); - $('#profile-social-link').click(function () { + + $socialLink.click(function () { renderTabDeferred(renderSocial) }); - $('#profile-favorites-link').click(function () { + + $favoritesLink.click(function () { renderTabDeferred(renderFavorites) }); // this doesn't need deferred because it's only shown when valid - $('#btn-add-friend').click(handleFriendChange); - $('#btn-follow-user').click(handleFollowingChange); - $('#btn-message-user').click(handleMessageMusician); + $btnAddFriend.click(handleFriendChange); + $btnFollowUser.click(handleFollowingChange); + $btnMessageUser.click(handleMessageMusician); + + // Hook up soundcloud player: + $soundCloudSamples.off("click", "a.sound-cloud-playable") .on("click", "a.sound-cloud-playable", playSoundCloudFile) + } + + function playSoundCloudFile(e) { + e.preventDefault(); + var url = $(this).attr("soundcloud_url") + var cap = $(this).text() + player.initialize(url, cap); + app.layout.showDialog('sound-cloud-player-dialog'); + return false; } function handleFriendChange(evt) { if (isFriend()) { removeFriend(evt); - } - else { + } else { sendFriendRequest(evt); } return false; @@ -176,8 +274,7 @@ function handleFollowingChange(evt) { if (isFollowing()) { removeFollowing(false, userId); - } - else { + } else { addFollowing(); } return false; @@ -221,10 +318,9 @@ function configureFriendButton() { if (isFriend()) { - $('#btn-add-friend').text('DISCONNECT'); - } - else { - $('#btn-add-friend').text('CONNECT'); + $btnAddFriend.text('DISCONNECT'); + } else { + $btnAddFriend.text('CONNECT'); } } @@ -267,32 +363,31 @@ function configureFollowingButton() { if (isFollowing()) { - $('#btn-follow-user').text('UNFOLLOW'); - } - else { - $('#btn-follow-user').text('FOLLOW'); + $btnFollowUser.text('UNFOLLOW'); + } else { + $btnFollowUser.text('FOLLOW'); } } function configureEditProfileButton() { - $('#btn-follow-user').click(addFollowing); + $btnFollowUser.click(addFollowing); } // refreshes the currently active tab function renderActive() { - if ($('#profile-about-link').hasClass('active')) { + if ($aboutLink.hasClass('active')) { renderAbout(); } - else if ($('#profile-history-link').hasClass('active')) { + else if ($historyLink.hasClass('active')) { renderHistory(); } - else if ($('#profile-bands-link').hasClass('active')) { + else if ($bandsLink.hasClass('active')) { renderBands(); } - else if ($('#profile-social-link').hasClass('active')) { + else if ($socialLink.hasClass('active')) { renderSocial(); } - else if ($('#profile-favorites-link').hasClass('active')) { + else if ($favoritesLink.hasClass('active')) { renderFavorites(); } } @@ -308,187 +403,203 @@ }) } - /****************** ABOUT TAB *****************/ - function renderAbout() { - $('#profile-instruments').empty(); - - $('#profile-about').show(); - $('#profile-history').hide(); - $('#profile-bands').hide(); - $('#profile-social').hide(); - $('#profile-favorites').hide(); - - $('.profile-nav a.active').removeClass('active'); - $('.profile-nav a#profile-about-link').addClass('active'); - - bindAbout(); + function hideElements(elements) { + $.each(elements, function(index, val) { + val.hide(); + }); } - function bindAbout() { + /****************** ABOUT TAB *****************/ + function renderAbout() { + $instruments.empty(); - $('#profile-instruments').empty(); + $aboutContent.show(); + $historyContent.hide(); + $bandsContent.hide(); + $socialContent.hide(); + $favoritesContent.hide(); + $('.profile-nav a.active').removeClass('active'); + $aboutLink.addClass('active'); + } + + function renderAllStats() { + + if (!isCurrentUser()) { + $btnEditBio.hide(); + $btnAddRecordings.hide(); + $btnAddSites.hide(); + $btnAddInterests.hide(); + } + + if (user && $userName) { + renderNameLocationStats(); + renderBio(); + renderMusicalExperience(); + renderPerformanceSamples(); + renderOnlinePresence(); + renderInterests(); + } + } + + function renderNameLocationStats() { // name - $('#profile-username').html(user.name); + $userName.html(user.name); // avatar - $('#profile-avatar').attr('src', context.JK.resolveAvatarUrl(user.photo_url)); - - // instruments - if (user.instruments) { - for (var i = 0; i < user.instruments.length; i++) { - var instrument = user.instruments[i]; - var description = instrument.instrument_id; - var proficiency = instrument.proficiency_level; - var instrument_icon_url = context.JK.getInstrumentIcon256(description); - - // add instrument info to layout - var template = $('#template-profile-instruments').html(); - var instrumentHtml = context.JK.fillTemplate(template, { - instrument_logo_url: instrument_icon_url, - instrument_description: description, - proficiency_level: proficiencyDescriptionMap[proficiency], - proficiency_level_css: proficiencyCssMap[proficiency] - }); - - $('#profile-instruments').append(instrumentHtml); - } - } - $('#profile-genres').empty(); - for (var i=0; i< user.genres.length; i++) { - $('#profile-genres').append(user.genres[i].description + '
    '); - } + $avatar.attr('src', context.JK.resolveAvatarUrl(user.photo_url)); // location - $('#profile-location').html(user.location); + $location.html(user.location); + + $age.html(user.age ? user.age + " years old" : ""); // stats var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend"; - $('#profile-friend-stats').html('' + user.friend_count + '' + text); + $friendStats.html('' + user.friend_count + '' + text); text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower"; - $('#profile-follower-stats').html('' + user.follower_count + '' + text); + $followerStats.html('' + user.follower_count + '' + text); if (isMusician()) { text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session"; - $('#profile-session-stats').html(user.session_count + text); + $sessionStats.html(user.session_count + text); text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording"; - $('#profile-recording-stats').html(user.recording_count + text); + $recordingStats.html(user.recording_count + text); } else { text = " Following"; - $('#profile-following-stats').html(user.following_count + text); + $followingStats.html(user.following_count + text); text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite"; - $('#profile-favorite-stats').html(user.favorite_count + text); + $favoriteStats.html(user.favorite_count + text); } - - renderBio(); } - /** The biography show/edit functionality */ function renderBio() { + $biography.html(user.biography ? user.biography : NOT_SPECIFIED_TEXT); + if (isCurrentUser() && !user.biography) { + $btnEditBio.show(); + } else { + $btnEditBio.hide(); + } + } + function renderMusicalExperience() { + profileUtils.renderMusicalExperience(user, $screen) + } - function initializeBioVisibility() { + function renderPerformanceSamples() { + profileUtils.renderPerformanceSamples(user, $screen) + } - $showBio.hide(); - $noBio.hide(); - $biographyEditor.hide(); + function renderOnlinePresence() { + profileUtils.renderOnlinePresence(user, $screen) + } - $bioTextArea.val(user.biography); + function renderInterests() { + // current interests + var noInterests = !user.paid_sessions && !user.free_sessions && !user.cowriting && !user.virtual_band && !user.traditional_band; + if (noInterests) { + $noInterests.show(); + $paidGigSection.hide(); + $freeGigSection.hide(); + $cowritingSection.hide(); + $traditionalBandSection.hide(); + $virtualBandSection.hide(); - if(user.biography) { - - $showBio.show(); - if(isCurrentUser()) { - $editBiographyButton.show(); - } - else { - $editBiographyButton.hide(); - } - $biographyText.text(user.biography).show(); + if (isCurrentUser()) { + $btnAddInterests.show(); } - else { - if(isCurrentUser()) { - $noBio.show(); - } + } else { + $btnAddInterests.hide(); + $noInterests.hide(); + + // paid sessions + if (user.paid_sessions) { + $paidGigSection.show(); + + var genreList = profileUtils.paidSessionGenreList(user.genres); + $paidGigDetails.find("ul").html(""); + $paidGigDetails.find("ul").append('
  • Genre(s): ' + (genreList.length > 0 ? genreList : NOT_SPECIFIED_TEXT) + '
  • '); + + var hourlyRate = user.paid_sessions_hourly_rate; + $paidGigDetails.find("ul").append('
  • Hourly rate = ' + (hourlyRate ? hourlyRate : NOT_SPECIFIED_TEXT) + '
  • '); + + var dailyRate = user.paid_sessions_daily_rate; + $paidGigDetails.find("ul").append('
  • Day rate = ' + (dailyRate ? dailyRate : NOT_SPECIFIED_TEXT) + '
  • '); + } else { + $paidGigSection.hide(); + } + + // free sessions + if (user.free_sessions) { + $freeGigSection.show(); + $freeGigDetails.find("ul").html(""); + var genreList = profileUtils.freeSessionGenreList(user.genres); + $freeGigDetails.find("ul").append('
  • Genre(s): ' + (genreList.length > 0 ? genreList : NOT_SPECIFIED_TEXT) + '
  • '); + } else { + $freeGigSection.hide(); + } + + // cowriting + if (user.cowriting) { + $cowritingSection.show(); + $cowritingDetails.find("ul").html(""); + var genreList = profileUtils.cowritingGenreList(user.genres); + $cowritingDetails.find("ul").append('
  • Genre(s): ' + (genreList.length > 0 ? genreList : NOT_SPECIFIED_TEXT) + '
  • '); + + var purpose = user.cowriting_purpose; + $cowritingDetails.find("ul").append('
  • Purpose: ' + (purpose ? profileUtils.cowritingPurposeMap[purpose] : NOT_SPECIFIED_TEXT) + '
  • '); + } else { + $cowritingSection.hide(); + } + + // traditional bands + if (user.traditional_band) { + $traditionalBandSection.show(); + $traditionalBandDetails.find("ul").html(""); + var genreList = profileUtils.traditionalBandGenreList(user.genres); + $traditionalBandDetails.find("ul").append('
  • Genre(s): ' + (genreList.length > 0 ? genreList : NOT_SPECIFIED_TEXT) + '
  • '); + + var commitment = user.traditional_band_commitment; + $traditionalBandDetails.find("ul").append('
  • Commitment: ' + (commitment ? profileUtils.bandCommitmentMap[commitment] : NOT_SPECIFIED_TEXT) + '
  • '); + + var canTour = user.traditional_band_touring; + var canTourResponse = canTour ? "Yes" : (canTour === false ? "No" : NOT_SPECIFIED_TEXT); + $traditionalBandDetails.find("ul").append('
  • Touring: ' + canTourResponse + '
  • '); + } else { + $traditionalBandSection.hide(); + } + + // virtual band + if (user.virtual_band) { + $virtualBandSection.show(); + $virtualBandDetails.find("ul").html(""); + var genreList = profileUtils.virtualBandGenreList(user.genres); + $virtualBandDetails.find("ul").append('
  • Genre(s): ' + (genreList.length > 0 ? genreList : NOT_SPECIFIED_TEXT) + '
  • '); + + var commitment = user.virtual_band_commitment; + $virtualBandDetails.find("ul").append('
  • Commitment: ' + (commitment ? profileUtils.bandCommitmentMap[commitment] : NOT_SPECIFIED_TEXT) + '
  • '); + } else { + $virtualBandSection.hide(); } } - - var $bioTextArea = $('.user-biography', profileScreen); - var $showBio = $('.have-bio', profileScreen); - var $noBio = $('.no-bio', profileScreen); - var $biographyEditor = $('.update-biography', profileScreen); - var $addBiographyButton = $('a.enter-bio', profileScreen); - var $editBiographyButton = $('#profile-edit-biography', profileScreen); - var $submitBiographyButton = $('#btn-update-user-biography', profileScreen); - var $cancelBiographyButton = $('#btn-cancel-user-biography', profileScreen); - var $biographyText = $('#profile-biography', profileScreen); - - initializeBioVisibility(); - - $addBiographyButton.unbind('click').click(function() { - $biographyEditor.val(user.biography).show(); - return false; - }); - - $editBiographyButton.unbind('click').click(function() { - $editBiographyButton.hide(); - $biographyText.hide(); - $bioTextArea.val(user.biography); - $biographyEditor.show(); - return false; - }) - - $submitBiographyButton.unbind('click').click(function() { - var bio = $bioTextArea.val(); - $bioTextArea.closest('div.field').removeClass('error'); - $('.error-text', $bioTextArea.closest('div.field')).remove(); - userDefer = rest.updateUser({ - biography: bio - }) - .done(function(response) { - user = response; - initializeBioVisibility(); - }) - .fail(function(jqXHR) { - if(jqXHR.status == 422) { - var errors = JSON.parse(jqXHR.responseText) - var biography = context.JK.format_errors("biography", errors); - if(biography != null) { - $bioTextArea.closest('div.field').addClass('error').end().after(biography); - } - else { - app.notifyServerError(jqXHR, "Unable to update biography") - } - } - else { - app.notifyServerError(jqXHR, "Unable to update biography") - } - }) - return false; - }) - - $cancelBiographyButton.unbind('click').click(function() { - initializeBioVisibility(); - return false; - }) } /****************** SOCIAL TAB *****************/ function renderSocial() { - $('#profile-social-friends').empty(); - $('#profile-social-followings').empty(); - $('#profile-social-followers').empty(); + $socialFriends.empty(); + $socialFollowings.empty(); + $socialFollowers.empty(); - $('#profile-about').hide(); - $('#profile-history').hide(); - $('#profile-bands').hide(); - $('#profile-social').show(); - $('#profile-favorites').hide(); + $aboutContent.hide(); + $historyContent.hide(); + $bandsContent.hide(); + $socialContent.show(); + $favoritesContent.hide(); $('.profile-nav a.active').removeClass('active'); - $('.profile-nav a#profile-social-link').addClass('active'); + $socialLink.addClass('active'); bindSocial(); } @@ -511,11 +622,11 @@ type: "Friends" }); - $('#profile-social-friends').append(friendHtml); + $socialFriends.append(friendHtml); }); } else { - $('#profile-social-friends').html(' '); + $socialFriends.html(' '); } context.JK.bindHoverEvents(); }) @@ -536,11 +647,11 @@ location: val.location }); - $('#profile-social-followings').append(followingHtml); + $socialFollowings.append(followingHtml); }); } else { - $('#profile-social-followings').html(' '); + $socialFollowings.html(' '); } context.JK.bindHoverEvents(); }) @@ -559,7 +670,7 @@ location: val.location }); - $('#profile-social-followers').append(followerHtml); + $socialFollowers.append(followerHtml); }); context.JK.bindHoverEvents(); }) @@ -568,14 +679,14 @@ /****************** HISTORY TAB *****************/ function renderHistory() { - $('#profile-about').hide(); - $('#profile-history').show(); - $('#profile-bands').hide(); - $('#profile-social').hide(); - $('#profile-favorites').hide(); + $aboutContent.hide(); + $historyContent.show(); + $bandsContent.hide(); + $socialContent.hide(); + $favoritesContent.hide(); $('.profile-nav a.active').removeClass('active'); - $('.profile-nav a#profile-history-link').addClass('active'); + $historyLink.addClass('active'); bindHistory(); } @@ -586,16 +697,16 @@ /****************** BANDS TAB *****************/ function renderBands() { - $('#profile-bands').empty(); + $bandsContent.empty(); - $('#profile-about').hide(); - $('#profile-history').hide(); - $('#profile-bands').show(); - $('#profile-social').hide(); - $('#profile-favorites').hide(); + $aboutContent.hide(); + $historyContent.hide(); + $bandsContent.show(); + $socialContent.hide(); + $favoritesContent.hide(); $('.profile-nav a.active').removeClass('active'); - $('.profile-nav a#profile-bands-link').addClass('active'); + $bandsLink.addClass('active'); bindBands(); } @@ -606,7 +717,7 @@ .done(function (response) { if ((!response || response.length === 0) && isCurrentUser()) { var noBandHtml = $('#template-no-bands').html(); - $("#profile-bands").html(noBandHtml); + $bandsContent.html(noBandHtml); } else { addMoreBandsLink(); @@ -652,8 +763,8 @@ bandId: val.id, biography: val.biography, profile_url: "/client#/bandProfile/" + val.id, - band_edit_url: "/client#/band/setup/" + val.id + '/step1', - band_member_url: "/client#/band/setup/" + val.id + '/step2', + band_edit_url: "/client#/band/setup/" + val.id + '/step0', + band_member_url: "/client#/band/setup/" + val.id + '/step4', avatar_url: context.JK.resolveBandAvatarUrl(val.photo_url), name: val.name, location: val.location, @@ -664,12 +775,12 @@ musicians: musicianHtml }); - $('#profile-bands').append(bandHtml); - - $('.profile-band-link-member-true').each(function(idx) { + $bandsContent.append(bandHtml); + + $('.profile-band-link-member-true').each(function(idx) { isBandMember ? $(this).show() : $(this).hide(); }); - $('.profile-band-link-member-false').each(function(idx) { + $('.profile-band-link-member-false').each(function(idx) { isBandMember ? $(this).hide() : $(this).show(); }); @@ -689,7 +800,7 @@ function addMoreBandsLink() { if (isCurrentUser()) { var moreBandsHtml = $('#template-more-bands').html(); - $("#profile-bands").append(moreBandsHtml); + $bandsContent.append(moreBandsHtml); } } @@ -716,13 +827,13 @@ } function updateFollowingCount(value) { - var followingCount = $('#profile-follower-stats span.follower-count'); - followingCount.text(value + parseInt(followingCount.text())); + var $followingCount = $('#follower-stats span.follower-count'); + $followingCount.text(value + parseInt($followingCount.text())); } function updateBandFollowingCount(bandId, value) { - var bandFollowing = $('div[band-id="' + bandId + '"].profile-bands span.follower-count'); - bandFollowing.text(value + parseInt(bandFollowing.text())); + var $bandFollowing = $('div[band-id="' + bandId + '"].profile-bands span.follower-count'); + $bandFollowing.text(value + parseInt($bandFollowing.text())); } function addBandFollowing(evt) { @@ -734,7 +845,6 @@ rest.addFollowing(newFollowing) .done(function (response) { - logger.debug("following band " + bandId); updateBandFollowingCount(bandId, 1); // increase counter configureBandFollowingButton(true, bandId); context.JK.GA.trackJKSocial(context.JK.GA.Categories.jkFollow, context.JK.GA.JKSocialTargets.band); @@ -743,7 +853,7 @@ } function configureBandFollowingButton(following, bandId) { - var $btnFollowBand = $('div[band-id=' + bandId + ']', '#profile-bands').find('#btn-follow-band-2'); + var $btnFollowBand = $('div[band-id=' + bandId + ']', '#bands-content').find('#btn-follow-band'); $btnFollowBand.unbind("click"); if (following) { @@ -753,8 +863,7 @@ evt.stopPropagation(); return false; }); - } - else { + } else { $btnFollowBand.text('FOLLOW'); $btnFollowBand.click(addBandFollowing); } @@ -762,14 +871,14 @@ /****************** FAVORITES TAB *****************/ function renderFavorites() { - $('#profile-about').hide(); - $('#profile-history').hide(); - $('#profile-bands').hide(); - $('#profile-social').hide(); - $('#profile-favorites').show(); + $aboutContent.hide(); + $historyContent.hide(); + $bandsContent.hide(); + $socialContent.hide(); + $favoritesContent.show(); $('.profile-nav a.active').removeClass('active'); - $('.profile-nav a#profile-favorites-link').addClass('active'); + $favoritesLink.addClass('active'); bindFavorites(); } @@ -780,16 +889,16 @@ function initializeFeed() { - var $scroller = profileScreen.find('.content-body-scroller#user-profile-feed-scroller'); - var $content = profileScreen.find('.feed-content#user-profile-feed-entry-list'); - var $noMoreFeeds = $('#user-profile-end-of-feeds-list'); - var $refresh = profileScreen.find('.btn-refresh-entries'); - var $sortFeedBy = profileScreen.find('#feed_order_by'); - var $includeDate = profileScreen.find('#feed_date'); - var $includeType = profileScreen.find('#feed_show'); + var $scroller = $screen.find('.content-body-scroller#user-profile-feed-scroller'); + var $content = $screen.find('.feed-content#user-profile-feed-entry-list'); + var $noMoreFeeds = $screen.find('#user-profile-end-of-feeds-list'); + var $refresh = $screen.find('.btn-refresh-entries'); + var $sortFeedBy = $screen.find('#feed_order_by'); + var $includeDate = $screen.find('#feed_date'); + var $includeType = $screen.find('#feed_show'); feed = new context.JK.Feed(app); - feed.initialize(profileScreen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType, {time_range: 'all'}); + feed.initialize($screen, $scroller, $content, $noMoreFeeds, $refresh, $sortFeedBy, $includeDate, $includeType, {time_range: 'all'}); } function initialize(textMessageDialogInstance) { @@ -800,9 +909,9 @@ 'beforeHide' : beforeHide }; app.bindScreen('profile', screenBindings); - profileScreen = $('#user-profile'); events(); initializeFeed(); + player = new context.JK.SoundCloudPlayerDialog(app); } this.initialize = initialize; diff --git a/web/app/assets/javascripts/profile_utils.js b/web/app/assets/javascripts/profile_utils.js new file mode 100644 index 000000000..6c226b9e8 --- /dev/null +++ b/web/app/assets/javascripts/profile_utils.js @@ -0,0 +1,515 @@ +/** + * Common utility functions. + */ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + var profileUtils = {}; + context.JK.ProfileUtils = profileUtils; + + // genre types + var PROFILE_GENRE_TYPE = 'profile'; + var VIRTUAL_BAND_GENRE_TYPE = 'virtual_band'; + var TRADITIONAL_BAND_GENRE_TYPE = 'traditional_band'; + var PAID_SESSION_GENRE_TYPE = 'paid_sessions'; + var FREE_SESSION_GENRE_TYPE = 'free_sessions'; + var COWRITING_GENRE_TYPE = 'cowriting'; + + var NOT_SPECIFIED_TEXT = 'Not specified'; + + var proficiencyDescriptionMap = { + "1": "BEGINNER", + "2": "INTERMEDIATE", + "3": "EXPERT" + }; + + var proficiencyCssMap = { + "1": "proficiency-beginner", + "2": "proficiency-intermediate", + "3": "proficiency-expert" + }; + + + // performance sample types + profileUtils.SAMPLE_TYPES = { + JAMKAZAM: {description: "jamkazam"}, + SOUNDCLOUD: {description: "soundcloud"}, + YOUTUBE: {description: "youtube"} + }; + + // online presence types + profileUtils.ONLINE_PRESENCE_TYPES = { + SOUNDCLOUD: {description: "soundcloud"}, + REVERBNATION: {description: "reverbnation"}, + BANDCAMP: {description: "bandcamp"}, + FANDALISM: {description: "fandalism"}, + YOUTUBE: {description: "youtube"}, + FACEBOOK: {description: "facebook"}, + TWITTER: {description: "twitter"} + }; + + var USER_TYPE = 'JamRuby::User'; + + profileUtils.skillLevelMap = { + "1": "Amateur", + "2": "Professional" + }; + + profileUtils.gigMap = { + "": "not specified", + "0": "zero", + "1": "under 10", + "2": "10 to 50", + "3": "50 to 100", + "4": "over 100" + }; + + profileUtils.studioMap = { + "0": "zero", + "1": "under 10", + "2": "10 to 50", + "3": "50 to 100", + "4": "over 100" + }; + + profileUtils.cowritingPurposeMap = { + "1": "just for fun", + "2": "sell music" + }; + + profileUtils.bandCommitmentMap = { + "1": "infrequent", + "2": "once a week", + "3": "2-3 times a week", + "4": "4+ times a week" + } + + function buildGenreList(genres) { + var list = ''; + + for (var i=0; i < genres.length; i++) { + list = list.concat(genres[i].genre_id); + if (i !== genres.length - 1) { + list = list.concat(', '); + } + } + + return list; + } + + // Initialize standard profile help bubbles (topics stored as attributes on element): + profileUtils.initializeHelpBubbles = function(parentElement) { + $(".help", parentElement).each(function( index ) { + context.JK.helpBubble($(this), $(this).attr("help-topic"), {}, {}) + }) + } + + // profile genres + profileUtils.profileGenres = function(genres) { + var matches = $.grep(genres, function(g) { + return g.player_type === USER_TYPE && g.genre_type === PROFILE_GENRE_TYPE; + }); + + return matches; + } + + profileUtils.profileGenreList = function(genres) { + var matches = profileUtils.profileGenres(genres); + return buildGenreList(matches); + } + + // virtual band genres + profileUtils.virtualBandGenres = function(genres) { + var matches = []; + if (genres) { + matches = $.grep(genres, function(g) { + return g.player_type === USER_TYPE && g.genre_type === VIRTUAL_BAND_GENRE_TYPE; + }); + } + + return matches; + } + + profileUtils.virtualBandGenreList = function(genres) { + var matches = profileUtils.virtualBandGenres(genres); + return buildGenreList(matches); + } + + // traditional band genres + profileUtils.traditionalBandGenres = function(genres) { + var matches = []; + if (genres) { + matches = $.grep(genres, function(g) { + return g.player_type === USER_TYPE && g.genre_type === TRADITIONAL_BAND_GENRE_TYPE; + }); + } + + return matches; + } + + profileUtils.traditionalBandGenreList = function(genres) { + var matches = profileUtils.traditionalBandGenres(genres); + return buildGenreList(matches); + } + + // paid session genres + profileUtils.paidSessionGenres = function(genres) { + var matches = []; + if (genres) { + matches = $.grep(genres, function(g) { + return g.player_type === USER_TYPE && g.genre_type === PAID_SESSION_GENRE_TYPE; + }); + } + + return matches; + } + + profileUtils.paidSessionGenreList = function(genres) { + var matches = profileUtils.paidSessionGenres(genres); + return buildGenreList(matches); + } + + // free session genres + profileUtils.freeSessionGenres = function(genres) { + var matches = []; + if (genres) { + matches = $.grep(genres, function(g) { + return g.player_type === USER_TYPE && g.genre_type === FREE_SESSION_GENRE_TYPE; + }); + } + + return matches; + } + + profileUtils.freeSessionGenreList = function(genres) { + var matches = profileUtils.freeSessionGenres(genres); + return buildGenreList(matches); + } + + // cowriting genres + profileUtils.cowritingGenres = function(genres) { + var matches = []; + if (genres) { + matches = $.grep(genres, function(g) { + return g.player_type === USER_TYPE && g.genre_type === COWRITING_GENRE_TYPE; + }); + } + + return matches; + } + + profileUtils.cowritingGenreList = function(genres) { + var matches = profileUtils.cowritingGenres(genres); + return buildGenreList(matches); + } + + profileUtils.jamkazamSamples = function(samples) { + var matches = $.grep(samples, function(s) { + return s.service_type === profileUtils.SAMPLE_TYPES.JAMKAZAM.description; + }); + + return matches; + } + + profileUtils.soundCloudSamples = function(samples) { + var matches = $.grep(samples, function(s) { + return s.service_type === profileUtils.SAMPLE_TYPES.SOUNDCLOUD.description; + }); + + return matches; + } + + profileUtils.youTubeSamples = function(samples) { + var matches = $.grep(samples, function(s) { + return s.service_type === profileUtils.SAMPLE_TYPES.YOUTUBE.description; + }); + + return matches; + } + + profileUtils.soundCloudPresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.SOUNDCLOUD.description; + }); + + return matches; + } + + profileUtils.reverbNationPresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.REVERBNATION.description; + }); + + return matches; + } + + profileUtils.bandCampPresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.BANDCAMP.description; + }); + + return matches; + } + + profileUtils.fandalismPresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.FANDALISM.description; + }); + + return matches; + } + + profileUtils.youTubePresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.YOUTUBE.description; + }); + + return matches; + } + + profileUtils.facebookPresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.FACEBOOK.description; + }); + + return matches; + } + + profileUtils.twitterPresences = function(presences) { + var matches = $.grep(presences, function(p) { + return p.service_type === profileUtils.ONLINE_PRESENCE_TYPES.TWITTER.description; + }); + + return matches; + } + + // Render band instruments to a string: + profileUtils.renderBandInstruments = function (band) { + var msg = "" + if (band.instruments) { + for (var i = 0; i < band.instruments.length; i++) { + var instrument = band.instruments[i] + var description = instrument.instrument_id + + if (msg.length > 0) { + msg += ", " + } + msg += instrument + msg += "(" + proficiencyDescriptionMap[instrument.proficiency_level] + ")" + } + } + if (msg.length==0) { + msg = "None specified" + } + return msg + } + + function formatTitle(title) { + return title && title.length > 30 ? title.substring(0, 30) + "..." : title; + } + + profileUtils.renderMusicalExperience = function(player, $root) { + var $instruments = $root.find('#instruments'); + var $musicianStatus = $root.find('#musician-status'); + var $genres = $root.find('#genres'); + var $concertCount = $root.find('#concert-count'); + var $studioCount = $root.find('#studio-count'); + + $instruments.empty(); + + if (player.instruments) { + for (var i = 0; i < player.instruments.length; i++) { + var instrument = player.instruments[i]; + var description = instrument.instrument_id; + var proficiency = instrument.proficiency_level; + var instrument_icon_url = context.JK.getInstrumentIcon256(description); + + // add instrument info to layout + var template = $('#template-profile-instruments').html(); + var instrumentHtml = context.JK.fillTemplate(template, { + instrument_logo_url: instrument_icon_url, + instrument_description: description, + proficiency_level: proficiencyDescriptionMap[proficiency], + proficiency_level_css: proficiencyCssMap[proficiency] + }); + + $instruments.append(instrumentHtml); + } + } + + // status + var status = player.skill_level; + $musicianStatus.html(status ? profileUtils.skillLevelMap[status] + ' musician' : NOT_SPECIFIED_TEXT) + + // genres + $genres.empty(); + var profileGenres = profileUtils.profileGenreList(player.genres); + $genres.append(profileGenres.length > 0 ? profileGenres : NOT_SPECIFIED_TEXT); + + // concert gigs + var concertCount = player.concert_count; + $concertCount.html(concertCount > 0 ? 'Has played ' + profileUtils.gigMap[concertCount] + ' live concert gigs' : NOT_SPECIFIED_TEXT); + + // studio gigs + var studioCount = player.studio_session_count; + $studioCount.html(studioCount > 0 ? 'Has played ' + profileUtils.gigMap[studioCount] + ' studio session gigs' : NOT_SPECIFIED_TEXT); + + }// function renderMusicalExperience + + profileUtils.renderPerformanceSamples = function(player, $root, isOwner) { + // performance samples + var performanceSamples = player.performance_samples; + var $noSamples = $root.find('.no-samples'); + var $jamkazamSamples = $root.find('.jamkazam-samples'); + var $soundCloudSamples = $root.find('.soundcloud-samples'); + var $youTubeSamples = $root.find('.youtube-samples'); + var $btnAddRecordings = $root.find('.add-recordings'); + + if (!performanceSamples || performanceSamples.length === 0) { + $noSamples.show() + $jamkazamSamples.hide() + $soundCloudSamples.hide() + $youTubeSamples.hide() + if (isOwner) { + $btnAddRecordings.show(); + } + } else { + $btnAddRecordings.hide(); + $noSamples.hide(); + + // show samples section + var jamkazamSamples = profileUtils.jamkazamSamples(player.performance_samples); + if (!jamkazamSamples || jamkazamSamples.length === 0) { + $jamkazamSamples.hide() + } else { + $jamkazamSamples.show() + } + + var soundCloudSamples = profileUtils.soundCloudSamples(player.performance_samples); + if (!soundCloudSamples || soundCloudSamples.length === 0) { + $soundCloudSamples.hide() + } else { + $soundCloudSamples.show() + } + + var youTubeSamples = profileUtils.youTubeSamples(player.performance_samples); + if (!youTubeSamples || youTubeSamples.length === 0) { + $youTubeSamples.hide() + } else { + $youTubeSamples.show() + } + + $.each(jamkazamSamples, function(index, sample) { + $jamkazamSamples.append("" + formatTitle(sample.claimed_recording.name) + "
    "); + }); + + $.each(soundCloudSamples, function(index, sample) { + $soundCloudSamples.append("" + formatTitle(sample.description) + "
    "); + }); + + $.each(youTubeSamples, function(index, sample) { + $youTubeSamples.append("" + formatTitle(sample.description) + "
    "); + }); + } + }// function renderPerformanceSamples + + profileUtils.renderOnlinePresence = function(player, $root, isOwner) { + var $noOnlinePresence = $root.find('.no-online-presence'); + var $userWebsite = $root.find('.user-website'); + var $soundCloudPresence = $root.find('.soundcloud-presence'); + var $reverbNationPresence = $root.find('.reverbnation-presence'); + var $bandCampPresence = $root.find('.bandcamp-presence'); + var $fandalismPresence = $root.find('.fandalism-presence'); + var $youTubePresence = $root.find('.youtube-presence'); + var $facebookPresence = $root.find('.facebook-presence'); + var $twitterPresence = $root.find('.twitter-presence'); + var $btnAddSites = $root.find('.add-sites'); + + + + // online presences + var onlinePresences = player.online_presences; + if ((!onlinePresences || onlinePresences.length === 0) && !player.website) { + $noOnlinePresence.show() + $userWebsite.show() + $soundCloudPresence.show() + $reverbNationPresence.show() + $bandCampPresence.show() + $fandalismPresence.show() + $youTubePresence.show() + $facebookPresence.show() + $twitterPresence.show() + + if (isOwner) { + $btnAddSites.show(); + } else { + $btnAddSites.hide(); + } + } else { + $btnAddSites.hide(); + $noOnlinePresence.hide(); + + if (player.website) { + $userWebsite.find('a').attr('href', player.website); + } + + var soundCloudPresences = profileUtils.soundCloudPresences(onlinePresences); + if (soundCloudPresences && soundCloudPresences.length > 0) { + $soundCloudPresence.find('a').attr('href', 'http://www.soundcloud.com/' + soundCloudPresences[0].username); + $soundCloudPresence.show(); + } else { + $soundCloudPresence.hide(); + } + + var reverbNationPresences = profileUtils.reverbNationPresences(onlinePresences); + if (reverbNationPresences && reverbNationPresences.length > 0) { + $reverbNationPresence.find('a').attr('href', 'http://www.reverbnation.com/' + reverbNationPresences[0].username); + $reverbNationPresence.show(); + } else { + $reverbNationPresence.hide(); + } + + var bandCampPresences = profileUtils.bandCampPresences(onlinePresences); + if (bandCampPresences && bandCampPresences.length > 0) { + $bandCampPresence.find('a').attr('href', 'http://' + bandCampPresences[0].username + '.bandcamp.com/'); + $bandCampPresence.show(); + } else { + $bandCampPresence.hide(); + } + + var fandalismPresences = profileUtils.fandalismPresences(onlinePresences); + if (fandalismPresences && fandalismPresences.length > 0) { + $fandalismPresence.find('a').attr('href', 'http://www.fandalism.com/' + fandalismPresences[0].username); + $fandalismPresence.show(); + } else { + $fandalismPresence.hide(); + } + + var youTubePresences = profileUtils.youTubePresences(onlinePresences); + if (youTubePresences && youTubePresences.length > 0) { + $youTubePresence.find('a').attr('href', 'http://www.youtube.com/' + youTubePresences[0].username); + $youTubePresence.show(); + } else { + $youTubePresence.hide(); + } + + var facebookPresences = profileUtils.facebookPresences(onlinePresences); + if (facebookPresences && facebookPresences.length > 0) { + $facebookPresence.find('a').attr('href', 'http://www.facebook.com/' + facebookPresences[0].username); + $facebookPresence.show(); + } else { + $facebookPresence.hide(); + } + + var twitterPresences = profileUtils.twitterPresences(onlinePresences); + if (twitterPresences && twitterPresences.length > 0) { + $twitterPresence.find('a').attr('href', 'http://www.twitter.com/' + twitterPresences[0].username); + $twitterPresence.show(); + } else { + $twitterPresence.hide(); + } + } + }// function renderOnlinePresence +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/site_validator.js.coffee b/web/app/assets/javascripts/site_validator.js.coffee index 268d87f0f..111457aff 100644 --- a/web/app/assets/javascripts/site_validator.js.coffee +++ b/web/app/assets/javascripts/site_validator.js.coffee @@ -4,12 +4,13 @@ context.JK ||= {}; context.JK.SiteValidator = class SiteValidator - constructor: (site_type) -> + constructor: (site_type, success_callback, fail_callback, parent) -> @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() @site_type = site_type - @input_div = $(".site_validator#"+site_type+"_validator") + @input_div = $(".site_validator."+site_type+"_validator", parent) @data_input = @input_div.find('input') + @logger = context.JK.logger @spinner = @input_div.find('span.spinner-small') @checkmark = @input_div.find('.validate-checkmark') @@ -18,6 +19,8 @@ context.JK.SiteValidator = class SiteValidator @is_rec_src = false @deferred_status_check = null @is_validating = false + @success_callback = success_callback + @fail_callback = fail_callback init: () => this.renderErrors({}) @@ -32,7 +35,7 @@ context.JK.SiteValidator = class SiteValidator dataToValidate: () => url = @data_input.val() - if 0 < url.length + if url && 0 < url.length url.substring(0,2000) else null @@ -47,12 +50,14 @@ context.JK.SiteValidator = class SiteValidator @checkmark.hide() yn - didBlur: () => + didBlur: () => if this.showFormatStatus() this.validateSite() validateSite: () => unless data = this.dataToValidate() + if @success_callback + @success_callback(@input_div) return null this.setSiteStatus(null) @spinner.show() @@ -68,13 +73,18 @@ context.JK.SiteValidator = class SiteValidator this.renderErrors({}) if @deferred_status_check @deferred_status_check.resolve() + if @success_callback + @success_callback(@input_div) else this.setSiteStatus(false) this.renderErrors(response) if @deferred_status_check @deferred_status_check.reject() + if @fail_callback + @fail_callback(@input_div) + @deferred_status_check = null - @logger.debug("site_status = "+@site_status) + @logger.debug("site_status = " + @site_status) processSiteCheckFail: (response) => @logger.error("site check error") @@ -90,14 +100,14 @@ context.JK.SiteValidator = class SiteValidator @checkmark.show() else @checkmark.hide() - - siteIsValid: () => + + siteIsValid: () => this.setSiteStatus(true) - - siteIsInvalid: () => + + siteIsInvalid: () => this.setSiteStatus(false) - - renderErrors: (errors) => + + renderErrors: (errors) => errdiv = @input_div.find('.error') if errmsg = context.JK.format_errors("site", errors) errdiv.show() @@ -110,7 +120,7 @@ context.JK.SiteValidator = class SiteValidator dfr = $.Deferred() if null == @site_status @deferred_status_check = dfr - this.validateSite() + this.validateSite() else if true == @site_status dfr.resolve() @@ -118,31 +128,42 @@ context.JK.SiteValidator = class SiteValidator dfr.reject() return dfr.promise() - -context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator - constructor: (site_type) -> - super(site_type) + +context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator + constructor: (site_type, success_callback, fail_callback, parent) -> + super(site_type, success_callback, fail_callback, parent) @recording_sources = [] @is_rec_src = true @add_btn = @input_div.find('a.add-recording-source') + @site_success_callback = success_callback + @site_fail_callback = fail_callback init: (sources) => super() if sources @recording_sources = sources - @add_btn.on 'click', => + @add_btn.off('click').on 'click', => this.attemptAdd() processSiteCheckSucceed: (response) => super(response) @add_btn.removeClass('disabled') - @recording_sources.push({ url: response.data, recording_id: response.recording_id }) + + if @site_status + @recording_sources.push({ url: response.data, recording_id: response.recording_id, recording_title: response.recording_title }) + if @site_success_callback + @site_success_callback(@input_div) + else + if @site_fail_callback + @site_fail_callback(@input_div) processSiteCheckFail: (response) => super(response) @add_btn.removeClass('disabled') + if @site_fail_callback + @site_fail_callback(@input_div) - didBlur: () => + didBlur: () => # do nothing, validate on add only validateSite: () => @@ -156,12 +177,16 @@ context.JK.RecordingSourceValidator = class RecordingSourceValidator extends Sit removeRecordingId: (recording_id) => start_len = @recording_sources.length - @recording_sources = $.grep @recording_sources, (src_data) -> - src_data['recording_id'] != recording_id + @recording_sources = @recording_sources.filter (src) -> + src["recording_id"] isnt recording_id.toString() start_len != @recording_sources.length containsRecordingUrl: (url) => vals = $.grep @recording_sources, (src_data) -> - src_data['url'] == url + src_data['url'] is url + 0 < vals.length + recordingSources: () => + @recording_sources + diff --git a/web/app/assets/javascripts/ui_helper.js b/web/app/assets/javascripts/ui_helper.js index 01f204bfb..11fb08af1 100644 --- a/web/app/assets/javascripts/ui_helper.js +++ b/web/app/assets/javascripts/ui_helper.js @@ -62,6 +62,24 @@ }); } + function launchInstrumentSelectorDialog(type, instruments, callback) { + var instrumentSelectorDialog = new JK.InstrumentSelectorDialog(JK.app, type, instruments, callback); + instrumentSelectorDialog.initialize(); + return instrumentSelectorDialog.showDialog(); + } + + function launchGenreSelectorDialog(type, genres, callback) { + var genreSelectorDialog = new JK.GenreSelectorDialog(JK.app, type, genres, callback); + genreSelectorDialog.initialize(); + return genreSelectorDialog.showDialog(); + } + + function launchRecordingSelectorDialog(recordings, selectedRecordings, callback) { + var recordingSelectorDialog = new JK.RecordingSelectorDialog(JK.app, recordings, selectedRecordings, callback); + recordingSelectorDialog.initialize(); + return recordingSelectorDialog.showDialog(); + } + this.addSessionLike = addSessionLike; this.addRecordingLike = addRecordingLike; this.launchCommentDialog = launchCommentDialog; @@ -70,6 +88,9 @@ this.launchRsvpCancelDialog = launchRsvpCancelDialog; this.launchRsvpCreateSlotDialog = launchRsvpCreateSlotDialog; this.launchSessionStartDialog = launchSessionStartDialog; + this.launchGenreSelectorDialog = launchGenreSelectorDialog; + this.launchInstrumentSelectorDialog = launchInstrumentSelectorDialog; + this.launchRecordingSelectorDialog = launchRecordingSelectorDialog; return this; }; diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index cd44c1a76..8af882715 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -4,7 +4,6 @@ (function (context, $) { "use strict"; - context.JK = context.JK || {}; var logger = context.JK.logger; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; @@ -22,6 +21,10 @@ var os = null; + context.JK.getGenreList = function() { + return context.JK.Rest().getGenres(); + } + context.JK.stringToBool = function (s) { switch (s.toLowerCase()) { case "true": diff --git a/web/app/assets/javascripts/web/recordings.js b/web/app/assets/javascripts/web/recordings.js index 783ca9356..c5611d48b 100644 --- a/web/app/assets/javascripts/web/recordings.js +++ b/web/app/assets/javascripts/web/recordings.js @@ -3,7 +3,7 @@ context.JK.ShowRecording = function(app) { var logger = context.JK.logger; var rest = JK.Rest(); - var ui = context.JK.UIHelper(); + var ui = new context.JK.UIHelper(app); var recordingId = null; var claimedRecordingId = null; var $scope = $(".landing-details"); diff --git a/web/app/assets/javascripts/web/sessions.js b/web/app/assets/javascripts/web/sessions.js index bfad4c1e5..d6bfbc53f 100644 --- a/web/app/assets/javascripts/web/sessions.js +++ b/web/app/assets/javascripts/web/sessions.js @@ -3,7 +3,7 @@ context.JK.ShowMusicSession = function(app) { var logger = context.JK.logger; var rest = JK.Rest(); - var ui = context.JK.UIHelper(); + var ui = new context.JK.UIHelper(app); var sessionId = null; var $scope = $(".landing-details"); var $controls = null; diff --git a/web/app/assets/stylesheets/client/account.css.scss b/web/app/assets/stylesheets/client/account.css.scss index db80c12b4..fae2ccd46 100644 --- a/web/app/assets/stylesheets/client/account.css.scss +++ b/web/app/assets/stylesheets/client/account.css.scss @@ -35,7 +35,7 @@ } form { - padding-right:10%; + //padding-right:10%; } label { @@ -107,6 +107,14 @@ margin-right:17%; } + textarea.biography { + width: 80%; + height: 90px; + } + + .actions { + margin-right: 125px; + } .location { position:relative; @@ -134,25 +142,6 @@ white-space: normal; } - .profile-instrumentlist { - background-color: #C5C5C5; - border: medium none; - box-shadow: 2px 2px 3px 0 #888888 inset; - color: #000; - font-size: 14px; - height: 100px; - overflow: auto; - width: 100%; - - .easydropdown-wrapper { - width:100%; - } - - select, .easydropdown { - @include flat_dropdown; - } - } - .account-sub-description { display: block; white-space: normal; @@ -212,11 +201,6 @@ } } - div.profile-instrumentlist table { - border-collapse: separate; - border-spacing: 6px; - } - .account-edit-email, .account-edit-password { width:35%; } diff --git a/web/app/assets/stylesheets/client/accountProfileExperience.css.scss b/web/app/assets/stylesheets/client/accountProfileExperience.css.scss new file mode 100644 index 000000000..a5d9171c9 --- /dev/null +++ b/web/app/assets/stylesheets/client/accountProfileExperience.css.scss @@ -0,0 +1,44 @@ +@import "common.css.scss"; + + +#account-profile-experience { + .genres { + width:100%; + height:200px; + background-color:#c5c5c5; + border:none; + -webkit-box-shadow: inset 2px 2px 3px 0px #888; + box-shadow: inset 2px 2px 3px 0px #888; + color:#000; + overflow:auto; + font-size:14px; + } + + .instruments { + background-color: #C5C5C5; + border: medium none; + box-shadow: 2px 2px 3px 0 #888888 inset; + color: #000; + font-size: 14px; + height: 200px; + overflow: auto; + width: 85%; + + .easydropdown-wrapper { + width:100%; + } + + select, .easydropdown { + @include flat_dropdown; + } + } + + div.instruments table { + border-collapse: separate; + border-spacing: 6px; + } + + div.actions { + margin-right: 100px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/accountProfileInterests.css.scss b/web/app/assets/stylesheets/client/accountProfileInterests.css.scss new file mode 100644 index 000000000..7e11df987 --- /dev/null +++ b/web/app/assets/stylesheets/client/accountProfileInterests.css.scss @@ -0,0 +1,42 @@ +@import "common.css.scss"; + +#account-profile-interests { + .interest { + font-weight: 600; + font-size: 16px; + } + + a.help { + font-weight: normal; + font-size: 14px; + } + + div.genres { + width: 20%; + margin-bottom: 15px; + } + + a.select-genre { + text-decoration: underline; + font-size: 12px; + font-weight: normal !important; + } + + span.genre-list { + font-style: italic; + font-size: 12px; + } + + .interest-options { + width: 30%; + margin-bottom: 15px; + + label { + margin-bottom: 10px; + } + } + + input[type=text].rate { + width: 100px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss new file mode 100644 index 000000000..76c76ebc8 --- /dev/null +++ b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss @@ -0,0 +1,54 @@ +@import "common.css.scss"; +@import "site_validator.css.scss"; + +.profile-online-sample-controls { + table.profile-table { + width: 100%; + tr:nth-child(even) td { + padding: 0.25em 0.25em 1em 0.25em; + vertical-align: top; + } + tr:nth-child(odd) td { + padding: 0.25em; + vertical-align: top; + } + } + + .sample-list { + border: 1px inset #cfcfcf; + padding: 0.5em; + .empty { + font-style: italic; + } + min-height: 150px; + overflow: scroll; + .close-button { + cursor:pointer; + } + } + + table.control-table { + width: 100%; + } + + .sample-row { + position: relative; + clear: both; + } + + .sample { + font-weight: 600; + font-size: 16px; + } + + .presence { + margin: 3px 30px 15px 0px; + } + + .site_validator { + a, .spinner-small { + margin: 1px 1px 2px 2px; + vertical-align: top; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/band.css.scss b/web/app/assets/stylesheets/client/band.css.scss index 4a9ff616d..a9645cd01 100644 --- a/web/app/assets/stylesheets/client/band.css.scss +++ b/web/app/assets/stylesheets/client/band.css.scss @@ -1,353 +1,466 @@ @import "client/common.css.scss"; +#band-setup, #band-profile { + font-family: Raleway, Arial, Helvetica, verdana, arial, sans-serif; + .band-field { + input[type="text"], select, textarea { + @include border_box_sizing; + width: 100%; + padding: 2px 4px 2px 2px; + overflow: hidden; + } -.band-setup-bio { - height:90px; - overflow:auto; -} + textarea { + overflow:hidden; + } -.band-setup-genres { - width:100%; - height:90px; - background-color:#c5c5c5; - border:none; - -webkit-box-shadow: inset 2px 2px 3px 0px #888; - box-shadow: inset 2px 2px 3px 0px #888; - color:#000; - overflow:auto; - font-size:14px; -} + } -.band-setup-photo { - .avatar-space { - color: $color2; - margin: 20px; - position:relative; - min-height:300px; - - img.preview_profile_avatar { + table.summary-table { + tr { + td { + padding-right: 1em; } } - - - .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; -} + // Mimic style of easydropdown selects: + input[type="number"] { + border-radius: 6px; + background-color: #c5c5c5; + border-right-style: outset; + border-bottom-style: outset; + height: 15px; + padding: 5px; + } -.band-profile-header h2 { - font-weight:200; - font-size: 28px; - float:left; - margin: 0px 150px 0px 0px; -} + .content-body-scroller { + + } -.band-profile-status { - font-size:12px; - float:left; - display:inline-block; - vertical-align:middle; - line-height:30px; -} - -.band-profile-photo { - height: 95px; - width: 15%; - float:left; -} - -.band-profile-nav { - width:85%; - position:relative; - float:right; - margin-right:-10px; -} - -.band-profile-nav a { - width:24%; - text-align:center; - height: 27px; - display: block; - float:left; - margin-right:5px; - vertical-align:bottom; - padding-top:65px; - background-color:#535353; - color:#ccc; - font-size:17px; - text-decoration:none; -} - -.band-profile-nav a:hover { - background-color:#666; - color:#fff; -} - -.band-profile-nav a.active { - background-color:#ed3618; - color:#fff; -} - -.band-profile-nav a.active:hover { - background-color:#ed3618; - cursor:default; -} - -.band-profile-nav a.last { - margin-right:0px !important; -} - -.avatar-profile { - float:left; - padding:2px; - width:88px; - height:88px; - background-color:#ed3618; - -webkit-border-radius:44px; - -moz-border-radius:44px; - border-radius:44px; -} - -.avatar-profile img { - width:88px; - height:88px; - -webkit-border-radius:44px; - -moz-border-radius:44px; - border-radius:44px; -} - -.band-profile-wrapper { - padding:10px 25px 10px 25px; - font-size:15px; - color:#ccc; - border-bottom: dotted 1px #444; - position:relative; - display:block; - - .result-name { - font-size: 12px; - font-weight: bold; - margin-bottom: 2px; - padding-right:4px; + .radio-field { + display: inline; + padding: 2px; + margin: 0.5em 2em 0.5em 0.25em; + label { + display: inline; + padding: 2px; } - .stats { - margin-top: 4px; - img { - vertical-align: middle; - } - } - .lcol { - width: 200px; - } - .whitespace { - // equal to lcol width. - padding-left: 200px; - } - .instruments { - width:128px; - img { - height:24px; - width:24px; - margin-right:2px; + .iradio-inline { + display: inline-block; + //padding: 2px; + } + } - &:last-child { - margin-right:0px; + + + .band-setup-genres { + width:100%; + height:200px; + background-color:#c5c5c5; + border:none; + -webkit-box-shadow: inset 2px 2px 3px 0px #888; + box-shadow: inset 2px 2px 3px 0px #888; + color:#000; + overflow:auto; + font-size:12px; + } + + .band-setup-photo { + + .avatar-space { + color: $color2; + margin: 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; } } - .button-row { - margin-top:10px; - margin-bottom:5px; - .result-list-button-wrapper { - margin:0; - } + + .band-profile-header { + padding:20px; + height:120px; + } + + .band-profile-header h2 { + font-weight:200; + font-size: 28px; + float:left; + margin: 0 0 1em 0; + } + + .band-profile-status { + font-size:12px; + float:left; + display:inline-block; + vertical-align:middle; + line-height:30px; + } + + .band-profile-photo { + height: 95px; + width: 15%; + float:left; + } + + .band-profile-nav { + width:85%; + position:relative; + float:right; + margin-right:-10px; + } + + .band-profile-nav a { + width:24%; + text-align:center; + height: 27px; + display: block; + float:left; + margin-right:5px; + vertical-align:bottom; + padding-top:65px; + background-color:#535353; + color:#ccc; + font-size:16px; + text-decoration:none; + } + + .band-profile-nav a:hover { + background-color:#666; + color:#fff; + } + + .band-profile-nav a.active { + background-color:#ed3618; + color:#fff; + } + + .band-profile-nav a.active:hover { + background-color:#ed3618; + cursor:default; + } + + .band-profile-nav a.last { + margin-right:0px !important; + } + + .band-avatar-profile { + padding:2px; + width:88px; + height:88px; + background-color:#ed3618; + -webkit-border-radius:44px; + -moz-border-radius:44px; + border-radius:44px; + } + + .band-avatar-profile img { + width:88px; + height:88px; + -webkit-border-radius:44px; + -moz-border-radius:44px; + border-radius:44px; + } + + + .band-entry { + .item-caption { + font-size: 1.4em; + font-weight: bold; + margin: 0.25em 0em 0.25em 0em; } -} - -.band-profile-about-left { - width:16%; - float:left; - font-size:13px; - line-height:140%; - display:block; -} - -.band-profile-about-left h3 { - color:#fff; - margin-bottom:0px; - font-size:13px; - font-weight:bold; - display:inline; -} - -.band-profile-about-right { - float:right; - font-size:13px; - width:84%; - line-height:140%; - display:block; -} - -.band-profile-about-right .band-profile-instrument { - text-align:center; - margin-right:15px; - float:left; -} - -.proficiency-beginner { - font-size:10px; - color:#8ea415; - font-weight:600; -} - -.proficiency-intermediate { - font-size:10px; - color:#0b6672; - font-weight:600; -} - -.proficiency-expert { - font-size:10px; - color:#ed3618; - font-weight:600; -} - -.band-profile-members { - width:100%; - min-height:90px; - background-color:#242323; - position:relative; - float:left; - margin:10px 20px 10px 0px; - padding-bottom:5px; -} - -.band-profile-member-name { - float:left; - font-size:12px; - margin-top:12px; - font-weight:bold; -} - -.band-profile-member-location { - font-size:12px; - font-weight:200; -} - -.band-profile-member-genres { - float:left; - width:40%; - font-size:10px; - margin-left:10px; - padding-right:5px; -} - -.band-profile-social-left { - float:left; - width:32%; - margin-right:12px; - border-right:solid 1px #666; -} - -.band-profile-social-mid { - float:left; - width:31%; - margin-right:12px; - border-right:solid 1px #666; -} - -.band-profile-social-right { - float:left; - width:31%; -} - -.band-profile-social-left h2, .band-profile-social-mid h2, .band-profile-social-right h2 { - font-weight:200; - color:#fff; - font-size:20px; -} - -.band-profile-block { - clear:left; - white-space:nowrap; - display:block; - margin-bottom:10px; -} - -.band-profile-outer-block { - float: left; - width: 25%; -} - -.band-profile-block-name { - display:inline-block; - margin-top:13px; - font-size:14px; - color:#fff; - font-weight:bold; -} - -.band-profile-block-city { - font-size:12px; -} - -#band-filter-results { - margin: 0 10px 5px 10px; -} - -.band-list-result { - padding-top: 5px; - padding-right: 5px; - padding-left: 5px; -} - -.band-wrapper { - overflow: auto; - height: 480px; - width: 100%; -} - -#band-setup-form { - .easydropdown { - padding: 0 3px; - width:100%; + .item-content { + font-size: 1.1em; + margin: 0.25em 0em 0.25em 0em; + } + margin: 0em 0em 1.5em 0em; } - .easydropdown-wrapper { - width:80%; + // .band-name, .band-photo { + // display: inline; + // } + + .band-profile-wrapper { + padding:10px 25px 10px 25px; + font-size:15px; + color:#ccc; + border-bottom: dotted 1px #444; + position:relative; + display:block; + + .result-name { + font-size: 12px; + font-weight: bold; + margin-bottom: 2px; + padding-right:4px; + } + .stats { + margin-top: 4px; + img { + vertical-align: middle; + } + } + .lcol { + width: 200px; + } + .whitespace { + // equal to lcol width. + padding-left: 200px; + } + .instruments { + width:128px; + img { + height:24px; + width:24px; + margin-right:2px; + + &:last-child { + margin-right:0px; + } + + } + } + .button-row { + margin-top:10px; + margin-bottom:5px; + .result-list-button-wrapper { + margin:0; + } + } } - div.field { - margin: 10px; + .band-profile-about-left { + width:16%; + float:left; + font-size:13px; + line-height:140%; + display:block; + } + + .band-profile-about-left h3 { + color:#fff; + margin-bottom:0px; + font-size:13px; + font-weight:bold; + display:inline; + } + + .band-profile-about-right { + float:right; + font-size:13px; + width:84%; + line-height:140%; + display:block; + } + + .band-profile-about-right .band-profile-instrument { + text-align:center; + margin-right:15px; + float:left; + } + + .proficiency-beginner { + font-size:10px; + color:#8ea415; + font-weight:600; + } + + .proficiency-intermediate { + font-size:10px; + color:#0b6672; + font-weight:600; + } + + .proficiency-expert { + font-size:10px; + color:#ed3618; + font-weight:600; + } + + .band-profile-members { + width:100%; + min-height:90px; + background-color:#242323; + position:relative; + float:left; + margin:10px 20px 10px 0px; + padding-bottom:5px; + } + + .band-profile-member-name { + float:left; + font-size:12px; + margin-top:12px; + font-weight:bold; + } + + .band-profile-member-location { + font-size:12px; + font-weight:200; + } + + .band-profile-member-genres { + float:left; + width:40%; + font-size:10px; + margin-left:10px; + padding-right:5px; + } + + .band-profile-social-left { + float:left; + width:32%; + margin-right:12px; + border-right:solid 1px #666; + } + + .band-profile-social-mid { + float:left; + width:31%; + margin-right:12px; + border-right:solid 1px #666; + } + + .band-profile-social-right { + float:left; + width:31%; + } + + .band-profile-social-left h2, .band-profile-social-mid h2, .band-profile-social-right h2 { + font-weight:200; + color:#fff; + font-size:20px; + } + + .band-profile-block { + clear:left; + white-space:nowrap; + display:block; + margin-bottom:10px; + } + + .band-profile-outer-block { + float: left; + width: 25%; + } + + .band-profile-block-name { + display:inline-block; + margin-top:13px; + font-size:14px; + color:#fff; + font-weight:bold; + } + + .band-profile-block-city { + font-size:12px; + } + + #band-filter-results { + margin: 0 10px 5px 10px; + } + + .band-list-result { + padding-top: 5px; + padding-right: 5px; + padding-left: 5px; + } + + .band-wrapper { + overflow: auto; + height: 480px; + width: 100%; + } + + + #band-setup-form { + margin: 0.25em 0.5em 1.25em 0.25em; + table.band-form-table { + width: 100%; + margin: 1em; + + tr:nth-child(even) td { + padding-bottom: 1em; + } + + td.band-biography, td.tdBandGenres { + height:100%; + vertical-align: top; + #band-biography { + + } + } + + } + + .easydropdown { + padding: 0 3px; + width:100%; + } + + .easydropdown-wrapper { + width:80%; + } + + div.field { + margin: 10px; + } + + label { + margin-bottom:2px; + } + + #desired-experience-label { + font-style: italic; + } + } + + + .band-setup-genre { + input { + display: inline; + width: auto !important; + } + label { + display: inline; + width: auto; + } } label { - margin-bottom:2px; + font-size: 1.05em; } + + label.strong-label { + font-weight: bold; + font-size: 1.1em; + } + } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 276bfb66a..c26547f68 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -32,6 +32,9 @@ *= require ./findSession *= require ./session *= require ./account + *= require ./accountProfileExperience + *= require ./accountProfileInterests + *= require ./accountProfileSamples *= require ./accountPaymentHistory *= require ./account_affiliate *= require ./search diff --git a/web/app/assets/stylesheets/client/musician.css.scss b/web/app/assets/stylesheets/client/musician.css.scss index 7ace07308..e075a212e 100644 --- a/web/app/assets/stylesheets/client/musician.css.scss +++ b/web/app/assets/stylesheets/client/musician.css.scss @@ -47,6 +47,9 @@ .button-row { float:none; + .result-list-button-wrapper { + margin:3px; + } } .latency-holder { @@ -138,6 +141,95 @@ #musician-filter-results { margin: 0 10px 0px 10px; } + + #musician-search-filter-results-header { + padding: 10px 10px 10px 10px; + } + + #btn-musician-search-builder { + float: left; + } + + #musician-search-filter-description { + padding: 5px 5px 5px 5px; + display: inline; + margin-left: auto; + margin-right: auto; + } + + #btn-musician-search-reset { + float: right; + } + + #musician-search-filter-results-list-blank { + font-weight: bold; + text-align: center; + margin: 30px 10px 10px 10px; + } + + #musician-search-filter-spinner { + display: block; + margin-left: auto; + margin-right: auto; + margin-top: 40px; + } + + .col-left { + float: left; + width: 50%; + margin-left: auto; + margin-right: auto; + } + + .col-right { + float: right; + width: 50%; + margin-left: auto; + margin-right: auto; + } + + .builder-section { + padding: 10px; + margin-bottom: 20px; + } + .builder-button { + float: right; + } + .band-setup-genres { + width: 80%; + } + .easydropdown-wrapper { + width: 80%; + } + .builder-sort-order { + text-align: right; + .easydropdown-wrapper { + width: 140px; + } + .text-label { + vertical-align: top; + margin-right: 5px; + display: inline; + line-height: 2em; + } + .select { + float: right; + } + } + .builder-ages { + width: 100%; + } + .builder-instruments { + width: 100%; + height: 150px; + } + .builder-selector { + margin-top: 10px; + } + .builder-action-buttons { + margin-top: 20px; + } + } .filter-element { diff --git a/web/app/assets/stylesheets/client/profile.css.scss b/web/app/assets/stylesheets/client/profile.css.scss index b19b16f9e..6e5665b5e 100644 --- a/web/app/assets/stylesheets/client/profile.css.scss +++ b/web/app/assets/stylesheets/client/profile.css.scss @@ -1,6 +1,6 @@ @import "client/common.css.scss"; -#user-profile { +#user-profile, #band-profile { .profile-about-right { textarea { @@ -9,32 +9,69 @@ padding:0; } } + + div.logo, div.item { + text-align: bottom; + } + + .online-presence-option, .performance-sample-option { + margin-right: 1em; + } + + img.logo { + margin-right: 20px; + } + + ul { + margin:0px 0px 10px 0px; + padding:0px; + } + + li { + margin-left: 15px; + margin-bottom: 0px !important; + list-style: disc; + } } .profile-head { } .profile-body { - + } .profile-header { padding:10px 20px; -// height:120px; + position: relative; } .profile-header h2 { font-weight:200; font-size: 28px; float:left; - margin: 0px 150px 0px 0px; + margin: 0px 0px 0px 0px; } -.profile-status { +.profile-about-right { + .section-header { + font-weight:600; + font-size:18px; + float:left; + margin: 0px 0px 10px 0px; + } + + .section-content { + font-weight:normal; + font-size:1.2em; + float:left; + margin: 0px 0px 10px 0px; + } +} + +.profile-details { font-size:12px; float:left; - display:inline-block; - vertical-align:middle; - line-height:30px; + width: 80px; } .profile-photo { @@ -159,7 +196,7 @@ font-weight:600; } -#profile-bands .when-empty { +#bands-content .when-empty { margin: 0px; padding:0px; display:block; @@ -170,7 +207,7 @@ line-height: 150%; } -#profile-bands .when-empty a { +#bands-content .when-empty a { text-decoration: underline; color: inherit; } @@ -205,18 +242,6 @@ padding-right:5px; } -.user-setup-genres { - width:40%; - height:90px; - background-color:#c5c5c5; - border:none; - -webkit-box-shadow: inset 2px 2px 3px 0px #888; - box-shadow: inset 2px 2px 3px 0px #888; - color:#000; - overflow:auto; - font-size:14px; -} - .profile-band-list-result { width:100%; min-height:85px; @@ -228,7 +253,7 @@ -moz-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing:border-box; - + .result-name { font-size: 12px; font-weight: bold; @@ -254,11 +279,11 @@ height:24px; width:24px; margin-right:2px; - + &:last-child { margin-right:0px; } - + } } .button-row { @@ -330,14 +355,14 @@ display:none; } -#profile-history { +#history-content, #band-profile-history { padding:0 10px 0 20px; width:100%; position:relative; height:100%; @include border_box_sizing; - #user-feed-controls { + #user-feed-controls, #band-feed-controls { width:100%; @include border_box_sizing; position:relative; diff --git a/web/app/assets/stylesheets/client/site_validator.css.scss b/web/app/assets/stylesheets/client/site_validator.css.scss index 4534ebd8c..09853cdae 100644 --- a/web/app/assets/stylesheets/client/site_validator.css.scss +++ b/web/app/assets/stylesheets/client/site_validator.css.scss @@ -11,7 +11,7 @@ input { width: 100%; - padding: 5px 5px 5px 30px; + float: left; } .validate-checkmark { @@ -24,7 +24,7 @@ display:inline-block; vertical-align: middle; position: relative; - margin-top: -40px; + margin-top: -10px; left: 0px; } .error { diff --git a/web/app/assets/stylesheets/dialogs/genreSelectorDialog.css.scss b/web/app/assets/stylesheets/dialogs/genreSelectorDialog.css.scss new file mode 100644 index 000000000..b6ab94944 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/genreSelectorDialog.css.scss @@ -0,0 +1,39 @@ +@import "client/common"; + +#genre-selector-dialog { + + min-height:initial; + + .dialog-inner { + display:block; + overflow:auto; + max-height:300px; + color:white; + } + + .action-buttons { + margin-bottom:10px; + } + + .three-column-list-container { + -moz-column-count: 3; + -moz-column-gap: 10px; + -webkit-column-count: 3; + -webkit-column-gap: 10px; + column-count: 3; + column-gap: 10px; + margin-left: 0; + ul { + list-style-type: none; + li { + margin: 1px 4px 1px 0; + font-size:12px; + line-height:14px; + } + } + } + + .genres { + + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/instrumentSelectorDialog.css.scss b/web/app/assets/stylesheets/dialogs/instrumentSelectorDialog.css.scss new file mode 100644 index 000000000..59760670d --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/instrumentSelectorDialog.css.scss @@ -0,0 +1,44 @@ +@import "client/common"; + +#instrument-selector-dialog { + + min-height:initial; + + + .dialog-inner { + .content-body { + max-height: auto; + .content-body-scroller { + height: 350px; + overflow: scroll; + background-color:#c5c5c5; + border: 1px inset; + } + border: 1px solid #222; + margin: 4px 4px 8px 4px; + } + + .instructions { + font-size:16px; + } + + .dropdown { + box-shadow:0 0 0 0; + } + } + + .action-buttons { + margin-bottom:10px; + } + + .instruments { + + } + + label { + display:inline; + color: black; + font-size:16px; + } + +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss b/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss new file mode 100644 index 000000000..c9994cff9 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/recordingSelectorDialog.css.scss @@ -0,0 +1,14 @@ +@import "client/common"; + +#recording-selector-dialog { + + min-height:initial; + + .dialog-inner { + color:white; + } + + .action-buttons { + margin-bottom:10px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/soundCloudPlayer.css.scss b/web/app/assets/stylesheets/dialogs/soundCloudPlayer.css.scss new file mode 100644 index 000000000..60cd88492 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/soundCloudPlayer.css.scss @@ -0,0 +1,6 @@ +#sound-cloud-player-dialog { + height:auto; + .caption { + margin: 0.1em 0.1em 0.5em 0em + } +} diff --git a/web/app/controllers/api_bands_controller.rb b/web/app/controllers/api_bands_controller.rb index 8b0c83ccd..879cb09f2 100644 --- a/web/app/controllers/api_bands_controller.rb +++ b/web/app/controllers/api_bands_controller.rb @@ -250,6 +250,7 @@ class ApiBandsController < ApiController def auth_band_member @band = Band.find(params[:id]) unless @band.users.exists? current_user + Rails.logger.info("Could not find #{current_user} in #{@band.users.inspect}") raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end end @@ -257,6 +258,7 @@ class ApiBandsController < ApiController uid = current_user.id @band = Band.find(params[:id]) unless @band.band_musicians.detect { |bm| bm.user_id == uid && bm.admin? } + Rails.logger.info("Could not find #{current_user} in #{@band.band_musicians.inspect}") raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end end diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb index dfe2fc322..0663265ff 100644 --- a/web/app/controllers/api_search_controller.rb +++ b/web/app/controllers/api_search_controller.rb @@ -21,4 +21,27 @@ class ApiSearchController < ApiController @search = Search.text_search(params, current_user) end end + + def musicians + if request.get? + if params[:results] + @search = MusicianSearch.user_search_filter(current_user).search_results_page + respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index' + else + render :json => MusicianSearch.search_filter_json(current_user), :status => 200 + end + + elsif request.post? + ms = MusicianSearch.user_search_filter(current_user) + filter = params[:filter] + if filter == 'reset' + @search = ms.reset_search_results + else + json = JSON.parse(filter, :create_additions => false) + @search = ms.search_results_page(json, [params[:page].to_i, 1].max) + end + respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index' + end + end + end diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 5d8e1cd00..ebe68290a 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -32,6 +32,17 @@ class ApiUsersController < ApiController respond_with @user, responder: ApiResponder, :status => 200 end + def profile_show + @profile = User.includes([{musician_instruments: :instrument}, + {band_musicians: :user}, + {genre_players: :genre}, + :bands, :instruments, :genres, + :online_presences, :performance_samples]) + .find(params[:id]) + + respond_with @profile, responder: ApiResponder, :status => 200 + end + # in other words, a minimal signup def create # today, this only accepts a minimal registration; it could be made to take in more if we wanted @@ -69,7 +80,10 @@ class ApiUsersController < ApiController respond_with_model(@user, new: true, location: lambda { return api_user_detail_url(@user.id) }) end end - + + def profile_save + end + def update @user = User.find(params[:id]) @@ -82,11 +96,46 @@ class ApiUsersController < ApiController @user.country = params[:country] if params.has_key?(:country) @user.musician = params[:musician] if params.has_key?(:musician) @user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments) - @user.update_genres(params[:genres].nil? ? [] : params[:genres]) if params.has_key?(:genres) + + # genres + @user.update_genres(params[:genres].nil? ? [] : params[:genres], GenrePlayer::PROFILE) if params.has_key?(:genres) + @user.update_genres(params[:virtual_band_genres].nil? ? [] : params[:virtual_band_genres], GenrePlayer::VIRTUAL_BAND) if params.has_key?(:virtual_band_genres) + @user.update_genres(params[:traditional_band_genres].nil? ? [] : params[:traditional_band_genres], GenrePlayer::TRADITIONAL_BAND) if params.has_key?(:traditional_band_genres) + @user.update_genres(params[:paid_session_genres].nil? ? [] : params[:paid_session_genres], GenrePlayer::PAID_SESSION) if params.has_key?(:paid_session_genres) + @user.update_genres(params[:free_session_genres].nil? ? [] : params[:free_session_genres], GenrePlayer::FREE_SESSION) if params.has_key?(:free_session_genres) + @user.update_genres(params[:cowriting_genres].nil? ? [] : params[:cowriting_genres], GenrePlayer::COWRITING) if params.has_key?(:cowriting_genres) + @user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next) @user.show_whats_next_count = params[:show_whats_next_count] if params.has_key?(:show_whats_next_count) @user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email) @user.biography = params[:biography] if params.has_key?(:biography) + + @user.website = params[:website] if params.has_key?(:website) + @user.skill_level = params[:skill_level] if params.has_key?(:skill_level) + @user.concert_count = params[:concert_count] if params.has_key?(:concert_count) + @user.studio_session_count = params[:studio_session_count] if params.has_key?(:studio_session_count) + + # virtual band + @user.virtual_band = params[:virtual_band] if params.has_key?(:virtual_band) + @user.virtual_band_commitment = params[:virtual_band_commitment] if params.has_key?(:virtual_band_commitment) + + # traditional band + @user.traditional_band = params[:traditional_band] if params.has_key?(:traditional_band) + @user.traditional_band_commitment = params[:traditional_band_commitment] if params.has_key?(:traditional_band_commitment) + @user.traditional_band_touring = params[:traditional_band_touring] if params.has_key?(:traditional_band_touring) + + # paid sessions + @user.paid_sessions = params[:paid_sessions] if params.has_key?(:paid_sessions) + @user.paid_sessions_hourly_rate = params[:paid_sessions_hourly_rate] if params.has_key?(:paid_sessions_hourly_rate) + @user.paid_sessions_daily_rate = params[:paid_sessions_daily_rate] if params.has_key?(:paid_sessions_daily_rate) + + # free sessions + @user.free_sessions = params[:free_sessions] if params.has_key?(:free_sessions) + + # co-writing + @user.cowriting = params[:cowriting] if params.has_key?(:cowriting) + @user.cowriting_purpose = params[:cowriting_purpose] if params.has_key?(:cowriting_purpose) + @user.want_jamblaster = params[:want_jamblaster] if params.has_key?(:want_jamblaster) @user.mod_merge(params[:mods]) if params[:mods] @@ -95,6 +144,9 @@ class ApiUsersController < ApiController @user.update_notification_seen_at params[:notification_seen_at] end + @user.update_online_presences(params[:online_presences]) if params.has_key?(:online_presences) + @user.update_performance_samples(params[:performance_samples]) if params.has_key?(:performance_samples) + @user.save if @user.errors.any? @@ -782,9 +834,9 @@ class ApiUsersController < ApiController if site.blank? || 'url'==site url = data elsif Utils.recording_source?(site) - rec_id = Utils.extract_recording_id(site, data) - if rec_id - render json: { message: 'Valid Site', recording_id: rec_id, data: data }, status: 200 + rec_data = Utils.extract_recording_data(site, data) + if rec_data + render json: { message: 'Valid Site', recording_id: rec_data["id"], recording_title: rec_data["title"], data: data }, status: 200 return else render json: { message: 'Invalid Site', data: data, errors: { site: ["Could not detect recording identifier"] } }, status: 200 diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index 52573388b..0edce85ce 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -67,4 +67,10 @@ class SpikesController < ApplicationController def recording_source render :layout => 'web' end + + def musician_search_filter + # gon.musician_search_meta = MusicianSearch::SEARCH_FILTER_META + render :layout => 'web' + end + end diff --git a/web/app/helpers/client_helper.rb b/web/app/helpers/client_helper.rb index 31364bd0b..c4977e130 100644 --- a/web/app/helpers/client_helper.rb +++ b/web/app/helpers/client_helper.rb @@ -67,6 +67,7 @@ module ClientHelper gon.ftue_network_test_duration = Rails.application.config.ftue_network_test_duration gon.ftue_network_test_max_clients = Rails.application.config.ftue_network_test_max_clients gon.ftue_maximum_gear_latency = Rails.application.config.ftue_maximum_gear_latency + gon.musician_search_meta = MusicianSearch::SEARCH_FILTER_META # is this the native client or browser? @nativeClient = is_native_client? @@ -77,4 +78,4 @@ module ClientHelper gon.use_cached_session_scores = Rails.application.config.use_cached_session_scores gon.allow_both_find_algos = Rails.application.config.allow_both_find_algos end -end \ No newline at end of file +end diff --git a/web/app/views/api_bands/show.rabl b/web/app/views/api_bands/show.rabl index 74d8dd08d..7c855b180 100644 --- a/web/app/views/api_bands/show.rabl +++ b/web/app/views/api_bands/show.rabl @@ -1,7 +1,9 @@ object @band 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 +:original_fpfile_photo, :cropped_fpfile_photo, :crop_selection_photo, +:band_type, :band_status, :concert_count, :add_new_members, :play_commitment, :touring_option, :paid_gigs, +:free_gigs, :hourly_rate, :gig_minimum child :users => :musicians do attributes :id, :first_name, :last_name, :name, :photo_url @@ -16,11 +18,27 @@ child :users => :musicians do end end +child :instruments => :instruments do + attributes :id, :instrument_id, :proficiency_level +end + child :genres => :genres do attributes :id, :description #partial('api_genres/index', :object => @band.genres) end +child :performance_samples => :performance_samples do + attributes :id, :url, :service_type, :claimed_recording_id, :service_id, :description + + child :claimed_recording => :claimed_recording do + attributes :id, :name + end +end + +child :online_presences => :online_presences do + attributes :id, :service_type, :username +end + if current_user node :is_following do |uu| current_user.following?(@band) diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index c22a49ad3..379d1cb96 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -6,7 +6,7 @@ object @claimed_recording -attributes :id, :name, :description, :is_public, :genre_id, :discarded +attributes :id, :user_id, :name, :description, :is_public, :genre_id, :discarded node :share_url do |claimed_recording| unless claimed_recording.share_token.nil? @@ -20,7 +20,7 @@ node :mix do |claimed_recording| end child(:recording => :recording) { - attributes :id, :created_at, :duration, :comment_count, :like_count, :play_count, :jam_track_id, :jam_track_initiator_id + attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id node :timeline do |recording| recording.timeline ? JSON.parse(recording.timeline) : {} @@ -78,4 +78,20 @@ child(:recording => :recording) { attributes :id, :first_name, :last_name, :name, :photo_url, :musician } } + + node do |recording| + { + helpers: { + avatar: asset_path(resolve_avatarables(recording.band, recording.owner)), + artist_name: recording_artist_name(recording), + artist_id: recording_artist_id(recording), + artist_hoveraction: recording_artist_hoveraction(recording), + artist_datakey: recording_artist_datakey(recording), + utc_created_at: recording.created_at.getutc.iso8601, + name: recording_name(recording, current_user), + description: recording_description(recording, current_user), + genre: recording_genre(recording) + } + } + end } diff --git a/web/app/views/api_feeds/show.rabl b/web/app/views/api_feeds/show.rabl index 31edd3497..1bbe751cd 100644 --- a/web/app/views/api_feeds/show.rabl +++ b/web/app/views/api_feeds/show.rabl @@ -78,7 +78,7 @@ glue :recording do 'recording' end - attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded? + attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded? node do |recording| { diff --git a/web/app/views/api_search/index.rabl b/web/app/views/api_search/index.rabl index 0f3479db6..ea256bf52 100644 --- a/web/app/views/api_search/index.rabl +++ b/web/app/views/api_search/index.rabl @@ -2,6 +2,64 @@ object @search node :search_type do |ss| ss.search_type end +if @search.is_a?(MusicianSearch) + + node :page_count do |foo| + @search.page_count + end + + node :my_audio_latency do |user| + current_user.last_jam_audio_latency.round if current_user.last_jam_audio_latency + end + + node :is_blank_filter do |foo| + @search.is_blank? + end + + node :filter_json do |foo| + @search.to_json + end + + child(:results => :musicians) { + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score + + node :is_friend do |musician| + @search.is_friend?(musician) + end + + node :is_following do |musician| + @search.is_follower?(musician) + end + + node :pending_friend_request do |musician| + musician.pending_friend_request?(current_user) + end + + node :biography do |musician| + musician.biography.nil? ? "" : musician.biography + end + + child :musician_instruments => :instruments do + attributes :instrument_id, :description, :proficiency_level, :priority + end + + child :top_followings => :followings do |uf| + node :user_id do |uu| uu.id end + node :photo_url do |uu| uu.photo_url end + node :name do |uu| uu.name end + end + + node :follow_count do |musician| @search.follow_count(musician) end + node :friend_count do |musician| @search.friend_count(musician) end + node :recording_count do |musician| @search.record_count(musician) end + node :session_count do |musician| @search.session_count(musician) end + + node :audio_latency do |musician| + last_jam_audio_latency(musician) + end + } +else + if @search.session_invite_search? child(:results => :suggestions) { node :value do |uu| uu.name end @@ -43,6 +101,14 @@ if @search.musicians_filter_search? current_user.last_jam_audio_latency.round if current_user.last_jam_audio_latency end + node :is_blank_filter do |foo| + @search.is_blank? + end + + node :filter_json do |foo| + @search.to_json + end + child(:results => :musicians) { attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score @@ -131,3 +197,4 @@ if @search.fans_text_search? } end +end diff --git a/web/app/views/api_users/index.rabl b/web/app/views/api_users/index.rabl index 27eb79ce0..00ecf01a6 100644 --- a/web/app/views/api_users/index.rabl +++ b/web/app/views/api_users/index.rabl @@ -1,4 +1,5 @@ collection @users # do not retrieve all child collections when showing a list of users -attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :musician, :photo_url, :biography +attributes :id, :first_name, :last_name, :name, :city, :state, :country, :email, :online, :musician, :photo_url, :biography, :age, :website, :skill_level, :concert_count, :studio_session_count, :virtual_band, :virtual_band_commitment, :traditional_band, :traditional_band_commitment, :traditional_band_touring, :paid_sessions, :paid_sessions_hourly_rate, +:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose diff --git a/web/app/views/api_users/profile_show.rabl b/web/app/views/api_users/profile_show.rabl new file mode 100644 index 000000000..56a822dfe --- /dev/null +++ b/web/app/views/api_users/profile_show.rabl @@ -0,0 +1,33 @@ +object @profile + +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, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :concert_count, :studio_session_count, :virtual_band, :virtual_band_commitment, :traditional_band, :traditional_band_commitment, :traditional_band_touring, :paid_sessions, :paid_sessions_hourly_rate, +:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email + +child :online_presences => :online_presences do + attributes :id, :service_type, :username +end + +child :performance_samples => :performance_samples do + attributes :id, :url, :service_type, :claimed_recording_id, :service_id, :description + + child :claimed_recording => :claimed_recording do + attributes :id, :name + end +end + +child :genre_players => :genres do + attributes :genre_id, :player_type, :genre_type +end + +child :band_musicians => :bands do + attributes :id, :name, :admin, :photo_url, :logo_url + + child :genres => :genres do + attributes :id, :description + #partial('api_genres/index', :object => @user.bands.genres) + end +end + +child :musician_instruments => :instruments do + attributes :description, :proficiency_level, :priority, :instrument_id +end \ No newline at end of file diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index 40cc96a77..412b38a6f 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -1,6 +1,7 @@ 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, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count +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, :follower_count, :following_count, +:recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :purchased_jamtracks_count if @user.musician? node :location do @user.location end diff --git a/web/app/views/clients/_account.html.erb b/web/app/views/clients/_account.html.erb index 29d806a94..2aa45e28f 100644 --- a/web/app/views/clients/_account.html.erb +++ b/web/app/views/clients/_account.html.erb @@ -105,19 +105,19 @@
    - UPDATE +


    -

    jamtracks:

    +

    jamtracks:

    - {{data.licenseDetail}} + {{data.licenseDetail}}
    JamTracks License diff --git a/web/app/views/clients/_account_profile.html.erb b/web/app/views/clients/_account_profile.html.erb index af76ee2e7..7d7f08256 100644 --- a/web/app/views/clients/_account_profile.html.erb +++ b/web/app/views/clients/_account_profile.html.erb @@ -1,135 +1,98 @@ -
    - +
    +
    - +
    <%= image_tag "content/icon_account.png", {:width => 27, :height => 20} %>
    - +

    my account

    <%= render "screen_navigation" %>
    - -
    -
    - - - - - - diff --git a/web/app/views/clients/_account_profile_avatar.html.erb b/web/app/views/clients/_account_profile_avatar.html.erb index 010ee663e..c9dc2eb49 100644 --- a/web/app/views/clients/_account_profile_avatar.html.erb +++ b/web/app/views/clients/_account_profile_avatar.html.erb @@ -46,10 +46,3 @@ - - diff --git a/web/app/views/clients/_account_profile_experience.html.erb b/web/app/views/clients/_account_profile_experience.html.erb new file mode 100644 index 000000000..6485b118f --- /dev/null +++ b/web/app/views/clients/_account_profile_experience.html.erb @@ -0,0 +1,97 @@ +
    +
    +
    + <%= image_tag "content/icon_account.png", {:width => 27, :height => 20} %> +
    +

    my account

    + <%= render "screen_navigation" %> +
    + +
    + +
    +
    + + + + + diff --git a/web/app/views/clients/_account_profile_interests.html.erb b/web/app/views/clients/_account_profile_interests.html.erb new file mode 100644 index 000000000..82aca53b9 --- /dev/null +++ b/web/app/views/clients/_account_profile_interests.html.erb @@ -0,0 +1,223 @@ +
    +
    +
    + <%= image_tag "content/icon_account.png", {:width => 27, :height => 20} %> +
    +

    my account

    + <%= render "screen_navigation" %> +
    + +
    + +
    +
    + diff --git a/web/app/views/clients/_account_profile_samples.html.erb b/web/app/views/clients/_account_profile_samples.html.erb new file mode 100644 index 000000000..22825b9f2 --- /dev/null +++ b/web/app/views/clients/_account_profile_samples.html.erb @@ -0,0 +1,33 @@ + diff --git a/web/app/views/clients/_bandProfile.html.erb b/web/app/views/clients/_bandProfile.html.erb index 3f58378ee..8b0f819ce 100644 --- a/web/app/views/clients/_bandProfile.html.erb +++ b/web/app/views/clients/_bandProfile.html.erb @@ -1,5 +1,5 @@ -
    +
    <%= image_tag "content/icon_bands.png", :size => "19x19" %> @@ -14,12 +14,17 @@

    +


    @@ -43,7 +48,7 @@
    -
    +

    Location:





    Stats:


    @@ -51,26 +56,105 @@

    -
    -
    - EDIT - INVITE - DELETE + + +
    +
    +
    Bio
    +
    +
    + Edit Bio +
    -

    -

    -
    -


    + +
    +
    +
    Musical Experience
    +
    + + + + + + + + + + + + + + + + + +
    Genres:Genres
    Concert Gigs:
    Status:
    Type:
    +
    +
    -
    + +
    +
    +
    Performance Samples
    +
    + <%=render "profile_summary_performance_samples" %> +
    +
    +
    + +
    +
    +
    Online Presence
    +
    + <%=render "profile_summary_online_presence" %> +
    +
    +
    + + +
    +
    +
    Current Interests
    +
      + + + +
    +
    +
    +
    -
    -
    -
    -
    + +
    +
    + <%= form_tag('', {:id => 'band-feed-form', :class => 'inner-content'}) do %> + <%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'band-feed-controls'}) %> +
    +
    +
    +
    +
    No more feed entries
    +
    Loading ...
    +
    +
    +
    + <% end %> +
    +

    diff --git a/web/app/views/clients/_band_setup.html.erb b/web/app/views/clients/_band_setup.html.erb deleted file mode 100644 index 7d56273f5..000000000 --- a/web/app/views/clients/_band_setup.html.erb +++ /dev/null @@ -1,154 +0,0 @@ - -
    -
    -
    - <%= image_tag "content/icon_bands.png", :size => "19x19" %> -
    - -

    set up band

    - - <%= render "screen_navigation" %> -
    -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - -
    -

    Step 1: General Information

    -
    - - <%= image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 } %> - -

    - Upload band photo.

    - -
    -
    - -
    -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    - CANCEL   - NEXT -
    -
    -
    - -
    -
    -
    -
    -
    - - - - diff --git a/web/app/views/clients/_band_setup.html.slim b/web/app/views/clients/_band_setup.html.slim new file mode 100644 index 000000000..05682a802 --- /dev/null +++ b/web/app/views/clients/_band_setup.html.slim @@ -0,0 +1,226 @@ +#band-setup.screen.secondary layout="screen" layout-id="band/setup" layout-arg="id" + .content-head + .content-icon + = image_tag "content/icon_bands.png", :size => "19x19" + h1#band-setup-title + | my band + = render "screen_navigation" + .content-body + .content-body-scroller + form#band-setup-form + #band-setup-step-0.band-step.content-wrapper + h2 set up band: basics + table.band-form-table + tr + td colspan="2" + + tr#name_row + td colspan="2" + .band-photo.hidden + span.field + a.band-avatar-profile href="#" + = image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 } + span.field + a#band-change-photo.small.ml20 href="#" + | Upload band photo. + + .band-name + .field.band-field + label for="band-name" + | Band Name: + input#band-name type="text" maxlength="1024" value="" + + tr + td.band-country + .field.band-field + label for="band-country" + | Country: + select#band-country + td.band-biography rowspan="3" + .field.band-field + label for="band-biography" + | Description / Bio: + textarea#band-biography rows="8" + tr + td.band-region + .field.band-field + label for="band-region" + | State/Region: + select#band-region + tr + td.band-city + .field.band-field + label for="band-city" + | City: + select#band-city + + + #band-setup-step-1.band-step.content-wrapper + h2 set up band: musical experience + + table.band-form-table + tr + td#tdBandGenres rowspan="3" + .field + label for="band-genres" What genres do you play? + .band-setup-genres + table#band-genres + + td + .field + label for="band-type" Type + select#band-type.easydropdown name="band_type" + option value="" Not Specified + option value="virtual" Virtual + option value="physical" Physical + + tr + td + .field + label for="band-status" Status + select#band-status.easydropdown name="band_status" + option value="" Not Specified + option value="amateur" Amateur + option value="professional" Professional + + tr + td + .field + label for="concert-count" Concert Gigs Played + select#concert-count.easydropdown name="concert_count" + option value="" Not Specified + option value="0" Zero + option value="1" Under 10 + option value="2" 10 to 50 + option value="3" 50 to 100 + option value="4" Over 100 + + #band-setup-step-2.band-step.content-wrapper + h2 set up band: current interests + table.band-form-table + tr + td + label.strong-label for="new-member" + | We want to add a new member   + a.help help-topic="band-profile-add-new-member" [?] + td.new-member-dependent + label for="desired-experience" + | Desired Experience   + a#choose-desired-experience select + td.new-member-dependent + label for="play-commitment" Play Commitment + td.new-member-dependent + label for="touring-option" Touring Option + tr + td + .radio-field + input#new-member-yes.iradio-inline.dependent-master type="radio" name="add_new_members" value='yes' + label for='new-member-yes' Yes + .radio-field + input#new-member-no.iradio-inline.dependent-master type="radio" name="add_new_members" value='no' + label for='new-member-no' No + td.new-member-dependent + #desired-experience-label None specified + td.new-member-dependent + select#play-commitment.easydropdown name="play_commitment" + option value="1" Infrequent + option value="2" Once a Week + option value="3" 2-3 Times Per Week + option value="4" 4+ Times Per Week + td.new-member-dependent + select#touring-option.easydropdown name="touring_option" + option value="yes" Yes + option value="no" No + tr + td + label.strong-label for="paid-gigs" + | We want to play paid gigs   + a.help help-topic="band-profile-play-paid-gigs" [?] + td.paid-gigs-dependent + label for="hourly-rate" Hourly Rate: + td.paid-gigs-dependent + label for="gig-minimum" Gig Minimum: + tr + td + .radio-field + input#paid-gigs-yes.iradio-inline.dependent-master type="radio" name="paid_gigs" value='yes' + label for="paid-gigs-yes" Yes + .radio-field + input#paid-gigs-no.iradio-inline.dependent-master type="radio" name="paid_gigs" value='no' + label for="paid-gigs-no" No + td.paid-gigs-dependent + input#hourly-rate type="number" name="hourly_rate" + td.paid-gigs-dependent + input#gig-minimum type="number" name="gig_minimum" + tr + td + label.strong-label for="free-gigs" + | We want to play free gigs   + a.help help-topic="band-profile-play-free-gigs" [?] + tr + td + .radio-field + input#free-gigs-yes.iradio-inline type="radio" name="free_gigs" value='yes' + label for="free-gigs-yes" Yes + .radio-field + input#free-gigs-no.iradio-inline type="radio" name="free_gigs" value='no' + label for="free-gigs-no" No + + #band-setup-step-3.band-step.content-wrapper + h2 set up band: online presence & performance samples + = render "clients/profile_edit_presence_controls" + + #band-setup-step-4.band-step.content-wrapper + h2 set up band: invite members + br + #band-setup-invite-musicians + 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 + .left.mr20 + .left + a.btn-email-invitation + = image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") + .right.mt5.ml5 + | E-mail + .left.mr20 + .left + a.btn-facebook-invitation + = image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") + .right.mt5.ml5 + | Facebook + .left.left + .left + a.btn-gmail-invitation + = image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") + .right.mt5.ml5 + | Google+ + + br clear="all" + .right + a#btn-band-setup-cancel.nav-button.button-grey + | CANCEL    + a#btn-band-setup-back.nav-button.button-grey.hidden + | BACK + a#btn-band-setup-next.nav-button.button-orange + | SAVE & NEXT + .clearall + br clear="all" + + +script#template-band-setup-genres type="text/template" + tr.band-setup-genre + tr + td + {description} + + +script#template-band-invitation type="text/template" + .invitation user-id="{{userId}}" + | {{userName}} + a + img src="shared/icon_delete_sm.png" width="13" height="13" + + diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index 9af12062e..c85cce272 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -263,6 +263,30 @@ script type="text/template" id="template-help-jamtrack-guide-session" .jamtrack-guide-open.big-help p To try out your JamTrack, click here to open it. +script type="text/template" id="template-help-profile-interests-virtual-band" + | For musicians who want to join a band that plays only online on JamKazam and does not travel and meet in person at a physical space to play. + +script type="text/template" id="template-help-profile-interests-traditional-band" + | For musicians who want to join a more traditional band that meets in person at a physical space to play. It may also play online on JamKazam. + +script type="text/template" id="template-help-profile-interests-paid-sessions" + | For professional session musicians who are qualified and interested in playing paid recording session gigs for bands, composers, and producers. + +script type="text/template" id="template-help-profile-interests-free-sessions" + | For skilled musicians who are interested in playing free recording session gigs for bands, composers, and producers, either for fun or to build experience and resume. + +script type="text/template" id="template-help-profile-interests-cowrite-partners" + | For composers and songwriters who want to collaborate with others in composing and creating new music. + +script type="text/template" id="template-help-band-profile-add-new-member" + | For bands that want to add one or more new members and let the JamKazam community of musicians know your band is looking for new members. + +script type="text/template" id="template-help-band-profile-play-paid-gigs" + | For professional bands that are qualified and interested in playing paid gigs at music venues and other events. + +script type="text/template" id="template-help-band-profile-play-free-gigs" + | For bands that are interested in playing free gigs, either for fun or to build experience. + script type="text/template" id="template-help-jamtrack-landing-preview" .jamtrack-landing-preview.big-help p click a play button to preview the master mix and individual tracks of the JamTrack @@ -285,4 +309,4 @@ script type="text/template" id="template-help-jamtrack-browse-master-mix" script type="text/template" id="template-help-jamtrack-browse-cta" .jamtrack-browse-cta.big-help - p Click to select your first free JamTrack! \ No newline at end of file + p Click to select your first free JamTrack! diff --git a/web/app/views/clients/_musician_search_filter.html.slim b/web/app/views/clients/_musician_search_filter.html.slim new file mode 100644 index 000000000..f9f87697d --- /dev/null +++ b/web/app/views/clients/_musician_search_filter.html.slim @@ -0,0 +1,148 @@ +.content-body-scroller + div#musician-search-filter-builder.content-wrapper + + div#musician-search-filter-results.content-wrapper + div#musician-search-filter-results-header + a#btn-musician-search-builder.button-orange href="#" SEARCH + a#btn-musician-search-reset.button-grey href="#" RESET + div#musician-search-filter-description + div.clearall + div#musician-search-filter-spinner.spinner-large + + div#musician-search-filter-results-wrapper + div#musician-search-filter-results-list-blank + div#musician-search-filter-results-list.content-wrapper + div.paginate-wait + Fetching more results... + div.spinner-small + +script#template-musician-search-filter type="text/template" + #musician-search-filter-builder-top.builder-section + .col-left + h2 search musicians + .col-right.builder-sort-order + .text-label Sort Results By: + select.easydropdown name="sort_order" + option selected="selected" value="{sort_order}" {sort_order} + .clearall + + #musician-search-filter-builder-middle.builder-section + .col-left + .field + label for="search-filter-genres" Genres: + .search-filter-setup-genres.band-setup-genres + table#search-filter-genres cellpadding="10" cellspacing="6" width="100%" + + .field.builder-selector + label Interests: + select.easydropdown name="interests" + option selected="selected" value="{interests}" {interests} + + .field.builder-selector + label Studio Sessions Played: + select.easydropdown name="studio_sessions" + option selected="selected" value="{studio_sessions}" {studio_sessions} + + .col-right + .field + label for="search-filter-instruments" + | Instruments & Skill Level: + .search-filter-setup-instruments.band-setup-genres.builder-instruments + table#search-filter-instruments cellpadding="10" cellspacing="6" width="100%" + + .col-left + .field.builder-selector + label Status: + select.easydropdown name="skill_level" + option selected="selected" value="{skill_level}" {skill_label} + + .field.builder-selector + label Concert Gigs Played: + select.easydropdown name="concert_gigs" + option selected="selected" value="{concert_gigs}" {concert_gigs} + + .col-right + .field.builder-selector + label for="search-filter-ages" Ages: + .search-filter-setup-ages.band-setup-genres.builder-ages + table#search-filter-ages cellpadding="10" cellspacing="6" width="100%" + .clearall + + .clearall + #musician-search-filter-builder-bottom.builder-section.builder-action-buttons + .col-right + a#btn-perform-musician-search.builder-button.button-orange href="#" SEARCH + a#btn-musician-search-cancel.builder-button.button-grey href="#" CANCEL + + +script#template-search-filter-setup-instrument type="text/template" + tr data-instrument-id="{id}" + td {description} + td align="right" width="50%" + select.proficiency_selector name="proficiency" + option value="1" Beginner + option value="2" Intermediate + option value="3" Expert + +script#template-search-filter-setup-genres type="text/template" + tr + td {description} + +script#template-search-filter-setup-ages type="text/template" + tr + td {description} + +/! Session Row Template +script#template-search-musician-row type="text/template" + .profile-band-list-result.musician-list-result data-musician-id="{musician_id}" + .f11 data-hint="container" + .left.musician-avatar + /! avatar + .avatar-small + img src="{avatar_url}" / + .left.musician-info + .first-row data-hint="top-row" + .musician-profile + .result-name + | {musician_name} + .result-location + | {musician_location} + #result_instruments.instruments.nowrap.mt10 + | {instruments} + .musician-stats + span.friend-count + | {friend_count} + img align="absmiddle" alt="friends" height="12" src="../assets/content/icon_friend.png" style="margin-right:4px;" width="14" / + span.follower-count + | {follow_count} + img align="absmiddle" alt="followers" height="12" src="../assets/content/icon_followers.png" style="margin-right:4px;" width="22" / + span.recording-count + | {recording_count} + img align="absmiddle" alt="recordings" height="13" src="../assets/content/icon_recordings.png" style="margin-right:4px;" width="12" / + span.session-count + | {session_count} + img align="absmiddle" alt="sessions" height="12" src="../assets/content/icon_session_tiny.png" style="margin-right:4px;" width="12" / + br clear="both" / + .left.musician-latency + .latency-help + | Your latency + br / + | to {musician_first_name} is: + .latency-holder + | {latency_badge} + br clear="both" / + .button-row data-hint="button-row" + .biography + | {biography} + .result-list-button-wrapper data-musician-id="{musician_id}" + | {musician_action_template} + br clear="both" / + + +script#template-search-musician-action-btns type="text/template" + a.button-orange.smallbutton href="{profile_url}" PROFILE + - if current_user && current_user.musician? + a.smallbutton.search-m-friend class="{friend_class}" href="#" {friend_caption} + a.smallbutton.search-m-follow class="{follow_class}" href="#" {follow_caption} + a.smallbutton.search-m-message class="{message_class}" href="#" {message_caption} + .clearall diff --git a/web/app/views/clients/_musicians.html.erb b/web/app/views/clients/_musicians.html.erb deleted file mode 100644 index c31f8648e..000000000 --- a/web/app/views/clients/_musicians.html.erb +++ /dev/null @@ -1,83 +0,0 @@ - -<%= content_tag(:div, :layout => 'screen', 'layout-id' => 'musicians', :class => "screen secondary", id: 'musicians-screen') do -%> - <%= content_tag(:div, :class => :content) do -%> - <%= content_tag(:div, :class => 'content-head') do -%> - <%= content_tag(:div, image_tag("content/icon_musicians.png", {:height => 19, :width => 19}), :class => 'content-icon') %> - <%= content_tag(:h1, 'musicians') %> - <%= render "screen_navigation" %> - <% end -%> - <%= content_tag(:div, :class => 'content-body') do -%> - <%= form_tag('', {:id => 'find-musician-form', :class => 'inner-content'}) do -%> - <%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_MUSICIAN}) %> - <%= content_tag(:div, :class => 'filter-body') do %> - <%= content_tag(:div, :class => 'content-body-scroller') do -%> - <%= content_tag(:div, :class => 'content-wrapper musician-wrapper') do -%> - <%= content_tag(:div, '', :id => 'musician-filter-results', :class => 'filter-results') %> -
    Fetching more results... -
    -
    - <%= content_tag(:div, 'No more results.', :class => 'end-of-list', :id => 'end-of-musician-list') %> - <% end -%> - <% end -%> - <% end -%> - <% end -%> - <% end -%> - <% end -%> -<% end -%> - - - - - \ No newline at end of file diff --git a/web/app/views/clients/_musicians.html.slim b/web/app/views/clients/_musicians.html.slim new file mode 100644 index 000000000..5356da76e --- /dev/null +++ b/web/app/views/clients/_musicians.html.slim @@ -0,0 +1,60 @@ +#musicians-screen.screen.secondary layout="screen" layout-id="musicians" + .content + .content-head + .content-icon + img alt="Icon_musicians" height="19" src="/assets/content/icon_musicians.png" width="19" / + h1 musicians + = render "screen_navigation" + .content-body + = render "clients/musician_search_filter" + +/! Session Row Template +script#template-find-musician-row type="text/template" + .profile-band-list-result.musician-list-result data-musician-id="{musician_id}" + .f11 data-hint="container" + .left.musician-avatar + /! avatar + .avatar-small + img src="{avatar_url}" / + .left.musician-info + .first-row data-hint="top-row" + .musician-profile + .result-name musician_name + .result-location musician_location + #result_instruments.instruments.nowrap.mt10 instruments + .musician-stats + span.friend-count + | {friend_count} + img align="absmiddle" alt="friends" height="12" src="../assets/content/icon_friend.png" style="margin-right:4px;" width="14" / + span.follower-count + | {follow_count} + img align="absmiddle" alt="followers" height="12" src="../assets/content/icon_followers.png" style="margin-right:4px;" width="22" / + span.recording-count + | {recording_count} + img align="absmiddle" alt="recordings" height="13" src="../assets/content/icon_recordings.png" style="margin-right:4px;" width="12" / + span.session-count + | {session_count} + img align="absmiddle" alt="sessions" height="12" src="../assets/content/icon_session_tiny.png" style="margin-right:4px;" width="12" / + br clear="both" / + .left.musician-latency + .latency-help + | Your latency + br>/ + | to {musician_first_name} is: + .latency-holder + | {latency_badge} + br clear="both" / + .button-row data-hint="button-row" + .biography biography + .result-list-button-wrapper data-musician-id="{musician_id}" + | {musician_action_template} + br clear="both" / + + +script#template-musician-action-btns type="text/template" + a.button-orange.smallbutton href="{profile_url}" PROFILE + - if current_user && current_user.musician? + a.smallbutton.search-m-friend class="{friend_class}" href="#" {friend_caption} + a.smallbutton.search-m-follow class="{follow_class}" href="#" {follow_caption} + a.smallbutton.search-m-message class="{message_class}" href="#" {message_caption" + .clearall diff --git a/web/app/views/clients/_profile.html.erb b/web/app/views/clients/_profile.html.erb index 2316b7730..52e3b459a 100644 --- a/web/app/views/clients/_profile.html.erb +++ b/web/app/views/clients/_profile.html.erb @@ -1,159 +1,244 @@
    -
    -
    - <%= image_tag "content/icon_profile.png", :size => "19x19" %> +
    +
    + <%= image_tag "content/icon_profile.png", :size => "19x19" %> +
    + +

    musician profile

    + + <%= render "screen_navigation" %> +
    +
    +
    +
    + +
    +

    + <%= link_to("EDIT PROFILE", '/client#/account/profile', :id => "btn-edit", :class => "button-orange") %>
    -

    musician profile

    + + +

    - <%= render "screen_navigation" %> -
    -
    - -
    + +
    +
    + +
    +
    - -

    + + +
    +
    - -
    -
    +
    +
    + +
    +
    +

    Location:


    +
    +

    +

    Stats:


    +
    +
    +
    +
    +
    +
    +
    +
    + +
    Bio
    +
    + +
    + + +
    - -
    - ADD FRIEND - FOLLOW - MESSAGE - <%= link_to("EDIT PROFILE", '/client#/account/profile', :id => "btn-profile-edit", :class => "button-orange") %> -
    -

    +
    Musical Experience
    +
    - -
    -
    - -
    -
    - - -
    +
    +
    +
    + +
    +
    Status:
    +
    +
    +
    +
    Genres:
    +
    +
    +
    +
    Concert Gigs:
    +
    +
    +
    +
    Studio Gigs:
    +
    +
    + +
    +
    + +
    Performance Samples
    +
    + + <%=render "profile_summary_performance_samples" %> + +
    + +
    +
    + +
    Online Presence
    +
    + + <%=render "profile_summary_online_presence" %> + +
    + +
    +
    + +
    Current Interests
    +
    +
    None specified
    + + -
    -
    - -
    -
    -

    Location:


    -


    -

    Stats:


    -
    -
    -
    -
    -
    -
    -
    -
    -
    - You have no bio to describe yourself as a musician. Enter one now! -
    -
    -

    EDIT BIO -
    -
    -
    - -
    -

    -
    - OK -
    -
    - CANCEL -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - <%= form_tag('', {:id => 'user-feed-form', :class => 'inner-content'}) do %> - <%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'user-feed-controls'}) %> -
    -
    -
    -
    -
    No more feed entries
    -
    Loading ...
    -
    -
    +
    +
    I'm interested in playing free gigs
    +
    +
    +
      + +
    +
    +
    + +
    +
    I'm interested in co-writing
    +
    +
    +
      + +
    +
    +
    + +
    +
    I'm interested in forming traditional band(s)
    +
    +
    +
      + +
    +
    +
    + +
    +
    I'm interested in forming virtual band(s)
    +
    +
    +
      + +
    +
    +
    + +
    + + +
    +
    +
    +
    + <%= form_tag('', {:id => 'user-feed-form', :class => 'inner-content'}) do %> + <%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_FEED, :id => 'user-feed-controls'}) %> +
    +
    +
    +
    +
    No more feed entries
    +
    Loading ...
    +
    +
    +
    + <% end %> +
    +
    +
    +
    +
    +
    +
    +

    Friends

    +
    +
    +

    Following

    +
    +
    +

    Followers

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    - - <% end %> -
    -
    -
    -
    -
    -
    -
    -

    Friends

    -
    -
    -

    Following

    -
    -
    -

    Followers

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    - -
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    diff --git a/web/app/views/layouts/landing.html.erb b/web/app/views/layouts/landing.html.erb index 54a316d1d..13f816530 100644 --- a/web/app/views/layouts/landing.html.erb +++ b/web/app/views/layouts/landing.html.erb @@ -62,6 +62,9 @@ <% end %> JK.app = JK.JamKazam(); + JK.getGenreList().done(function(genres) { + JK.genres = genres; + }); JK.app.initialize({inClient: false, layoutOpts: {layoutFooter: false, sizeOverlayToContent: true}}); }) diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index 9a8965853..19f8fd91b 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -117,6 +117,10 @@ JK.bindHoverEvents(); + JK.getGenreList().done(function(genres) { + JK.genres = genres; + }); + JK.JamServer.connect() // singleton here defined in JamServer.js .done(function() { console.log("websocket connected") diff --git a/web/app/views/spikes/musician_search_filter.html.slim b/web/app/views/spikes/musician_search_filter.html.slim new file mode 100644 index 000000000..777cf2636 --- /dev/null +++ b/web/app/views/spikes/musician_search_filter.html.slim @@ -0,0 +1,15 @@ += javascript_include_tag "profile_utils" += javascript_include_tag "musician_search_filter" += stylesheet_link_tag "client/musician" +#musician_search_spike += render "clients/musician_search_filter" + +javascript: + var initialized = false; + $(document).on('JAMKAZAM_READY', function(e, data) { + setTimeout(function() { + window.musician_search_filter = new JK.MusicianSearchFilter(); + musician_search_filter.init(); + musician_search_filter.afterShow(); + }, 1) + }); diff --git a/web/app/views/users/_instrument_selector.html.erb b/web/app/views/users/_instrument_selector.html.erb index 0b8c1d180..3101f6473 100644 --- a/web/app/views/users/_instrument_selector.html.erb +++ b/web/app/views/users/_instrument_selector.html.erb @@ -2,7 +2,7 @@ <% Instrument.standard_list.each do |instrument| %> <%= instrument.description %> - + <% end %> \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index 7be4d9f68..43ee05098 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -113,6 +113,7 @@ SampleApp::Application.routes.draw do match '/widgets/jam_track_preview', to: 'spikes#jam_track_preview' match '/site_validate', to: 'spikes#site_validate' match '/recording_source', to: 'spikes#recording_source' + match '/musician_search_filter', to: 'spikes#musician_search_filter' # junk pages match '/help', to: 'static_pages#help' @@ -140,7 +141,7 @@ SampleApp::Application.routes.draw do match '/about', to: 'corps#about', as: 'corp_about' # news routes - match '/news', to: 'corps#news', as: 'corp_news' + match '/news', to: 'corps#news', as: 'corp_news' # media center routes match '/media_center', to: 'corps#media_center', as: 'corp_media_center' @@ -236,7 +237,7 @@ SampleApp::Application.routes.draw do match '/shopping_carts' => 'api_shopping_carts#index', :via => :get match '/shopping_carts' => 'api_shopping_carts#remove_cart', :via => :delete match '/shopping_carts/clear_all' => 'api_shopping_carts#clear_all', :via => :delete - + # RSVP requests match '/rsvp_requests' => 'api_rsvp_requests#index', :via => :get match '/rsvp_requests' => 'api_rsvp_requests#create', :via => :post @@ -267,7 +268,7 @@ SampleApp::Application.routes.draw do match '/users' => 'api_users#index', :via => :get match '/users' => 'api_users#create', :via => :post match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail' - #match '/users' => 'api_users#create', :via => :post + #match '/users' => 'api_users#create', :via => :post match '/users/:id' => 'api_users#update', :via => :post match '/users/:id' => 'api_users#delete', :via => :delete match '/users/confirm/:signup_token' => 'api_users#signup_confirm', :via => :post, :as => 'api_signup_confirmation' @@ -276,7 +277,7 @@ SampleApp::Application.routes.draw do match '/users/:id/set_password' => 'api_users#set_password', :via => :post # recurly - match '/recurly/create_account' => 'api_recurly#create_account', :via => :post + match '/recurly/create_account' => 'api_recurly#create_account', :via => :post match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete match '/recurly/get_account' => 'api_recurly#get_account', :via => :get match '/recurly/payment_history' => 'api_recurly#payment_history', :via => :get @@ -310,7 +311,7 @@ SampleApp::Application.routes.draw do match '/users/:id/likings' => 'api_users#liking_index', :via => :get, :as => 'api_user_liking_index' match '/users/:id/likings' => 'api_users#liking_create', :via => :post match '/users/:id/likings/:likable_id' => 'api_users#liking_destroy', :via => :delete - + # user followers match '/users/:id/followers' => 'api_users#follower_index', :via => :get, :as => 'api_user_follower_index' @@ -374,6 +375,10 @@ SampleApp::Application.routes.draw do match '/users/:id/share/session/:provider' => 'api_users#share_session', :via => :get match '/users/:id/share/recording/:provider' => 'api_users#share_recording', :via => :get + #profile + match '/users/:id/profile' => 'api_users#profile_show', :via => :get, :as => 'api_users_profile_show' + match '/users/:id/profile' => 'api_users#profile_save', :via => :post + # broadcast notification match '/users/:id/broadcast_notification' => 'api_users#broadcast_notification', :via => :get match '/users/:id/broadcast_notification/:broadcast_id/quiet' => 'api_users#quiet_broadcast_notification', :via => :post @@ -425,7 +430,7 @@ SampleApp::Application.routes.draw do # band likers match '/bands/:id/likers' => 'api_bands#liker_index', :via => :get - + # band followers match '/bands/:id/followers' => 'api_bands#follower_index', :via => :get @@ -460,6 +465,7 @@ SampleApp::Application.routes.draw do # search match '/search' => 'api_search#index', :via => :get + match '/search/musicians' => 'api_search#musicians', :via => [:get, :post] # join requests match '/join_requests/:id' => 'api_join_requests#show', :via => :get, :as => 'api_join_request_detail' @@ -467,13 +473,13 @@ SampleApp::Application.routes.draw do match '/join_requests' => 'api_join_requests#create', :via => :post match '/join_requests/:id' => 'api_join_requests#update', :via => :put match '/join_requests' => 'api_join_requests#index', :via => :get - + # Location lookups match '/countries' => 'api_maxmind_requests#countries', :via => :get match '/regions' => 'api_maxmind_requests#regions', :via => :get match '/cities' => 'api_maxmind_requests#cities', :via => :get match '/resolved_location' => 'api_maxmind_requests#resolved_location', :via => :get - + # Recordings match '/recordings/uploads' => 'api_recordings#list_uploads', :via => :get, :as => 'api_recordings_list_uploads' diff --git a/web/lib/max_mind_manager.rb b/web/lib/max_mind_manager.rb index 3bb24c966..fceef6353 100644 --- a/web/lib/max_mind_manager.rb +++ b/web/lib/max_mind_manager.rb @@ -5,14 +5,25 @@ class MaxMindManager < BaseManager end def self.countries - Country.get_all.map { |c| {countrycode: c.countrycode, countryname: c.countryname} } + Country.get_all.map do |c| + country = Carmen::Country.coded(c.countrycode) + { + countrycode: c.countrycode, + countryname: country.name + } + end end - def self.regions(country) - Region.get_all(country).map { |r| { region: r.region, name: r.regionname } } - end - + Region.get_all(country).map do |r| + country = Carmen::Country.coded(r.countrycode) + region = country.subregions.coded(r.region) + { + region: r.region, + name: region.name + } + end + end def self.cities(country, region) City.get_all(country, region).map { |c| c.city } @@ -22,7 +33,7 @@ class MaxMindManager < BaseManager GeoIpBlocks.connection.execute("select generate_scores_dataset()").check end - private +private def clear_location_table @pg_conn.exec("DELETE FROM geoiplocations").clear @@ -31,6 +42,4 @@ class MaxMindManager < BaseManager def clear_isp_table @pg_conn.exec("DELETE FROM geoispip").clear end - -end - +end # class MaxMindManager \ No newline at end of file diff --git a/web/lib/tasks/sample_data.rake b/web/lib/tasks/sample_data.rake index e996d06c4..8ba62eb5a 100644 --- a/web/lib/tasks/sample_data.rake +++ b/web/lib/tasks/sample_data.rake @@ -34,10 +34,13 @@ namespace :db do end task populate: :environment do - make_users(10) if 14 > User.count + make_users(30) if 14 > User.count make_friends + make_followings make_bands make_band_members + # make_music_sessions_history + # make_music_sessions_user_history make_recording end @@ -101,16 +104,18 @@ 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| + genres = Genre.all + 20.times do |nn| obj = MusicSession.new obj.music_session_id = rand(100000000).to_s - obj.description = Faker::Lorem.paragraph + obj.description = 'description goes here' # Faker::Lorem.paragraph avoid accidental profanity 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.genre = genres.sample + obj.legal_terms = true + obj.name = Faker::Lorem.sentence obj.save! end end @@ -182,20 +187,65 @@ end def make_users(num=99) admin = User.create!(first_name: Faker::Name.name, last_name: Faker::Name.name, - email: "example@railstutorial.org", + email: Faker::Internet.safe_email, password: "foobar", password_confirmation: "foobar", terms_of_service: true) admin.toggle!(:admin) + + instruments = Instrument.all + genres = Genre.all num.times do |n| - email = "example-#{n+1}@railstutorial.org" password = "password" - User.create!(first_name: Faker::Name.name, + uu = User.create!(first_name: Faker::Name.name, last_name: Faker::Name.name, terms_of_service: true, - email: email, + email: Faker::Internet.email, password: password, + city: Faker::Address.city, + state: Faker::Address.state_abbr, + country: 'US', password_confirmation: password) + uu.musician = true + uu.birth_date = Time.now - 13.years - rand(65).years + num_instrument = rand(4) + 1 + user_instruments = instruments.sample(num_instrument) + num_instrument.times do |mm| + musician_instrument = MusicianInstrument.new + musician_instrument.player = uu + musician_instrument.instrument = user_instruments[mm] + musician_instrument.proficiency_level = rand(3) + 1 + musician_instrument.priority = rand(num_instrument) + uu.musician_instruments << musician_instrument + end + num_genre = rand(4) + 1 + user_genres = genres.sample(num_genre) + num_genre.times do |mm| + genre_player = GenrePlayer.new + genre_player.player_id = uu.id + genre_player.player_type = uu.class.name + genre_player.genre_id = user_genres[mm].id + genre_player.genre_type = GenrePlayer::PROFILE + uu.genre_players << genre_player + end + uu.skill_level = rand(2) + 1 + uu.studio_session_count = rand(100) + uu.concert_count = rand(40) + uu.virtual_band = 0==rand(2) + uu.traditional_band = 0==rand(2) + uu.paid_sessions = 0==rand(2) + uu.free_sessions = 0==rand(2) + uu.cowriting = 0==rand(2) + uu.save! + yn = true + while yn + begin + uu.biography = Faker::Lorem.sentence + uu.save! + yn = false + rescue + end + end end end @@ -220,8 +270,11 @@ def make_followings users = User.all users.each do |uu| users[0..rand(users.count)].shuffle.each do |uuu| - uuu.followings << uu unless 0 < Follow.where(:followable_id => uu.id, :user_id => uuu.id).count - uu.followings << uuu unless 0 < Follow.where(:followable_id => uuu.id, :user_id => uu.id).count if rand(3)==0 + next if 0 < Follow.where(:followable_id => uu.id, :user_id => uuu.id).count + follow = Follow.new + follow.followable = uu + follow.user = uuu + follow.save end end end diff --git a/web/lib/utils.rb b/web/lib/utils.rb index 80fe08cbe..13732db48 100644 --- a/web/lib/utils.rb +++ b/web/lib/utils.rb @@ -12,14 +12,30 @@ class Utils RECORDING_SOURCES.include?(site) end - def self.extract_recording_id(site, recording_url) + def self.extract_recording_data(site, recording_url) recording_url.strip! + rec_data = {} case site when 'rec_youtube' # regex derived from: https://gist.github.com/afeld/1254889 - if recording_url =~ /(youtu.be\/|youtube.com\/(watch\?(.*&)?v=|(embed|v)\/))([^\?&\"\'>]+)/ - return $5 + watch = (recording_url =~ /(youtu.be\/|youtube.com\/(watch\?(.*&)?v=|(embed|v)\/))([^\?&\"\'>]+)/) + rec_data["id"] = $5 if watch + + uri = URI.parse("https://gdata.youtube.com/feeds/api/videos/#{$5}?v=2&alt=json") + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if uri.scheme == 'https' + req = Net::HTTP::Get.new(uri) + response = http.request(req) + if response && response.body.length > 2 + json = response.body.force_encoding("UTF-8")# .encode('UTF-8', :invalid => :replace, :replace => '') + json_hash = JSON.parse(json) + + if watch && json_hash["entry"] && json_hash["entry"]["title"] + rec_data["title"] = json_hash["entry"]["title"]["$t"] + end end + when 'rec_soundcloud' if recording_url =~ /^https?:\/\/.*soundcloud.com\/.+/ tmpfile = Tempfile.new(site) @@ -29,11 +45,15 @@ class Utils result = File.read(tmpfile.path) File.delete(tmpfile.path) if result =~ /"soundcloud:\/\/sounds:(\d+)"/ - return $1 + rec_data["id"] = $1 end + + if result =~ /property=\"og:title\" content=\"([\w\W]+)\"> true, :type => :feature, :capybara_feature => true do fill_in "first_name", with: "Bobby" fill_in "last_name", with: "Toes" uncheck('subscribe_email') - find("#account-edit-profile-submit").trigger(:click) + find(".account-edit-profile-submit").trigger(:click) end it { user.subscribe_email.should be_true # we haven't user.reload yet should have_selector('h1', text: 'my account') - should have_selector('#notification h2', text: 'Profile Changed') user.reload user.subscribe_email.should be_false user.first_name.should == "Bobby" @@ -108,7 +107,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do jk_select("Jan", '#account-edit-profile-form #user_birth_date_2i') jk_select("12", '#account-edit-profile-form #user_birth_date_3i') jk_select("1960", '#account-edit-profile-form #user_birth_date_1i') - find("#account-edit-profile-submit").trigger(:click) + find(".account-edit-profile-submit").trigger(:click) user.reload user.birth_date == "1960-01-12" @@ -126,7 +125,7 @@ describe "Account", :js => true, :type => :feature, :capybara_feature => true do before(:each) do fill_in "first_name", with: "" fill_in "last_name", with: "" - find("#account-edit-profile-submit").trigger(:click) + find(".account-edit-profile-submit").trigger(:click) end it { diff --git a/web/spec/features/affiliate_visit_tracking.rb b/web/spec/features/affiliate_visit_tracking.rb new file mode 100644 index 000000000..09ee9dcd5 --- /dev/null +++ b/web/spec/features/affiliate_visit_tracking.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe "affiliate visit tracking" do + + subject { page } + + let(:user) { FactoryGirl.create(:user) } + let(:partner) { FactoryGirl.create(:affiliate_partner) } + let(:affiliate_params) { partner.affiliate_query_params } + + before(:each) do + AffiliateReferralVisit.delete_all + end + + it "tracks" do + visit '/?' + affiliate_params + + should_be_at_root + AffiliateReferralVisit.count.should eq(1) + visit = AffiliateReferralVisit.first + visit.visited_url.should eq('/?' + affiliate_params) + visit.affiliate_partner_id.should eq(partner.id) + visit.first_visit.should be_true + + download_url = '/downloads?' + affiliate_params + visit download_url + find('h2.create-account-header') + + + AffiliateReferralVisit.count.should eq(2) + visit = AffiliateReferralVisit.find_by_visited_url(download_url) + visit.affiliate_partner_id.should eq(partner.id) + visit.first_visit.should be_false + end + +end diff --git a/web/spec/features/bands_spec.rb b/web/spec/features/bands_spec.rb index eb3834be3..01fcf1e71 100644 --- a/web/spec/features/bands_spec.rb +++ b/web/spec/features/bands_spec.rb @@ -20,38 +20,61 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do def navigate_band_setup login=user sign_in_poltergeist(login) if current_url == 'about:blank' find('div.homecard.profile').trigger(:click) - find('#profile-bands-link').trigger(:click) + find('#bands-link').trigger(:click) find('#band-setup-link').trigger(:click) expect(page).to have_selector('#band-setup-title') end - def fill_out_band_setup_form(band, biography, params={}) + def fill_out_band_setup_form(band, biography, country="United States", region="Texas", city="Austin", params={}) navigate_band_setup unless URI.parse(current_url).fragment == '/band/setup/new' params['band-name'] ||= band || "Default band name" params['band-biography'] ||= biography || "Default band biography" within('#band-setup-form') do params.each do |field, value| - fill_in field, with: "#{value}" + fill_in field, with: value end - first('#band-genres input[type="checkbox"]').trigger(:click) + # Move to experience pane: + #first('#band-genres input[type="checkbox"]').trigger(:click) end + jk_select(country, '#band-country') + jk_select(region, '#band-region') + jk_select(city, '#band-city') + sleep 1 # work around race condition + find('#btn-band-setup-next').trigger(:click) - find('h2', text: 'Step 2: Add Band Members') + find('h2', text: 'set up band: musical experience') end - def complete_band_setup_form(band, biography, params={}) - fill_out_band_setup_form(band, biography, params) - find('#btn-band-setup-save').trigger(:click) + def complete_band_setup_form(band, biography, country="United States", region="Texas", city="Austin", params={}) + navigate_to_friend_page(band, biography, country, region, city, params) + + # Save + find('#btn-band-setup-next').trigger(:click) + sleep(1) + end + + def navigate_to_friend_page(band, biography, country="United States", region="Texas", city="Austin", params={}) + fill_out_band_setup_form(band, biography, country, region, city, params) + + find(:css, "#african").set(true) + find('#btn-band-setup-next').trigger(:click) + find('h2', text: 'set up band: current interests') + + find('#btn-band-setup-next').trigger(:click) + find('h2', text: 'set up band: online presence & performance samples') + + find('#btn-band-setup-next').trigger(:click) + find('h2', text: 'set up band: invite members') end context "band profile - new band setup" do it "displays 'Set up your band' link to user" do sign_in_poltergeist user view_profile_of user - find('#profile-bands-link').trigger(:click) + find('#bands-link').trigger(:click) expect(page).to have_selector('#band-setup-link') end @@ -59,7 +82,7 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do in_client(fan) do sign_in_poltergeist fan view_profile_of user - find('#profile-bands-link').trigger(:click) + find('#bands-link').trigger(:click) expect(page).to_not have_selector('#band-setup-link') end @@ -68,17 +91,20 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do it "indicates required fields and user may eventually complete" do navigate_band_setup find('#btn-band-setup-next').trigger(:click) - expect(page).to have_selector('#tdBandName .error-text li', text: "can't be blank") - expect(page).to have_selector('#tdBandBiography .error-text li', text: "can't be blank") - expect(page).to have_selector('#tdBandGenres .error-text li', text: "At least 1 genre is required.") + expect(page).to have_selector('#band-setup .band-name .error-text li', text: "can't be blank") + expect(page).to have_selector('#band-setup .band-biography .error-text li', text: "can't be blank") - complete_band_setup_form("Band name", "Band biography") + complete_band_setup_form("Test Band name", "Test Band biography") - expect(page).to have_selector('#band-profile-name', text: "Band name") - expect(page).to have_selector('#band-profile-biography', text: "Band biography") + + expect(page).to have_selector('#band-profile-name', text: "Test Band name") + expect(page).to have_selector('#band-profile-biography', text: "Test Band biography") end it "limits genres to 3" do + pending "Move this to experience pane" + #expect(page).to have_selector('#band-setup .band-genres .error-text li', text: "At least 1 genre is required.") + genres = Genre.limit(4) navigate_band_setup within('#band-setup-form') do @@ -89,7 +115,7 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do end end find('#btn-band-setup-next').trigger(:click) - expect(page).to have_selector('#tdBandGenres .error-text li', text: "No more than 3 genres are allowed.") + expect(page).to have_selector('#band-setup .band-genres .error-text li', text: "No more than 3 genres are allowed.") end it "handles max-length field input" do @@ -106,11 +132,11 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do within('#band-setup-form') do fill_in 'band-name', with: band_name fill_in 'band-biography', with: band_bio - all('#band-genres input[type="checkbox"]').first.trigger(:click) + #all('#band-genres input[type="checkbox"]').first.trigger(:click) end sleep 1 find('#btn-band-setup-next').trigger(:click) - expect(page).to have_selector('#tdBandBiography .error-text li', text: "is too long (maximum is 4000 characters)") + #expect(page).to have_selector('#band-biography .error-text li', text: "is too long (maximum is 4000 characters)") end it "handles special characters in text fields" do @@ -118,7 +144,7 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do band_name = garbage(3) + ' ' + garbage(50) band_bio = garbage(500) band_website = garbage(2000) - complete_band_setup_form(band_name, band_bio, 'band-website' => band_website) + complete_band_setup_form(band_name, band_bio)#, 'band-website' => band_website) expect(page).to have_selector('#band-profile-name', text: Sanitize.fragment(band_name)) expect(page).to have_selector('#band-profile-biography', text: Sanitize.fragment(band_bio)) @@ -134,17 +160,19 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do band_attributes = { name: 'Radical D00dz', biography: 'Just a bunch of EXTREME dudes who ROCK professionally!!', - # website: 'www.radicald00dz.com', # not displayed - VRFS-1617 - # country: 'US', # not displayed - city: 'Lubbock', - state: 'TX' + country: 'US', + state: 'TX', + city: 'Lubbock' } some_band = FactoryGirl.create(:band, band_attributes) sign_in_poltergeist fan view_band_profile_of some_band - band_attributes.each_value { |v| expect(page).to have_content v } + + expect(page).to have_content 'Radical D00dz' + expect(page).to have_content 'Just a bunch of EXTREME dudes who ROCK professionally!!' + expect(page).to have_content 'Lubbock, TX' end it "allows a user to follow the band" @@ -179,14 +207,14 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do band_bio = "Good, good friends" band_website = "http://www.sounds.com/thetwoguysfrom2009.html" - fill_out_band_setup_form(band_name, band_bio, 'band-website' => band_website) + navigate_to_friend_page(band_name, band_bio)#, 'band-website' => band_website) #invite somebody using the picker find('#btn-choose-friends-band').trigger(:click) find("tr[user-id='#{friend.id}']").trigger(:click) expect(page).to have_selector("tr.selected[user-id='#{friend.id}']") find('#btn-save-friends').trigger(:click) - find('#btn-band-setup-save').trigger(:click) + find('#btn-band-setup-next').trigger(:click) sleep 1 # ensure the transaction commits.. find('#band-profile-members-link').trigger(:click) @@ -233,7 +261,7 @@ describe "Bands", :js => true, :type => :feature, :capybara_feature => true do expect(page).to have_selector('#btn-edit-band-profile') find('#btn-edit-band-profile').trigger(:click) - find('h2', text: 'General Information') + find('h2', text: 'set up band: basics') expect(page).to have_content band.name expect(page).to have_content band.biography diff --git a/web/spec/features/feed_spec.rb b/web/spec/features/feed_spec.rb index 62004cbda..b31fafd4f 100644 --- a/web/spec/features/feed_spec.rb +++ b/web/spec/features/feed_spec.rb @@ -98,7 +98,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do # confirm navigate to user profile page find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click) - find("#user-profile h2[id=profile-username]", text: user.name) + find("#user-profile h2[id=username]", text: user.name) end # it "should render play widget" do @@ -198,7 +198,7 @@ describe "Feed", :js => true, :type => :feature, :capybara_feature => true do # confirm navigate to user profile page find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click) - find("#user-profile h2[id=profile-username]", text: user.name) + find("#user-profile h2[id=username]", text: user.name) end end diff --git a/web/spec/features/musician_profile_spec.rb b/web/spec/features/musician_profile_spec.rb new file mode 100644 index 000000000..f6d1de28f --- /dev/null +++ b/web/spec/features/musician_profile_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe "Musicians", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + before(:all) do + Capybara.default_wait_time = 15 + end + + let(:fan) { FactoryGirl.create(:fan) } + let(:user) { FactoryGirl.create(:user) } + let(:finder) { FactoryGirl.create(:user) } + let(:claimed_recording) { FactoryGirl.create(:claimed_recording) } + + before(:each) do + UserMailer.deliveries.clear + end + + def navigate_musician_setup login=user + sign_in_poltergeist(login) if current_url == 'about:blank' + find('div.homecard.profile').trigger(:click) + end + + it "Basic Test" do + navigate_musician_setup + #save_screenshot("setup.png") + expect(page).to have_selector('#user-profile', visible: true) + expect(page).to have_selector('#no-samples', visible: true) + end + + + it "shows Online Presence" do + PerformanceSample.create!(:player_id => user.id, :service_type => "jamkazam", :claimed_recording_id => claimed_recording.id) + PerformanceSample.create!(:player_id => user.id, :service_type => "soundcloud", :service_id => "67890") + PerformanceSample.create!(:player_id => user.id, :service_type => "soundcloud", :service_id => "67891") + + navigate_musician_setup + expect(page).to have_selector('#no-samples', visible: true) + + expect(page).to have_selector('.jamkazam-playable', count: 1) + expect(page).to have_selector('.twitter-playable', count: 0) + expect(page).to have_selector('.sound-cloud-playable', count: 2) + end + + it "shows Performance Samples" do + OnlinePresence.create!({:player_id => user.id, :username => "myonlineusername", :service_type => "facebook"}) + OnlinePresence.create!({:player_id => user.id, :username => "myonlineusername", :service_type => "twitter"}) + OnlinePresence.create!({:player_id => user.id, :username => "myonlineusername", :service_type => "soundcloud"}) + + navigate_musician_setup + expect(page).to have_selector('#facebook-presence', visible: true) + expect(page).to have_selector('#soundcloud-presence', visible: true) + expect(page).to have_selector('#twitter-presence', visible: true) + end + +end \ No newline at end of file diff --git a/web/spec/features/musician_search_spec.rb b/web/spec/features/musician_search_spec.rb index 9e7cc8733..0bf51ffb6 100644 --- a/web/spec/features/musician_search_spec.rb +++ b/web/spec/features/musician_search_spec.rb @@ -26,16 +26,12 @@ describe "Musician Search", :js => true, :type => :feature, :capybara_feature => fast_signin(austin_user, "/client#/musicians") end - it "shows the musician search page" do - expect(page).to have_selector('#find-musician-form') - end - it "shows search results" do - expect(page).to have_selector('#musician-filter-results .musician-list-result') + expect(page).to have_selector('#musician-search-filter-results .musician-list-result') end it "shows submits query" do - expect(page).to have_selector('#musician-filter-results .musician-list-result') + expect(page).to have_selector('#musician-search-filter-results .musician-list-result') end it "shows blank result set" do @@ -61,6 +57,7 @@ describe "Musician Search", :js => true, :type => :feature, :capybara_feature => verify_find_musician_score(110, austin_user, dallas_user) end + # TODO: This test is broken due to this bug: VRFS-3119 - bad location / distance in SQL: it "shows search by distance" do # this test does a distance search with the austin user, then opens up the 'change search location' dialog, @@ -68,7 +65,6 @@ describe "Musician Search", :js => true, :type => :feature, :capybara_feature => miami_user.touch # no scores, but should still show seattle_user.touch # no scores, but should still show - wait_for_easydropdown('#musician_order_by') jk_select('Distance', '#musician_order_by') diff --git a/web/spec/features/profile_history_spec.rb b/web/spec/features/profile_history_spec.rb index 551b1dc22..4603383e3 100644 --- a/web/spec/features/profile_history_spec.rb +++ b/web/spec/features/profile_history_spec.rb @@ -83,7 +83,7 @@ describe "Profile History", :js => true, :type => :feature, :capybara_feature => # confirm navigate to user profile page find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click) - find("#user-profile h2[id=profile-username]", text: user.name) + find("#user-profile h2[id=username]", text: user.name) end end @@ -167,7 +167,7 @@ describe "Profile History", :js => true, :type => :feature, :capybara_feature => # confirm navigate to user profile page find(".avatar-tiny[user-id=\"#{user.id}\"][hoveraction=\"musician\"]").trigger(:click) - find("#user-profile h2[id=profile-username]", text: user.name) + find("#user-profile h2[id=username]", text: user.name) end it "should allow edit and delete" do diff --git a/web/spec/features/profile_menu_spec.rb b/web/spec/features/profile_menu_spec.rb index c70240b4f..ea25ab3fa 100644 --- a/web/spec/features/profile_menu_spec.rb +++ b/web/spec/features/profile_menu_spec.rb @@ -67,7 +67,8 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr if pops_external page.driver.window_handles.last page.within_window page.driver.window_handles.last do - should have_content('JamTracks are multi-track pro recordings you can use to:') + should have_content("Play and control your JamTracks multi-track recordings") + #should have_content('JamTracks are multi-track pro recordings you can use to:') end else should have_selector("a.current-os-download") diff --git a/web/spec/javascripts/fixtures/musicianSearch.html.slim b/web/spec/javascripts/fixtures/musicianSearch.html.slim new file mode 100644 index 000000000..587b23540 --- /dev/null +++ b/web/spec/javascripts/fixtures/musicianSearch.html.slim @@ -0,0 +1,4 @@ +# = javascript_include_tag "profile_utils" +# = javascript_include_tag "musician_search_filter" +# = stylesheet_link_tag "client/musician" += render "clients/musician_search_filter" diff --git a/web/spec/javascripts/musician_search_filter_spec.js.coffee b/web/spec/javascripts/musician_search_filter_spec.js.coffee new file mode 100644 index 000000000..1fbddb452 --- /dev/null +++ b/web/spec/javascripts/musician_search_filter_spec.js.coffee @@ -0,0 +1,57 @@ +describe "MusicianSearchFilter", -> + fixture.preload("musicianSearch.html") + + beforeEach => + @filterServer = sinon.fakeServer.create(); + window.jamClient = sinon.stub() + @fixtures = fixture.load("musicianSearch.html", true) + window.gon = {} + window.gon.isNativeClient = true + @searchMeta = {"per_page":10,"filter_keys":{"keys":["sort_order","instruments","interests","genres","concert_gigs","studio_sessions","skill_level","ages"],"multi":["instruments","genres","ages"],"single":["sort_order","interests","concert_gigs","studio_sessions","skill_level"]},"sort_order":{"keys":["latency","distance"],"map":{"latency":"Latency to Me","distance":"Distance to Me"}},"interests":{"keys":["any","virtual_band","traditional_band","paid_sessions","free_sessions","cowriting"],"map":{"any":"Any","virtual_band":"Virtual Band","traditional_band":"Traditional Band","paid_sessions":"Paid Sessions","free_sessions":"Free Sessions","cowriting":"Co-Writing"}},"ages":{"keys":[-1,10,20,30,40,50],"map":{"-1":"Any","10":"Teens","20":"20's","30":"30's","40":"40's","50":"50+"}}} + window.gon.musician_search_meta = @searchMeta + @defaultFilter = {sort_order: "latency", instruments: [], interests: "any", genres: [], concert_gigs: "-1", studio_sessions: "-1", skill_level: "-1", ages: [-1]} + @genres = [{"description":"African","id":"african"},{"description":"Alternative Rock","id":"alternative rock"},{"description":"Ambient","id":"ambient"},{"description":"Americana","id":"americana"},{"description":"Asian","id":"asian"},{"description":"Avante-Garde","id":"avante-garde"},{"description":"Bluegrass","id":"bluegrass"},{"description":"Blues","id":"blues"},{"description":"Breakbeat","id":"breakbeat"},{"description":"Classical","id":"classical"},{"description":"Country","id":"country"},{"description":"Downtempo","id":"downtempo"},{"description":"Drum & Bass","id":"drum & bass"},{"description":"Electronic","id":"electronic"},{"description":"Folk","id":"folk"},{"description":"Hip Hop","id":"hip hop"},{"description":"House","id":"house"},{"description":"Industrial","id":"industrial"},{"description":"Jazz","id":"jazz"},{"description":"Latin","id":"latin"},{"description":"Metal","id":"metal"},{"description":"Other","id":"other"},{"description":"Pop","id":"pop"},{"description":"Psychedelic","id":"psychedelic"},{"description":"Punk","id":"punk"},{"description":"R&B","id":"r&b"},{"description":"Reggae","id":"reggae"},{"description":"Religious","id":"religious"},{"description":"Rock","id":"rock"},{"description":"Ska","id":"ska"},{"description":"Techno","id":"techno"},{"description":"Trance","id":"trance"}] + @instruments = [{"id":"acoustic guitar","description":"Acoustic Guitar","popularity":3},{"id":"bass guitar","description":"Bass Guitar","popularity":3},{"id":"computer","description":"Computer","popularity":3},{"id":"drums","description":"Drums","popularity":3},{"id":"electric guitar","description":"Electric Guitar","popularity":3},{"id":"keyboard","description":"Keyboard","popularity":3},{"id":"voice","description":"Voice","popularity":3},{"id":"accordion","description":"Accordion","popularity":2},{"id":"banjo","description":"Banjo","popularity":2},{"id":"clarinet","description":"Clarinet","popularity":2},{"id":"flute","description":"Flute","popularity":2},{"id":"harmonica","description":"Harmonica","popularity":2},{"id":"piano","description":"Piano","popularity":2},{"id":"saxophone","description":"Saxophone","popularity":2},{"id":"trombone","description":"Trombone","popularity":2},{"id":"trumpet","description":"Trumpet","popularity":2},{"id":"upright bass","description":"Upright Bass","popularity":2},{"id":"violin","description":"Violin","popularity":2},{"id":"cello","description":"Cello","popularity":1},{"id":"euphonium","description":"Euphonium","popularity":1},{"id":"french horn","description":"French Horn","popularity":1},{"id":"mandolin","description":"Mandolin","popularity":1},{"id":"oboe","description":"Oboe","popularity":1},{"id":"other","description":"Other","popularity":1},{"id":"tuba","description":"Tuba","popularity":1},{"id":"ukulele","description":"Ukulele","popularity":1},{"id":"viola","description":"Viola","popularity":1}] + + afterEach => + @filterServer.restore() + + describe "displays empty search criteria", => + beforeEach => + @filterUI = new JK.MusicianSearchFilter() + @filterUI.init() + + it "displays validator widget for default filter", => + @filterServer.respondWith("GET", "/api/search/musicians.json", [200, { "content-type": "application/json" }, JSON.stringify(@defaultFilter)]) + @filterServer.respondWith("GET", "/api/genres.json", [200, { "content-type": "application/json" }, JSON.stringify(@genres)]) + @filterServer.respondWith("GET", "/api/getInstruments.json", [200, { "content-type": "application/json" }, JSON.stringify(@instruments)]) + + @filterUI.afterShow(); + @filterServer.respond() + expect($('#search-filter-genres input').length).toEqual(@genres.length) + expect($('#search-filter-instruments input').length).toEqual(@instruments.length) + expect($('select[name="sort_order"]').val()).toEqual(@searchMeta.sort_order.keys[0]) + expect($('select[name="interests"]').val()).toEqual(@searchMeta.interests.keys[0]) + expect($('select[name="skill_level"]').val()).toEqual("-1") + expect($('select[name="studio_sessions"]').val()).toEqual("-1") + expect($('select[name="concert_gigs"]').val()).toEqual("-1") + expect($('#search-filter-genres input[selected]').length).toEqual(0) + expect($('#search-filter-ages input[selected]').length).toEqual(0) + expect($('#search-filter-instruments input[selected]').length).toEqual(0) + expect($('#search-filter-instruments select.proficiency_selector[value=2]').length).toEqual(0) + + xit "displays validator widget for edited filter", => + @defaultFilter = {sort_order: @searchMeta.sort_order.keys[1], instruments: [], interests: @searchMeta.interests.keys[1], genres: ['ambient','asian'], concert_gigs: "1", studio_sessions: "1", skill_level: "1", ages: [-1]} + @filterServer.respondWith("GET", "/api/search/musicians.json", [200, { "content-type": "application/json" }, JSON.stringify(@defaultFilter)]) + sinon.spy() + @filterServer.respondWith("GET", "/api/genres.json", [200, { "content-type": "application/json" }, JSON.stringify(@genres)]) + sinon.spy() + @filterUI.afterShow(); + @filterServer.respond() + expect($('select[name="sort_order"]').val()).toEqual(@searchMeta.sort_order.keys[1]) + expect($('select[name="interests"]').val()).toEqual(@searchMeta.interests.keys[1]) + expect($('select[name="skill_level"]').val()).toEqual(@defaultFilter.skill_level) + expect($('select[name="studio_sessions"]').val()).toEqual(@defaultFilter.studio_sessions) + expect($('select[name="concert_gigs"]').val()).toEqual(@defaultFilter.concert_gigs) + expect($('#search-filter-genres input[checked]').length).toEqual(@defaultFilter.genres.length) + diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 39b109ee9..83a285a2e 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -736,5 +736,5 @@ end def nav_profile_history(user) visit Nav.profile(user) - find('#profile-history-link').trigger(:click) + find('#history-link').trigger(:click) end \ No newline at end of file diff --git a/web/vendor/assets/javascripts/bugsnag.js b/web/vendor/assets/javascripts/bugsnag.js new file mode 100644 index 000000000..0bdefaadb --- /dev/null +++ b/web/vendor/assets/javascripts/bugsnag.js @@ -0,0 +1,10 @@ +// 2.4.8 http://d2wy8f7a9ursnm.cloudfront.net/bugsnag-2.4.8.min.js + + +// START COPY/PASTE FROM BUGSNAG CDN +!function(a,b){function c(a,b){try{if("function"!=typeof a)return a;if(!a.bugsnag){var c=e();a.bugsnag=function(d){if(b&&b.eventHandler&&(u=d),v=c,!y){var e=a.apply(this,arguments);return v=null,e}try{return a.apply(this,arguments)}catch(f){throw l("autoNotify",!0)&&(x.notifyException(f,null,null,"error"),s()),f}finally{v=null}},a.bugsnag.bugsnag=a.bugsnag}return a.bugsnag}catch(d){return a}}function d(){B=!1}function e(){var a=document.currentScript||v;if(!a&&B){var b=document.scripts||document.getElementsByTagName("script");a=b[b.length-1]}return a}function f(a){var b=e();b&&(a.script={src:b.src,content:l("inlineScript",!0)?b.innerHTML:""})}function g(b){var c=l("disableLog"),d=a.console;void 0===d||void 0===d.log||c||d.log("[Bugsnag] "+b)}function h(b,c,d){if(d>=5)return encodeURIComponent(c)+"=[RECURSIVE]";d=d+1||1;try{if(a.Node&&b instanceof a.Node)return encodeURIComponent(c)+"="+encodeURIComponent(r(b));var e=[];for(var f in b)if(b.hasOwnProperty(f)&&null!=f&&null!=b[f]){var g=c?c+"["+f+"]":f,i=b[f];e.push("object"==typeof i?h(i,g,d):encodeURIComponent(g)+"="+encodeURIComponent(i))}return e.join("&")}catch(j){return encodeURIComponent(c)+"="+encodeURIComponent(""+j)}}function i(a,b){if(null==b)return a;a=a||{};for(var c in b)if(b.hasOwnProperty(c))try{a[c]=b[c].constructor===Object?i(a[c],b[c]):b[c]}catch(d){a[c]=b[c]}return a}function j(a,b){a+="?"+h(b)+"&ct=img&cb="+(new Date).getTime();var c=new Image;c.src=a}function k(a){var b={},c=/^data\-([\w\-]+)$/;if(a)for(var d=a.attributes,e=0;e\n";var f=[];try{for(var h=arguments.callee.caller.caller;h&&f.length"}return a.nodeName}}function s(){z+=1,a.setTimeout(function(){z-=1})}function t(a,b,c){var d=a[b],e=c(d);a[b]=e}var u,v,w,x={},y=!0,z=0,A=10;x.noConflict=function(){return a.Bugsnag=b,x},x.refresh=function(){A=10},x.notifyException=function(a,b,c,d){b&&"string"!=typeof b&&(c=b,b=void 0),c||(c={}),f(c),n({name:b||a.name,message:a.message||a.description,stacktrace:p(a)||o(),file:a.fileName||a.sourceURL,lineNumber:a.lineNumber||a.line,columnNumber:a.columnNumber?a.columnNumber+1:void 0,severity:d||"warning"},c)},x.notify=function(b,c,d,e){n({name:b,message:c,stacktrace:o(),file:a.location.toString(),lineNumber:1,severity:e||"warning"},d)};var B="complete"!==document.readyState;document.addEventListener?(document.addEventListener("DOMContentLoaded",d,!0),a.addEventListener("load",d,!0)):a.attachEvent("onload",d);var C,D=/^[0-9a-f]{32}$/i,E=/function\s*([\w\-$]+)?\s*\(/i,F="https://notify.bugsnag.com/",G=F+"js",H="2.4.8",I=document.getElementsByTagName("script"),J=I[I.length-1];if(a.atob){if(a.ErrorEvent)try{0===new a.ErrorEvent("test").colno&&(y=!1)}catch(K){}}else y=!1;if(l("autoNotify",!0)){t(a,"onerror",function(b){return function(c,d,e,g,h){var i=l("autoNotify",!0),j={};!g&&a.event&&(g=a.event.errorCharacter),f(j),v=null,i&&!z&&n({name:h&&h.name||"window.onerror",message:c,file:d,lineNumber:e,columnNumber:g,stacktrace:h&&p(h)||o(),severity:"error"},j),b&&b(c,d,e,g,h)}});var L=function(a){return function(b,d){if("function"==typeof b){b=c(b);var e=Array.prototype.slice.call(arguments,2);return a(function(){b.apply(this,e)},d)}return a(b,d)}};t(a,"setTimeout",L),t(a,"setInterval",L),a.requestAnimationFrame&&t(a,"requestAnimationFrame",function(a){return function(b){return a(c(b))}}),a.setImmediate&&t(a,"setImmediate",function(a){return function(){var b=Array.prototype.slice.call(arguments);return b[0]=c(b[0]),a.apply(this,b)}}),"EventTarget Window Node ApplicationCache AudioTrackList ChannelMergerNode CryptoOperation EventSource FileReader HTMLUnknownElement IDBDatabase IDBRequest IDBTransaction KeyOperation MediaController MessagePort ModalWindow Notification SVGElementInstance Screen TextTrack TextTrackCue TextTrackList WebSocket WebSocketWorker Worker XMLHttpRequest XMLHttpRequestEventTarget XMLHttpRequestUpload".replace(/\w+/g,function(b){var d=a[b]&&a[b].prototype;d&&d.hasOwnProperty&&d.hasOwnProperty("addEventListener")&&(t(d,"addEventListener",function(a){return function(b,d,e,f){try{d&&d.handleEvent&&(d.handleEvent=c(d.handleEvent,{eventHandler:!0}))}catch(h){g(h)}return a.call(this,b,c(d,{eventHandler:!0}),e,f)}}),t(d,"removeEventListener",function(a){return function(b,d,e,f){return a.call(this,b,d,e,f),a.call(this,b,c(d),e,f)}}))})}a.Bugsnag=x,"function"==typeof define&&define.amd?define([],function(){return x}):"object"==typeof module&&"object"==typeof module.exports&&(module.exports=x)}(window,window.Bugsnag); +// END COPY/PASTE FROM BUGSNAG CDN + + +// manual code: make sure Bugsnag has it's API KEY +window.Bugsnag.apiKey = gon.global.bugsnag_key \ No newline at end of file diff --git a/web/vendor/assets/stylesheets/icheck/minimal/minimal.css b/web/vendor/assets/stylesheets/icheck/minimal/minimal.css index fea9c54f1..ffda1e558 100644 --- a/web/vendor/assets/stylesheets/icheck/minimal/minimal.css +++ b/web/vendor/assets/stylesheets/icheck/minimal/minimal.css @@ -12,6 +12,7 @@ cursor: pointer; } + .icheckbox_minimal { background-position: 0 0; } diff --git a/websocket-gateway/spec/factories.rb b/websocket-gateway/spec/factories.rb index ed446a48f..c08ac5d0e 100644 --- a/websocket-gateway/spec/factories.rb +++ b/websocket-gateway/spec/factories.rb @@ -19,7 +19,7 @@ FactoryGirl.define do end before(:create) do |user| - user.musician_instruments << FactoryGirl.build(:musician_instrument, user: user) + user.musician_instruments << FactoryGirl.build(:musician_instrument, player: user) end end