diff --git a/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js index 0c6d2e712..349742ad7 100644 --- a/web/app/assets/javascripts/dialog/openJamTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openJamTrackDialog.js @@ -24,6 +24,7 @@ } function beforeShow() { + $dialog.data('result', null) emptyList(); resetPagination(); showing = true; diff --git a/web/app/assets/javascripts/download_jamtrack.js.coffee b/web/app/assets/javascripts/download_jamtrack.js.coffee index ff9669530..77fa5aa34 100644 --- a/web/app/assets/javascripts/download_jamtrack.js.coffee +++ b/web/app/assets/javascripts/download_jamtrack.js.coffee @@ -13,7 +13,7 @@ context.JK ||= {}; # * packaging # * downloading # -# errored state can be entered from @jamTrackRightId +# errored state can be entered from @jamTrack.jam_track_right_id # # other state; you augment the error to the user by suppling @errorMessage before transitioning # @@ -29,12 +29,12 @@ context.JK ||= {}; context.JK.DownloadJamTracks = {} context.JK.DownloadJamTrack = class DownloadJamTrack - constructor: (@app, jamTrackId, jamTrackRightId) -> + constructor: (@app, jamTrack, size = 'large') -> @EVENTS = context.JK.EVENTS @rest = context.JK.Rest() @logger = context.JK.logger - @jamTrackId = jamTrackId - @jamTrackRightId = jamTrackRightId + @jamTrack = jamTrack + @size = size @attemptedEnqueue = false @errorReason = null @errorMessage = null @@ -46,6 +46,12 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @startTime = null @attempts = 0 @tracked = false + @ajaxEnqueueAborted = false + @ajaxGetJamTrackRightAborted = false + + throw "no JamTrack specified" unless @jamTrack? + throw "invalid size" if @size != 'large' && @size != 'small' + @path = [] @states = { no_client: { name: 'no-client', show: @showNoClient, leaf: true }, @@ -54,38 +60,46 @@ context.JK.DownloadJamTrack = class DownloadJamTrack downloading: { name: 'downloading', show: @showDownloading }, keying: { name: 'keying', show: @showKeying, max_time: 10000 }, initial: { name: 'initial', show: @showInitial }, + quiet: { name: 'quiet', show: @showQuiet }, errored: { name: 'errored', show: @showError, leaf: true} } - context.JK.DownloadJamTracks[@jamTrackId] = this + context.JK.DownloadJamTracks[@jamTrack.id] = this downloadJamTrackTemplate = $('#template-download-jamtrack') throw "no download jamtrack template" if not downloadJamTrackTemplate.exists() @root = $(downloadJamTrackTemplate.html()) - - # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack - # is downloaded and ready to open - init: () => - @active = true - this.reset() - - - @root.on('remove', this.destroy) # automatically destroy self when removed from DOM @stateHolder = @root.find('.state') + @root.on('remove', this.destroy) # automatically destroy self when removed from DOM # populate in template and visual transition functions for state, data of @states data.template = $("#template-download-jamtrack-state-#{data.name}") + # start off in quiet state, but don't do it through transition system. The transition system expects a change, not initial state + @state = @states.quiet + + this.showState() + + + # after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack + # is downloaded and ready to open + init: () => + @active = true + @root.addClass('active') + this.reset() + # check if we are in a browser or client if !gon.isNativeClient this.transition(@states.no_client) else - @states.initial.show() + this.transition(@states.initial) # when done with the widget, call destroy; this ensures it's not still active, and tracks final metrics destroy: () => + $(this).off() @active = false + @root.removeClass('active') this.trackProgress() # since we are not in a leave node, we need to report a state since this is effectively our end state this.reset() @@ -95,16 +109,40 @@ context.JK.DownloadJamTrack = class DownloadJamTrack @attempts = 0 @tracked = false @startTime = new Date() - @state = @states.initial # reset attemptedEnqueue to false, to allow one attempt to enqueue @attemptedEnqueue = false this.clearDownloadTimer() this.clearTransitionTimer() + this.abortEnqueue() + this.abortGetJamTrackRight() for state, data of @states if data.timer? clearInterval(data.timer) data.timer = null + abortEnqueue: () => + if @ajaxEnqueueAborted + @logger.debug("DownloadJamTrack: aborting ajax enqueue") + # we need to clear out @ajaxEnqueue *before* calling abort(), because the .fail callback fires inline + ajax = @ajaxEnqueueAborted + @ajaxEnqueueAborted = true + ajax.abort() + + abortGetJamTrackRight: () => + if @ajaxGetJamTrackRightAborted + @logger.debug("DownloadJamTrack: aborting ajax GetJamTrackRight") + # we need to clear out @ajaxEnqueue *before* calling abort(), because the .fail callback fires inline + ajax = @ajaxGetJamTrackRightAborted + @ajaxGetJamTrackRightAborted = true + ajax.abort() + + showState: () => + @state.stateStartTime = new Date(); + @stateHolder.children().remove() + @stateHolder.append(context._.template(@state.template.html(), @jamTrack, { variable: 'data' })) + @stateHolder.find('.' + @size).removeClass('hidden') + @state.show() + # report a stat now that we've reached the end of this widget's journey trackProgress: () => @@ -112,6 +150,9 @@ context.JK.DownloadJamTrack = class DownloadJamTrack if @tracked return + if @path.length == 0 + return + unless @state.leaf # we've been asked to report at a non-leaf node, meaning the user must have cancelled @path.push('user-cancelled') @@ -141,25 +182,35 @@ context.JK.DownloadJamTrack = class DownloadJamTrack showDownloading: () => @logger.debug("showing #{@state.name}") # while downloading, we don't run the transition timer, because the download API is guaranteed to call success, or failure, eventually - context.jamClient.JamTrackDownload(@jamTrackId, this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback()) + context.jamClient.JamTrackDownload(@jamTrack.id, this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback()) showKeying: () => @logger.debug("showing #{@state.name}") context.jamClient.JamTrackKeysRequest() this.waitForState() + showQuiet: () => + @logger.debug("showing #{@state.name}") + showInitial: () => @logger.debug("showing #{@state.name}") @attempts = @attempts + 1 this.expectTransition() - context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrackRightId).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) + context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrack.jam_track_right_id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent) this.checkState() showError: () => @logger.debug("showing #{@state.name}") - context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) - @stateHolder.find('.msg').text(@errorMessage) - @stateHolder.find('.retry-button').click(this.retry) + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id) + + if @size == 'large' + @stateHolder.find('.msg').text(@errorMessage) + @stateHolder.find('.retry-button').click(this.retry) + else + @stateHolder.find('.msg').text(@jamTrack.name + ' (error)') + @stateHolder.find('.errormsg').text(@errorMessage) + @stateHolder.find('.retry-button').on('click', this.retry) + retryMsg = '' if @attempts > 1 retryMsg = 'Continue retrying or contact support@jamkazam.com' @@ -168,7 +219,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack showSynchronized: () => @logger.debug("showing #{@state.name}") - context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrackRightId) + context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id) showNoClient: () => @logger.debug("showing #{@state.name}") @@ -256,16 +307,22 @@ context.JK.DownloadJamTrack = class DownloadJamTrack transition: (newState) => unless @active @logger.error("DownloadJamTrack: ignoring state change because we are not active") - - if newState == @state - @logger.debug("DownloadJamTrack: ignoring state change #{@state.name}") return - @logger.debug("DownloadJamTrack: state change: #{@state.name} => #{newState.name}") + if newState == @state + @logger.debug("DownloadJamTrack: ignoring state change #{@state.name} #{newState}") + return - # make sure there is no timer running on the old state - this.clearTransitionTimer() - this.clearStateTimer() + if @state? + @logger.debug("DownloadJamTrack: state change: #{@state.name} => #{newState.name}") + # make sure there is no timer running on the old state + this.clearTransitionTimer() + this.clearStateTimer() + this.abortEnqueue() + @logger.debug("aborting getJamTrack right on state change") + this.abortGetJamTrackRight() + else + @logger.debug("DownloadJamTrack: initial state: #{newState.name}") @state = newState @@ -275,18 +332,13 @@ context.JK.DownloadJamTrack = class DownloadJamTrack if @state.leaf this.trackProgress() - @state.stateStartTime = new Date(); - - @stateHolder.children().remove() - @stateHolder.append(@state.template.html()) - - @state.show() + this.showState() $(this).triggerHandler(@EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, {state: @state}) checkState: () => # check for the success state against the local state of the client... if it's playable, then we should be OK - @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrackId) + @trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id) @logger.debug("DownloadJamTrack: JamTrackGetTrackDetail.key_state: " + @trackDetail.key_state) @@ -299,7 +351,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack when 'ready' this.transition(@states.synchronized) when 'unknown' - @rest.getJamTrackRight({id: @jamTrackId}) + @ajaxGetJamTrackRightAborted = false + @rest.getJamTrackRight({id: @jamTrack.id}) .done(this.processJamTrackRight) .fail(this.processJamTrackRightFail) @@ -315,10 +368,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack else this.expectTransition() - @attemptedEnqueue = true - @rest.enqueueJamTrack({id: @jamTrackId}) - .done(this.processEnqueueJamTrack) - .fail(this.processEnqueueJamTrackFail) + this.attemptToEnqueue() when 'QUEUED' # when it's queued, there is nothing to do except wait. this.transition(@states.packaging) @@ -337,29 +387,45 @@ context.JK.DownloadJamTrack = class DownloadJamTrack else this.expectTransition() - @attemptedEnqueue = true - @rest.enqueueJamTrack({id: @jamTrackId}) - .done(this.processEnqueueJamTrack) - .fail(this.processEnqueueJamTrackFail) + this.attemptToEnqueue() else @logger.error("unknown state: " + signingState) this.transitionError("unknown-state-#{signingState}", "The server sent an unknown state message: " + signingState) - processJamTrackRightFail: () => - this.transitionError("status-check-error", "Unable to check with the server on the status of your JamTrack.") + attemptToEnqueue: () => + @attemptedEnqueue = true + @ajaxEnqueueAborted = false + @rest.enqueueJamTrack({id: @jamTrack.id}) + .done(this.processEnqueueJamTrack) + .fail(this.processEnqueueJamTrackFail) - processEnqueueJamTrack: (enqueueResponse) => - this.expectTransition() # the act of enqueuing should send down events to the client. we wait... - - processEnqueueJamTrackFail: () => - this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.") processJamTrackRight: (myJamTrack) => - this.processSigningState(myJamTrack.signing_state) + unless @ajaxGetJamTrackRightAborted + this.processSigningState(myJamTrack.signing_state) + else + @logger.debug("DownloadJamTrack: ignoring processJamTrackRight response") + + processJamTrackRightFail: () => + unless @ajaxGetJamTrackRightAborted? + this.transitionError("status-check-error", "Unable to check with the server on the status of your JamTrack.") + else + @logger.debug("DownloadJamTrack: ignoring processJamTrackRightFail response") + + processEnqueueJamTrack: (enqueueResponse) => + unless @ajaxEnqueueAborted + this.expectTransition() # the act of enqueuing should send down events to the client. we wait... + else + @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrack response") + + processEnqueueJamTrackFail: () => + unless @ajaxEnqueueAborted + this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.") + else + @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrackFail response") onJamTrackRightEvent: (e, data) => @logger.debug("DownloadJamTrack: subscription notification received: type:" + data.type) - this.expectTransition() this.processSigningState(data.body.signing_state) @@ -372,6 +438,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack downloadSuccessCallback: (updateLocation) => # is the package loadable yet? + @logger.debug("DownloadJamTrack: download complete - on to keying") this.transition(@states.keying) downloadFailureCallback: (errorMsg) => @@ -380,13 +447,13 @@ context.JK.DownloadJamTrack = class DownloadJamTrack # makes a function name for the backend makeDownloadProgressCallback: () => - "JK.DownloadJamTracks['#{@jamTrackId}'].downloadProgressCallback" + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadProgressCallback" # makes a function name for the backend makeDownloadSuccessCallback: () => - "JK.DownloadJamTracks['#{@jamTrackId}'].downloadSuccessCallback" + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadSuccessCallback" # makes a function name for the backend makeDownloadFailureCallback: () => - "JK.DownloadJamTracks['#{@jamTrackId}'].downloadFailureCallback" + "JK.DownloadJamTracks['#{@jamTrack.id}'].downloadFailureCallback" diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 37a0b65e6..e8bdfcaaf 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -685,10 +685,10 @@ return true; } function JamTrackGetTrackDetail() { - return {key_state: 'ready'} + return {key_state: 'unknown'} } function JamTrackKeysRequest() {} - + function JamTrackDownload() {} // Method which sets volume function UpdateMixer(mixerId) {} @@ -988,6 +988,7 @@ this.JamTrackIsPlayable = JamTrackIsPlayable; this.JamTrackGetTrackDetail = JamTrackGetTrackDetail; this.JamTrackKeysRequest = JamTrackKeysRequest; + this.JamTrackDownload = JamTrackDownload; // Scoring Knobs this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index fb741ad31..c5eb34e65 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -4,9 +4,12 @@ context.JK = context.JK || {}; context.JK.OrderScreen = function(app) { + var EVENTS = context.JK.EVENTS; var logger = context.JK.logger; var $screen = null; + var $templateOrderContent = null; + var $templatePurchasedJamTrack = null; var $navigation = null; var $billingInfo = null; var $shippingInfo = null; @@ -16,15 +19,23 @@ var $paymentInfoPanel = null; var $orderPanel = null; var $thanksPanel = null; + var $jamTrackInBrowser = null; + var $purchasedJamTrack = null; + var $purchasedJamTrackHeader = null; + var $purchasedJamTracks = null; var $orderContent = null; var userDetail = null; var step = null; var billing_info = null; var shipping_info = null; var shipping_as_billing = null; + var downloadJamTracks = []; + var purchasedJamTracks = null; + var purchasedJamTrackIterator = 0; function beforeShow() { - beforeShowPaymentInfo(); + beforeShowPaymentInfo(); + resetJamTrackDownloadInfo(); } function beforeShowPaymentInfo() { @@ -33,6 +44,12 @@ renderAccountInfo(); } + function resetJamTrackDownloadInfo() { + $purchasedJamTrack.addClass('hidden'); + $purchasedJamTracks.children().remove() + $jamTrackInBrowser.hide('hidden'); + } + function renderAccountInfo() { rest.getUserDetail() .done(populateAccountInfo) @@ -81,6 +98,21 @@ } function afterShow(data) { + // XXX : style-test code + // moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]}); + } + + function beforeHide() { + if(downloadJamTracks) { + context._.each(downloadJamTracks, function(downloadJamTrack) { + downloadJamTrack.destroy(); + downloadJamTrack.root.remove(); + }) + + downloadJamTracks = []; + } + purchasedJamTracks = null; + purchasedJamTrackIterator = 0; } function next(e) { @@ -412,7 +444,7 @@ data.shipping_as_billing = shipping_as_billing var orderContentHtml = $( context._.template( - $('#template-order-content').html(), + $templateOrderContent.html(), data, {variable: 'data'} ) @@ -430,13 +462,86 @@ beforeShowOrder(); } - function moveToThanks() { + function moveToThanks(purchaseResponse) { $("#order_error").addClass("hidden") $paymentInfoPanel.addClass("hidden") $orderPanel.addClass("hidden") $thanksPanel.removeClass("hidden") rest.clearShoppingCart() beforeShowOrder() + handleJamTracksPurchased(purchaseResponse.jam_tracks) + } + + function handleJamTracksPurchased(jamTracks) { + // were any JamTracks purchased? + var jamTracksPurchased = jamTracks && jamTracks.length > 0; + if(jamTracksPurchased) { + if(gon.isNativeClient) { + startDownloadJamTracks(jamTracks) + } + else { + $jamTrackInBrowser.removeClass('hidden'); + } + } + } + function startDownloadJamTracks(jamTracks) { + // there can be multiple purchased JamTracks, so we cycle through them + + purchasedJamTracks = jamTracks; + + // populate list of jamtracks purchased, that we will iterate through graphically + context._.each(jamTracks, function(jamTrack) { + var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small'); + var $purchasedJamTrack = $(context._.template( + $templatePurchasedJamTrack.html(), + jamTrack, + {variable: 'data'} + )); + + $purchasedJamTracks.append($purchasedJamTrack) + + // show it on the page + $purchasedJamTrack.append(downloadJamTrack.root) + + downloadJamTracks.push(downloadJamTrack) + }) + + iteratePurchasedJamTracks(); + } + + function iteratePurchasedJamTracks() { + if(purchasedJamTrackIterator < purchasedJamTracks.length ) { + var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++]; + + // make sure the 'purchasing JamTrack' section can be seen + $purchasedJamTrack.removeClass('hidden'); + + // the widget indicates when it gets to any transition; we can hide it once it reaches completion + $(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) { + + if(data.state == downloadJamTrack.states.synchronized) { + logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;") + //downloadJamTrack.root.remove(); + downloadJamTrack.destroy(); + + // go to the next JamTrack + iteratePurchasedJamTracks() + } + }) + + logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing") + + // kick off the download JamTrack process + downloadJamTrack.init() + + // XXX style-test code + // downloadJamTrack.transitionError("package-error", "The server failed to create your package.") + + } + else { + logger.debug("done iterating over purchased JamTracks") + $purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.') + } } function moveToPaymentInfo(e) { @@ -506,21 +611,28 @@ function initialize() { var screenBindings = { 'beforeShow': beforeShow, - 'afterShow': afterShow + 'afterShow': afterShow, + 'beforeHide' : beforeHide }; app.bindScreen('order', screenBindings); - $screen = $("#orderScreen"); - $paymentInfoPanel = $screen.find(".checkout-payment-info"); - $orderPanel = $screen.find(".order-panel"); - $thanksPanel = $screen.find(".thanks-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"); + $screen = $("#orderScreen"); + $templateOrderContent = $("#template-order-content"); + $templatePurchasedJamTrack = $('#template-purchased-jam-track'); + $paymentInfoPanel = $screen.find(".checkout-payment-info"); + $orderPanel = $screen.find(".order-panel"); + $thanksPanel = $screen.find(".thanks-panel"); + $jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser"); + $purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track"); + $purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header"); + $purchasedJamTracks = $purchasedJamTrack.find(".purchased-list") + $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"; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index d5a6e0c31..051143f73 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -503,6 +503,15 @@ } } + function resetOtherAudioContent() { + if ($('.session-recordings .track').length === 0 && $('.session-recordings .download-jamtrack').length === 0) { + $('.session-recordings .when-empty').show(); + $('.session-recording-name-wrapper').hide(); + $('.session-recordings .recording-controls').hide(); + $('.session-recordings .session-recording-name').text('(No audio loaded)') + } + } + function renderSession() { $myTracksContainer.empty(); $('.session-track').remove(); // Remove previous tracks @@ -518,11 +527,7 @@ if ($('.session-livetracks .track').length === 0) { $('.session-livetracks .when-empty').show(); } - if ($('.session-recordings .track').length === 0 && $('.session-recordings .download-jamtrack').length === 0) { - $('.session-recordings .when-empty').show(); - $('.session-recording-name-wrapper').hide(); - $('.session-recordings .recording-controls').hide(); - } + resetOtherAudioContent(); } function _initDialogs() { @@ -1933,8 +1938,6 @@ var jamTrack = data.result.jamTrack; - logger.debug("JamTrack INFO", jamTrack.id, jamTrack.jam_track_right_id) - // hide 'other audio' placeholder otherAudioFilled(); @@ -1946,7 +1949,7 @@ downloadJamTrack = null } - downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack.id, jamTrack.jam_track_right_id); + downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack.id, 'large'); // the widget indicates when it gets to any transition; we can hide it once it reaches completion $(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) { @@ -1957,7 +1960,10 @@ downloadJamTrack.destroy(); downloadJamTrack = null; + // XXX: test with this removed; it should be unnecessary context.jamClient.JamTrackStopPlay(); + + // JamTrackPlay means 'load' var result = context.jamClient.JamTrackPlay(jamTrack.id); if(!result) { @@ -1972,7 +1978,7 @@ // show it on the page $otherAudioContainer.append(downloadJamTrack.root) - // kick off the download jamtrack process + // kick off the download JamTrack process downloadJamTrack.init() } else { @@ -2005,15 +2011,30 @@ if(sessionModel.recordedTracks()) { closeRecording(); } - else if(sessionModel.jamTracks()) { + else if(sessionModel.jamTracks() || downloadJamTrack) { closeJamTrack(); } else { - logger.error("don't know how to close open media (backing track?)"); + logger.error("don't know how to close open media (backing track maybe?)"); } + return false; } function closeJamTrack() { + + logger.debug("closing recording"); + + if(downloadJamTrack) { + logger.debug("closing DownloadJamTrack widget") + downloadJamTrack.root.remove(); + downloadJamTrack.destroy(); + downloadJamTrack = null; + + // this is necessary because a syncing widget means no jamtracks are loaded; + // so removing the widget will not cause a backend media change event (and so renderSession will not be called, ultimately) + resetOtherAudioContent(); + } + rest.closeJamTrack({id: sessionModel.id()}) .done(function() { sessionModel.refreshCurrentSession(); @@ -2032,6 +2053,8 @@ } function closeRecording() { + logger.debug("closing recording"); + rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) .done(function() { sessionModel.refreshCurrentSession(); diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss index 6f9bc6a40..efe918e22 100644 --- a/web/app/assets/stylesheets/client/checkout.css.scss +++ b/web/app/assets/stylesheets/client/checkout.css.scss @@ -168,6 +168,40 @@ .thanks-panel { padding: 30px; + + span.notice { + font-style:italic; + font-size:12px; + } + + br.purchase-downloads { + clear:both; + margin-bottom:20px; + } + + .thanks-detail.purchased-jam-track { + + margin-top:20px; + + .purchased-jam-track-header { + font-size: 15px; + margin-bottom:10px; + } + + ul.purchased-list { + float:left; + margin:20px 100px 0 20px; + + li { + margin:0; + } + } + + .download-jamtrack { + width:auto; + vertical-align: middle; // to make bullets mid-align when error shows + } + } } .order-panel { diff --git a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss index 762a4103b..27eb232a0 100644 --- a/web/app/assets/stylesheets/client/downloadJamTrack.css.scss +++ b/web/app/assets/stylesheets/client/downloadJamTrack.css.scss @@ -2,10 +2,8 @@ .download-jamtrack { display:inline-block; + width:100%; - .state { - text-align:center; - } .retry-button { margin-top:20px; } @@ -23,4 +21,57 @@ text-align:center; } + .small { + .state { + text-align:left; + } + font-size:inherit; + .msg { + line-height: 32px; + height: 32px; + display: inline-block; + vertical-align: middle; + } + .spinner-small { + display:inline-block; + vertical-align:middle; + } + } + + .large { + .state { + text-align:center; + } + } + + &.active { + + .small { + margin-bottom:5px; + + .msg { + font-weight:bold; + color:white; + display:inline; + } + .errormsg { + display:block; + font-size:14px; + } + .retry { + display:block; + margin:3px 0 0 0; + font-size:14px; + } + .retry-button { + float:right; + margin:5px 0 5px 20px; + } + + .msg-holder { + display:block; + } + } + + } } \ No newline at end of file diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 575366d55..c00f85254 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -57,20 +57,36 @@ class ApiRecurlyController < ApiController def place_order error=nil puts "PLACING ORDER #{params.inspect}" + response = {jam_tracks:[]} + + # 1st confirm that all specified JamTracks exist + jam_tracks = [] + params[:jam_tracks].each do |jam_track_id| jam_track = JamTrack.where("id=?", jam_track_id).first if jam_track - @client.place_order(current_user, jam_track) + jam_tracks << jam_track else error="JamTrack not found for '#{jam_track_id}'" break end end + # then buy each + unless error + jam_tracks.each do |jam_track| + jam_track_right = @client.place_order(current_user, jam_track) + # build up the response object with JamTracks that were purchased. + # if this gets more complicated, we should switch to RABL + response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id} + end + end + + if error render json: { errors: {message:error}}, :status => 404 else - render :json=>{}, :status=>200 + render :json=>response, :status=>200 end rescue RecurlyClientError => x render json: { message: x.inspect, errors: x.errors}, :status => 404 diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb index 910ff88b3..36b0596a4 100644 --- a/web/app/controllers/spikes_controller.rb +++ b/web/app/controllers/spikes_controller.rb @@ -47,6 +47,7 @@ class SpikesController < ApplicationController gon.jamTrackId = jamTrack.id gon.jamTrackRightId = jamTrackRight.id + gon.size = params[:size] ? params[:size] : 'large' gon.switchState = params[:state] render :layout => 'web' diff --git a/web/app/views/clients/_download_jamtrack_templates.html.slim b/web/app/views/clients/_download_jamtrack_templates.html.slim index 38aba682b..fef7f2cd8 100644 --- a/web/app/views/clients/_download_jamtrack_templates.html.slim +++ b/web/app/views/clients/_download_jamtrack_templates.html.slim @@ -4,38 +4,83 @@ script type="text/template" id='template-download-jamtrack' script type="text/template" id="template-download-jamtrack-state-no-client" .state-no-client - | To play your JamTrack, launch the JamKazam application and open it while in a session. + .large.hidden + .msg + | To play your JamTrack, launch the JamKazam application and open the JamTrack while in a session. + .small.hidden + .msg + | {{data.name}} (launch client) script type="text/template" id="template-download-jamtrack-state-synchronized" .state-synchronized - | Your JamTrack is on your system and ready to play. + .large.hidden + .msg + | Your JamTrack is on your system and ready to play. + .small.hidden + .msg + | {{data.name}} (done) script type="text/template" id="template-download-jamtrack-state-packaging" .state-packaging - .msg - | Your JamTrack is currently being created on the JamKazam server. - .spinner-large + .large.hidden + .msg + | Your JamTrack is currently being created on the JamKazam server. + .spinner-large + .small.hidden + .msg + | {{data.name}} (packaging) + .spinner-small script type="text/template" id="template-download-jamtrack-state-downloading" .state-downloading - .msg - | Your JamTrack is currently being downloaded. - .spinner-large + .large.hidden + .msg + | Your JamTrack is currently being downloaded. + .spinner-large + .small.hidden + .msg + | {{data.name}} (downloading) + .spinner-small script type="text/template" id="template-download-jamtrack-state-keying" .state-keying - .msg - | Your JamTrack is being authenticated. - .spinner-large + .large.hidden + .msg + | Your JamTrack is being authenticated. + .spinner-large + .small.hidden + .msg + | {{data.name}} (keying) + .spinner-small script type="text/template" id="template-download-jamtrack-state-initial" .state-initial - .msg - | Initializing JamTrack... - .spinner-large + .large.hidden + .msg + | Initializing JamTrack... + .spinner-large + .small.hidden + .msg + | {{data.name}} (initializing) + .spinner-small + +script type="text/template" id="template-download-jamtrack-state-quiet" + .state-quiet + .large.hidden + .msg + .small.hidden + .msg + | {{data.name}} (pending) script type="text/template" id="template-download-jamtrack-state-errored" .state-errored - .msg - .retry - a.button-orange.retry-button RETRY \ No newline at end of file + .large.hidden + .msg + .retry + a.button-orange.retry-button RETRY + .small.hidden + .msg-holder + .msg + a.button-orange.retry-button RETRY + .errormsg + .retry diff --git a/web/app/views/clients/_order.html.slim b/web/app/views/clients/_order.html.slim index 241af7217..ffab91d7a 100644 --- a/web/app/views/clients/_order.html.slim +++ b/web/app/views/clients/_order.html.slim @@ -178,8 +178,15 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" br .thanks-detail We'll send you an email confirming your order shortly. br - .thanks-detail If you purchased any JamTracks, the next time you run the JamKazam application, your JamTracks will automatically be downloaded to the app, and you will receive a notification when the download is complete. - + .thanks-detail.jam-tracks-in-browser.hidden + | To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session. + .thanks-detail.purchased-jam-track.hidden + h2.purchased-jam-track-header Downloading Your Purchased JamTracks + span Each JamTrack will be downloaded sequentially. + br + span.notice Note that you do not have to wait for this to complete in order to use your JamTrack later. + br.clear + ul.purchased-list @@ -276,4 +283,7 @@ script type='text/template' id='template-order-content' span and ' a href="http://www.jamkazam.com/corp/returns" returns policy - span . \ No newline at end of file + span . + +script type='text/template' id='template-purchased-jam-track' + li data-jam-track-id="{{data.jam_track_id}}" \ No newline at end of file diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 377ec043d..45403f7c9 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -105,7 +105,7 @@