diff --git a/admin/Gemfile b/admin/Gemfile index 7502a288e..6f5c0b21e 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -74,6 +74,7 @@ gem 'sanitize' gem 'slim' gem 'influxdb', '0.1.8' gem 'influxdb-rails', '0.1.10' +gem 'recurly' group :libv8 do gem 'libv8', "~> 3.11.8" diff --git a/admin/app/admin/jam_track_right.rb b/admin/app/admin/jam_track_right.rb new file mode 100644 index 000000000..5e7c3687b --- /dev/null +++ b/admin/app/admin/jam_track_right.rb @@ -0,0 +1,87 @@ +require 'jam_ruby/recurly_client' +ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do + + menu :label => 'Purchased JamTracks', :parent => 'JamTracks' + + config.sort_order = 'updated_at DESC' + config.batch_actions = false + + #form :partial => 'form' + + index do + default_actions + + column "Order" do |right| + link_to("Place", order_admin_jam_track_right_path(right)) + " | " + + link_to("Refund", refund_admin_jam_track_right_path(right)) + end + + column "Last Name" do |right| + right.user.last_name + end + column "First Name" do |right| + right.user.first_name + end + column "Jam Track" do |right| + link_to(right.jam_track.name, admin_jam_track_right_path(right.jam_track)) + # right.jam_track + end + column "Plan Code" do |right| + + right.jam_track.plan_code + end + + + end + + form do |f| + f.inputs 'New Jam Track Right' do + f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false + f.input :user, :required=>true, collection: User.all, include_blank: false + end + f.actions + end + + member_action :order, :method => :get do + right = JamTrackRight.where("id=?",params[:id]).first + user = right.user + jam_track = right.jam_track + client = RecurlyClient.new + billing_info = { + first_name: user.first_name, + last_name: user.last_name, + address1: 'Test Address 1', + address2: 'Test Address 2', + city: user.city, + state: user.state, + country: user.country, + zip: '12345', + number: '4111-1111-1111-1111', + month: '08', + year: '2017', + verification_value: '111' + } + + begin + client.find_or_create_account(user, billing_info) + client.place_order(user, jam_track) + rescue RecurlyClientError=>x + redirect_to admin_jam_track_rights_path, notice: "Could not order #{jam_track} for #{user.to_s}: #{x.errors.inspect}" + else + redirect_to admin_jam_track_rights_path, notice: "Placed order of #{jam_track} for #{user.to_s}." + end + end + + member_action :refund, :method => :get do + right = JamTrackRight.where("id=?",params[:id]).first + client = RecurlyClient.new + + begin + client.refund_user_subscription(right.user, right.jam_track) + rescue RecurlyClientError=>x + redirect_to admin_jam_track_rights_path, notice: "Could not issue refund on #{right.jam_track} for #{right.user.to_s}: #{x.errors.inspect}" + else + redirect_to admin_jam_track_rights_path, notice: "Issued full refund on #{right.jam_track} for #{right.user.to_s}" + end + end +end \ No newline at end of file diff --git a/db/manifest b/db/manifest index 2cafa6501..ca6c614fb 100755 --- a/db/manifest +++ b/db/manifest @@ -253,6 +253,7 @@ recorded_backing_tracks.sql recorded_backing_tracks_add_filename.sql user_syncs_include_backing_tracks.sql remove_bpm_from_jamtracks.sql +widen_user_authorization_token.sql jam_track_version.sql recorded_jam_track_tracks.sql -jam_track_jmep_data.sql \ No newline at end of file +jam_track_jmep_data.sql diff --git a/db/up/recorded_jam_track_tracks.sql b/db/up/recorded_jam_track_tracks.sql index 504c5e6ad..08a485643 100644 --- a/db/up/recorded_jam_track_tracks.sql +++ b/db/up/recorded_jam_track_tracks.sql @@ -12,4 +12,4 @@ CREATE TABLE recorded_jam_track_tracks ( updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -ALTER TABLE recorded_jam_track_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); \ No newline at end of file +ALTER TABLE recorded_jam_track_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); diff --git a/db/up/widen_user_authorization_token.sql b/db/up/widen_user_authorization_token.sql new file mode 100644 index 000000000..070f67b83 --- /dev/null +++ b/db/up/widen_user_authorization_token.sql @@ -0,0 +1 @@ +alter table user_authorizations ALTER COLUMN token TYPE character varying(2000); \ No newline at end of file diff --git a/ruby/Gemfile b/ruby/Gemfile index 7a31165d1..8ef539d9a 100644 --- a/ruby/Gemfile +++ b/ruby/Gemfile @@ -49,6 +49,7 @@ gem 'iso-639' gem 'rubyzip' gem 'sanitize' gem 'influxdb', '0.1.8' +gem 'recurly' group :test do gem 'simplecov', '~> 0.7.1' diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index a2dc1f4e7..67f81933b 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -62,6 +62,7 @@ require "jam_ruby/resque/scheduled/stats_maker" require "jam_ruby/resque/google_analytics_event" require "jam_ruby/resque/batch_email_job" require "jam_ruby/mq_router" +require "jam_ruby/recurly_client" require "jam_ruby/base_manager" require "jam_ruby/connection_manager" require "jam_ruby/version" diff --git a/ruby/lib/jam_ruby/init.rb b/ruby/lib/jam_ruby/init.rb index adf32cf04..d74b80b5e 100644 --- a/ruby/lib/jam_ruby/init.rb +++ b/ruby/lib/jam_ruby/init.rb @@ -1,3 +1,18 @@ # initialize actionmailer ActionMailer::Base.raise_delivery_errors = true -ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__) \ No newline at end of file +ActionMailer::Base.view_paths = File.expand_path('../../jam_ruby/app/views/', __FILE__) + +# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more. +case JamRuby::Environment + when 'production' + Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6" + Recurly.subdomain = 'jamkazam' + when 'development' + Recurly.api_key = "7d623daabfc2434fa2a893bb008eb3e6" + Recurly.subdomain = 'jamkazam-development' + else + Recurly.api_key = "4631527f203b41848523125b3ae51341" + Recurly.subdomain = 'jamkazam-test' +end + +Recurly.default_currency = 'USD' diff --git a/web/lib/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb similarity index 74% rename from web/lib/recurly_client.rb rename to ruby/lib/jam_ruby/recurly_client.rb index e10fa81d6..3bf68e991 100644 --- a/web/lib/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -8,9 +8,11 @@ module JamRuby options = account_hash(current_user, billing_info) account = nil begin + #puts "Recurly.api_key: #{Recurly.api_key}" account = Recurly::Account.create(options) raise RecurlyClientError.new(account.errors) if account.errors.any? rescue Recurly::Error, NoMethodError => x + puts "Error: #{x} : #{Kernel.caller}" raise RecurlyClientError, x.to_s else if account @@ -68,6 +70,46 @@ module JamRuby account end + def refund_user_subscription(current_user, jam_track) + jam_track_right=JamRuby::JamTrackRight.where("user_id=? AND jam_track_id=?", current_user.id, jam_track.id).first + if jam_track_right + refund_subscription(jam_track_right) + else + raise RecurlyClientError, "The user #{current_user} does not have a subscription to #{jam_track}" + end + end + + def refund_subscription(jam_track_right) + account = get_account(jam_track_right.user) + if (account.present?) + terminated = false + begin + jam_track = jam_track_right.jam_track + account.subscriptions.find_each do |subscription| + puts "subscription.plan.plan_code: #{subscription.plan.plan_code} / #{jam_track.plan_code} / #{subscription.plan.plan_code == jam_track.plan_code}" + if(subscription.plan.plan_code == jam_track.plan_code) + subscription.terminate(:full) + raise RecurlyClientError.new(subscription.errors) if subscription.errors.any? + terminated = true + end + end + + if terminated + jam_track_right.destroy() + else + raise RecurlyClientError, "Subscription '#{jam_track.plan_code}' not found for this user; could not issue refund." + end + + rescue Recurly::Error, NoMethodError => x + raise RecurlyClientError, x.to_s + end + + else + raise RecurlyClientError, "Could not find account to refund order." + end + account + end + def place_order(current_user, jam_track) jam_track_right = nil account = get_account(current_user) diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 0da938fc1..07008dda7 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -739,6 +739,7 @@ FactoryGirl.define do licensor_royalty_amount 0.999 pro_royalty_amount 0.999 available true + plan_code 'jamtrack-acdc-backinblack' genre JamRuby::Genre.first association :licensor, factory: :jam_track_licensor diff --git a/web/spec/managers/recurly_client_spec.rb b/ruby/spec/jam_ruby/recurly_client_spec.rb similarity index 83% rename from web/spec/managers/recurly_client_spec.rb rename to ruby/spec/jam_ruby/recurly_client_spec.rb index 8b1825cba..82217851d 100644 --- a/web/spec/managers/recurly_client_spec.rb +++ b/ruby/spec/jam_ruby/recurly_client_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require "recurly_client" +require "jam_ruby/recurly_client" describe RecurlyClient do let(:jamtrack) { FactoryGirl.create(:jam_track) } #let(:client) { RecurlyClient.new } @@ -98,6 +98,24 @@ describe RecurlyClient do @user.jam_track_rights.last.jam_track.id.should eq(@jamtrack.id) end + it "can refund subscription" do + @client.find_or_create_account(@user, @billing_info) + + # Place order: + expect{@client.place_order(@user, @jamtrack)}.not_to raise_error() + active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'} + @jamtrack.reload + @jamtrack.jam_track_rights.should have(1).items + + # Refund: + expect{@client.refund_user_subscription(@user, @jamtrack)}.not_to raise_error() + active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'} + active_subs.should have(0).items + + @jamtrack.reload + @jamtrack.jam_track_rights.should have(0).items + end + it "detects error on double order" do @client.find_or_create_account(@user, @billing_info) expect{@client.place_order(@user, @jamtrack)}.not_to raise_error() diff --git a/web/README.md b/web/README.md index b32f3ab99..f2dccf0a1 100644 --- a/web/README.md +++ b/web/README.md @@ -1,10 +1,4 @@ Jasmine Javascript Unit Tests ============================= -1. Ensure you have the jasmine Gem installed; -$ bundle - -2. Start the jasmine server (defaults to :8888) -$ rake jasmine - -Open browser to localhost:8888 +Open browser to localhost:3000/teaspoon diff --git a/web/app/assets/javascripts/dialog/localRecordingsDialog.js b/web/app/assets/javascripts/dialog/localRecordingsDialog.js index d08911d7c..10fdae9a1 100644 --- a/web/app/assets/javascripts/dialog/localRecordingsDialog.js +++ b/web/app/assets/javascripts/dialog/localRecordingsDialog.js @@ -7,6 +7,7 @@ var rest = context.JK.Rest(); var showing = false; var perPage = 10; + var openingRecording = false; function tbody() { return $('#local-recordings-dialog table.local-recordings tbody'); @@ -22,6 +23,7 @@ function beforeShow() { + openingRecording = false; emptyList(); resetPagination(); showing = true; @@ -89,6 +91,12 @@ function registerStaticEvents() { $('#local-recordings-dialog table.local-recordings tbody').on('click', 'tr', function(e) { + if(openingRecording) { + // prevent double-click spam + logger.debug("localRecordingDialog: ignoring duplicate open attempt") + return false; + } + var localState = $(this).attr('data-local-state'); if(localState == 'MISSING') { @@ -109,6 +117,8 @@ { var claimedRecording = $(this).data('server-model'); + openingRecording = true; + // tell the server we are about to start a recording rest.startPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id}) .done(function(response) { @@ -146,6 +156,9 @@ app.notifyServerError(jqXHR, "Unable to Open Recording For Playback"); }) + .always(function() { + openingRecording = false; + }) } diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js index f89aeacba..4564cd9b0 100644 --- a/web/app/assets/javascripts/everywhere/everywhere.js +++ b/web/app/assets/javascripts/everywhere/everywhere.js @@ -7,6 +7,7 @@ //= require backend_alerts //= require stun //= require influxdb-latest +//= require jam_track_utils (function (context, $) { @@ -17,6 +18,7 @@ var ALERT_NAMES = context.JK.ALERT_NAMES; var logger = context.JK.logger; var stun = null; + var rest = context.JK.Rest(); $(document).on('JAMKAZAM_CONSTRUCTED', function(e, data) { @@ -51,6 +53,8 @@ operationalEvents(app); handleGettingStarted(app); + + initShoppingCart(app); }); function watchPreferencesEvent(app) { @@ -207,4 +211,14 @@ } } + function initShoppingCart(app) { + + var user = app.user() + if(user) { + user.done(function(userProfile) { + context.JK.JamTrackUtils.checkShoppingCart(); + }) + } + } + })(window, jQuery); diff --git a/web/app/assets/javascripts/jam_track_utils.js.coffee b/web/app/assets/javascripts/jam_track_utils.js.coffee new file mode 100644 index 000000000..2935665cc --- /dev/null +++ b/web/app/assets/javascripts/jam_track_utils.js.coffee @@ -0,0 +1,29 @@ + + +$ = jQuery +context = window +context.JK ||= {}; + +class JamTrackUtils + constructor: () -> + @logger = context.JK.logger + @rest = new context.JK.Rest(); + + init: () => + + # check if the shopping cart should be shown + checkShoppingCart: () => + @rest.getShoppingCarts().done(this.displayCartIcon) + + displayCartIcon: (carts) => + cartLink = $("a[href='" + "/client#/shoppingCart" + "']") + if carts.length > 0 + cartLink.removeClass("hidden") + else + cartLink.addClass("hidden") + + + + +# global instance +context.JK.JamTrackUtils = new JamTrackUtils() \ No newline at end of file diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index 4489f0c71..3930635e6 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -162,7 +162,7 @@ var duration = context.jamClient.SessionGetJamTracksPlayDurationMs(); var durationMs = duration.media_len; var start = duration.start; // needed to understand start offset, and prevent slider from moving in tapins - console.log("JamTrack start: " + start) + //console.log("JamTrack start: " + start) } else { var positionMs = context.jamClient.SessionCurrrentPlayPosMs(); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 75d427266..114c12c8a 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -99,6 +99,7 @@ var $liveTracksContainer = null; var downloadJamTrack = null; var $closePlaybackRecording = null; + var $openBackingTrack = null; var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]; var rest = context.JK.Rest(); @@ -144,6 +145,8 @@ function afterShow(data) { + $openBackingTrack.removeClass('disabled'); + if(!context.JK.JamServer.connected) { promptLeave = false; app.notifyAlert("Not Connected", 'To create or join a session, you must be connected to the server.'); @@ -618,7 +621,6 @@ function _updateMixers() { masterMixers = context.jamClient.SessionGetAllControlState(true); personalMixers = context.jamClient.SessionGetAllControlState(false); - context.jamClient //logger.debug("masterMixers", masterMixers) //logger.debug("personalMixers", personalMixers) @@ -642,10 +644,15 @@ var personalMixer = personalMixers[i]; if(personalMixer.group_id == ChannelGroupIds.MediaTrackGroup) { - continue; + // the reason we do this is because some media tracks have same ID in both master and personal moe + personalMixer.uniqueId = 'P--' + personalMixer.id + allMixers[personalMixer.uniqueId] = personalMixer + } + else { + allMixers[personalMixer.id] = personalMixer + } - allMixers[personalMixer.id] = personalMixer // populate other side of mixer pair @@ -926,8 +933,11 @@ var mediaType = mixer.media_type; var groupId = mixer.group_id; - // mediaType == null is for backwards compat with older clients. Can be removed soon - if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { + if(mediaType == 'MetronomeTrack' || groupId==ChannelGroupIds.MetronomeGroup) { + // Metronomes come across with a blank media type, so check group_id: + metronomeTrackMixers.push(mixer); + } + else if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { // additional check; if we can match an id in backing tracks or recorded backing track, // we need to remove it from the recorded track set, but move it to the backing track set @@ -958,9 +968,6 @@ } else if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { // BackingTrack backingTrackMixers.push(mixer); - } else if(mediaType == 'MetronomeTrack' || groupId==ChannelGroupIds.MetronomeGroup) { - // Metronomes come across with a blank media type, so check group_id: - metronomeTrackMixers.push(mixer); } else if(mediaType == 'JamTrack') { jamTrackMixers.push(mixer); mixer.group_id == ChannelGroupIds.MediaTrackGroup; @@ -1061,6 +1068,14 @@ // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener var isOpener = mixer.group_id == ChannelGroupIds.MediaTrackGroup; + if(isOpener) { + var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); + var mixerId = mixer.id + "," + oppositeMixer.uniqueId + } + else { + var mixerId = mixer.id; + } + var shortFilename = context.JK.getNameOfFile(backingTrack.filename); if(!sessionModel.isPlayingRecording()) { @@ -1086,6 +1101,7 @@ gainPercent: 0, muteClass: 'muted', showLoop: isOpener && !sessionModel.isPlayingRecording(), + loopState: mixer.loop, mixerId: "", avatarClass: 'avatar-recording', preMasteredClass: "" @@ -1100,14 +1116,14 @@ trackData.gainPercent = gainPercent; trackData.muteClass = muteClass; - trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.mixerId = mixerId; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) trackData.mediaTrackOpener = isOpener; trackData.mediaControlsDisabled = !isOpener; trackData.showHelpAboutMediaMixers = sessionModel.isPersonalMixMode() && isOpener; - _addRecordingTrack(trackData, mixer); + _addRecordingTrack(trackData, mixer, oppositeMixer); }); } @@ -1158,6 +1174,14 @@ name = oneOfTheTracks.instrument; } + if(isOpener) { + var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); + var mixerId = mixer.id + "," + oppositeMixer.uniqueId + } + else { + var mixerId = mixer.id; + } + // Default trackData to participant + no Mixer state. var trackData = { type: 'jam_track', @@ -1182,14 +1206,14 @@ } trackData.gainPercent = gainPercent; trackData.muteClass = muteClass; - trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.mixerId = mixerId; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) trackData.mediaTrackOpener = isOpener; trackData.mediaControlsDisabled = !isOpener; trackData.showHelpAboutMediaMixers = sessionModel.isPersonalMixMode() && isOpener; - _addRecordingTrack(trackData); + _addRecordingTrack(trackData, mixer, oppositeMixer); }); if(!noCorrespondingTracks && jamTracks.length > 0) { @@ -1259,7 +1283,15 @@ // _addRecordingTrack(trackData); - + if(isOpener) { + var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); + var mixerId = mixer.id + "," + oppositeMixer.uniqueId + } + else { + var mixerId = mixer.id; + } + + // Default trackData to participant + no Mixer state. var trackData = { type: 'metronome', @@ -1285,7 +1317,7 @@ } trackData.gainPercent = gainPercent; trackData.muteClass = muteClass; - trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.mixerId = mixerId; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) trackData.mediaTrackOpener = isOpener; @@ -1293,7 +1325,7 @@ trackData.showHelpAboutMediaMixers = sessionModel.isPersonalMixMode() && isOpener; - _addRecordingTrack(trackData, mixer); + _addRecordingTrack(trackData, mixer, oppositeMixer); }// if setFormFromMetronome() } @@ -1360,6 +1392,14 @@ name = oneOfTheTracks.user.first_name + ' ' + oneOfTheTracks.user.last_name; } + if(isOpener) { + var oppositeMixer = getMixerByResourceId(mixer.rid, MIX_MODES.PERSONAL); + var mixerId = mixer.id + "," + oppositeMixer.uniqueId + } + else { + var mixerId = mixer.id; + } + // Default trackData to participant + no Mixer state. var trackData = { type: 'recorded_track', @@ -1384,14 +1424,14 @@ } trackData.gainPercent = gainPercent; trackData.muteClass = muteClass; - trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.mixerId = mixerId; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) trackData.mediaControlsDisabled = !isOpener; trackData.mediaTrackOpener = isOpener; trackData.showHelpAboutMediaMixers = sessionModel.isPersonalMixMode() && isOpener; - _addRecordingTrack(trackData, mixer); + _addRecordingTrack(trackData, mixer, oppositeMixer); }); if(!noCorrespondingTracks && recordedTracks.length > 0) { @@ -1812,7 +1852,7 @@ $('.session-recording-name-wrapper').show(); } - function _addRecordingTrack(trackData, mixer) { + function _addRecordingTrack(trackData, mixer, oppositeMixer) { otherAudioFilled(); @@ -1837,13 +1877,14 @@ if(trackData.mediaControlsDisabled) { $trackIconMute.data('media-controls-disabled', true).data('media-track-opener', trackData.mediaTrackOpener) } - $trackIconMute.data('mixer', mixer).data('opposite-mixer', null) + $trackIconMute.data('mixer', mixer).data('opposite-mixer', oppositeMixer) $trackIconMute.data('showHelpAboutMediaMixers', trackData.showHelpAboutMediaMixers) if(trackData.showLoop) { var $trackIconLoop = $track.find('.track-icon-loop') - var $trackIconLoopCheckbox = $trackIconLoop.find('input') + var $trackIconLoopCheckbox = $trackIconLoop.find('input'); + $trackIconLoopCheckbox.prop('checked', trackData.loopState); context.JK.checkbox($trackIconLoopCheckbox) $trackIconLoopCheckbox.on('ifChanged', function() { @@ -1869,6 +1910,7 @@ $.each(mixerIds, function(i,v) { var broadcast = !(data.dragging); // If fader is still dragging, don't broadcast var mixer = fillTrackVolumeObject(v, broadcast); + setMixerVolume(mixer, data.percentage); if(groupId == ChannelGroupIds.UserMusicInputGroup) { @@ -1976,20 +2018,36 @@ } function handleBackingTrackSelectedCallback(result) { + + $openBackingTrack.removeClass('disabled'); + + if(!sessionModel.inSession()) { + return; + } + if(result.success) { logger.debug("backing track selected: " + result.file); rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: result.file}) .done(function(response) { var openResult = context.jamClient.SessionOpenBackingTrackFile(result.file, false); - //context.JK.CurrentSessionModel.refreshCurrentSession(true); - sessionModel.setBackingTrack(result.file); + + if(openResult) { + sessionModel.setBackingTrack(result.file); + } + else { + app.notify({ + "title": "Couldn't Open Backing Track", + "text": "Is the file a valid audio file?", + "icon_url": "/assets/content/icon_alert_big.png" + }); + closeBackingTrack(); + } + }) .fail(function(jqXHR) { - app.notifyServerError(jqXHR, "Unable to Open BackingTrack For Playback"); + app.notifyServerError(jqXHR, "Unable to Open Backing Track For Playback"); }) - - } else { logger.debug("no backing track selected") @@ -2087,12 +2145,14 @@ var mixer = $control.data('mixer'); var oppositeMixer = $control.data('opposite-mixer') - if(mixer && oppositeMixer && mixer.group_id == ChannelGroupIds.AudioInputMusicGroup) { + if(mixer && oppositeMixer && (mixer.group_id == ChannelGroupIds.AudioInputMusicGroup || mediaTrackGroups.indexOf(mixer.group_id) > -1)) { // this is the user's local track; mute both personal and master mode + logger.debug("muting both master and personal mode mixers") _toggleAudioMute(mixer.id, muting, getMixer(mixer.id).mode) - _toggleAudioMute(oppositeMixer.id, muting, getMixer(oppositeMixer.id).mode) + _toggleAudioMute(oppositeMixer.id, muting, getMixer(oppositeMixer.uniqueId || oppositeMixer.id).mode) } else { + logger.debug("muting mixer") _toggleAudioMute(mixer.id, muting, getMixer(mixer.id).mode) } @@ -2138,7 +2198,11 @@ context.trackVolumeObject.name = mixer.name; context.trackVolumeObject.record = mixer.record; context.trackVolumeObject.volL = mixer.volume_left; - context.trackVolumeObject.volR = mixer.volume_right; + + // today we treat all tracks as mono, but this is required to make a stereo track happy + //context.trackVolumeObject.volR = mixer.volume_right; + context.trackVolumeObject.volR = mixer.volume_left; + context.trackVolumeObject.loop = mixer.loop; // trackVolumeObject doesn't have a place for range min/max currentMixerRangeMin = mixer.range_low; @@ -2196,11 +2260,11 @@ context.jamClient.SessionSetMasterLocalMix(dbValue); // context.jamClient.SessionSetMasterLocalMix(sliderValue); } else { - var isMediaMixer = mediaTrackGroups.indexOf(mixer.group_id) > -1; + //var isMediaMixer = mediaTrackGroups.indexOf(mixer.group_id) > -1; // if this is a media file (Metronome, JamTrack, BackingTrack, RecordedTrack), then we only modify master - var mixMode = isMediaMixer ? MIX_MODES.MASTER : sessionModel.getMixMode(); - context.jamClient.SessionSetControlState(mixer.id, mixMode); + //var mixMode = isMediaMixer ? MIX_MODES.MASTER : sessionModel.getMixMode(); + context.jamClient.SessionSetControlState(mixer.id, mixer.mode); } } @@ -2352,6 +2416,12 @@ } function openBackingTrack(e) { + + if($openBackingTrack.is('.disabled')) { + logger.debug("backing track dialog already open") + return false; + } + // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { app.notify({ @@ -2362,13 +2432,8 @@ return false; } + $openBackingTrack.addClass('disabled'); context.jamClient.ShowSelectBackingTrackDialog("window.JK.HandleBackingTrackSelectedCallback"); - - //app.layout.showDialog('open-backing-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { - // if(!data.cancel && data.result){ - // sessionModel.setBackingTrack(data.result); - // } - //}) return false; } @@ -2451,6 +2516,7 @@ } function openBackingTrackFile(e) { + // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { app.notify({ @@ -2520,8 +2586,6 @@ rest.openMetronome({id: sessionModel.id()}) .done(function() { context.jamClient.SessionOpenMetronome(120, "Click", 1, 0) - context.JK.CurrentSessionModel.refreshCurrentSession(true) - context.JK.CurrentSessionModel.refreshCurrentSession(true) }) .fail(function(jqXHR) { logger.debug(jqXHR, jqXHR) @@ -2583,13 +2647,14 @@ }) .fail(function(jqXHR) { app.notify({ - "title": "Couldn't Close BackingTrack", - "text": "Couldn't inform the server to close BackingTrack. msg=" + jqXHR.responseText, + "title": "Couldn't Close Backing Track", + "text": "Couldn't inform the server to close Backing Track. msg=" + jqXHR.responseText, "icon_url": "/assets/content/icon_alert_big.png" }); }); // '' closes all open backing tracks + context.jamClient.SessionStopPlay(); context.jamClient.SessionCloseBackingTrackFile(''); return false; @@ -2773,7 +2838,7 @@ $('#recording-start-stop').on('click', startStopRecording); $('#open-a-recording').on('click', openRecording); $('#open-a-jamtrack').on('click', openJamTrack); - $('#open-a-backingtrack').on('click', openBackingTrack); + $openBackingTrack.on('click', openBackingTrack); $('#open-a-metronome').on('click', openMetronome); $('#session-invite-musicians').on('click', inviteMusicians); $('#session-invite-musicians2').on('click', inviteMusicians); @@ -2821,6 +2886,7 @@ $myTracksContainer = $('#session-mytracks-container') $liveTracksContainer = $('#session-livetracks-container'); $closePlaybackRecording = $('#close-playback-recording') + $openBackingTrack = $('#open-a-backingtrack'); events(); diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js index ac02b8b39..4a7d2bf7e 100644 --- a/web/app/assets/javascripts/shopping_cart.js +++ b/web/app/assets/javascripts/shopping_cart.js @@ -5,6 +5,7 @@ context.JK.ShoppingCartScreen = function(app) { var logger = context.JK.logger; + var jamTrackUtils = context.JK.JamTrackUtils; var $screen = null; var $content = null; @@ -16,6 +17,10 @@ function afterShow(data) { } + function afterHide() { + jamTrackUtils.checkShoppingCart(); + } + function events() { $screen.find("a.remove-cart").on('click', removeCart); $screen.find("a.proceed-checkout").on('click', proceedCheckout); @@ -94,7 +99,8 @@ function initialize() { var screenBindings = { 'beforeShow': beforeShow, - 'afterShow': afterShow + 'afterShow': afterShow, + 'afterHide' : afterHide }; app.bindScreen('shoppingCart', screenBindings); diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index c00f85254..28d16933b 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -1,4 +1,4 @@ -require 'recurly_client' +require 'jam_ruby/recurly_client' class ApiRecurlyController < ApiController before_filter :api_signed_in_user before_filter :create_client diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim index b82a98fbf..60646b6ec 100644 --- a/web/app/views/clients/_help.html.slim +++ b/web/app/views/clients/_help.html.slim @@ -204,7 +204,7 @@ script type="text/template" id="template-help-jamtrack-controls-disabled" script type="text/template" id="template-help-volume-media-mixers" - | Audio files only expose master mix controls, so any change here will also affect everyone in the session. + | Audio files only expose both master and personal mix controls, so any change here will also affect everyone in the session. script type="text/template" id="template-help-downloaded-jamtrack" .downloaded-jamtrack diff --git a/web/app/views/clients/_jamtrack.html.haml b/web/app/views/clients/_jamtrack.html.haml index a45e932fe..2573197b3 100644 --- a/web/app/views/clients/_jamtrack.html.haml +++ b/web/app/views/clients/_jamtrack.html.haml @@ -69,7 +69,7 @@ .jamtrack-price {{"$ " + data.jamtrack.price}} = "{% if (data.jamtrack.added_cart) { %}" - %a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Added to Cart + %a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Purchased = "{% } else { %}" %a.jamtrack-add-cart.button-orange{href: "#", "data-jamtrack-id" => "{{data.jamtrack.id}}"} Add to Cart = "{% }; %}" diff --git a/web/app/views/dialogs/_banner.html.slim b/web/app/views/dialogs/_banner.html.slim index 4083903e3..fe406d529 100644 --- a/web/app/views/dialogs/_banner.html.slim +++ b/web/app/views/dialogs/_banner.html.slim @@ -62,7 +62,7 @@ script type='text/template' id='template-mixer-mode-change' div The personal mix controls the audio mix that you individually hear while playing in the session, and you can customize this mix to hear more or less of the music stream from each other musician playing in the session. This does not affect the master mix used for recordings or broadcasts. With personal mix selected, when you adjust the faders on the session screen up or down, it changes the personal mix only for you locally. li span.definition Note on Audio Files - div The volume control on any audio file is always a master volume control, regardless of the current mode. + div The volume control on any audio file is always both the master and personal volume control, regardless of the current mode. br div | For more detailed information on this topic, read our knowledge base article on  diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index f2a79d552..fb0eeabfd 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -1,13 +1,9 @@ require 'spec_helper' -require 'recurly_client' -#require 'recurly/account' +require 'jam_ruby/recurly_client' describe ApiRecurlyController, :type=>:controller do render_views - # let(:user) { FactoryGirl.create(:user) } - # let(:jamtrack) { FactoryGirl.create(:jam_track) } - before(:each) do @user = FactoryGirl.create(:user) #@jamtrack = FactoryGirl.create(:jam_track) diff --git a/web/spec/features/jamtrack_shopping_spec.rb b/web/spec/features/jamtrack_shopping_spec.rb index 85684725d..049f0101a 100644 --- a/web/spec/features/jamtrack_shopping_spec.rb +++ b/web/spec/features/jamtrack_shopping_spec.rb @@ -53,7 +53,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature end if options[:added_cart] - jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Added to Cart') + jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Purchased') else jamtrack_record.find('a.jamtrack-add-cart.button-orange', text: 'Add to Cart') end diff --git a/websocket-gateway/Gemfile b/websocket-gateway/Gemfile index 3e32bbc4c..77ce57cc3 100644 --- a/websocket-gateway/Gemfile +++ b/websocket-gateway/Gemfile @@ -54,6 +54,7 @@ gem 'language_list' gem 'rubyzip' gem 'sanitize' gem 'influxdb', '0.1.8' +gem 'recurly' group :development do gem 'pry'