diff --git a/admin/.rakeTasks b/admin/.rakeTasks new file mode 100644 index 000000000..c6865d9a1 --- /dev/null +++ b/admin/.rakeTasks @@ -0,0 +1,7 @@ + + diff --git a/admin/Gemfile b/admin/Gemfile index 3ec3ed16b..750fb4e6d 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -71,6 +71,7 @@ gem 'rest-client' gem 'iso-639' gem 'rubyzip' gem 'sanitize' +gem 'slim' group :libv8 do gem 'libv8', "~> 3.11.8" diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index b6afa5e35..3e8c1dd6b 100644 --- a/admin/app/admin/jam_ruby_users.rb +++ b/admin/app/admin/jam_ruby_users.rb @@ -14,27 +14,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do filter :updated_at - form do |ff| - ff.inputs "Details" do - ff.input :email - ff.input :admin - ff.input :raw_password, :label => 'Password' - ff.input :first_name - ff.input :last_name - ff.input :city - ff.input :state - ff.input :country - ff.input :musician - ff.input :can_invite - ff.input :photo_url - ff.input :session_settings - end - ff.inputs "Signup" do - ff.input :email_template, :label => 'Welcome Email Template Name' - ff.input :confirm_url, :label => 'Signup Confirm URL' - end - ff.actions - end + form :partial => "form" show do |user| attributes_table do @@ -53,8 +33,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do row :gender row :email_confirmed row :image do user.photo_url ? image_tag(user.photo_url) : '' end - row :session_settings - row :can_invite end active_admin_comments end @@ -114,6 +92,30 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do # call `create!` to ensure that the rest of the action continues as normal create! end + + def update + + @user = resource + @user.email = params[:jam_ruby_user][:email] + @user.admin = params[:jam_ruby_user][:admin] + @user.musician = params[:jam_ruby_user][:musician] + @user.first_name = params[:jam_ruby_user][:first_name] + @user.last_name = params[:jam_ruby_user][:last_name] + @user.state = params[:jam_ruby_user][:state] + @user.city = params[:jam_ruby_user][:city] + + + if params[:jam_ruby_user][:show_frame_options].to_i == 1 + @user.mod_merge({User::MOD_GEAR => {User::MOD_GEAR_FRAME_OPTIONS => true}}) + else + @user.delete_mod(User::MOD_GEAR, User::MOD_GEAR_FRAME_OPTIONS) + end + + @user.save! + + redirect_to edit_admin_user_path(@user) + + end end end diff --git a/admin/app/admin/jam_track_licensors.rb b/admin/app/admin/jam_track_licensors.rb new file mode 100644 index 000000000..65fdaed6d --- /dev/null +++ b/admin/app/admin/jam_track_licensors.rb @@ -0,0 +1,8 @@ +ActiveAdmin.register JamRuby::JamTrackLicensor, :as => 'JamTrack Licensors' do + + menu :label => 'JamTrack Licensors', :parent => 'JamTracks' + + config.sort_order = 'name_asc' + config.batch_actions = false + +end \ No newline at end of file diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb new file mode 100644 index 000000000..0f6d99e08 --- /dev/null +++ b/admin/app/admin/jam_tracks.rb @@ -0,0 +1,57 @@ +ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do + + menu :label => 'JamTracks', :parent => 'JamTracks' + + config.sort_order = 'name_asc' + config.batch_actions = false + + filter :genre + filter :status, :as => :select, collection: JamRuby::JamTrack::STATUS + + form :partial => 'form' + + index do + column :id + column :name + column :description + column :bpm + column :time_signature + column :status + column :recording_type + column :original_artist + column :songwriter + column :publisher + column :licensor + column :pro + column :genre + column :sales_region + column :price + column :reproduction_royalty + column :public_performance_royalty + column :reproduction_royalty_amount + column :licensor_royalty_amount + column :pro_royalty_amount + column :url + column :created_at + + column :jam_track_tracks do |jam_track| + table_for jam_track.jam_track_tracks.order('position ASC') do + column :id + column :track_type + column :instrument + column :part + column :track do |track| + link_to 'Play', '#' + end + end + end + + # default_actions # use this for all view/edit/delete links + column "Actions" do |jam_track| + links = ''.html_safe + links << link_to("Show Tracks", '#', :class => "member_link view_link show_tracks") + links << link_to("Update", edit_resource_path(jam_track), :class => "member_link edit_link") + links + end + end +end \ No newline at end of file diff --git a/admin/app/assets/javascripts/jam_track.js b/admin/app/assets/javascripts/jam_track.js new file mode 100644 index 000000000..a8781672a --- /dev/null +++ b/admin/app/assets/javascripts/jam_track.js @@ -0,0 +1,43 @@ +$(document).ready(function() { + $("th.jam_track_tracks").css('display', 'none'); + $("td.jam_track_tracks").css('display', 'none'); + + $(".show_tracks").click(function(e) { + e.preventDefault(); + + var $rowJamTrack = $(this).parents('tr'); + var $jamTrackTracks = $($rowJamTrack).find("td.jam_track_tracks"); + + var count = $jamTrackTracks.find("table tbody tr").length; + + if ($rowJamTrack.next().attr('id') == "jam_track_tracks_detail") { + $(this).html("Show Tracks"); + $rowJamTrack.next().remove(); + } + else { + $(this).html('Hide Tracks'); + if (count == 0) { + $rowJamTrack.after( + $("").html( + $("") + ).append( + $("").html( + "No Tracks" + ) + ) + ); + } + else { + $rowJamTrack.after( + $("").html( + $("") + ).append( + $("").html( + $jamTrackTracks.html() + ) + ) + ); + } + } + }) +}); \ No newline at end of file diff --git a/admin/app/views/admin/jam_tracks/_form.html.haml b/admin/app/views/admin/jam_tracks/_form.html.haml new file mode 100644 index 000000000..5c2028673 --- /dev/null +++ b/admin/app/views/admin/jam_tracks/_form.html.haml @@ -0,0 +1,31 @@ += semantic_form_for([:admin, resource], :html => {:multipart => true}, :url => resource.new_record? ? admin_jam_tracks_path : "#{ENV['RAILS_RELATIVE_URL_ROOT']}/admin/jam_tracks/#{resource.id}") do |f| + = f.semantic_errors *f.object.errors.keys + = f.inputs name: 'JamTrack fields' do + + = f.input :name + = f.input :description + = f.input :bpm + = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES + = f.input :status, collection: JamRuby::JamTrack::STATUS + = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE + = f.input :original_artist + = f.input :songwriter + = f.input :publisher + = f.input :licensor, collection: JamRuby::JamTrackLicensor.all + = f.input :pro, collection: JamRuby::JamTrack::PRO + = f.input :genre, collection: JamRuby::Genre.all + = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION + = f.input :price + = f.input :reproduction_royalty, :label => 'Reproduction Royalty' + = f.input :public_performance_royalty, :label => 'Public Performance Royalty' + = f.input :reproduction_royalty_amount + = f.input :licensor_royalty_amount + = f.input :pro_royalty_amount + = f.input :url, :as => :file, :label => 'Audio File' + + = f.semantic_fields_for :jam_track_tracks do |track| + = render 'jam_track_track_fields', f: track + .links + = link_to_add_association 'Add Track', f, :jam_track_tracks, class: 'button', style: 'margin:20px;padding:10px 20px' + + = f.actions \ No newline at end of file diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml new file mode 100644 index 000000000..11200771b --- /dev/null +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml @@ -0,0 +1,18 @@ += f.inputs name: 'Track fields' do + + %ol.nested-fields + = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE + = f.input :instrument, collection: Instrument.all + = f.input :part + = f.input :position + + - if f.object.new_record? + %p{style: 'margin-left:10px'} + %i before you can upload, you must select 'Update JamTrack' + - else + = f.input :url, :as => :file, :label => 'Track file' + - unless f.object.nil? || f.object[:url].nil? + .current_file_holder{style: 'margin-bottom:10px'} + %a{href: f.object.sign_url(3600), style: 'padding:0 0 0 20px'} Download + + = link_to_remove_association "Delete Track", f, class: 'button', style: 'margin-left:10px' \ No newline at end of file diff --git a/admin/app/views/admin/users/_form.html.slim b/admin/app/views/admin/users/_form.html.slim new file mode 100644 index 000000000..204d9a4da --- /dev/null +++ b/admin/app/views/admin/users/_form.html.slim @@ -0,0 +1,12 @@ += semantic_form_for([:admin_users, resource], :url => resource.new_record? ? admin_users_path : "#{ENV['RAILS_RELATIVE_URL_ROOT']}/admin/users/#{resource.id}") do |f| + = f.inputs "Details" do + = f.input :email, label: 'Email' + = f.input :admin + = f.input :first_name + = f.input :last_name + = f.input :city + = f.input :state + = f.input :musician + = f.inputs "Gear Mods" do + = f.input :show_frame_options, as: :boolean + = f.actions diff --git a/admin/config/application.rb b/admin/config/application.rb index 08b11d58c..1b9485036 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -134,5 +134,10 @@ module JamAdmin # recording upload/download configs config.max_track_upload_failures = 10 config.max_track_part_upload_failures = 3 + + # Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more. + config.recurly_private_api_key = '7d623daabfc2434fa2a893bb008eb3e6' + # Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more. + config.recurly_public_api_key = 'sc-SZlO11shkeA1WMGuISLGg5' end end diff --git a/admin/config/environments/test.rb b/admin/config/environments/test.rb index 1f9229001..33880f53b 100644 --- a/admin/config/environments/test.rb +++ b/admin/config/environments/test.rb @@ -42,4 +42,9 @@ JamAdmin::Application.configure do config.twitter_app_secret = 'PfG1jAUMnyrimPcDooUVQaJrG1IuDjUyGg5KciOo' config.redis_host = "localhost:6379:1" # go to another db to not cross pollute into dev/production redis dbs + + # Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more. + config.recurly_private_api_key = '4631527f203b41848523125b3ae51341' + # Use Public Keys to identify your site when using Recurly.js. See https://docs.recurly.com/js/#include to learn more. + config.recurly_public_api_key = 'sc-s6G2OA80Rwyvsb1RmS3mAE' end diff --git a/admin/config/initializers/jam_ruby_user.rb b/admin/config/initializers/jam_ruby_user.rb index 980c82b9b..6d7dca153 100644 --- a/admin/config/initializers/jam_ruby_user.rb +++ b/admin/config/initializers/jam_ruby_user.rb @@ -1,11 +1,5 @@ class JamRuby::User - EMAIL_TMPL_WELCOME = 'welcome_message' - EMAIL_TMPL_WELCOME_BETA = 'welcome_betauser' - EMAIL_TMPL_WELCOMES = [EMAIL_TMPL_WELCOME_BETA, EMAIL_TMPL_WELCOME] - - CONFIRM_URL = "http://www.jamkazam.com/confirm" # we can't get request.host_with_port, so hard-code confirm url (with user override) - attr_accessible :admin, :raw_password, :musician, :can_invite, :photo_url, :session_settings, :confirm_url, :email_template # :invite_email def raw_password @@ -25,50 +19,12 @@ end end - def confirm_url - @signup_confirm_url ||= CONFIRM_URL - end - - def confirm_url= url - @signup_confirm_url = url - end - - def email_template - @signup_email_template ||= EMAIL_TMPL_WELCOME_BETA - end - - def email_template= tmpl - @signup_email_template = tmpl - end def admin_user User.where(:email => self.email)[0] end - after_create do - self.update_attribute(:signup_token, SecureRandom.urlsafe_base64) - url = confirm_url - url.chomp!('/') - - # only supporting two welcome templates ATM - if EMAIL_TMPL_WELCOMES.index(self.email_template).nil? - self.email_template = EMAIL_TMPL_WELCOME - end - # UserMailer.send(self.email_template, self, "#{url}/#{self.signup_token}").deliver + def show_frame_options + self.get_gear_mod(MOD_GEAR_FRAME_OPTIONS) end - - after_save do - logger.debug("*** after_save: #{self.admin_changed?}") - if self.admin_changed? - if self.admin - if self.admin_user.nil? - au = User.create(:email => self.email) - au.update_attribute(:encrypted_password, self.password_digest) - end - else - self.admin_user.try(:destroy) - end - end - end - end diff --git a/admin/spec/factories.rb b/admin/spec/factories.rb index 3ba6c50aa..08f668ff3 100644 --- a/admin/spec/factories.rb +++ b/admin/spec/factories.rb @@ -205,4 +205,54 @@ FactoryGirl.define do latency_tester.save end end + + factory :jam_track_licensor, :class => JamRuby::JamTrackLicensor do + sequence(:name) { |n| "licensor-#{n}" } + sequence(:description) { |n| "description-#{n}" } + sequence(:attention) { |n| "attention-#{n}" } + sequence(:address_line_1) { |n| "address1-#{n}" } + sequence(:address_line_2) { |n| "address2-#{n}" } + sequence(:city) { |n| "city-#{n}" } + sequence(:state) { |n| "state-#{n}" } + sequence(:zip_code) { |n| "zipcode-#{n}" } + sequence(:contact) { |n| "contact-#{n}" } + sequence(:email) { |n| "email-#{n}" } + sequence(:phone) { |n| "phone-#{n}" } + end + + factory :jam_track, :class => JamRuby::JamTrack do + sequence(:name) { |n| "jam-track-#{n}" } + sequence(:description) { |n| "description-#{n}" } + bpm 100.1 + time_signature '4/4' + status 'Production' + recording_type 'Cover' + sequence(:original_artist) { |n| "original-artist-#{n}" } + sequence(:songwriter) { |n| "songwriter-#{n}" } + sequence(:publisher) { |n| "publisher-#{n}" } + pro 'ASCAP' + sales_region 'United States' + price 1.99 + reproduction_royalty true + public_performance_royalty true + reproduction_royalty_amount 0.999 + licensor_royalty_amount 0.999 + pro_royalty_amount 0.999 + + genre JamRuby::Genre.first + association :licensor, factory: :jam_track_licensor + end + + factory :jam_track_track, :class => JamRuby::JamTrackTrack do + position 1 + part 'lead guitar' + track_type 'Track' + instrument JamRuby::Instrument.find('electric guitar') + association :jam_track, factory: :jam_track + end + + factory :jam_track_right, :class => JamRuby::JamTrackRight do + association :jam_track, factory: :jam_track + association :user, factory: :user + end end diff --git a/db/manifest b/db/manifest index 3625cb735..13d9784e6 100755 --- a/db/manifest +++ b/db/manifest @@ -226,3 +226,6 @@ add_session_create_type.sql user_syncs_and_quick_mix.sql user_syncs_fix_dup_tracks_2408.sql deletable_recordings.sql +jam_tracks.sql +shopping_carts.sql +recurly.sql \ No newline at end of file diff --git a/db/up/jam_tracks.sql b/db/up/jam_tracks.sql new file mode 100644 index 000000000..a6e716fa2 --- /dev/null +++ b/db/up/jam_tracks.sql @@ -0,0 +1,68 @@ +CREATE TABLE jam_track_licensors ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + name VARCHAR NOT NULL UNIQUE, + description TEXT, + attention TEXT, + address_line_1 VARCHAR, + address_line_2 VARCHAR, + city VARCHAR, + state VARCHAR, + zip_code VARCHAR, + contact VARCHAR, + email VARCHAR, + phone VARCHAR, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); + +CREATE TABLE jam_tracks ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + name VARCHAR NOT NULL UNIQUE, + description TEXT, + bpm decimal, + time_signature VARCHAR, + status VARCHAR, + recording_type VARCHAR, + original_artist TEXT, + songwriter TEXT, + publisher TEXT, + pro VARCHAR, + sales_region VARCHAR, + price decimal, + reproduction_royalty BOOLEAN, + public_performance_royalty BOOLEAN, + reproduction_royalty_amount DECIMAL, + licensor_royalty_amount DECIMAL, + pro_royalty_amount DECIMAL, + url VARCHAR, + md5 VARCHAR, + length BIGINT, + licensor_id VARCHAR(64) REFERENCES jam_track_licensors (id) ON DELETE SET NULL, + genre_id VARCHAR(64) REFERENCES genres (id) ON DELETE SET NULL, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); + +CREATE TABLE jam_track_tracks ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + position INTEGER, + track_type VARCHAR, + jam_track_id VARCHAR(64) REFERENCES jam_tracks(id) ON DELETE CASCADE, + instrument_id VARCHAR(64) REFERENCES instruments(id) ON DELETE SET NULL, + part VARCHAR, + url VARCHAR, + md5 VARCHAR, + length BIGINT, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); + +CREATE INDEX jam_track_tracks_position_uniqkey ON jam_track_tracks (position, jam_track_id); + +CREATE TABLE jam_track_rights ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_id VARCHAR(64) NOT NULL REFERENCES users(id), + jam_track_id VARCHAR(64) NOT NULL REFERENCES jam_tracks(id) +); + +CREATE INDEX jam_tracks_rights_uniqkey ON jam_track_rights (user_id, jam_track_id); \ No newline at end of file diff --git a/db/up/recurly.sql b/db/up/recurly.sql new file mode 100644 index 000000000..4745fa14d --- /dev/null +++ b/db/up/recurly.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN recurly_code VARCHAR(50) DEFAULT NULL; +ALTER TABLE jam_tracks ADD COLUMN plan_code VARCHAR(50) DEFAULT NULL; \ No newline at end of file diff --git a/db/up/shopping_carts.sql b/db/up/shopping_carts.sql new file mode 100644 index 000000000..1ec83122f --- /dev/null +++ b/db/up/shopping_carts.sql @@ -0,0 +1,10 @@ +CREATE TABLE shopping_carts ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(), + quantity INTEGER NOT NULL DEFAULT 1, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + cart_id VARCHAR(64) NOT NULL, + cart_class_name VARCHAR(64), + cart_type VARCHAR(64), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 22f97a24f..07234042f 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -72,6 +72,8 @@ require "jam_ruby/app/uploaders/perf_data_uploader" require "jam_ruby/app/uploaders/recorded_track_uploader" require "jam_ruby/app/uploaders/mix_uploader" require "jam_ruby/app/uploaders/music_notation_uploader" +require "jam_ruby/app/uploaders/jam_track_uploader" +require "jam_ruby/app/uploaders/jam_track_track_uploader" require "jam_ruby/app/uploaders/max_mind_release_uploader" require "jam_ruby/lib/desk_multipass" require "jam_ruby/lib/ip" @@ -171,11 +173,16 @@ require "jam_ruby/models/email_batch_new_musician" require "jam_ruby/models/email_batch_progression" require "jam_ruby/models/email_batch_scheduled_sessions" require "jam_ruby/models/email_batch_set" +require "jam_ruby/models/jam_track_licensor" +require "jam_ruby/models/jam_track" +require "jam_ruby/models/jam_track_track" +require "jam_ruby/models/jam_track_right" require "jam_ruby/app/mailers/async_mailer" require "jam_ruby/app/mailers/batch_mailer" require "jam_ruby/app/mailers/progress_mailer" require "jam_ruby/models/affiliate_partner" require "jam_ruby/models/chat_message" +require "jam_ruby/models/shopping_cart" require "jam_ruby/models/generic_state" require "jam_ruby/models/score_history" require "jam_ruby/models/jam_company" diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb new file mode 100644 index 000000000..4a4d8cc4f --- /dev/null +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_track_uploader.rb @@ -0,0 +1,28 @@ +class JamTrackTrackUploader < CarrierWave::Uploader::Base + # include CarrierWaveDirect::Uploader + include CarrierWave::MimeTypes + + process :set_content_type + + def initialize(*) + super + JamRuby::UploaderConfiguration.set_aws_private_configuration(self) + end + + # Add a white list of extensions which are allowed to be uploaded. + def extension_white_list + %w(ogg) + end + + def store_dir + nil + end + + def md5 + @md5 ||= ::Digest::MD5.file(current_path).hexdigest + end + + def filename + "#{model.store_dir}/#{model.filename}" if model.id + end +end diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb new file mode 100644 index 000000000..784d646c4 --- /dev/null +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb @@ -0,0 +1,28 @@ +class JamTrackUploader < CarrierWave::Uploader::Base + # include CarrierWaveDirect::Uploader + include CarrierWave::MimeTypes + + process :set_content_type + + def initialize(*) + super + JamRuby::UploaderConfiguration.set_aws_private_configuration(self) + end + + # Add a white list of extensions which are allowed to be uploaded. + def extension_white_list + %w(jka) + end + + def store_dir + nil + end + + def md5 + @md5 ||= ::Digest::MD5.file(current_path).hexdigest + end + + def filename + "#{model.store_dir}/#{model.filename}" if model.id + end +end diff --git a/ruby/lib/jam_ruby/constants/validation_messages.rb b/ruby/lib/jam_ruby/constants/validation_messages.rb index ea914a4c9..7dc4d955a 100644 --- a/ruby/lib/jam_ruby/constants/validation_messages.rb +++ b/ruby/lib/jam_ruby/constants/validation_messages.rb @@ -40,6 +40,11 @@ module ValidationMessages EMAIL_MATCHES_CURRENT = "is same as your current email" INVALID_FPFILE = "is not valid" + # recurly + RECURLY_ERROR = "Error occurred during Recurly transaction." + RECURLY_ACCOUNT_ERROR = "You don't have Recurly account yet." + RECURLY_PARAMETER_ERROR = "You didn't input correct information for Recurly transaction." + #connection USER_OR_LATENCY_TESTER_PRESENT = "user or latency_tester must be present" SELECT_AT_LEAST_ONE = "Please select at least one track" # DO NOT CHANGE THIS TEXT MESSAGE UNLESS YOU CHANGE createSession.js.erb, which is looking for it @@ -80,7 +85,7 @@ module ValidationMessages DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender' # mods - MODS_NO_SHOW_MUST_BE_HASH = 'no_show must be a hash' + MODS_MUST_BE_HASH = 'must be a hash' MODS_UNKNOWN_KEY = 'unknown mod' # takes either a string/string hash, or a string/array-of-strings|symbols hash, diff --git a/ruby/lib/jam_ruby/lib/s3_manager.rb b/ruby/lib/jam_ruby/lib/s3_manager.rb index 8e31532d1..3b6a6bf77 100644 --- a/ruby/lib/jam_ruby/lib/s3_manager.rb +++ b/ruby/lib/jam_ruby/lib/s3_manager.rb @@ -88,6 +88,14 @@ module JamRuby end end + def exists?(filename) + s3_bucket.objects[filename].exists? + end + + def length(filename) + s3_bucket.objects[filename].content_length + end + private def s3_bucket diff --git a/ruby/lib/jam_ruby/models/genre.rb b/ruby/lib/jam_ruby/models/genre.rb index b8e61a270..e2e0faa1d 100644 --- a/ruby/lib/jam_ruby/models/genre.rb +++ b/ruby/lib/jam_ruby/models/genre.rb @@ -14,6 +14,8 @@ module JamRuby # genres has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_genres" + # jam tracks + has_many :jam_tracks, :class_name => "JamRuby::JamTrack" def to_s description diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb new file mode 100644 index 000000000..b3f11dbb1 --- /dev/null +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -0,0 +1,106 @@ +module JamRuby + class JamTrack < ActiveRecord::Base + include JamRuby::S3ManagerMixin + + TIME_SIGNATURES = %w{4/4 3/4 2/4 6/8 5/8'} + STATUS = %w{Staging Production Retired} + RECORDING_TYPE = %w{Cover Original} + PRO = %w{ASCAP BMI SESAC} + SALES_REGION = ['United States', 'Worldwide'] + + PRODUCT_TYPE = 'JamTrack' + + mount_uploader :url, JamTrackUploader + + attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type, + :original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price, + :reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount, + :licensor_royalty_amount, :pro_royalty_amount, :jam_track_tracks_attributes, as: :admin + + validates :name, presence: true, uniqueness: true, length: {maximum: 200} + validates :description, length: {maximum: 1000} + validates_format_of :bpm, with: /^\d+\.*\d{0,1}$/ + validates :time_signature, inclusion: {in: [nil] + TIME_SIGNATURES} + validates :status, inclusion: {in: [nil] + STATUS} + validates :recording_type, inclusion: {in: [nil] + RECORDING_TYPE} + validates :original_artist, length: {maximum: 200} + validates :songwriter, length: {maximum: 1000} + validates :publisher, length: {maximum: 1000} + validates :pro, inclusion: {in: [nil] + PRO} + validates :sales_region, inclusion: {in: [nil] + SALES_REGION} + validates_format_of :price, with: /^\d+\.*\d{0,2}$/ + + validates :reproduction_royalty, inclusion: {in: [nil, true, false]} + validates :public_performance_royalty, inclusion: {in: [nil, true, false]} + validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,3}$/ + validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,3}$/ + validates_format_of :pro_royalty_amount, with: /^\d+\.*\d{0,3}$/ + + before_save :sanitize_active_admin + + belongs_to :genre, class_name: "JamRuby::Genre" + belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id' + + has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC' + + has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight", inverse_of: 'jam_track', :foreign_key => "jam_track_id" + has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user + + accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true + + # create storage directory that will house this jam_track, as well as + def store_dir + "jam_tracks/#{created_at.strftime('%m-%d-%Y')}/#{id}" + end + + # create name of the file + def filename + "#{name}.jka" + end + + # creates a short-lived URL that has access to the object. + # the idea is that this is used when a user who has the rights to this tries to download this JamTrack + # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download + # but the url is short lived enough so that it wouldn't be easily shared + def sign_url(expiration_time = 120) + s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/jka', :secure => false}) + end + + def can_download?(user) + owners.include?(user) + end + + def self.index user, options = {} + limit = options[:limit] + limit ||= 20 + limit = limit.to_i + + start = options[:start].presence + start = start.to_i || 0 + + query = JamTrack.joins(:jam_track_tracks) + .offset(start) + .limit(limit) + + query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? + query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank? + query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank? + query = query.group("jam_tracks.id") + + if query.length == 0 + [query, nil] + elsif query.length < limit + [query, nil] + else + [query, start + limit] + end + end + + private + + def sanitize_active_admin + self.genre_id = nil if self.genre_id == '' + self.licensor_id = nil if self.licensor_id == '' + end + end +end diff --git a/ruby/lib/jam_ruby/models/jam_track_licensor.rb b/ruby/lib/jam_ruby/models/jam_track_licensor.rb new file mode 100644 index 000000000..d5ee3df75 --- /dev/null +++ b/ruby/lib/jam_ruby/models/jam_track_licensor.rb @@ -0,0 +1,21 @@ +module JamRuby + class JamTrackLicensor < ActiveRecord::Base + + attr_accessible :name, :description, :attention, :address_line_1, :address_line_2, + :city, :state, :zip_code, :contact, :email, :phone, as: :admin + + validates :name, presence: true, uniqueness: true, length: {maximum: 200} + validates :description, length: {maximum: 1000} + validates :attention, length: {maximum: 200} + validates :address_line_1, length: {maximum: 200} + validates :address_line_2, length: {maximum: 200} + validates :city, length: {maximum: 200} + validates :state, length: {maximum: 200} + validates :zip_code, length: {maximum: 200} + validates :contact, length: {maximum: 200} + validates :email, length: {maximum: 200} + validates :phone, length: {maximum: 200} + + has_many :jam_tracks, :class_name => "JamRuby::JamTrack", foreign_key: 'licensor_id' + end +end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb new file mode 100644 index 000000000..2a741a3a3 --- /dev/null +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -0,0 +1,14 @@ +module JamRuby + + # describes what users have rights to which tracks + class JamTrackRight < ActiveRecord::Base + + belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track + belongs_to :jam_track, class_name: "JamRuby::JamTrack" + + validates :user, presence:true + validates :jam_track, presence:true + + validates_uniqueness_of :user_id, scope: :jam_track_id + end +end diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb new file mode 100644 index 000000000..f9e8ebcb9 --- /dev/null +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -0,0 +1,47 @@ +module JamRuby + + # describes an audio track (like the drums, or guitar) that comprises a JamTrack + class JamTrackTrack < ActiveRecord::Base + include JamRuby::S3ManagerMixin + + # there should only be one Master per JamTrack, but there can be N Track per JamTrack + TRACK_TYPE = %w{Master Track} + + mount_uploader :url, JamTrackTrackUploader + + attr_accessible :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin + + validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000} + validates :part, length: {maximum: 20} + validates :track_type, inclusion: {in: TRACK_TYPE } + validates_uniqueness_of :position, scope: :jam_track_id + validates_uniqueness_of :part, scope: :jam_track_id + # validates :jam_track, presence: true + + belongs_to :instrument, class_name: "JamRuby::Instrument" + belongs_to :jam_track, class_name: "JamRuby::JamTrack" + + # create storage directory that will house this jam_track, as well as + def store_dir + "#{jam_track.store_dir}/tracks" + end + + # create name of the file + def filename + track_type == 'Master' ? 'master.ogg' : "#{part}.ogg" + end + + # creates a short-lived URL that has access to the object. + # the idea is that this is used when a user who has the rights to this tries to download this JamTrack + # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download + # but the url is short lived enough so that it wouldn't be easily shared + def sign_url(expiration_time = 120) + s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) + end + + def can_download?(user) + # I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download? + jam_track.owners.include?(user) + end + end +end diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index 750ddf462..5194517c8 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -68,7 +68,6 @@ module JamRuby validate :validate_timezone before_create :generate_share_token - before_create :add_to_feed #before_save :update_scheduled_start before_save :check_scheduling_info_changed @@ -81,12 +80,6 @@ module JamRuby true end - def add_to_feed - feed = Feed.new - feed.music_session = self - end - - def update_scheduled_start # it's very important that this only run if timezone changes, or scheduled_start changes @@ -625,12 +618,6 @@ module JamRuby hist.end_history if hist - feed = Feed.find_by_music_session_id(session_id) - unless feed.nil? - feed.active = false - feed.save - end - Notification.send_session_ended(session_id) end diff --git a/ruby/lib/jam_ruby/models/recorded_track.rb b/ruby/lib/jam_ruby/models/recorded_track.rb index ccd5a2a3c..e11a76bf0 100644 --- a/ruby/lib/jam_ruby/models/recorded_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_track.rb @@ -16,6 +16,7 @@ module JamRuby attr_writer :is_skip_mount_uploader attr_accessible :discard, :user, :user_id, :instrument_id, :sound, :client_id, :track_id, :client_track_id, :url, as: :admin + attr_writer :current_user SOUND = %w(mono stereo) diff --git a/ruby/lib/jam_ruby/models/search.rb b/ruby/lib/jam_ruby/models/search.rb index c83730172..97f6b80de 100644 --- a/ruby/lib/jam_ruby/models/search.rb +++ b/ruby/lib/jam_ruby/models/search.rb @@ -90,6 +90,7 @@ module JamRuby PARAM_MUSICIAN = :srch_m PARAM_BAND = :srch_b PARAM_FEED = :srch_f + PARAM_JAMTRACK = :srch_j F_PER_PAGE = B_PER_PAGE = M_PER_PAGE = 20 M_MILES_DEFAULT = 500 diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb new file mode 100644 index 000000000..ccad1b9d4 --- /dev/null +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -0,0 +1,34 @@ +module JamRuby + class ShoppingCart < ActiveRecord::Base + + attr_accessible :quantity, :cart_type, :product_info + + belongs_to :user, :inverse_of => :shopping_carts, :class_name => "JamRuby::User", :foreign_key => "user_id" + + validates :cart_id, presence: true + validates :cart_type, presence: true + validates :cart_class_name, presence: true + + default_scope order('created_at DESC') + + def product_info + product = self.cart_product + {name: product.name, price: product.price} unless product.nil? + end + + def cart_product + self.cart_class_name.classify.constantize.find_by_id self.cart_id unless self.cart_class_name.blank? + end + + def self.create user, product, quantity = 1 + cart = ShoppingCart.new + cart.user = user + cart.cart_type = product.class::PRODUCT_TYPE + cart.cart_class_name = product.class.name + cart.cart_id = product.id + cart.quantity = quantity + cart.save + cart + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index af931a0ac..2aec3475d 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -19,6 +19,12 @@ module JamRuby JAM_REASON_IMPORT = 'i' JAM_REASON_LOGIN = 'l' + # MOD KEYS + MOD_GEAR = "gear" + MOD_GEAR_FRAME_OPTIONS = "show_frame_options" + + MOD_NO_SHOW = "no_show" + devise :database_authenticatable, :recoverable, :rememberable acts_as_mappable @@ -121,6 +127,13 @@ module JamRuby # diagnostics has_many :diagnostics, :class_name => "JamRuby::Diagnostic" + # jam_tracks + has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight", :foreign_key => "user_id" + has_many :purchased_jam_tracks, :through => :jam_track_rights, :class_name => "JamRuby::JamTrack", :source => :jam_track, :order => :created_at + + # Shopping carts + has_many :shopping_carts, :class_name => "JamRuby::ShoppingCart" + # score history has_many :from_score_histories, :class_name => "JamRuby::ScoreHistory", foreign_key: 'from_user_id' has_many :to_score_histories, :class_name => "JamRuby::ScoreHistory", foreign_key: 'to_user_id' @@ -189,8 +202,8 @@ module JamRuby # let's work to stop junk from getting into the mods array; this is essentially the schema def validate_mods mods_json.each do |key, value| - if key == "no_show" - errors.add(:mods, ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH) unless value.is_a?(Hash) + if key == MOD_NO_SHOW || key == MOD_GEAR + errors.add(:mods, ValidationMessages::MODS_MUST_BE_HASH) unless value.is_a?(Hash) else errors.add(:mods, ValidationMessages::MODS_UNKNOWN_KEY) end @@ -373,14 +386,52 @@ module JamRuby # new_modes should be a regular hash with non-symbolized keys (vs symbolized keys) def mod_merge(new_mods) self.mods = (mods_json.merge(new_mods) do |key, old_val, new_val| +<<<<<<< HEAD if key == "no_show" # we take the values from previous no_shows, and merge it with the new no_shows +======= + if key == MOD_NO_SHOW || key == MOD_GEAR + # we take the values from previous hash, and merge it with the new hash +>>>>>>> develop old_val.merge(new_val) else raise "unknown in mode_merge key: #{key}" end end).to_json @mods_json = nil # invalidate this since we've updated self.mods +<<<<<<< HEAD +======= + end + + # any mod with the value 'null' will be deleted + def delete_mod(root_key, sub_key) + mod = mods_json + root = mod[root_key] + if root + root.delete(sub_key) + # check if root key is completely empty + mod.delete(root_key) if root.length == 0 + # check if mod key is empty + mod = nil if mod.length == 0 + end + + self.mods = mod.nil? ? nil : mod.to_json + @mods_json = nil # invalidate this since we've updated self.mods + end + + def get_mod(root_key, sub_key) + mod = mods_json + root = mod[root_key] + root[sub_key] if root + end + + def get_gear_mod(sub_key) + get_mod(MOD_GEAR, sub_key) + end + + def get_no_show_mod(sub_key) + get_mod(MOD_NO_SHOW, sub_key) +>>>>>>> develop end def heartbeat_interval_client diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb index 4927e6568..66f01c179 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb @@ -31,7 +31,7 @@ module JamRuby end - def run # if we haven't seen updated_at be tickled in 5 minutes, but config_changed is still set to TRUE, this record has gotten stale + def run # if we have seen that sourced_needs_changing_at is older than 15 seconds ago, re-poke clients in the session IcecastMount.find_each(lock: true, :conditions => "sourced_needs_changing_at < (NOW() - interval '#{APP_CONFIG.icecast_max_sourced_changed} second')", :batch_size => 100) do |mount| if mount.music_session_id mount.with_lock do diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 264b44323..a044f9659 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -673,4 +673,54 @@ FactoryGirl.define do factory :max_mind_release, :class => JamRuby::MaxMindRelease do released_at Time.now.to_date end + + factory :jam_track_licensor, :class => JamRuby::JamTrackLicensor do + sequence(:name) { |n| "licensor-#{n}" } + sequence(:description) { |n| "description-#{n}" } + sequence(:attention) { |n| "attention-#{n}" } + sequence(:address_line_1) { |n| "address1-#{n}" } + sequence(:address_line_2) { |n| "address2-#{n}" } + sequence(:city) { |n| "city-#{n}" } + sequence(:state) { |n| "state-#{n}" } + sequence(:zip_code) { |n| "zipcode-#{n}" } + sequence(:contact) { |n| "contact-#{n}" } + sequence(:email) { |n| "email-#{n}" } + sequence(:phone) { |n| "phone-#{n}" } + end + + factory :jam_track, :class => JamRuby::JamTrack do + sequence(:name) { |n| "jam-track-#{n}" } + sequence(:description) { |n| "description-#{n}" } + bpm 100.1 + time_signature '4/4' + status 'Production' + recording_type 'Cover' + sequence(:original_artist) { |n| "original-artist-#{n}" } + sequence(:songwriter) { |n| "songwriter-#{n}" } + sequence(:publisher) { |n| "publisher-#{n}" } + pro 'ASCAP' + sales_region 'United States' + price 1.99 + reproduction_royalty true + public_performance_royalty true + reproduction_royalty_amount 0.999 + licensor_royalty_amount 0.999 + pro_royalty_amount 0.999 + + genre JamRuby::Genre.first + association :licensor, factory: :jam_track_licensor + end + + factory :jam_track_track, :class => JamRuby::JamTrackTrack do + position 1 + part 'lead guitar' + track_type 'Track' + instrument JamRuby::Instrument.find('electric guitar') + association :jam_track, factory: :jam_track + end + + factory :jam_track_right, :class => JamRuby::JamTrackRight do + association :jam_track, factory: :jam_track + association :user, factory: :user + end end diff --git a/ruby/spec/jam_ruby/models/jam_track_licensor_spec.rb b/ruby/spec/jam_ruby/models/jam_track_licensor_spec.rb new file mode 100644 index 000000000..80bfc377c --- /dev/null +++ b/ruby/spec/jam_ruby/models/jam_track_licensor_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe JamTrackLicensor do + + it "created" do + FactoryGirl.create(:jam_track_licensor) + end + +end + diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb new file mode 100644 index 000000000..5d136d62a --- /dev/null +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe JamTrackRight do + + it "created" do + jam_track_right = FactoryGirl.create(:jam_track_right) + + user = jam_track_right.user + jam_track = jam_track_right.jam_track + + # verify that the user sees this as a purchased jam_track + user.purchased_jam_tracks.should == [jam_track] + + # verify that the jam_track sees the user as an owner + jam_track.owners.should == [user] + + end + + describe "validations" do + it "one purchase per user/jam_track combo" do + user = FactoryGirl.create(:user) + jam_track = FactoryGirl.create(:jam_track) + + right_1 = FactoryGirl.create(:jam_track_right, user: user, jam_track: jam_track) + right_2 = FactoryGirl.build(:jam_track_right, user: user, jam_track: jam_track) + right_2.valid?.should be_false + right_2.errors[:user_id].should == ['has already been taken'] + end + end +end + diff --git a/ruby/spec/jam_ruby/models/jam_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_spec.rb new file mode 100644 index 000000000..85cf9059c --- /dev/null +++ b/ruby/spec/jam_ruby/models/jam_track_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' + +require 'carrierwave/test/matchers' + +describe JamTrack do + include CarrierWave::Test::Matchers + include UsesTempFiles + + + it "created" do + jam_track = FactoryGirl.create(:jam_track) + jam_track.licensor.should_not be_nil + jam_track.licensor.jam_tracks.should == [jam_track] + end + + describe "validations" do + describe "bpm" do + it "1" do + FactoryGirl.build(:jam_track, bpm: 1).valid?.should be_true + end + + it "100" do + FactoryGirl.build(:jam_track, bpm: 100).valid?.should be_true + end + + it "100.1" do + FactoryGirl.build(:jam_track, bpm: 100.1).valid?.should be_true + end + + it "100.12" do + jam_track = FactoryGirl.build(:jam_track, bpm: 100.12) + jam_track.valid?.should be_false + jam_track.errors[:bpm].should == ['is invalid'] + end + end + + describe "price" do + + it "0.99" do + FactoryGirl.build(:jam_track, price: 0.99).valid?.should be_true + end + + it "1" do + FactoryGirl.build(:jam_track, price: 1).valid?.should be_true + end + + it "100" do + FactoryGirl.build(:jam_track, price: 100).valid?.should be_true + end + + it "100.1" do + FactoryGirl.build(:jam_track, price: 100.1).valid?.should be_true + end + + it "100.12" do + FactoryGirl.build(:jam_track, price: 100.12).valid?.should be_true + end + + it "100.123" do + jam_track = FactoryGirl.build(:jam_track, price: 100.123) + jam_track.valid?.should be_false + jam_track.errors[:price].should == ['is invalid'] + end + end + + describe "reproduction_royalty_amount" do + it "0.99" do + FactoryGirl.build(:jam_track, reproduction_royalty_amount: 0.99).valid?.should be_true + end + + it "1" do + FactoryGirl.build(:jam_track, reproduction_royalty_amount: 1).valid?.should be_true + end + + it "100" do + FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100).valid?.should be_true + end + + it "100.1" do + FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100.1).valid?.should be_true + end + + it "100.12" do + FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100.12).valid?.should be_true + end + + it "100.123" do + FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100.123).valid?.should be_true + end + + it "100.1234" do + jam_track = FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100.1234) + jam_track.valid?.should be_false + jam_track.errors[:reproduction_royalty_amount].should == ['is invalid'] + end + end + end + + describe "upload/download" do + JKA_NAME = 'blah.jka' + + in_directory_with_file(JKA_NAME) + + before(:all) do + original_storage = JamTrackUploader.storage = :fog + end + + after(:all) do + JamTrackUploader.storage = @original_storage + end + + before(:each) do + content_for_file('abc') + end + + it "uploads to s3 with correct name, and then downloads via signed URL" do + jam_track = FactoryGirl.create(:jam_track) + uploader = JamTrackUploader.new(jam_track, :url) + uploader.store!(File.open(JKA_NAME)) # uploads file + jam_track.save! + + # verify that the uploader stores the correct path + jam_track[:url].should == jam_track.store_dir + '/' + jam_track.filename + + # verify it's on S3 + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track[:url]).should be_true + s3.length(jam_track[:url]).should == 'abc'.length + + # download it via signed URL, and check contents + url = jam_track.sign_url + downloaded_contents = open(url).read + downloaded_contents.should == 'abc' + end + + end +end + diff --git a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb new file mode 100644 index 000000000..6cc5c3773 --- /dev/null +++ b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +describe JamTrackTrack do + include CarrierWave::Test::Matchers + include UsesTempFiles + + it "created" do + jam_track_track = FactoryGirl.create(:jam_track_track) + jam_track_track.jam_track.should_not be_nil + jam_track_track.jam_track.jam_track_tracks.should == [jam_track_track] + end + + describe "validations" do + it "position" do + jam_track = FactoryGirl.create(:jam_track) + jam_track_track_1 = FactoryGirl.create(:jam_track_track, position: 1, jam_track: jam_track) + jam_track_track_2 = FactoryGirl.build(:jam_track_track, position: 1, jam_track: jam_track) + jam_track_track_2.valid?.should == false + jam_track_track_2.errors[:position].should == ['has already been taken'] + end + + it "jam_track required" do + pending "Need to be not mandatory because of activeadmin" + jam_track = FactoryGirl.build(:jam_track_track, jam_track: nil) + jam_track.valid?.should be_false + jam_track.errors[:jam_track].should == ["can't be blank"] + end + end + + + describe "upload/download" do + TRACK_NAME = 'lead guitar.ogg' + + in_directory_with_file(TRACK_NAME) + + before(:all) do + original_storage = JamTrackTrackUploader.storage = :fog + end + + after(:all) do + JamTrackTrackUploader.storage = @original_storage + end + + before(:each) do + content_for_file('abc') + end + + it "uploads to s3 with correct name, and then downloads via signed URL" do + jam_track_track = FactoryGirl.create(:jam_track_track) + uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader.store!(File.open(TRACK_NAME)) # uploads file + jam_track_track.save! + + # verify that the uploader stores the correct path + jam_track_track[:url].should == jam_track_track.store_dir + '/' + jam_track_track.filename + + # verify it's on S3 + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track_track[:url]).should be_true + s3.length(jam_track_track[:url]).should == 'abc'.length + + # download it via signed URL, and check contents + url = jam_track_track.sign_url + downloaded_contents = open(url).read + downloaded_contents.should == 'abc' + end + + end + +end diff --git a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb new file mode 100644 index 000000000..db3f4d75c --- /dev/null +++ b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe ShoppingCart do + + let(:user) { FactoryGirl.create(:user) } + let(:jam_track) {FactoryGirl.create(:jam_track) } + + before(:each) do + ShoppingCart.delete_all + end + + it "can reference a shopping cart" do + shopping_cart = ShoppingCart.create user, jam_track, 1 + + ShoppingCart.count.should == 1 + user.shopping_carts.count.should == 1 + user.shopping_carts[0].product_info[:name].should == jam_track.name + user.shopping_carts[0].product_info[:price].should == jam_track.price + user.shopping_carts[0].cart_type.should == jam_track.class::PRODUCT_TYPE + user.shopping_carts[0].quantity.should == 1 + end + +end diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 36425e495..54797df8c 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -608,7 +608,7 @@ describe User do it "does not allow non-hash no_show" do user.mod_merge({no_show:true}) user.valid?.should be_false - user.errors[:mods].should == [ValidationMessages::MODS_NO_SHOW_MUST_BE_HASH] + user.errors[:mods].should == [ValidationMessages::MODS_MUST_BE_HASH] end end diff --git a/ruby/spec/jam_ruby/resque/unused_music_notation_cleaner_spec.rb b/ruby/spec/jam_ruby/resque/unused_music_notation_cleaner_spec.rb index f47f186df..1f0ff3e55 100644 --- a/ruby/spec/jam_ruby/resque/unused_music_notation_cleaner_spec.rb +++ b/ruby/spec/jam_ruby/resque/unused_music_notation_cleaner_spec.rb @@ -5,9 +5,9 @@ require 'fileutils' describe UnusedMusicNotationCleaner do include UsesTempFiles - NOTATION_TEMP_FILE='detail.png' + UNUSED_NOTATION_TEMP_FILE='detail.png' - in_directory_with_file(NOTATION_TEMP_FILE) + in_directory_with_file(UNUSED_NOTATION_TEMP_FILE) before do content_for_file("this is music notation test file") @@ -30,7 +30,8 @@ describe UnusedMusicNotationCleaner do it "find no music notataions if music_session_id is nil and created at 1 hour ago" do notation = MusicNotation.new - notation.file_url = File.open(NOTATION_TEMP_FILE) + + notation.file_url = File.open(UNUSED_NOTATION_TEMP_FILE) notation.size = 10 notation.user = FactoryGirl.create(:user) notation.created_at = Time.now - 1.hours @@ -44,7 +45,7 @@ describe UnusedMusicNotationCleaner do music_session = FactoryGirl.create(:music_session, :session_removed_at => Time.now - 1.hours) notation = MusicNotation.new - notation.file_url = File.open(NOTATION_TEMP_FILE) + notation.file_url = File.open(UNUSED_NOTATION_TEMP_FILE) notation.size = 10 notation.user = FactoryGirl.create(:user) notation.created_at = Time.now - 1.hours @@ -56,7 +57,7 @@ describe UnusedMusicNotationCleaner do it "find music notataions if music_session_id is nil and created at 2 days ago" do notation = MusicNotation.new - notation.file_url = File.open(NOTATION_TEMP_FILE) + notation.file_url = File.open(UNUSED_NOTATION_TEMP_FILE) notation.size = 10 notation.user = FactoryGirl.create(:user) notation.created_at = Time.now - 2.days @@ -70,7 +71,7 @@ describe UnusedMusicNotationCleaner do music_session = FactoryGirl.create(:music_session, :session_removed_at => Time.now - 2.days) notation = MusicNotation.new - notation.file_url = File.open(NOTATION_TEMP_FILE) + notation.file_url = File.open(UNUSED_NOTATION_TEMP_FILE) notation.size = 10 notation.user = FactoryGirl.create(:user) notation.created_at = Time.now - 3.days diff --git a/web/Gemfile b/web/Gemfile index e2b8e8a44..816dbc6c1 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -83,6 +83,8 @@ gem 'rubyzip' gem 'slim' gem 'htmlentities' gem 'sanitize' +gem 'recurly' +gem 'guard', '2.7.3' group :development, :test do gem 'rspec-rails', '2.14.2' diff --git a/web/app/assets/images/content/bkg_home_jamtracks.jpg b/web/app/assets/images/content/bkg_home_jamtracks.jpg new file mode 100644 index 000000000..700f012c6 Binary files /dev/null and b/web/app/assets/images/content/bkg_home_jamtracks.jpg differ diff --git a/web/app/assets/images/content/bkg_home_jamtracks_x.jpg b/web/app/assets/images/content/bkg_home_jamtracks_x.jpg new file mode 100644 index 000000000..22df61a3d Binary files /dev/null and b/web/app/assets/images/content/bkg_home_jamtracks_x.jpg differ diff --git a/web/app/assets/images/content/checkmark.png b/web/app/assets/images/content/checkmark.png new file mode 100644 index 000000000..8ce5e42ee Binary files /dev/null and b/web/app/assets/images/content/checkmark.png differ diff --git a/web/app/assets/images/content/icon_jamtracks.png b/web/app/assets/images/content/icon_jamtracks.png new file mode 100644 index 000000000..4cdb1e1f5 Binary files /dev/null and b/web/app/assets/images/content/icon_jamtracks.png differ diff --git a/web/app/assets/images/content/icon_shopping_cart.png b/web/app/assets/images/content/icon_shopping_cart.png new file mode 100644 index 000000000..24bf9b09f Binary files /dev/null and b/web/app/assets/images/content/icon_shopping_cart.png differ diff --git a/web/app/assets/images/content/shopping-cart.png b/web/app/assets/images/content/shopping-cart.png new file mode 100644 index 000000000..eef1a4c69 Binary files /dev/null and b/web/app/assets/images/content/shopping-cart.png differ diff --git a/web/app/assets/javascripts/accounts_session_detail.js b/web/app/assets/javascripts/accounts_session_detail.js index a3debe85e..d75a5ea2e 100644 --- a/web/app/assets/javascripts/accounts_session_detail.js +++ b/web/app/assets/javascripts/accounts_session_detail.js @@ -87,48 +87,54 @@ var instrumentIds = $(e.target).attr('data-instrument-text'); var params = buildRsvpRequestActionParams(rsvpId, true); - // first check if any open slots exist for these instruments - rest.getOpenSessionSlots(sessionData.id, true) - .done(function(openSlots) { - if (openSlots) { - if (openSlots.length === 0) { - ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName, function() { - approve(rsvpId, params); - }); - } - else { - var arrInstrumentIds = instrumentIds.split('|'); - var openSlotInstrumentIds = []; - var unavailableSlotInstrumentIds = []; + if (sessionData['is_unstructured_rsvp?']) { + approve(rsvpId, params); + } - // ensure each instrument in the user's list is available in the open slots list - $.each(openSlots, function(index, slot) { - openSlotInstrumentIds.push(slot.instrument_id); - }); - - // build list of instrument IDs in the RSVP request for which there are no open slots - for (var i=0; i < arrInstrumentIds.length; i++) { - if ($.inArray(arrInstrumentIds[i], openSlotInstrumentIds) === -1) { - unavailableSlotInstrumentIds.push(arrInstrumentIds[i]); - } - } - - if (unavailableSlotInstrumentIds.length > 0) { - ui.launchRsvpCreateSlotDialog(sessionData.id, unavailableSlotInstrumentIds, userName, function() { - approve(rsvpId, params); - }); + // check if any open slots exist for these instruments + else { + rest.getOpenSessionSlots(sessionData.id, true) + .done(function(openSlots) { + if (openSlots) { + if (openSlots.length === 0) { + ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName, function() { + approve(rsvpId, params); + }); } else { - approve(rsvpId, params); + var arrInstrumentIds = instrumentIds.split('|'); + var openSlotInstrumentIds = []; + var unavailableSlotInstrumentIds = []; + + // ensure each instrument in the user's list is available in the open slots list + $.each(openSlots, function(index, slot) { + openSlotInstrumentIds.push(slot.instrument_id); + }); + + // build list of instrument IDs in the RSVP request for which there are no open slots + for (var i=0; i < arrInstrumentIds.length; i++) { + if ($.inArray(arrInstrumentIds[i], openSlotInstrumentIds) === -1) { + unavailableSlotInstrumentIds.push(arrInstrumentIds[i]); + } + } + + if (unavailableSlotInstrumentIds.length > 0) { + ui.launchRsvpCreateSlotDialog(sessionData.id, unavailableSlotInstrumentIds, userName, function() { + approve(rsvpId, params); + }); + } + else { + approve(rsvpId, params); + } } } - } - else { - ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName, function() { - approve(rsvpId, params); - }); - } - }); + else { + ui.launchRsvpCreateSlotDialog(sessionData.id, instrumentIds.split('|'), userName, function() { + approve(rsvpId, params); + }); + } + }); + } } function approve(rsvpId, params) { @@ -333,7 +339,7 @@ if ("instrument_list" in pending_rsvp_request && pending_rsvp_request.instrument_list != null) { $.each(pending_rsvp_request.instrument_list, function (index, instrument) { - var instrumentId = instrument == null ? null : instrument.id; + var instrumentId = context.JK.getInstrumentId(instrument.id); var inst = context.JK.getInstrumentIcon24(instrumentId); instrumentLogoHtml += ' '; instrumentDesc.push(instrumentId); @@ -372,7 +378,7 @@ $.each(sessionData.approved_rsvps, function(index, approved_rsvp) { if ("instrument_list" in approved_rsvp) { $.each(approved_rsvp.instrument_list, function(index, instrument) { - var instrumentId = instrument == null ? null : instrument.id; + var instrumentId = context.JK.getInstrumentId(instrument.id); var inst = context.JK.getInstrumentIcon24(instrumentId); instrumentLogoHtml += ' '; }); diff --git a/web/app/assets/javascripts/band_setup.js b/web/app/assets/javascripts/band_setup.js index efe25c42b..dccd67937 100644 --- a/web/app/assets/javascripts/band_setup.js +++ b/web/app/assets/javascripts/band_setup.js @@ -22,6 +22,7 @@ var nilOptionText = 'n/a'; var bandId = ''; var friendInput=null; + var isSaving = false; function is_new_record() { return bandId.length == 0; @@ -126,16 +127,21 @@ } function saveBand() { + if (isSaving) return; + isSaving = true; + var band = buildBand() if (is_new_record()) { rest.createBand(band) .done(function (response) { + isSaving = false; createBandInvitations(response.id, function () { context.location = "/client#/bandProfile/" + response.id; }); }) .fail(function (jqXHR) { + isSaving = false; app.notifyServerError(jqXHR, "Unable to create band") }); ; @@ -144,11 +150,13 @@ band.id = bandId; rest.updateBand(band) .done(function (response) { + isSaving = false; createBandInvitations(band.id, function () { context.location = "/client#/bandProfile/" + band.id; }); }) .fail(function (jqXHR) { + isSaving = false; app.notifyServerError(jqXHR, "Unable to create band") }); } diff --git a/web/app/assets/javascripts/checkout_signin.js b/web/app/assets/javascripts/checkout_signin.js new file mode 100644 index 000000000..beb536d3d --- /dev/null +++ b/web/app/assets/javascripts/checkout_signin.js @@ -0,0 +1,106 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.CheckoutSignInScreen = function(app) { + + var logger = context.JK.logger; + + var $screen = null; + var $navigation = null; + var $signinForm = null; + var $self = $(this); + var $email = null; + var $password = null; + var $signinBtn = null; + var $signupBtn = null; + + function beforeShow(data) { + renderNavigation(); + } + + function afterShow(data) { + } + + function events() { + $signinBtn.on('click', login); + $signupBtn.on('click', signup); + } + + function signup(e) { + app.layout.showDialog('signup-dialog'); + return false; + } + + function reset() { + $signinForm.removeClass('login-error'); + + $email.val(''); + $password.val(''); + } + + function login() { + var email = $email.val(); + var password = $password.val(); + + reset(); + + $signinBtn.text('TRYING...'); + + rest.login({email: email, password: password, remember_me: false}) + .done(function() { + window.location = '/client#/order' + }) + .fail(function(jqXHR) { + if(jqXHR.status == 422) { + $signinForm.addClass('login-error') + } + else { + app.notifyServerError(jqXHR, "Unable to log in") + } + }) + .always(function() { + $signinBtn.text('SIGN IN') + }) + } + + function renderNavigation() { + $navigation.html(""); + + var navigationHtml = $( + context._.template( + $('#template-checkout-navigation').html(), + {current: 1}, + {variable: 'data'} + ) + ); + + $navigation.append(navigationHtml); + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('signin', screenBindings); + + $screen = $("#signInScreen"); + $navigation = $screen.find(".checkout-navigation-bar"); + $signinForm = $screen.find(".signin-form"); + $signinBtn = $signinForm.find('.signin-submit'); + $email = $signinForm.find('input[name="session[email]"]'); + $password = $signinForm.find('input[name="session[password]"]'); + $signupBtn = $signinForm.find('.show-signup-dialog'); + + if($screen.length == 0) throw "$screen must be specified"; + if($navigation.length == 0) throw "$navigation must be specified"; + + events(); + } + + this.initialize = initialize; + + return this; + } +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/adjustGearSpeedDialog.js b/web/app/assets/javascripts/dialog/adjustGearSpeedDialog.js new file mode 100644 index 000000000..1172ae500 --- /dev/null +++ b/web/app/assets/javascripts/dialog/adjustGearSpeedDialog.js @@ -0,0 +1,274 @@ +(function (context, $) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.AdjustGearSpeedDialog = function (app) { + var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; + var gearUtils = context.JK.GearUtils; + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var $dialog = null; + var modUtils = context.JK.ModUtils; + + var $runTestBtn = null; + var $scoreReport = null; + var $speedOptions = null; + var $saveBtn = null; + var $cancelBtn = null; + var $fairLabel = null; + var $slowLabel = null; + var $fastLabel = null; + + var startingFramesize = null; + var startingBufferIn = null; + var startingBufferOut = null; + + var frameBuffers = new context.JK.FrameBuffers(app); + var gearTest = new context.JK.GearTest(app); + + var selectedDeviceInfo; + var deviceInformation; + var operatingSystem; + var frameBuffers; + var $frameBuffers; + var gearTest; + + var $advanced; + + function attemptScore() { + gearTest.attemptScore(selectedDeviceInfo); + } + + function invalidateScore() { + gearTest.invalidateScore(); + } + + function getGearTest() { + return gearTest; + } + + function updateDefaultBuffers() { + gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers) + } + + function onFramesizeChanged() { + //context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + updateDefaultBuffers(); + context.jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + invalidateScore(); + } + + function onBufferInChanged() { + //context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + invalidateScore(); + } + + function onBufferOutChanged() { + //context.JK.prodBubble($resyncBtn, 'push-resync-when-done', {}, {positions:['top']}); + context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + invalidateScore(); + } + + function freezeAudioInteraction() { + logger.debug("adjustGearSpeed: freezing audio interaction"); + frameBuffers.disable(); + $speedOptions.iCheck('disable') + $cancelBtn.on('click', false).addClass('disabled'); + $runTestBtn.on('click', false).addClass('disabled'); + } + + function unfreezeAudioInteraction() { + logger.debug("adjustGearSpeed: unfreezing audio interaction"); + frameBuffers.enable(); + $speedOptions.iCheck('enable') + $cancelBtn.off('click', false).removeClass('disabled') + $runTestBtn.off('click', false).removeClass('disabled') + } + + + function updateDefaultLabel() { + if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { + + } + else { + } + } + + function translateFrameSizeToSpeed(framesize) { + if(framesize == 2.5) { + return 'fast' + } + else if(framesize == 5) { + return 'fair' + } + else if (framesize == 10) { + return "slow" + } + else { + throw "unknown framesize in translateFrameSizeToSpeed: " + framesize + } + } + + function beforeShow() { + selectedDeviceInfo = gearUtils.selectedDeviceInfo(context.jamClient.FTUEGetInputMusicDevice(), context.jamClient.FTUEGetOutputMusicDevice()); + deviceInformation = gearUtils.loadDeviceInfo(); + startingFramesize = context.jamClient.FTUEGetFrameSize(); + startingBufferIn = context.jamClient.FTUEGetInputLatency(); + startingBufferOut = context.jamClient.FTUEGetOutputLatency(); + var startingSpeed = translateFrameSizeToSpeed(startingFramesize) + logger.debug("speed upon entry: " + startingSpeed) + $speedOptions.filter('[value=' + startingSpeed + ']').iCheck('check') + setBuffers(startingSpeed); + updateDefaultLabel(); + invalidateScore(); + $saveBtn.on('click', false).addClass('disabled'); + app.user().done(function() { + if(modUtils.getGear('show_frame_options')) { + $advanced.show(); + } + }) + } + + function beforeHide() { + } + + function onCancel() { + var scoring = gearTest.isScoring(); + + if(!scoring) { + logger.debug("resetting framesize/buffers on cancel of adjust-gear-speed") + // make sure the frame/buffer values are the same as when entered + context.jamClient.FTUESetFrameSize(startingFramesize); + context.jamClient.FTUESetInputLatency(startingBufferIn); + context.jamClient.FTUESetOutputLatency(startingBufferOut); + } + + return !scoring; + } + + function onSave() { + + if($(this).is('.disabled')) { + logger.debug("cancelling save because not allowed yet") + return; + } + + context.jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); + context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + + app.layout.closeDialog('adjust-gear-speed-dialog') + + return false; + } + + function onGearTestStarted(e, data) { + renderScoringStarted(); + } + + function onGearTestDone(e, data) { + renderScoringStopped(); + $saveBtn.off('click', false).removeClass('disabled'); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function onGearTestFail(e, data) { + renderScoringStopped(); + $saveBtn.on('click', false).addClass('disabled'); + gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); + } + + function renderScoringStarted() { + freezeAudioInteraction(); + } + + function renderScoringStopped() { + unfreezeAudioInteraction(); + } + + function setBuffers(speed) { + var newFrameSize = null; + if(speed == 'fast') { + newFrameSize = 2.5 + } + else if (speed == 'fair') { + newFrameSize = 5 + } + else if (speed == 'slow') { + newFrameSize = 10 + } + else { + throw "unknown speed setting: " + speed; + } + + frameBuffers.setFramesize(newFrameSize); + jamClient.FTUESetFrameSize(newFrameSize); + updateDefaultBuffers(); + } + + function onSpeedChanged() { + + setTimeout(function() { + var speed = $dialog.find('.speed-option .iradio_minimal.checked input').val() + + setBuffers(speed); + + attemptScore(); + }, 1) + + } + + function initialize() { + var dialogBindings = { + 'beforeShow': beforeShow, + 'beforeHide': beforeHide, + 'onCancel' : onCancel + }; + + app.bindDialog('adjust-gear-speed-dialog', dialogBindings); + + $dialog = $('#adjust-gear-speed-dialog'); + + $runTestBtn = $dialog.find('.run-test-btn'); + $scoreReport = $dialog.find('.results'); + $saveBtn = $dialog.find('.btnSave') + $cancelBtn = $dialog.find('.btnCancel') + $slowLabel = $dialog.find('label[for="adjust-gear-speed-slow"]') + $fairLabel = $dialog.find('label[for="adjust-gear-speed-fair"]') + $fastLabel = $dialog.find('label[for="adjust-gear-speed-fast"]') + + operatingSystem = context.JK.GetOSAsString(); + + $frameBuffers = $dialog.find('.frame-and-buffers'); + frameBuffers.initialize($frameBuffers); + $(frameBuffers) + .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged) + .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged) + .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged) + + + gearTest.initialize($scoreReport, true) + $(gearTest) + .on(gearTest.GEAR_TEST_START, onGearTestStarted) + .on(gearTest.GEAR_TEST_DONE, onGearTestDone) + .on(gearTest.GEAR_TEST_FAIL, onGearTestFail) + + $runTestBtn.click(attemptScore); + + $dialog.data('result', gearTest); // so that others can peek into gear test data after dialog is closed + + $advanced = $dialog.find('.advanced'); + $speedOptions = $dialog.find('.speed-option input'); + context.JK.checkbox($speedOptions).on('ifClicked', onSpeedChanged) + + $saveBtn.click(onSave) + }; + + + this.getGearTest = getGearTest; + this.initialize = initialize; + } + + return this; +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/banner.js b/web/app/assets/javascripts/dialog/banner.js index 991d14d67..2ced92f0c 100644 --- a/web/app/assets/javascripts/dialog/banner.js +++ b/web/app/assets/javascripts/dialog/banner.js @@ -62,7 +62,7 @@ } hide(); - + logger.debug("opening banner:" + options.title); var $h1 = $banner.find('h1'); diff --git a/web/app/assets/javascripts/dialog/jamtrackAvailabilityDialog.js b/web/app/assets/javascripts/dialog/jamtrackAvailabilityDialog.js new file mode 100644 index 000000000..c900cf54c --- /dev/null +++ b/web/app/assets/javascripts/dialog/jamtrackAvailabilityDialog.js @@ -0,0 +1,46 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.JamtrackAvailabilityDialog = function(app) { + var logger = context.JK.logger; + var $dialog = null; + var dialogId = 'jamtrack-availability-dialog'; + + function beforeShow(data) { + } + + function afterShow(data) { + } + + function afterHide() { + } + + function showDialog() { + return app.layout.showDialog(dialogId); + } + + function events() { + } + + function initialize() { + + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterShow' : afterShow, + 'afterHide': afterHide + }; + + app.bindDialog(dialogId, dialogBindings); + + $dialog = $('[layout-id="' + dialogId + '"]'); + + 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/rsvpCancelDialog.js b/web/app/assets/javascripts/dialog/rsvpCancelDialog.js index bd0079bf3..feb4ff9d6 100644 --- a/web/app/assets/javascripts/dialog/rsvpCancelDialog.js +++ b/web/app/assets/javascripts/dialog/rsvpCancelDialog.js @@ -21,7 +21,7 @@ $('.session-name', $dialog).html(response.name); $('.scheduled-start', $dialog).html(response.pretty_scheduled_start_with_timezone); - if (response.recurring_mode !== null) { + if (response.recurring_mode !== null && response.recurring_mode !== 'once') { $('.schedule-recurrence', $dialog).html("Recurs " + response.recurring_mode + " on this day at this time"); } } diff --git a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js index 03ccb6a5f..9b29fcb28 100644 --- a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js +++ b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js @@ -24,7 +24,7 @@ $('.scheduled-start', $dialog).html(response.pretty_scheduled_start_with_timezone); - if (response.recurring_mode !== null && response.recurring_mode === 'weekly') { + if (response.recurring_mode !== null && response.recurring_mode !== 'once') { $('.schedule-recurrence', $dialog).html("Recurs " + response.recurring_mode + " on this day at this time"); } diff --git a/web/app/assets/javascripts/feed_item_recording.js b/web/app/assets/javascripts/feed_item_recording.js index e9d1778de..8679b01c5 100644 --- a/web/app/assets/javascripts/feed_item_recording.js +++ b/web/app/assets/javascripts/feed_item_recording.js @@ -137,6 +137,10 @@ $controls.data('view-context', 'feed') $controls.addClass(mixInfo.mixStateClass) $status.text(mixInfo.mixStateMsg) + + if(mixInfo.mixState == 'mixed' || mixInfo.mixState == 'stream-mix') { + $controls.addClass('has-mix') + } } initialize(); diff --git a/web/app/assets/javascripts/hoverBand.js b/web/app/assets/javascripts/hoverBand.js index b167baddc..9eb5e8d5c 100644 --- a/web/app/assets/javascripts/hoverBand.js +++ b/web/app/assets/javascripts/hoverBand.js @@ -9,7 +9,7 @@ this.showBubble = function($hoverElement) { - rest.getBand(bandId) + return rest.getBand(bandId) .done(function(response) { $(hoverSelector).html(''); @@ -24,7 +24,7 @@ instrumentHtml = '
'; if (val.instruments) { // @FIXME: edge case for Test user that has no instruments? $.each(val.instruments, function(index, instrument) { - instrumentHtml += ' '; + instrumentHtml += ' '; }); } diff --git a/web/app/assets/javascripts/hoverFan.js b/web/app/assets/javascripts/hoverFan.js index 34c2400d2..6ca880df5 100644 --- a/web/app/assets/javascripts/hoverFan.js +++ b/web/app/assets/javascripts/hoverFan.js @@ -10,7 +10,7 @@ this.showBubble = function($hoverElement) { - rest.getUserDetail({id: userId}) + return rest.getUserDetail({id: userId}) .done(function(response) { $(hoverSelector).html(''); diff --git a/web/app/assets/javascripts/hoverMusician.js b/web/app/assets/javascripts/hoverMusician.js index 7cac6517e..56042effd 100644 --- a/web/app/assets/javascripts/hoverMusician.js +++ b/web/app/assets/javascripts/hoverMusician.js @@ -13,14 +13,14 @@ this.showBubble = function($hoverElement) { $templateLatency = $("#template-account-session-latency"); - rest.getUserDetail({id: userId}) + return rest.getUserDetail({id: userId}) .done(function(response) { $(hoverSelector).html(''); // instruments var instrumentHtml = ''; $.each(response.instruments, function(index, val) { - instrumentHtml += '
'; + instrumentHtml += '
'; }); // followings @@ -88,7 +88,7 @@ var latencyBadge = context._.template( $templateLatency.html(), - $.extend(sessionUtils.createLatency(response), response), + $.extend(response, sessionUtils.createLatency(response)), {variable: 'data'} ); diff --git a/web/app/assets/javascripts/hoverRecording.js b/web/app/assets/javascripts/hoverRecording.js index 5321df114..8ba1239c7 100644 --- a/web/app/assets/javascripts/hoverRecording.js +++ b/web/app/assets/javascripts/hoverRecording.js @@ -39,7 +39,7 @@ this.showBubble = function($hoverElement) { - rest.getClaimedRecording(recordingId) + return rest.getClaimedRecording(recordingId) .done(function(response) { var claimedRecording = response; var recording = response.recording; @@ -58,7 +58,7 @@ instrumentHtml = '
'; $.each(val.instrument_ids, function(index, val) { - instrumentHtml += '  '; + instrumentHtml += '  '; }); instrumentHtml += '
'; diff --git a/web/app/assets/javascripts/hoverSession.js b/web/app/assets/javascripts/hoverSession.js index d64dfee71..35a7baa5e 100644 --- a/web/app/assets/javascripts/hoverSession.js +++ b/web/app/assets/javascripts/hoverSession.js @@ -10,7 +10,7 @@ this.showBubble = function($hoverElement) { - rest.getSessionHistory(sessionId) + return rest.getSessionHistory(sessionId) .done(function(response) { $(hoverSelector).html(''); @@ -25,7 +25,7 @@ instrumentHtml = '
'; var instruments = val.instruments.split("|"); $.each(instruments, function(index, instrument) { - instrumentHtml += ' '; + instrumentHtml += ' '; }); instrumentHtml += '
'; diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 47368278c..ad1993899 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -238,6 +238,10 @@ } function cancelRsvpRequest(sessionId, rsvpRequestId, cancelAll) { + var cancel = "yes"; + if (cancelAll) { + cancel = "all"; + } return $.ajax({ url: '/api/rsvp_requests/' + rsvpRequestId, type: "DELETE", @@ -939,7 +943,11 @@ dataType: "json", contentType: 'application/json', url: "/api/users/progression/certified_gear", - data: JSON.stringify(options) + processData: false, + data: JSON.stringify({ + success: options.success, + reason: options.reason + }) }); } @@ -1257,6 +1265,98 @@ }); } + function updateAudioLatency(options) { + var id = getId(options); + return $.ajax({ + type: "POST", + url: '/api/users/' + id + '/audio_latency', + dataType: "json", + contentType: 'application/json', + data: options, + }); + } + + function getJamtracks(options) { + return $.ajax({ + type: "GET", + url: '/api/jamtracks?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + + function addJamtrackToShoppingCart(options) { + return $.ajax({ + type: "POST", + url: '/api/shopping_carts/add_jamtrack?' + $.param(options), + dataType: "json", + contentType: 'applications/json' + }); + } + + function getShoppingCarts() { + return $.ajax({ + type: "GET", + url: '/api/shopping_carts', + dataType: "json", + contentType: 'application/json' + }); + } + + function removeShoppingCart(options) { + return $.ajax({ + type: "DELETE", + url: '/api/shopping_carts?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }) + } + + function getRecurlyAccount() { + return $.ajax({ + type: "GET", + url: '/api/recurly/get_account', + dataType: "json", + contentType: 'application/json' + }); + } + + function createRecurlyAccount(options) { + return $.ajax({ + type: "POST", + url: '/api/recurly/create_account?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + + function getBillingInfo() { + return $.ajax({ + type: "GET", + url: '/api/recurly/billing_info', + dataType: "json", + contentType: 'application/json' + }); + } + + function updateBillingInfo(options) { + return $.ajax({ + type: "PUT", + url: '/api/recurly/update_billing_info?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + + function placeOrder(options) { + return $.ajax({ + type: "PUT", + url: '/api/recurly/place_order?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function searchMusicians(query) { return $.ajax({ type: "GET", @@ -1375,6 +1475,16 @@ this.getChatMessages = getChatMessages; this.createDiagnostic = createDiagnostic; this.getLatencyTester = getLatencyTester; + this.updateAudioLatency = updateAudioLatency; + this.getJamtracks = getJamtracks; + this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; + this.getShoppingCarts = getShoppingCarts; + this.removeShoppingCart = removeShoppingCart; + this.getRecurlyAccount = getRecurlyAccount; + this.createRecurlyAccount = createRecurlyAccount; + this.getBillingInfo = getBillingInfo; + this.updateBillingInfo = updateBillingInfo; + this.placeOrder = placeOrder; this.searchMusicians = searchMusicians; return this; diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index d8fb6e4aa..ba3d4e25b 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -22,7 +22,8 @@ var rest = context.JK.Rest(); var inBadState = false; var userDeferred = null; - + var userData = null; + var self = this; var opts = { inClient: true, // specify false if you want the app object but none of the client-oriented features @@ -301,6 +302,7 @@ logger.debug("updating user info") userDeferred = update; // update the global user object if this succeeded }) + update.done(this.updateUserCache) return update; } @@ -309,6 +311,15 @@ return userDeferred; } + // gets the most recent user data. can be null when app is still initializing. + // user app.user() if initialize sequence is unknown/asynchronous + this.currentUser = function() { + if(userData == null) { + throw "currentUser has null user data" + } + return userData; + } + this.activeElementEvent = function(evtName, data) { return this.layout.activeElementEvent(evtName, data); } @@ -333,6 +344,10 @@ } }; + this.updateUserCache = function(_userData) { + userData = _userData + } + this.initialize = function (inOpts) { var url, hash; app = this; @@ -343,6 +358,7 @@ this.layout.handleDialogState(); userDeferred = rest.getUserDetail(); + userDeferred.done(this.updateUserCache) if (opts.inClient) { registerBadStateRecovered(); diff --git a/web/app/assets/javascripts/jamtrack.js b/web/app/assets/javascripts/jamtrack.js new file mode 100644 index 000000000..6ae767514 --- /dev/null +++ b/web/app/assets/javascripts/jamtrack.js @@ -0,0 +1,259 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.JamTrackScreen = function(app) { + + var logger = context.JK.logger; + + var $screen = null; + var $content = null; + var $scroller = null; + var $genre = null; + var $instrument = null; + var $availability = null; + var $nextPager = null; + var $noMoreJamtracks = null; + + var currentQuery = defaultQuery(); + var currentPage = 0; + var LIMIT = 10; + var next = null; + var instrument_logo_map = context.JK.getInstrumentIconMap24(); + + function beforeShow(data) { + refresh(); + } + + function afterShow(data) { + } + + function events() { + $genre.on("change", search); + $instrument.on("change", search); + $availability.on("change", search); + } + + function clearResults() { + //logger.debug("CLEARING CONTENT") + currentPage = 0; + $content.empty(); + $noMoreJamtracks.hide(); + next = null; + } + + function refresh() { + currentQuery = buildQuery(); + rest.getJamtracks(currentQuery) + .done(function(response) { + clearResults(); + handleJamtrackResponse(response); + }) + .fail(function(jqXHR) { + clearResults(); + $noMoreJamtracks.show(); + app.notifyServerError(jqXHR, 'Jamtrack Unavailable') + }) + } + + function search() { + logger.debug("Searching for jamtracks..."); + refresh(); + return false; + } + + function defaultQuery() { + var query = { limit:LIMIT, page:currentPage}; + + if(next) { + query.since = next; + } + + return query; + } + + function buildQuery() { + currentQuery = defaultQuery(); + + // genre filter + var genres = $screen.find('#jamtrack_genre').val(); + if (genres !== undefined) { + currentQuery.genre = genres; + } + + // instrument filter + var instrument = $instrument.val(); + if (instrument !== undefined) { + currentQuery.instrument = instrument; + } + + // availability filter + var availability = $availability.val(); + if (availability !== undefined) { + currentQuery.availability = availability; + } + + return currentQuery; + } + + function handleJamtrackResponse(response) { + //logger.debug("Handling response", JSON.stringify(response)) + next = response.next; + + renderJamtracks(response); + + if(response.next == null) { + // if we less results than asked for, end searching + $scroller.infinitescroll('pause'); + logger.debug("end of jamtracks"); + + if(currentPage == 0 && response.jamtracks.length == 0) { + $content.append("
There's no jamtracks.
") ; + } + + if(currentPage > 0) { + $noMoreJamtracks.show(); + // there are bugs with infinitescroll not removing the 'loading'. + // it's most noticeable at the end of the list, so whack all such entries + $('.infinite-scroll-loader').remove(); + } + } + else { + currentPage++; + buildQuery(); + registerInfiniteScroll(); + } + } + + function registerInfiniteScroll() { + $scroller.infinitescroll({ + behavior: 'local', + navSelector: '#jamtrackScreen .btn-next-pager', + nextSelector: '#jamtrackScreen .btn-next-pager', + binder: $scroller, + dataType: 'json', + appendCallback: false, + prefill: false, + bufferPx: 100, + loading: { + msg: $('
Loading ...
'), + img: '/assets/shared/spinner.gif' + }, + path: function(page) { + return '/api/jamtracks?' + $.param(buildQuery()); + } + },function(json, opts) { + handleJamtrackResponse(json); + }); + $scroller.infinitescroll('resume'); + } + + function playJamtrack(e) { + e.preventDefault(); + } + + function addToCartJamtrack(e) { + e.preventDefault(); + + var params = {id: $(e.target).attr("data-jamtrack-id")}; + + rest.addJamtrackToShoppingCart(params) + .done(function(response) { + context.location = "/client#/shoppingCart"; + }) + .fail(app.ajaxError); + } + + function licenseUSWhy(e) { + e.preventDefault(); + + app.layout.showDialog('jamtrack-availability-dialog'); + } + + function registerEvents() { + $screen.find('.jamtrack-detail-btn').on("click", showJamtrackDescription); + $screen.find('.play-button').on('click', playJamtrack); + $screen.find('.jamtrack-add-cart').on('click', addToCartJamtrack); + $screen.find('.license-us-why').on('click', licenseUSWhy); + } + + function renderJamtracks(data) { + $.each(data.jamtracks, function(i, jamtrack) { + $.each(jamtrack.tracks, function (index, track) { + var inst = '../assets/content/icon_instrument_default24.png'; + if (track.instrument.id in instrument_logo_map) { + inst = instrument_logo_map[track.instrument.id].asset; + } + track.instrument_url = inst; + + track.instrument_desc = track.instrument.description; + if (track.part != "") { + track.instrument_desc += " ( " + track.part + " )"; + } + }); + + var options = { + jamtrack: jamtrack + }; + + var $jamtrackItem = $( + context._.template( + $('#template-jamtrack').html(), + options, + {variable: 'data'} + ) + ); + renderJamtrack($jamtrackItem ); + }); + + registerEvents(); + } + + function showJamtrackDescription(e) { + e.preventDefault(); + + var $description = $(e.target).parent(".detail-arrow").next(); + if ($description.css("display") == "none") { + $description.show(); + } + else { + $description.hide(); + } + } + + function renderJamtrack(jamtrack) { + $content.append(jamtrack); + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('jamtrack', screenBindings); + + $screen = $("#jamtrack-find-form"); + $scroller = $screen.find('.content-body-scroller'); + $content = $screen.find(".jamtrack-content"); + $genre = $screen.find("#jamtrack_genre"); + $instrument = $screen.find("#jamtrack_instrument"); + $availability = $screen.find("#jamtrack_availability"); + $nextPager = $screen.find("a.btn-next-pager"); + $noMoreJamtracks = $screen.find("#end-of-jamtrack-list"); + + if($screen.length == 0) throw "$screen must be specified"; + if($scroller.length == 0) throw "$scroller must be specified"; + if($content.length == 0) throw "$content must be specified"; + if($noMoreJamtracks.length == 0) throw "$noMoreJamtracks must be specified"; + if($genre.length == 0) throw "$genre must be specified"; + if($instrument.length == 0) throw "$instrument must be specified"; + if($availability.length ==0) throw "$availability must be specified"; + + events(); + } + + this.initialize = initialize; + + return this; + } +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/mods_utils.js.coffee b/web/app/assets/javascripts/mods_utils.js.coffee index b10250c32..c4bb5321d 100644 --- a/web/app/assets/javascripts/mods_utils.js.coffee +++ b/web/app/assets/javascripts/mods_utils.js.coffee @@ -31,5 +31,11 @@ class ModUtils deferred.resolve(shouldShowForName) )) return deferred; + + # returns a gear mod by name + getGear: (name) => + gear = context.JK.app.currentUser().mods?.gear + if gear? then gear[name] else undefined + # global instance context.JK.ModUtils = new ModUtils() \ No newline at end of file diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js new file mode 100644 index 000000000..b6a78bff9 --- /dev/null +++ b/web/app/assets/javascripts/order.js @@ -0,0 +1,511 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.OrderScreen = function(app) { + + var logger = context.JK.logger; + + var $screen = null; + var $navigation = null; + var $billingInfo = null; + var $shippingInfo = null; + var $paymentMethod = null; + var $shippingAddress = null; + var $shippingAsBilling = null; + var $paymentInfoPanel = null; + var $orderPanel = null; + var $orderContent = null; + var userDetail = null; + var step = null; + var billing_info = null; + var shipping_info = null; + var shipping_as_billing = null; + + function beforeShow() { + beforeShowPaymentInfo(); +// moveToOrder(); + } + + function beforeShowPaymentInfo() { + step = 2; + renderNavigation(); + renderAccountInfo(); + } + + function renderAccountInfo() { + rest.getUserDetail() + .done(populateAccountInfo) + .error(app.ajaxError); + } + + function populateAccountInfo(user) { + userDetail = user; + + if (userDetail.has_recurly_account) { + rest.getBillingInfo() + .done(function(response) { + $billingInfo.find("#billing-first-name").val(response.first_name); + $billingInfo.find("#billing-last-name").val(response.last_name); + $billingInfo.find("#billing-address1").val(response.address1); + $billingInfo.find("#billing-address2").val(response.address2); + $billingInfo.find("#billing-city").val(response.city); + $billingInfo.find("#billing-state").val(response.state); + $billingInfo.find("#billing-zip").val(response.zip); + $billingInfo.find("#billing-country").val(response.country); + + $shippingAddress.find("#shipping-first-name").val(response.first_name); + $shippingAddress.find("#shipping-last-name").val(response.last_name); + $shippingAddress.find("#shipping-address1").val(response.address1); + $shippingAddress.find("#shipping-address2").val(response.address2); + $shippingAddress.find("#shipping-city").val(response.city); + $shippingAddress.find("#shipping-state").val(response.state); + $shippingAddress.find("#shipping-zip").val(response.zip); + $shippingAddress.find("#shipping-country").val(response.country); + }) + .error(app.ajaxError); + } + else { + $billingInfo.find("#billing-first-name").val(userDetail.first_name); + $billingInfo.find("#billing-last-name").val(userDetail.last_name); + $billingInfo.find("#billing-city").val(userDetail.city); + $billingInfo.find("#billing-state").val(userDetail.state); + $billingInfo.find("#billing-country").val(userDetail.country); + + $shippingAddress.find("#shipping-first-name").val(userDetail.first_name); + $shippingAddress.find("#shipping-last-name").val(userDetail.last_name); + $shippingAddress.find("#shipping-city").val(userDetail.city); + $shippingAddress.find("#shipping-state").val(userDetail.state); + $shippingAddress.find("#shipping-country").val(userDetail.country); + } + } + + function afterShow(data) { + } + + function next(e) { + e.preventDefault(); + + // validation + var billing_first_name = $billingInfo.find("#billing-first-name").val(); + var billing_last_name = $billingInfo.find("#billing-last-name").val(); + var billing_address1 = $billingInfo.find("#billing-address1").val(); + var billing_address2 = $billingInfo.find("#billing-address2").val(); + var billing_city = $billingInfo.find("#billing-city").val(); + var billing_state = $billingInfo.find("#billing-state").val(); + var billing_zip = $billingInfo.find("#billing-zip").val(); + var billing_country = $billingInfo.find("#billing-country").val(); + + if (!billing_first_name) { + $billingInfo.find('#divBillingFirstName .error-text').remove(); + $billingInfo.find('#divBillingFirstName').addClass("error"); + $billingInfo.find('#billing-first-name').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingFirstName').removeClass("error"); + } + + if (!billing_last_name) { + $billingInfo.find('#divBillingLastName .error-text').remove(); + $billingInfo.find('#divBillingLastName').addClass("error"); + $billingInfo.find('#billing-last-name').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingLastName').removeClass("error"); + } + + if (!billing_address1) { + $billingInfo.find('#divBillingAddress1 .error-text').remove(); + $billingInfo.find('#divBillingAddress1').addClass("error"); + $billingInfo.find('#billing-address1').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingAddress1').removeClass("error"); + } + + if (!billing_zip) { + $billingInfo.find('#divBillingZip .error-text').remove(); + $billingInfo.find('#divBillingZip').addClass("error"); + $billingInfo.find('#billing-zip').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingZip').removeClass("error"); + } + + if (!billing_state) { + $billingInfo.find('#divBillingState .error-text').remove(); + $billingInfo.find('#divBillingState').addClass("error"); + $billingInfo.find('#billing-zip').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingState').removeClass("error"); + } + + if (!billing_city) { + $billingInfo.find('#divBillingCity .error-text').remove(); + $billingInfo.find('#divBillingCity').addClass("error"); + $billingInfo.find('#billing-city').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingCity').removeClass("error"); + } + + if (!billing_country) { + $billingInfo.find('#divBillingCountry .error-text').remove(); + $billingInfo.find('#divBillingCountry').addClass("error"); + $billingInfo.find('#billing-country').after(""); + + return false; + } + else { + $billingInfo.find('#divBillingCountry').removeClass("error"); + } + + shipping_as_billing = $shippingAsBilling.is(":checked"); + var shipping_first_name, shipping_last_name, shipping_address1, shipping_address2; + var shipping_city, shipping_state, shipping_zip, shipping_country; + + if (!shipping_as_billing) { + shipping_first_name = $shippingAddress.find("#shipping-first-name").val(); + shipping_last_name = $shippingAddress.find("#shipping-last-name").val(); + shipping_address1 = $shippingAddress.find("#shipping-address1").val(); + shipping_address2 = $shippingAddress.find("#shipping-address2").val(); + shipping_city = $shippingAddress.find("#shipping-city").val(); + shipping_state = $shippingAddress.find("#shipping-state").val(); + shipping_zip = $shippingAddress.find("#shipping-zip").val(); + shipping_country = $shippingAddress.find("#shipping-country").val(); + + if (!shipping_first_name) { + $shippingAddress.find('#divShippingFirstName .error-text').remove(); + $shippingAddress.find('#divShippingFirstName').addClass("error"); + $shippingAddress.find('#shipping-first-name').after(""); + + return false; + } + else { + $shippingInfo.find('#divShippingFirstName').removeClass("error"); + } + + if (!shipping_last_name) { + $shippingAddress.find('#divShippingLastName .error-text').remove(); + $shippingAddress.find('#divShippingLastName').addClass("error"); + $shippingAddress.find('#shipping-last-name').after(""); + + return false; + } + else { + $shippingInfo.find('#divShippingLastName').removeClass("error"); + } + + if (!shipping_address1) { + $shippingAddress.find('#divShippingAddress1 .error-text').remove(); + $shippingAddress.find('#divShippingAddress1').addClass("error"); + $shippingAddress.find('#shipping-address1').after(""); + + return false; + } + else { + $shippingInfo.find('#divShippingAddress1').removeClass("error"); + } + + if (!shipping_zip) { + $shippingAddress.find('#divShippingZip .error-text').remove(); + $shippingAddress.find('#divShippingZip').addClass("error"); + $shippingAddress.find('#shipping-zip').after(""); + + return false; + } + else { + $shippingInfo.find('#divShippingZip').removeClass("error"); + } + + if (!shipping_state) { + $shippingAddress.find('#divShippingState .error-text').remove(); + $shippingAddress.find('#divShippingState').addClass("error"); + $shippingAddress.find('#shipping-zip').after(""); + + return false; + } + else { + $shippingInfo.find('#divShippingState').removeClass("error"); + } + + if (!shipping_city) { + $shippingAddress.find('#divShippingCity .error-text').remove(); + $shippingAddress.find('#divShippingCity').addClass("error"); + $shippingAddress.find('#shipping-city').after(""); + + return false; + } + else { + $shippingInfo.find('#divShippingCity').removeClass("error"); + } + + if (!shipping_country) { + $shippingAddress.find('#divShippingCountry .error-text').remove(); + $shippingAddress.find('#divShippingCountry').addClass("error"); + $shippingAddress.find('#shipping-country').after(""); + + return false; + } + else { + $shippingAddress.find('#divShippingCountry').removeClass("error"); + } + } + + var card_name = $paymentMethod.find("#card-name").val(); + var card_number = $paymentMethod.find("#card-number").val(); + var card_year = $paymentMethod.find("#card_expire-date_3i").val(); + var card_month = $paymentMethod.find("#card_expire-date_2i").val(); + var card_verify = $paymentMethod.find("#card-verify").val(); + + if (!card_name) { + $paymentMethod.find('#divCardName .error-text').remove(); + $paymentMethod.find('#divCardName').addClass("error"); + $paymentMethod.find('#card-name').after(""); + + return false; + } + else { + $paymentMethod.find('#divCardName').removeClass("error"); + } + + if (!card_number) { + $paymentMethod.find('#divCardNumber .error-text').remove(); + $paymentMethod.find('#divCardNumber').addClass("error"); + $paymentMethod.find('#card-number').after(""); + + return false; + } + else { + $paymentMethod.find('#divCardNumber').removeClass("error"); + } + + if (!card_verify) { + $paymentMethod.find('#divCardVerify .error-text').remove(); + $paymentMethod.find('#divCardVerify').addClass("error"); + $paymentMethod.find('#card_verify').after(""); + + return false; + } + else { + $paymentMethod.find('#divCardVerify').removeClass("error"); + } + + billing_info = {}; + shipping_info = {}; + billing_info.first_name = billing_first_name; + billing_info.last_name = billing_last_name; + billing_info.address1 = billing_address1; + billing_info.address2 = billing_address2; + billing_info.city = billing_city; + billing_info.state = billing_state; + billing_info.country = billing_country; + billing_info.zip = billing_zip; + billing_info.number = card_number; + billing_info.month = card_month; + billing_info.year = card_year; + billing_info.verification_value = card_verify; + + if (shipping_as_billing) { + shipping_info = billing_info; + delete shipping_info.number; + delete shipping_info.month; + delete shipping_info.year; + delete shipping_info.verification_value; + } + else { + shipping_info.first_name = shipping_first_name; + shipping_info.last_name = shipping_last_name; + shipping_info.address1 = shipping_address1; + shipping_info.address2 = shipping_address2; + shipping_info.city = shipping_city; + shipping_info.state = shipping_state; + shipping_info.country = shipping_country; + shipping_info.zip = shipping_zip; + } + + $paymentInfoPanel.find("#payment-info-next").addClass("disabled"); + $paymentInfoPanel.find("#payment-info-next").off("click"); + + if (userDetail.has_recurly_account) { + rest.updateBillingInfo(billing_info) + .done(function() { + }) + .fail(errorHandling); + } + else { + rest.createRecurlyAccount({billing_info: billing_info}) + .done(function() { + }) + .fail(errorHandling); + } + } + + function errorHandling(xhr, ajaxOptions, thrownError) { + $.each(xhr.responseJSON.errors, function(key, error) { + if (key == 'number') { + $paymentMethod.find('#divCardNumber .error-text').remove(); + $paymentMethod.find('#divCardNumber').addClass("error"); + $paymentMethod.find('#card-number').after(""); + } + else if (key == 'verification_value') { + $paymentMethod.find('#divCardVerify .error-text').remove(); + $paymentMethod.find('#divCardVerify').addClass("error"); + $paymentMethod.find('#card-verify').after(""); + } + }); + + $paymentInfoPanel.find("#payment-info-next").removeClass("disabled"); + $paymentInfoPanel.find("#payment-info-next").on("click", next); +// moveToOrder(); + } + + function beforeShowOrder() { + step = 3; + renderNavigation(); + populateOrderPage(); + } + + function clearOrderPage() { + $orderContent.empty(); + } + + function populateOrderPage() { + clearOrderPage(); + + rest.getShoppingCarts() + .done(renderOrderPage) + .fail(app.ajaxError); + } + + function renderOrderPage(carts) { + var data = {}; + + var sub_total = 0; + $.each(carts, function(index, cart) { + sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity); + }); + data.sub_total = sub_total.toFixed(2); + data.taxes = 12.01; + + data.carts = carts; + data.billing_info = billing_info; + data.shipping_info = shipping_info; + data.shipping_as_billing = shipping_as_billing; + var orderContentHtml = $( + context._.template( + $('#template-order-content').html(), + data, + {variable: 'data'} + ) + ); + + $orderContent.append(orderContentHtml); + + $orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo); + $orderContent.find(".place-order").on('click', placeOrder); + } + + function moveToOrder() { + $paymentInfoPanel.addClass("hidden"); + $orderPanel.removeClass("hidden"); + beforeShowOrder(); + } + + function moveToPaymentInfo(e) { + e.preventDefault(); + $paymentInfoPanel.removeClass("hidden"); + $orderPanel.addClass("hidden"); + beforeShowPaymentInfo(); + } + + function toggleShippingAsBilling(e) { + e.preventDefault(); + + var shipping_as_billing = $(e.target).is(':checked'); + + if (!shipping_as_billing) { + $shippingAddress.removeClass("hidden"); + } + else { + $shippingAddress.addClass("hidden"); + } + } + + function placeOrder(e) { + e.preventDefault(); + } + + function events() { + $paymentInfoPanel.find("#payment-info-next").on('click', next); + $shippingAsBilling.on('ifChanged', toggleShippingAsBilling); + } + + function reset() { + } + + function renderNavigation() { + $navigation.html(""); + var navigationHtml = $( + context._.template( + $('#template-checkout-navigation').html(), + {current: step}, + {variable: 'data'} + ) + ); + + $navigation.append(navigationHtml); + } + + function initializeControls() { + $("form.payment-info").iCheck({ + checkboxClass: 'icheckbox_minimal', + radioClass: 'iradio_minimal', + inheritClass: true + }); + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('order', screenBindings); + + $screen = $("#orderScreen"); + $paymentInfoPanel = $screen.find(".checkout-payment-info"); + $orderPanel = $screen.find(".order-panel"); + $navigation = $screen.find(".checkout-navigation-bar"); + $billingInfo = $paymentInfoPanel.find(".billing-address"); + $shippingInfo = $paymentInfoPanel.find(".shipping-address"); + $paymentMethod = $paymentInfoPanel.find(".payment-method"); + $shippingAddress = $paymentInfoPanel.find(".shipping-address-detail"); + $shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing"); + $orderContent = $orderPanel.find(".order-content"); + + if($screen.length == 0) throw "$screen must be specified"; + if($navigation.length == 0) throw "$navigation must be specified"; + + initializeControls(); + + events(); + } + + this.initialize = initialize; + + return this; + } +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index e113e4b86..1fc4d1156 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -1237,7 +1237,7 @@ $languageList = $screen.find('#session-language-list'); $sessionPlusMusiciansLabel = $screen.find('label[for="session-plus-musicians"]'); $editScheduledSessions = $screen.find('#edit_scheduled_sessions'); - $btnSelectFiles = $screen.find('.btn-select-files'); + $btnSelectFiles = $screen.find('#session-notation-file-selection'); $selectedFilenames = $screen.find('#selected-filenames'); $uploadSpinner = $screen.find('#file-upload-spinner'); $policyTypes = $screen.find('input[name="session-policy-type"]'); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 54efbf840..6c07ba1e3 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -495,9 +495,6 @@ var mixerIds = context.jamClient.SessionGetIDs(); var holder = $.extend(true, {}, {mixers: context.jamClient.SessionGetControlState(mixerIds)}); mixers = holder.mixers; - - //console.log("mixers", mixers) - // grab the first mixer, and check the mode var newMixerMode;; @@ -884,6 +881,7 @@ // particular client, in a particular group, and I'll need to further // identify by track id or something similar. + var mixers = _groupedMixersForClientId( participant.client_id, [ @@ -908,6 +906,7 @@ oppositeMixer = mixers[ChannelGroupIds.PeerAudioInputMusicGroup][0] } } + if (mixer) { usedMixers[mixer.id] = true; myTrack = (mixer.group_id === ChannelGroupIds.AudioInputMusicGroup); @@ -982,6 +981,7 @@ var keysToDelete = []; for (var key in lookingForMixers) { var clientId = lookingForMixers[key]; +<<<<<<< HEAD var mixers = _groupedMixersForClientId( clientId, [ @@ -1004,6 +1004,27 @@ mixer = mixers[ChannelGroupIds.UserMusicInputGroup][0] oppositeMixer = mixers[ChannelGroupIds.PeerAudioInputMusicGroup][0] } +======= + var mixer = null; + if(sessionModel.isMasterMixMode()) { + mixer = _mixerForClientId( + clientId, + [ + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.PeerAudioInputMusicGroup + ], + usedMixers); + } + else { + // don't pass in used mixers; we need to associate multiple tracks with the same mixer + mixer = _mixerForClientId( + clientId, + [ + ChannelGroupIds.AudioInputMusicGroup, + ChannelGroupIds.UserMusicInputGroup + ], + {}); +>>>>>>> develop } if (mixer) { var participant = (sessionModel.getParticipant(clientId) || {name:'unknown'}).name; diff --git a/web/app/assets/javascripts/sessionList.js b/web/app/assets/javascripts/sessionList.js index 2dae2c083..3f3ee478d 100644 --- a/web/app/assets/javascripts/sessionList.js +++ b/web/app/assets/javascripts/sessionList.js @@ -380,7 +380,7 @@ var track = participant.tracks[j]; logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks); var inst = context.JK.getInstrumentIcon24(track.instrument_id); - instrumentLogoHtml += ' '; + instrumentLogoHtml += ' '; } var id = participant.user.id; @@ -411,7 +411,7 @@ for (j=0; j < user.instrument_list.length; j++) { var instrument = user.instrument_list[j]; var inst = context.JK.getInstrumentIcon24(instrument.id); - instrumentLogoHtml += ' '; + instrumentLogoHtml += ' '; } } diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js new file mode 100644 index 000000000..ac02b8b39 --- /dev/null +++ b/web/app/assets/javascripts/shopping_cart.js @@ -0,0 +1,112 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.ShoppingCartScreen = function(app) { + + var logger = context.JK.logger; + + var $screen = null; + var $content = null; + + function beforeShow(data) { + loadShoppingCarts(); + } + + function afterShow(data) { + } + + function events() { + $screen.find("a.remove-cart").on('click', removeCart); + $screen.find("a.proceed-checkout").on('click', proceedCheckout); + } + + function proceedCheckout(e) { + e.preventDefault(); + + if (!context.JK.currentUserId) { + window.location = '/client#/signin'; + } + else { + window.location = '/client#/order'; + } + } + + function removeCart(e) { + e.preventDefault(); + + var options = {}; + options.id = $(e.target).attr("cart-id"); + + rest.removeShoppingCart(options) + .done(loadShoppingCarts) + .fail(app.ajaxError); + } + + function clearContent() { + $content.empty(); + } + + function loadShoppingCarts() { + clearContent(); + + rest.getShoppingCarts() + .done(renderShoppingCarts) + .fail(app.ajaxError); + } + + function renderShoppingCarts(carts) { + var data = {}; + var latest_cart = carts[carts.length - 1]; + + var $latestCartHtml = ""; + + if (latest_cart) { + $latestCartHtml = $( + context._.template( + $('#template-shopping-cart-header').html(), + latest_cart, + {variable: 'data'} + ) + ); + } + + var sub_total = 0; + $.each(carts, function(index, cart) { + sub_total += parseFloat(cart.product_info.price) * parseFloat(cart.quantity); + }); + data.sub_total = sub_total.toFixed(2); + + data.carts = carts; + var $cartsHtml = $( + context._.template( + $('#template-shopping-cart-body').html(), + data, + {variable: 'data'} + ) + ); + + $content.append($latestCartHtml).append($cartsHtml); + + events(); + } + + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + app.bindScreen('shoppingCart', screenBindings); + + $screen = $("#shoppingCartScreen"); + $content = $screen.find(".shopping-cart-content"); + + if($screen.length == 0) throw "$screen must be specified"; + if($content.length == 0) throw "$content must be specified"; + } + + this.initialize = initialize; + + return this; + } +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index b7641dbbc..7e45b7c46 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -88,6 +88,7 @@ var instrumentIconMap24 = {}; var instrumentIconMap45 = {}; var instrumentIconMap256 = {}; + var notSpecifiedText = "Not specified"; $.each(icon_map_base, function (instrumentId, icon) { instrumentIconMap24[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "24.png", name: instrumentId}; @@ -332,24 +333,28 @@ function showBubble(bubble, $hoverElement) { $hoverElement.attr("bubble-id", bubble.id); - bubble.showBubble($hoverElement); + bubble.showBubble($hoverElement) + .done(function() { + + $(bubble.id()).hover( + function () { + $(this).data('hovering', true) + // do nothing when entering the bubble + }, + function () { + $(this).data('hovering', false).fadeOut(100); + } + ); + + }) } function hideBubble($hoverElement) { var bubbleSelector = $hoverElement.attr("bubble-id"); - $(bubbleSelector).hover( - function () { - // do nothing when entering the bubble - }, - function () { - $(this).fadeOut(fadeoutValue); - } - ); - // first check to see if the user isn't hovering over the hover bubble - if (!$(bubbleSelector).is(":hover")) { + if (!$(bubbleSelector).data('hovering')) { $(bubbleSelector).fadeOut(fadeoutValue); } } @@ -358,8 +363,10 @@ $("[hoveraction='musician']", $parent).hoverIntent({ over: function(e) { var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), e.pageX, e.pageY); + showBubble(bubble, $(this)); + }, out: function () { // this registers for leaving the hoverable element hideBubble($(this)); @@ -507,6 +514,10 @@ return instrumentIconMap256["_default"].asset; }; + context.JK.getInstrumentId = function(instrumentId) { + return instrumentId ? instrumentId : notSpecifiedText; + } + // meant to pass in a bunch of images with an instrument-id attribute on them. context.JK.setInstrumentAssetPath = function ($elements) { diff --git a/web/app/assets/javascripts/wizard/frame_buffers.js b/web/app/assets/javascripts/wizard/frame_buffers.js index f96dde085..a19fdfdf7 100644 --- a/web/app/assets/javascripts/wizard/frame_buffers.js +++ b/web/app/assets/javascripts/wizard/frame_buffers.js @@ -9,11 +9,14 @@ var $bufferIn = null; var $bufferOut = null; var $frameSize = null; + var $adjustSettingsLink = null; var $self = $(this); + var logger = context.JK.logger; var FRAMESIZE_CHANGED = 'frame_buffers.framesize_changed'; var BUFFER_IN_CHANGED = 'frame_buffers.buffer_in_changed'; var BUFFER_OUT_CHANGED = 'frame_buffers.buffer_out_changed'; + var ADJUST_GEAR_LINK_CLICKED = 'frame_buffers.adjust_gear_settings_clicked'; function selectedFramesize() { return parseFloat($frameSize.val()); @@ -77,6 +80,12 @@ logger.debug("buffer-out changed: " + selectedBufferOut()); $self.triggerHandler(BUFFER_OUT_CHANGED, {value: selectedBufferOut()}); }); + + $adjustSettingsLink.click(function() { + logger.debug("adjust-gear-settings clicked"); + $self.triggerHandler(ADJUST_GEAR_LINK_CLICKED); + return false; + }); } function initialize(_$knobs) { @@ -89,6 +98,7 @@ $bufferIn = $knobs.find('.select-buffer-in'); $bufferOut = $knobs.find('.select-buffer-out'); $frameSize = $knobs.find('.select-frame-size'); + $adjustSettingsLink = $knobs.find('.adjust-gear-settings') events(); render(); @@ -97,6 +107,7 @@ this.FRAMESIZE_CHANGED = FRAMESIZE_CHANGED; this.BUFFER_IN_CHANGED = BUFFER_IN_CHANGED; this.BUFFER_OUT_CHANGED = BUFFER_OUT_CHANGED; + this.ADJUST_GEAR_LINK_CLICKED = ADJUST_GEAR_LINK_CLICKED; this.initialize = initialize; this.selectedFramesize = selectedFramesize; this.selectedBufferIn = selectedBufferIn; diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js index 349032d8c..cb0cd4317 100644 --- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js +++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js @@ -13,6 +13,7 @@ var $wizardSteps = null; var $templateSteps = null; var loopbackWizard = null; + var adjustGearSettings = null; var inputs = null; var self = this; @@ -183,9 +184,10 @@ return inputs; } - function initialize(_loopbackWizard) { + function initialize(_loopbackWizard, _adjustGearSettings) { loopbackWizard = _loopbackWizard; + adjustGearSettings = _adjustGearSettings; // on initial page load, we are not in the FTUE. so cancel the FTUE and call FTUESetStatus(true) if needed if(context.jamClient.FTUEGetStatus() == false) { @@ -224,6 +226,7 @@ this.createFTUEProfile = createFTUEProfile; this.getWizard = function() {return wizard; } this.getLoopbackWizard = function() { return loopbackWizard; }; + this.getAdjustGearSettings = function() { return adjustGearSettings; }; self = this; return this; diff --git a/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js b/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js index f76e0abaf..ac9a40c25 100644 --- a/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js +++ b/web/app/assets/javascripts/wizard/gear/step_direct_monitoring.js @@ -5,9 +5,11 @@ context.JK = context.JK || {}; context.JK.StepDirectMonitoring = function (app) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var $step = null; var $directMonitoringBtn = null; + var $adjustSettingsDirectMonitor = null; var isPlaying = false; var playCheckInterval = null; var trackDurationMs = null; @@ -83,12 +85,29 @@ } } + function onAdjustGearRequested() { + app.layout.showDialog('adjust-gear-speed-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + + var adjustGearTest = data.result; + + if(!data.canceled) { + if(adjustGearTest.isGoodFtue()) { + + } + } + else { + logger.debug("adjust-gear-speed was cancelled; ignoring") + } + }) + } + function initialize(_$step) { $step = _$step; $directMonitoringBtn = $step.find('.test-direct-monitoring'); - $directMonitoringBtn.on('click', togglePlay); + $adjustSettingsDirectMonitor = $step.find('.adjust-settings-direct-monitor'); + $adjustSettingsDirectMonitor.on('click', onAdjustGearRequested) } this.handleHelp = handleHelp; diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js index d1d53543f..9479f86c9 100644 --- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js +++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js @@ -11,6 +11,7 @@ var VOICE_CHAT = context.JK.VOICE_CHAT; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; var gearUtils = context.JK.GearUtils; + var modUtils = context.JK.ModUtils; var self = null; var $step = null; @@ -19,6 +20,7 @@ var frameBuffers = new context.JK.FrameBuffers(app); var gearTest = new context.JK.GearTest(app); var loopbackShowing = false; + var adjustGearSettingsShowing = false; var wizard = null; // the goal of lastFailureAnalytics and trackedPass are to send only a single AudioTest 'Pass' or 'Failed' event, per FTUE wizard open/close @@ -32,6 +34,8 @@ var $inputChannels = null; var $outputChannels = null; var $knobs = null; + var $adjustSettingsLink = null; + var $adjustGearForIoFail = null; var $scoreReport = null; var $asioInputControlBtn = null; var $asioOutputControlBtn = null; @@ -86,7 +90,7 @@ } function initializeNextButtonState() { - dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue()); + dialog.setNextState(gearTest.isGoodFtue() || dialog.getLoopbackWizard().getGearTest().isGoodFtue() || dialog.getAdjustGearSettings().getGearTest().isGoodFtue()); } function initializeBackButtonState() { @@ -365,8 +369,9 @@ if(dialog.getLoopbackWizard().getGearTest().isGoodFtue()) { gearTest.resetScoreReport(); gearTest.showLoopbackDone(); - context.JK.prodBubble(dialog.getWizard().getNextButton(), 'move-on-loopback-success', {}, {positions:['top']}); - + setTimeout(function() { + context.JK.prodBubble(dialog.getWizard().getNextButton(), 'can-move-on', {}, {positions:['top'], offsetParent: dialog.getWizard().getDialog()}); + }, 300); } initializeNextButtonState(); @@ -379,6 +384,42 @@ }) } + function onAdjustGearRequested() + { + if(gearTest.isScoring()) {logger.debug("ignoring adjust-gear request while scoring"); return false;} + + app.layout.showDialog('adjust-gear-speed-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + adjustGearSettingsShowing = false; + + var adjustGearTest = data.result; + + if(!data.canceled) { + if(adjustGearTest.isGoodFtue()) { + // update our own frame buffers to reflect any changes made by the adjust dialog + frameBuffers.setFramesize(context.jamClient.FTUEGetFrameSize()) + frameBuffers.setBufferIn(context.jamClient.FTUEGetInputLatency()) + frameBuffers.setBufferOut(context.jamClient.FTUEGetOutputLatency()) + + gearTest.resetScoreReport(); + gearTest.showGearAdjustmentDone(); + + setTimeout(function() { + context.JK.prodBubble(dialog.getWizard().getNextButton(), 'can-move-on', {}, {positions:['top'], offsetParent: dialog.getWizard().getDialog()}); + }, 300); + } + + initializeNextButtonState(); + initializeBackButtonState(); + } + else { + logger.debug("adjust-gear-speed was cancelled; ignoring") + } + }) + + adjustGearSettingsShowing = true; + return false; + } + function initializeFormElements() { if (!deviceInformation) throw "devices are not initialized"; @@ -486,6 +527,7 @@ function invalidateScore() { gearTest.invalidateScore(); dialog.getLoopbackWizard().getGearTest().invalidateScore(); + dialog.getAdjustGearSettings().getGearTest().invalidateScore(); initializeNextButtonState(); } @@ -755,6 +797,7 @@ validDevice = autoSelectMinimumValidChannels(); if (!validDevice) { + $adjustSettingsLink.hide(); return false; } @@ -774,6 +817,11 @@ shownOutputProdOnce = true; } + // further, check if we have both inputs and outputs defined; if so, show ? to allow launch of adjust gear settings dialog + if(modUtils.getGear('show_frame_options')) { + $adjustSettingsLink.show(); + } + return true; } @@ -821,11 +869,12 @@ } // handle framesize/buffers - if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs)) { + if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs || modUtils.getGear('show_frame_options'))) { $knobs.show(); } else { $knobs.hide(); + $adjustSettingsLink.hide(); } // handle ASIO visibility @@ -865,36 +914,9 @@ jamClient.FTUESetFrameSize(frameBuffers.selectedFramesize()); } + function updateDefaultBuffers() { - - // handle specific framesize settings - if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { - var framesize = frameBuffers.selectedFramesize(); - - if(framesize == 2.5) { - logger.debug("setting default buffers to 1/1"); - frameBuffers.setBufferIn('1'); - frameBuffers.setBufferOut('1'); - } - else if(framesize == 5) { - logger.debug("setting default buffers to 3/2"); - frameBuffers.setBufferIn('3'); - frameBuffers.setBufferOut('2'); - } - else { - logger.debug("setting default buffers to 6/5"); - frameBuffers.setBufferIn('6'); - frameBuffers.setBufferOut('5'); - } - } - else { - logger.debug("setting default buffers to 0/0"); - frameBuffers.setBufferIn(0); - frameBuffers.setBufferOut(0); - } - - jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); - jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers) } // refocused affects how IO testing occurs. @@ -940,11 +962,26 @@ } } + function prodUserToTweakASIOSettings($btn) { + setTimeout(function() { + context.JK.prodBubble($btn, 'tweak-asio-settings', {}, {positions:['top']}); + }, 300) + } + function onGearTestFail(e, data) { renderScoringStopped(); gearUtils.postDiagnostic(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, true); if(data.reason == "latency") { + + console.log("selectedDeviceInfo", selectedDeviceInfo) + if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1) { + prodUserToTweakASIOSettings($asioInputControlBtn) + } + else if(selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) { + prodUserToTweakASIOSettings($asioOutputControlBtn) + } + storeLastFailureForAnalytics(context.JK.detectOS(), context.JK.GA.AudioTestFailReasons.latency, data.latencyScore); } else if(data.reason = "io") { @@ -973,11 +1010,13 @@ var specificResolutions = []; - if(selectedDeviceInfo.input.behavior.type.indexOf('Win32_asio') > -1 || selectedDeviceInfo.output.behavior.type.indexOf('Win32_asio') > -1) { + if(selectedDeviceInfo.input.info.type.indexOf('Win32_asio') > -1 || selectedDeviceInfo.output.info.type.indexOf('Win32_asio') > -1) { + // specificResolutions.push("Read over this article") specificResolutions.push("Select the ASIO SETTINGS... button and try different settings."); } - if(selectedDeviceInfo.input.behavior.type == 'Win32_wdm' || selectedDeviceInfo.output.behavior.type == 'Win32_wdm') { + if(selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm') { + // specificResolutions.push("Read over this article") specificResolutions.push("Change the Frame, Buffer In, or Buffer Out settings."); } @@ -1063,7 +1102,7 @@ } function onFocus() { - if(validDevice && !loopbackShowing && !gearTest.isScoring() && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) { + if(validDevice && !loopbackShowing && !adjustGearSettingsShowing && !gearTest.isScoring() && getSelectedInputs().length > 0 && getSelectedOutputs().length == 2 ) { scheduleRescanSystem(function() { attemptScore(true); }, 3000, false) } } @@ -1160,6 +1199,8 @@ $inputChannels = $step.find('.input-ports'); $outputChannels = $step.find('.output-ports'); $knobs = $step.find('.frame-and-buffers'); + $adjustSettingsLink = $knobs.find('.adjust-gear-settings') + $adjustGearForIoFail = $step.find(".adjust-gear-for-io-fail") $scoreReport = $step.find('.results'); $asioInputControlBtn = $step.find('.asio-settings-input-btn'); $asioOutputControlBtn = $step.find('.asio-settings-output-btn'); @@ -1175,6 +1216,7 @@ .on(frameBuffers.FRAMESIZE_CHANGED, onFramesizeChanged) .on(frameBuffers.BUFFER_IN_CHANGED, onBufferInChanged) .on(frameBuffers.BUFFER_OUT_CHANGED, onBufferOutChanged) + .on(frameBuffers.ADJUST_GEAR_LINK_CLICKED, onAdjustGearRequested) gearTest.initialize($scoreReport, true) $(gearTest) @@ -1182,6 +1224,7 @@ .on(gearTest.GEAR_TEST_DONE, onGearTestDone) .on(gearTest.GEAR_TEST_FAIL, onGearTestFail) .on(gearTest.GEAR_TEST_INVALIDATED_ASYNC, onGearTestInvalidated) + $adjustGearForIoFail.click(onAdjustGearRequested); } this.getLastAudioTestFailAnalytics = getLastAudioTestFailAnalytics; diff --git a/web/app/assets/javascripts/wizard/gear_test.js b/web/app/assets/javascripts/wizard/gear_test.js index 7b56534db..fc397c2f9 100644 --- a/web/app/assets/javascripts/wizard/gear_test.js +++ b/web/app/assets/javascripts/wizard/gear_test.js @@ -33,6 +33,8 @@ var $resultsText = null; var $unknownText = null; var $loopbackCompleted = null; + var $adjustGearSpeedCompleted = null; + var $adjustGearForIoFail = null; var $ioScoreSection = null; var $latencyScoreSection = null; @@ -78,6 +80,10 @@ medianIOClass = 'acceptable'; } + // uncomment one to force a particular type of I/O failure + // medianIOClass = "bad"; + // stdIOClass = "bad" + // take worst between median or std var ioClassToNumber = {bad: 2, acceptable: 1, good: 0} var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass; @@ -240,6 +246,10 @@ latencyClass = 'unknown'; } + // uncomment these two lines to fail test due to latency + // latencyClass = "bad"; + // validLatency = false; + validLatencyScore = validLatency; if(refocused) { @@ -300,7 +310,7 @@ logger.debug("gear_test: onInvalidAudioDevice") asynchronousInvalidDevice = true; $self.triggerHandler(GEAR_TEST_INVALIDATED_ASYNC); - context.JK.Banner.showAlert('Invalid Audio Device', 'It appears this audio device is not currently connected. Attach the device to your computer and restart the application, or select a different device.') + context.JK.Banner.showAlert('Invalid Audio Device', 'It appears this audio device is not currently connected. Attach the device to your computer and restart the application, or select a different device.

If you think your gear is connected and working, this support article can help.') } @@ -308,6 +318,10 @@ $loopbackCompleted.show(); } + function showGearAdjustmentDone() { + $adjustGearSpeedCompleted.show(); + } + function resetScoreReport() { $ioHeader.hide(); $latencyHeader.hide(); @@ -322,6 +336,7 @@ $resultsText.removeAttr('scored'); $unknownText.hide(); $loopbackCompleted.hide(); + $adjustGearSpeedCompleted.hide(); $ioScoreSection.removeClass('good acceptable bad unknown starting skip'); $latencyScoreSection.removeClass('good acceptable bad unknown starting') } @@ -398,6 +413,8 @@ $resultsText = $scoreReport.find('.results-text'); $unknownText = $scoreReport.find('.unknown-text'); $loopbackCompleted = $scoreReport.find('.loopback-completed') + $adjustGearSpeedCompleted = $scoreReport.find('.adjust-gear-speed-completed'); + $adjustGearForIoFail = $scoreReport.find(".adjust-gear-for-io-fail") $latencyScoreSection = $scoreReport.find('.latency-score-section'); function onGearTestStart(e, data) { @@ -514,6 +531,7 @@ this.attemptScore = attemptScore; this.resetScoreReport = resetScoreReport; this.showLoopbackDone = showLoopbackDone; + this.showGearAdjustmentDone = showGearAdjustmentDone; this.invalidateScore = invalidateScore; this.isValidLatencyScore = isValidLatencyScore; this.isValidIOScore = isValidIOScore; diff --git a/web/app/assets/javascripts/wizard/gear_utils.js b/web/app/assets/javascripts/wizard/gear_utils.js index ba5dd70e0..c9d67f533 100644 --- a/web/app/assets/javascripts/wizard/gear_utils.js +++ b/web/app/assets/javascripts/wizard/gear_utils.js @@ -145,6 +145,54 @@ return loadedDevices; } + gearUtils.updateDefaultBuffers = function(selectedDeviceInfo, frameBuffers) { + function hasWDMAssociated() { + return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm') + } + + function hasASIOAssociated() { + return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio') + } + + // handle specific framesize settings + if(hasWDMAssociated() || hasASIOAssociated()) { + var framesize = frameBuffers.selectedFramesize(); + + if(framesize == 2.5) { + // if there is a WDM device, start off at 1/1 due to empirically observed issues with 0/0 + if(hasWDMAssociated()) { + logger.debug("setting default buffers to 1/1"); + frameBuffers.setBufferIn('1'); + frameBuffers.setBufferOut('1'); + } + else { + // otherwise, it's ASIO, so go with 0/0 + logger.debug("setting default buffers to 0/0"); + frameBuffers.setBufferIn('0'); + frameBuffers.setBufferOut('0'); + } + } + else if(framesize == 5) { + logger.debug("setting default buffers to 3/2"); + frameBuffers.setBufferIn('3'); + frameBuffers.setBufferOut('2'); + } + else { + logger.debug("setting default buffers to 6/5"); + frameBuffers.setBufferIn('6'); + frameBuffers.setBufferOut('5'); + } + } + else { + logger.debug("setting default buffers to 0/0"); + frameBuffers.setBufferIn(0); + frameBuffers.setBufferOut(0); + } + + context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); + context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + } + gearUtils.ftueSummary = function(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) { return { os: operatingSystem, diff --git a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js index d22732519..ccba2ae88 100644 --- a/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js +++ b/web/app/assets/javascripts/wizard/loopback/step_loopback_test.js @@ -58,35 +58,7 @@ } function updateDefaultBuffers() { - - // handle specific framesize settings - if(selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')) { - var framesize = frameBuffers.selectedFramesize(); - - if(framesize == 2.5) { - logger.debug("setting default buffers to 1/1"); - frameBuffers.setBufferIn('1'); - frameBuffers.setBufferOut('1'); - } - else if(framesize == 5) { - logger.debug("setting default buffers to 3/2"); - frameBuffers.setBufferIn('3'); - frameBuffers.setBufferOut('2'); - } - else { - logger.debug("setting default buffers to 6/5"); - frameBuffers.setBufferIn('6'); - frameBuffers.setBufferOut('5'); - } - } - else { - logger.debug("setting default buffers to 0/0"); - frameBuffers.setBufferIn(0); - frameBuffers.setBufferOut(0); - } - - jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn()); - jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut()); + gearUtils.updateDefaultBuffers(selectedDeviceInfo, frameBuffers) } function onFramesizeChanged() { diff --git a/web/app/assets/javascripts/wizard/wizard.js b/web/app/assets/javascripts/wizard/wizard.js index e3a6eaed3..6d8600e3e 100644 --- a/web/app/assets/javascripts/wizard/wizard.js +++ b/web/app/assets/javascripts/wizard/wizard.js @@ -217,6 +217,10 @@ return $currentWizardStep; } + function getDialog() { + return $dialog; + } + function initialize(_$dialog, _$wizardSteps, _STEPS, _options) { $dialog = _$dialog; dialogName = $dialog.attr('layout-id'); @@ -239,6 +243,7 @@ this.onCloseDialog = onCloseDialog; this.onBeforeShow = onBeforeShow; this.onAfterHide = onAfterHide; + this.getDialog = getDialog; this.initialize = initialize; } diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss new file mode 100644 index 000000000..7cb65eac0 --- /dev/null +++ b/web/app/assets/stylesheets/client/checkout.css.scss @@ -0,0 +1,252 @@ +.checkout-navigation { + padding: 20px 0px; + .nav-signin, .nav-payment-info, .nav-place-order { + width: 30%; + float: left; + } + + .nav-signin { + margin-left: 5%; + } + + .nav-place-order { + margin-right: 5%; + } + + .nav-text { + font-size: 17px; + float: left; + } + + .nav-text.selected { + font-weight: bold; + } + + .nav-arrow { + float: left; + margin-left: 30px; + } +} + +.checkout-signin, .checkout-payment-info, .checkout-place-order { + padding: 30px; + + .signin-form { + padding: 10px; + + strong { + font-weight: bold; + } + + label { + display: inline; + } + + .signin-password { + margin-left: 33px; + } + + .login-error { + background-color: #330000; + border: 1px solid #990000; + padding:4px; + + div.actions { + margin-top:10px; + } + } + + .login-error-msg { + display:none; + margin-top:10px; + text-align:center; + color:#F00; + font-size:11px; + } + + .login-error .login-error-msg { + display:block; + } + } + + form.payment-info { + width: 100%; + + input[type="text"] { + width: 90%; + } + + .billing-address { + float: left; + width: 50%; + + h2.billing-caption { + margin: 20px 5px; + font-size: 16px; + } + + .billing-label { + padding-top: 8px; + width: 30%; + float: left; + text-align: right; + margin-right: 5px; + } + + .billing-value { + width: 65%; + text-align: left; + float: left; + } + } + + .payment-method { + float: left; + width: 50%; + + h2.payment-method-caption { + margin: 20px 5px; + font-size: 16px; + } + + .card-label { + padding-top: 8px; + width: 35%; + float: left; + text-align: right; + margin-right: 5px; + } + + .card-value { + width: 60%; + text-align: left; + float: left; + } + + .save-card-checkbox { + float:left; + display:block; + margin-right:5px; + } + } + + .shipping-address { + float: left; + width: 50%; + + h2.shipping-address-label { + margin: 20px 5px; + font-size: 16px; + } + + .shipping-as-billing { + float:left; + display:block; + margin-right:5px; + } + + .divBillingHelper { + padding-top: 2px; + } + + .shipping-label { + padding-top: 8px; + width: 30%; + float: left; + text-align: right; + margin-right: 5px; + } + + .shipping-value { + width: 65%; + text-align: left; + float: left; + } + } + } +} + +.order-panel { + padding: 30px; + + .order-header { + h2 { + font-size: 16px; + } + } + + .order-content { + margin-top: 20px; + } + + .order-left-page { + float: left; + width: 60%; + + .payment-info-page { + padding: 5px; + + .info-caption-link { + .caption-text { + float: left; + } + .caption-link { + float: left; + margin-left: 5px; + } + } + + .address-info { + width: 50%; + float: left; + } + + .payment-method-info { + width: 50%; + float: left; + } + } + .order-items-page { + padding: 5px; + + .cart-item-caption { + width: 50%; + text-align: left; + float: left; + } + + .cart-item-caption#header { + font-weight: bold; + } + + .cart-item-price { + width: 25%; + text-align: right; + float: left; + } + + .cart-item-quantity { + width: 25%; + text-align: right; + float: left; + } + + .cart-items { + margin-top: 10px; + } + + .cart-item { + margin-top: 10px; + } + } + } + .order-right-page { + float: right; + width: 35%; + text-align: center; + + .order-total { + color: #ed3618; + } + } +} \ 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 446a19fbd..e18189462 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -49,6 +49,9 @@ *= require ./terms *= require ./createSession *= require ./feed + *= require ./jamtrack + *= require ./shoppingCart + *= require ./checkout *= require ./genreSelector *= require ./sessionList *= require ./searchResults diff --git a/web/app/assets/stylesheets/client/header.css.scss b/web/app/assets/stylesheets/client/header.css.scss index 2812d087f..8bdedd4ea 100644 --- a/web/app/assets/stylesheets/client/header.css.scss +++ b/web/app/assets/stylesheets/client/header.css.scss @@ -7,6 +7,15 @@ z-index:5; } +.header-shopping-cart { + float: right; + margin-right: 20px; + + img { + width: 60px; + } +} + div[layout="header"] h1 { cursor:pointer; width: 247px; diff --git a/web/app/assets/stylesheets/client/home.css.scss b/web/app/assets/stylesheets/client/home.css.scss index 953d7f87e..d9b7d9d45 100644 --- a/web/app/assets/stylesheets/client/home.css.scss +++ b/web/app/assets/stylesheets/client/home.css.scss @@ -40,6 +40,9 @@ .homecard.musicians { background-image: url(/assets/content/bkg_home_musicians.jpg); } +.homecard.jamtrack { + background-image: url(/assets/content/bkg_home_jamtracks.jpg); +} .homebox-info { position: absolute; @@ -93,6 +96,9 @@ .homecard.musicians.hover { background-image: url(/assets/content/bkg_home_musicians_x.jpg); } +.homecard.jamtrack.hover { + background-image: url(/assets/content/bkg_home_jamtracks_x.jpg); +} diff --git a/web/app/assets/stylesheets/client/hoverBubble.css.scss b/web/app/assets/stylesheets/client/hoverBubble.css.scss index b8d6ec315..fda3967c9 100644 --- a/web/app/assets/stylesheets/client/hoverBubble.css.scss +++ b/web/app/assets/stylesheets/client/hoverBubble.css.scss @@ -10,7 +10,7 @@ &.musician-bubble { - width:425px; + width:438px; } h2 { diff --git a/web/app/assets/stylesheets/client/jamtrack.css.scss b/web/app/assets/stylesheets/client/jamtrack.css.scss new file mode 100644 index 000000000..7d69b1b6f --- /dev/null +++ b/web/app/assets/stylesheets/client/jamtrack.css.scss @@ -0,0 +1,116 @@ +#jamtrackScreen { + a.jamtrack_help { + color: #fff; + text-decoration: none; + margin: 4px 0px 0px 60px; + + &:hover { + text-decoration: underline; + } + } + + .jamtrack-content { + text-align: center; + } + + .no-jamtracks-msg { + margin-top: 10px; + } + + .jamtrack-record { + border-bottom: 1px solid black; + text-align: left; + } + + .jamtrack-detail { + float: left; + width: 50%; + padding: 10px 0px; + + .detail-label { + width: 40%; + float: left; + margin-top: 5px; + } + + .detail-value { + width: 50%; + float: left; + margin-top: 5px; + } + + .copyright-value { + width: 40%; + float: left; + margin-top: 5px; + } + + .detail-arrow { + float: left; + margin-left: 10px; + } + + .jamtrack-description { + display: none; + } + + .jamtrack-detail-btn { + cursor: pointer; + margin-top: 5px; + margin-right: 5px; + padding-top: 5px; + } + } + + .jamtrack-tracks { + float: left; + width: 25%; + padding: 10px 0px; + + .tracks-caption { + margin-top: 5px; + margin-bottom: 10px; + } + + .track-instrument { + margin-top: 5px; + } + + .instrument-image { + float: left; + } + + .instrument-desc { + margin-top: 6px; + float: left; + margin-left: 10px; + } + } + + .jamtrack-action { + float: left; + width: 25%; + padding: 10px 0px; + text-align: center; + + .play-button { + margin-top: 5px; + } + + .jamtrack-price { + margin-top: 5px; + font-size: 20px; + } + + .jamtrack-add-cart, .jamtrack-add-cart-disabled { + margin: 8px 0px; + } + + .jamtrack-license { + margin-left: 20%; + margin-right: 20%; + font-size: 13px; + width: 60%; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/shoppingCart.css.scss b/web/app/assets/stylesheets/client/shoppingCart.css.scss new file mode 100644 index 000000000..63f333543 --- /dev/null +++ b/web/app/assets/stylesheets/client/shoppingCart.css.scss @@ -0,0 +1,78 @@ +#shoppingCartScreen { + + .content-body { + padding: 50px 20px 20px 20px; + + .checkout-image { + width: 10%; + float: left; + } + + .checkout-desc { + width: 90%; + float: left; + + div { + margin-bottom: 7px; + } + + div#note { + font-style: italic; + font-size: 13px; + } + } + + .cart-item-caption { + width: 50%; + text-align: left; + float: left; + } + + .cart-item-caption#header { + font-weight: bold; + } + + .cart-item-price { + width: 15%; + text-align: right; + float: left; + } + + .cart-item-quantity { + width: 15%; + text-align: right; + float: left; + } + + .cart-item-actions { + width: 20%; + text-align: center; + float: left; + } + + .cart-items { + width: 100%; + height: 300px; + overflow: auto; + margin-top: 30px; + } + + .cart-item { + margin-top: 10px; + } + + .shopping-sub-total { + width: 65%; + float: left; + text-align: right; + margin-bottom: 20px; + font-weight: bold; + font-size: 17px; + } + + .no-cart-items { + margin-top: 30px; + text-align: center; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss index b3f205d3f..abc010194 100644 --- a/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss +++ b/web/app/assets/stylesheets/client/wizard/framebuffers.css.scss @@ -14,6 +14,10 @@ width:45%; } + .adjust-gear-settings { + display:none; + margin-left:4px; + } .buffers { float:left; diff --git a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss index 4710ba12c..a542212a7 100644 --- a/web/app/assets/stylesheets/client/wizard/gearResults.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearResults.css.scss @@ -20,6 +20,11 @@ display:inline; } } + &[data-type=adjust-gear-speed] { + span.conditional[data-type=adjust-gear-speed] { + display:inline; + } + } .io, .latency { display: none; } @@ -47,7 +52,7 @@ padding: 8px; } - .loopback-completed { + .loopback-completed, .adjust-gear-speed-completed { display:none; } @@ -115,6 +120,14 @@ display: none } + span.io-failure { + display:none; + } + + %span.no-io-failure { + display:inline; + } + &[latency-score="unknown"] { display: none; } @@ -159,6 +172,16 @@ } } + &[io-rate-score="bad"], &[io-var-score="bad"] { + span.io-failure { + display:inline; + } + + span.no-io-failure { + display:none; + } + } + &[latency-score="unknown"] { li.success { display: none; diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 3a7572ec4..29d498f69 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -274,7 +274,7 @@ } .watch-video { - margin-top: 26px; + margin-top: 12px; } .help-content { diff --git a/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss b/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss new file mode 100644 index 000000000..2bdcb6d7f --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/adjustGearSpeedDialog.css.scss @@ -0,0 +1,160 @@ +@import "client/common.css.scss"; +@charset "UTF-8"; +#adjust-gear-speed-dialog { + min-height: 420px; + max-height: 420px; + width:800px; + + h2 { + color: #FFFFFF; + font-size: 15px; + font-weight: normal; + margin-bottom: 6px; + white-space:nowrap; + + } + + h2.settings { + display:inline; + position:absolute; + left:0; + margin-left:0; // override global .settings from sessions.css + } + + .ftue-box { + background-color: #222222; + font-size: 13px; + padding: 8px; + } + + .dialog-inner { + line-height: 1.3em; + width:800px; + padding:25px; + font-size:15px; + color:#aaa; + @include border_box_sizing; + + } + + .dialog-column { + + @include border_box_sizing; + + &:nth-of-type(1) { + width:75%; + margin-top:38px; + } + + &:nth-of-type(2) { + width:25%; + float:right; + } + } + + .speed-options { + display:inline-block; + width:60%; + margin:auto; + margin-left:25%; + } + + .speed-option { + @include border_box_sizing; + float:left; + width:33%; + text-align:center; + + .iradio_minimal { + margin:auto; + display:inline-block; + } + + label { + display:inline-block; + margin-left: 10px; + position: relative; + top: -3px; + } + } + + .speed-text { + margin:10px 0; + } + + .run-test-btn { + margin-top:5px; + margin-left:25px; + left:40%; + position:relative; + } + + .frame-and-buffers { + display:inline-block; + margin-left:30%; + + .framesize { + width:auto; + display:inline-block; + } + + .buffers { + width:auto; + display:inline-block; + margin-left:20px; + } + h2 { + display:inline-block; + line-height:18px; + vertical-align: top; + } + + .easydropdown-wrapper { + display:inline-block; + top:-3px; + margin-left:8px; + } + + + .select-frame-size { + margin-left:-2px; + } + } + + .help-text { + margin-bottom:20px; + } + + .basic { + margin:0 5px 20px 0; + } + .advanced { + display:none; + border-width:1px 0 0 0; + border-style:solid; + border-color:white; + padding-top:20px; + margin:0 5px 0 0; + .help-text { + text-align:left; + } + } + + .test-results-header { + position:static; + } + + + .ftue-box.results { + margin-top:20px; + height: 268px !important; + padding:0; + @include border_box_sizing; + } + + h2.results-text-header { + margin-top:5px; + } + + +} diff --git a/web/app/assets/stylesheets/dialogs/jamtrackAvailability.css.scss b/web/app/assets/stylesheets/dialogs/jamtrackAvailability.css.scss new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/controllers/api_jamtracks_controller.rb b/web/app/controllers/api_jamtracks_controller.rb new file mode 100644 index 000000000..8bbc2c179 --- /dev/null +++ b/web/app/controllers/api_jamtracks_controller.rb @@ -0,0 +1,15 @@ +class ApiJamtracksController < ApiController + + # have to be signed in currently to see this screen + before_filter :api_signed_in_user + + respond_to :json + + def index + data = JamTrack.index current_user, params + + @jamtracks = data[0] + @next = data[1] + end + +end diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb new file mode 100644 index 000000000..4c841320d --- /dev/null +++ b/web/app/controllers/api_recurly_controller.rb @@ -0,0 +1,155 @@ +class ApiRecurlyController < ApiController + + before_filter :api_signed_in_user + respond_to :json + + # create Recurly account + def create_account + logger.debug(params[:billing_info]) + if current_user.recurly_code.nil? + @account = Recurly::Account.create( + account_code: current_user.id, + email: current_user.email, + first_name: current_user.first_name, + last_name: current_user.last_name, + address: { + city: current_user.city, + state: current_user.state, + country: current_user.country + } + ) + else + @account = Recurly::Account.find(current_user.recurly_code) + end + + if @account.errors.any? + response.status = 404 + else + current_user.recurly_code = @account.account_code + current_user.save + + @account.billing_info = params[:billing_info] + @account.billing_info.save + + logger.debug @account + end + respond_with @account + + rescue Recurly::Error, NoMethodError => e + render :json => { :message => e.inspect }, :status => 404 + end + + # get Recurly account + def get_account + @account = Recurly::Account.find(current_user.reculry_code) + respond_with @account + rescue Recurly::Error, NoMethodError => e + render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + end + + # update Recurly account + def update_account + if current_user.recurly_code.nil? + @account = Recurly::Account.create( + account_code: current_user.id, + email: current_user.email, + first_name: current_user.first_name, + last_name: current_user.last_name, + address: { + city: current_user.city, + state: current_user.state, + country: current_user.country + } + ) + else + @account = Recurly::Account.get(current_user.recurly_code) + end + + @account.first_name = current_user.first_name + @account.last_name = current_user.last_name + @account.email = current_user.email + @account.update + + if @account.errors.any? + response.status = 404 + else + current_user.recurly_code = @account.account_code + current_user.save + end + respond_with @account + + rescue Recurly::Error, NoMethodError => e + render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + end + + # get subscription + def get_subscription + @account = Recurly::Acount.find(current_user.reculry_code) + respond_with @account.subscriptions.last + rescue Recurly::Error, NoMethodError => e + render :json => { message: ValidationMessages::RECURLY_GET_ACCOUNT_ERROR}, :status => 404 + end + + # create subscription + def create_subscription + end + + # get Billing Information + def billing_info + if current_user.recurly_code.nil? + render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + else + @account = Recurly::Account.find(current_user.recurly_code) + logger.debug @account + respond_with @account.billing_info + end + rescue Recurly::Error, NoMethodError => e + render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + end + + # update Billing Information + def update_billing_info + if current_user.recurly_code.nil? + render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + else + if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank? + render :json => { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return + end + @account = Recurly::Acount.find(current_user.reculry_code) + @account.billing_info = params + @account.billing_info.save + + if @account.erros.any? + response.status = :unprocessable_entity + end + + respond_with @account + end + rescue Recurly::Error, NoMethodError => e + render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + end + + def place_order + if current_user.recurly_code.nil? + render :json => { message: ValidationMessages::RECURLY_ACCOUNT_ERROR }, :status => 404 and return + else + if params[:first_name].blank? or params[:last_name].blank? or params[:number].blank? or params[:year].blank? or params[:month].blank? or params[:verification_value].blank? + render :json => { message: ValidationMessages::RECURLY_PARAMETER_ERROR }, :status => 404 and return + end + @account = Recurly::Account.find(current_user.recurly_code) + @account.billing_info = params + @account.billing_info.save + + # create subscription. + + if @account.erros.any? + response.status = :unprocessable_entity + end + + respond_with @account + end + rescue Recurly::Error, NoMethodError => e + render :json => { message: ValidationMessages::RECURLY_ERROR}, :status => 404 + end + +end \ No newline at end of file diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb new file mode 100644 index 000000000..d1109e821 --- /dev/null +++ b/web/app/controllers/api_shopping_carts_controller.rb @@ -0,0 +1,53 @@ +class ApiShoppingCartsController < ApiController + + before_filter :api_signed_in_user + + respond_to :json + + def index + @carts = current_user.shopping_carts + end + + def add_jamtrack + jam_track = JamTrack.find_by_id(params[:id]) + + # verify JamTrack exists + if jam_track.nil? + raise StateError, "Invalid JamTrack." + end + + @cart = ShoppingCart.create current_user, jam_track + + if @cart.errors.any? + response.status = :unprocessable_entity + respond_with @cart + else + respond_with @cart, responder: ApiResponder, :statue => 201 + end + end + + def update_cart + @cart = ShoppingCart.find_by_id params[:id] + + #verify Cart exists + raise StateError, "Invalid Cart." if @cart.nil? + + @cart.quantity = params[:quantity] + + if @cart.errors.any? + response.statue = :unprocessable_entity + respond_with @cart + else + respond_with @cart, responder: ApiResponder, :status => 200 + end + end + + def remove_cart + @cart = current_user.shopping_carts.find_by_id(params[:id]) + raise StateError, "Invalid Cart." if @cart.nil? + + @cart.destroy + respond_with responder: ApiResponder, :status => 204 + end + +end diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index d9b473307..e475a4a70 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -1,7 +1,7 @@ require 'sanitize' class ApiUsersController < ApiController - before_filter :api_signed_in_user, :except => [:create, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play] + before_filter :api_signed_in_user, :except => [:create, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump] before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :liking_create, :liking_destroy, # likes :following_create, :following_show, :following_destroy, # followings diff --git a/web/app/helpers/music_session_helper.rb b/web/app/helpers/music_session_helper.rb index b550e70f3..03001dc0d 100644 --- a/web/app/helpers/music_session_helper.rb +++ b/web/app/helpers/music_session_helper.rb @@ -25,20 +25,27 @@ module MusicSessionHelper else unique_users = music_session.unique_users if sharer && unique_users.exists?(sharer) - "LIVE SESSION: #{sharer.name}#{additional_member_count(unique_users)}" + "LIVE SESSION: #{sharer.name}#{additional_member_count(unique_users, sharer)}" else - "LIVE SESSION: #{music_session.creator.name}#{additional_member_count(unique_users)}" + "LIVE SESSION: #{music_session.creator.name}#{additional_member_count(unique_users, music_session.creator)}" end end end - def additional_member_count(unique_users) + def additional_member_count(unique_users, target_user) length = unique_users.length if length < 2 "" else - " & #{length} OTHERS" + other_length = length - 1 + if other_length == 1 + other_user_in_array = unique_users - [target_user] + other_user = other_user_in_array[0] + " & #{other_user.name}" + else + " & #{length - 1} OTHERS" + end end end diff --git a/web/app/helpers/recording_helper.rb b/web/app/helpers/recording_helper.rb index cbad1805c..3d8a6eeec 100644 --- a/web/app/helpers/recording_helper.rb +++ b/web/app/helpers/recording_helper.rb @@ -23,22 +23,29 @@ module RecordingHelper if claimed_recording.recording.band "RECORDING: #{claimed_recording.recording.band.name}" else - unique_users = claimed_recording.recording.users - if sharer && unique_users.exists?(sharer) - "RECORDING: #{sharer.name}#{additional_member_count(unique_users)}" + unique_users = claimed_recording.recording.users.uniq + if sharer && unique_users.include?(sharer) + "RECORDING: #{sharer.name}#{additional_member_count(unique_users, sharer)}" else - "RECORDING: #{claimed_recording.user.name}#{additional_member_count(unique_users)}" + "RECORDING: #{claimed_recording.user.name}#{additional_member_count(unique_users, claimed_recording.user)}" end end end - def additional_member_count(unique_users) + def additional_member_count(unique_users, target_user) length = unique_users.length if length < 2 "" else - " & #{length} OTHERS" + other_length = length - 1 + if other_length == 1 + other_user_in_array = unique_users - [target_user] + other_user = other_user_in_array[0] + " & #{other_user.name}" + else + " & #{length - 1} OTHERS" + end end end diff --git a/web/app/views/api_jamtracks/index.rabl b/web/app/views/api_jamtracks/index.rabl new file mode 100644 index 000000000..25c3f9da5 --- /dev/null +++ b/web/app/views/api_jamtracks/index.rabl @@ -0,0 +1,7 @@ +node :next do |page| + @next +end + +node :jamtracks do |page| + partial "api_jamtracks/show", object: @jamtracks +end \ No newline at end of file diff --git a/web/app/views/api_jamtracks/show.rabl b/web/app/views/api_jamtracks/show.rabl new file mode 100644 index 000000000..dde7bb3dd --- /dev/null +++ b/web/app/views/api_jamtracks/show.rabl @@ -0,0 +1,19 @@ +object @jamtrack + +attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price + +node :genres do |item| + [item.genre.description] # XXX: need to return single genre; not array +end + +node :added_cart do |item| + current_user.shopping_carts.map(&:cart_id).include? item.id +end + +child(:jam_track_tracks => :tracks) { + attributes :id, :part, :instrument +} + +child(:licensor => :licensor) { + attributes :id, :name +} \ No newline at end of file diff --git a/web/app/views/api_shopping_carts/add_jamtrack.rabl b/web/app/views/api_shopping_carts/add_jamtrack.rabl new file mode 100644 index 000000000..c61e7b52b --- /dev/null +++ b/web/app/views/api_shopping_carts/add_jamtrack.rabl @@ -0,0 +1 @@ +extends "api_shopping_carts/show" \ No newline at end of file diff --git a/web/app/views/api_shopping_carts/index.rabl b/web/app/views/api_shopping_carts/index.rabl new file mode 100644 index 000000000..bc63bab24 --- /dev/null +++ b/web/app/views/api_shopping_carts/index.rabl @@ -0,0 +1,3 @@ +object @carts + +extends "api_shopping_carts/show" \ No newline at end of file diff --git a/web/app/views/api_shopping_carts/show.rabl b/web/app/views/api_shopping_carts/show.rabl new file mode 100644 index 000000000..e371ba4c9 --- /dev/null +++ b/web/app/views/api_shopping_carts/show.rabl @@ -0,0 +1,3 @@ +object @cart + +attributes :id, :quantity, :cart_type, :product_info \ 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 59d72f304..aa1030f48 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -21,6 +21,10 @@ if @user == current_user user.mods_json end + node :has_recurly_account do + @user.recurly_code == @user.id + end + elsif current_user node :is_friend do |uu| current_user.friends?(@user) @@ -85,4 +89,4 @@ end node :last_jam_audio_latency do |user| user.last_jam_audio_latency.round if user.last_jam_audio_latency -end \ No newline at end of file +end diff --git a/web/app/views/clients/_header.html.erb b/web/app/views/clients/_header.html.erb index 5195512ff..bad8f5229 100644 --- a/web/app/views/clients/_header.html.erb +++ b/web/app/views/clients/_header.html.erb @@ -11,6 +11,11 @@ <%= render "users/user_dropdown" %> + + + + + - + +