diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb index f7895d6a3..135238a02 100644 --- a/ruby/lib/jam_ruby/connection_manager.rb +++ b/ruby/lib/jam_ruby/connection_manager.rb @@ -316,6 +316,9 @@ SQL #ensure that there is no active claimed recording if the owner of that recording left the session conn.exec("UPDATE active_music_sessions set claimed_recording_id = NULL, claimed_recording_initiator_id = NULL where claimed_recording_initiator_id = $1 and id = $2", [user_id, previous_music_session_id]) + + conn.exec("UPDATE active_music_sessions set jam_track_id = NULL, jam_track_initiator_id = NULL where jam_track_initiator_id = $1 and id = $2", + [user_id, previous_music_session_id]) end end diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 758888865..7f383f5dc 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -64,8 +64,7 @@ module JamRuby start = start.to_i || 0 query = JamTrack.joins(:jam_track_tracks) - .offset(start) - .limit(limit) + .paginate(page: 1 + start/limit, per_page: limit) if options[:show_purchased_only] query = query.joins(:jam_track_rights) diff --git a/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js new file mode 100644 index 000000000..5ac306595 --- /dev/null +++ b/web/app/assets/javascripts/dialog/openJamTrackDialog.js @@ -0,0 +1,130 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.OpenJamTrackDialog = function(app) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var showing = false; + var perPage = 10; + var $dialog = null; + var $tbody = null; + var $paginatorHolder = null; + var $templateOpenJamTrackRow = null; + var $downloadedTrackHelp = null; + var $whatAreJamTracks = null; + + + function emptyList() { + $tbody.empty(); + } + + function resetPagination() { + $dialog.find('.paginator').remove(); + } + + function beforeShow() { + emptyList(); + resetPagination(); + showing = true; + getPurchasedJamTracks(0) + .done(function(data, textStatus, jqXHR) { + // initialize pagination + var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected) + $paginatorHolder.append($paginator); + }); + } + + function afterHide() { + showing = false; + } + + + function onPageSelected(targetPage) { + return getPurchasedJamTracks(targetPage); + } + + function getPurchasedJamTracks(page) { + return rest.getPurchasedJamTracks({page:page + 1, per_page:10}) + .done(function(purchasedJamTracks) { + + emptyList(); + + $.each(purchasedJamTracks.jamtracks, function(index, jamTrack) { + + var options = {} + options.jamTrackState = null; + options.jamTrackId = jamTrack.id; + options.name = jamTrack.name; + options.artist = jamTrack.original_artist; + options.downloaded = 'Yes' + + var $tr = $(context._.template($templateOpenJamTrackRow.html(), options, { variable: 'data' })); + $tr.data('server-model', jamTrack); + $tbody.append($tr); + }); + }) + .fail(function(jqXHR, textStatus, errorMessage) { + app.ajaxError(jqXHR, textStatus, errorMessage); + }); + } + + + function registerStaticEvents() { + $tbody.on('click', 'tr', function(e) { + var jamTrack = $(this).data('server-model'); + + // tell the server we are about to start a recording + rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id}) + .done(function(response) { + var result = context.jamClient.JamTrackPlay('t'); + + logger.debug("JamTrackPlay response: %o", result); + + if(result) { + app.layout.closeDialog('open-jam-track-dialog'); + } + else { + logger.error("unable to open jam track") + } + + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to Open JamTrack For Playback"); + }) + + return false; + }) + + context.JK.helpBubble($downloadedTrackHelp, 'downloaded-jamtrack', {}, {width:'400px'}) + $downloadedTrackHelp.on('click', false) + + context.JK.helpBubble($whatAreJamTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog}) + $whatAreJamTracks.on('click', false) // no help yet + } + + function initialize(){ + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterHide': afterHide + }; + + app.bindDialog('open-jam-track-dialog', dialogBindings); + + $dialog = $('#open-jam-track-dialog'); + $tbody = $dialog.find('table.open-jam-tracks tbody'); + $paginatorHolder = $dialog.find('.paginator-holder'); + $templateOpenJamTrackRow = $('#template-jam-track-row') + $downloadedTrackHelp = $dialog.find('.downloaded-jamtrack-help') + $whatAreJamTracks = $dialog.find('.what-are-jamtracks') + + registerStaticEvents(); + }; + + + this.initialize = initialize; + this.isShowing = function isShowing() { return showing; } + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 104911218..230e6d23b 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1157,6 +1157,34 @@ }) } + function openJamTrack(options) { + var musicSessionId = options["id"]; + var jamTrackId = options["jam_track_id"]; + delete options["id"]; + delete options["jam_track_id"]; + + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + url: "/api/sessions/" + musicSessionId + "/jam_tracks/" + jamTrackId + "/open", + data: JSON.stringify(options) + }) + } + + function closeJamTrack(options) { + var musicSessionId = options["id"]; + delete options["id"]; + + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + url: "/api/sessions/" + musicSessionId + "/jam_tracks/close", + data: JSON.stringify(options) + }) + } + function discardRecording(options) { var recordingId = options["id"]; @@ -1319,6 +1347,15 @@ }); } + function getPurchasedJamTracks(options) { + return $.ajax({ + type: "GET", + url: '/api/jamtracks/purchased?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function addJamtrackToShoppingCart(options) { return $.ajax({ type: "POST", @@ -1515,6 +1552,8 @@ this.claimRecording = claimRecording; this.startPlayClaimedRecording = startPlayClaimedRecording; this.stopPlayClaimedRecording = stopPlayClaimedRecording; + this.openJamTrack = openJamTrack; + this.closeJamTrack = closeJamTrack; this.discardRecording = discardRecording; this.putTrackSyncChange = putTrackSyncChange; this.createBand = createBand; @@ -1543,6 +1582,7 @@ this.getLatencyTester = getLatencyTester; this.updateAudioLatency = updateAudioLatency; this.getJamtracks = getJamtracks; + this.getPurchasedJamTracks = getPurchasedJamTracks; this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index ab9d35a01..e355d7cd0 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -1658,7 +1658,7 @@ } function bailOut() { - promptLeave = false; + promptLeave = false; context.window.location = '/client#/home'; } @@ -1796,6 +1796,22 @@ .fail(app.ajaxError); } + function openJamTrack(e) { + // just ignore the click if they are currently recording for now + if(sessionModel.recordingModel.isRecording()) { + app.notify({ + "title": "Currently Recording", + "text": "You can't open a jam track while creating a recording.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + return false; + } + + app.layout.showDialog('open-jam-track-dialog'); + + return false; + } + function openRecording(e) { // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { @@ -1814,6 +1830,36 @@ return false; } + function closeOpenMedia() { + if(sessionModel.recordedTracks()) { + closeRecording(); + } + else if(sessionModel.jamTrack()) { + closeJamTrack(); + } + else { + logger.error("don't know how to close open media (backing track?)"); + } + } + + function closeJamTrack() { + rest.closeJamTrack({id: sessionModel.id()}) + .done(function() { + sessionModel.refreshCurrentSession(); + }) + .fail(function(jqXHR) { + app.notify({ + "title": "Couldn't Close JamTrack", + "text": "Couldn't inform the server to close JamTrack. msg=" + jqXHR.responseText, + "icon_url": "/assets/content/icon_alert_big.png" + }); + }); + + context.jamClient.JamTrackStopPlay(); + + return false; + } + function closeRecording() { rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) .done(function() { @@ -1857,7 +1903,7 @@ } function inviteMusicians() { - friendInput = inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians', + friendInput = inviteMusiciansUtil.inviteSessionUpdate('#update-session-invite-musicians', sessionId); inviteMusiciansUtil.loadFriends(); $(friendInput).show(); @@ -1892,6 +1938,7 @@ $('#tracks').on('click', 'div[control="mute"]', toggleMute); $('#recording-start-stop').on('click', startStopRecording); $('#open-a-recording').on('click', openRecording); + $('#open-a-jamtrack').on('click', openJamTrack); $('#session-invite-musicians').on('click', inviteMusicians); $('#session-invite-musicians2').on('click', inviteMusicians); $('#track-settings').click(function() { @@ -1900,7 +1947,7 @@ configureTrackDialog.showMusicAudioPanel(true); }); - $('#close-playback-recording').on('click', closeRecording); + $('#close-playback-recording').on('click', closeOpenMedia); $(playbackControls) .on('pause', onPause) .on('play', onPlay) diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index a0b549958..052b7d20e 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -105,7 +105,7 @@ /** * Associates a help bubble on hover (by default) with the specified $element, using jquery.bt.js (BeautyTips) * @param $element The element that should show the help when hovered - * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.erb + * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.slim * @param data (optional) data for your template, if applicable * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ @@ -146,7 +146,7 @@ /** * Meant to show a little bubble to confirm something happened, in a way less obtrusive than a app.notify * @param $element The element that should show the help when hovered - * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.erb + * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.slim * @param data (optional) data for your template, if applicable * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ @@ -167,7 +167,7 @@ * This will open a bubble immediately and show it for 4 seconds, * if you call it again before the 4 second timer is up, it will renew the 4 second timer. * @param $element The element that should show the help when hovered - * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.erb + * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.slim * @param data (optional) data for your template, if applicable * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index ca509c714..8c37af233 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -106,6 +106,33 @@ top:3px; } } + + + .session-recording-name-wrapper{ + position:relative; + white-space:nowrap; + display:none; + + .session-add { + margin-top:9px; + } + .session-add a { + vertical-align:top; + outline:none; + + img { + margin-top:-3px; + } + } + } + + .session-recording-name { + width:60%; + overflow:hidden; + margin-top:9px; + margin-bottom:8px; + font-size:16px; + } } @@ -312,33 +339,6 @@ table.vu td { vertical-align:bottom; } -.session-recording-name-wrapper { - position:relative; - white-space:nowrap; - display:none; - - .session-add { - margin-top:9px; - } - .session-add a { - vertical-align:top; - outline:none; - - img { - margin-top:-3px; - } - } - - -} - -.session-recording-name { - width:60%; - overflow:hidden; - margin-top:9px; - margin-bottom:8px; - font-size:16px; -} .session-tracks-scroller { position:relative; diff --git a/web/app/assets/stylesheets/client/sessionList.css.scss b/web/app/assets/stylesheets/client/sessionList.css.scss index b445886e7..a1b65a214 100644 --- a/web/app/assets/stylesheets/client/sessionList.css.scss +++ b/web/app/assets/stylesheets/client/sessionList.css.scss @@ -1,7 +1,7 @@ @import "client/common"; -table.findsession-table, table.local-recordings, #account-session-detail { +table.findsession-table, table.local-recordings, table.open-jam-tracks, #account-session-detail { .latency-unacceptable { width: 50px; @@ -64,7 +64,7 @@ table.findsession-table, table.local-recordings, #account-session-detail { text-align:center; } } -table.findsession-table, table.local-recordings { +table.findsession-table, table.local-recordings, table.open-jam-tracks { width:98%; height:10%; font-size:11px; @@ -77,6 +77,9 @@ table.findsession-table, table.local-recordings { background-color:#4d4d4d; padding:6px; border-right:solid 1px #333; + a { + color: #fc0; + } } td { diff --git a/web/app/assets/stylesheets/dialogs/localRecordingsDialog.css.scss b/web/app/assets/stylesheets/dialogs/localRecordingsDialog.css.scss index c1e5b9225..50fe2e290 100644 --- a/web/app/assets/stylesheets/dialogs/localRecordingsDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/localRecordingsDialog.css.scss @@ -1,16 +1,26 @@ +@import "client/common"; + #local-recordings-dialog { table.local-recordings { tbody { tr:hover { - background-color: #400606; + background-color: #777; cursor:pointer; } tr[data-local-state=MISSING], tr[data-local-state=PARTIALLY_MISSING] { - background-color:#777; + background-color:#400606; color:#aaa; } } } + + .right { + margin-right:10px; + } + + .paginator-holder { + padding-top:3px; + } } diff --git a/web/app/assets/stylesheets/dialogs/openJamTrackDialog.css.scss b/web/app/assets/stylesheets/dialogs/openJamTrackDialog.css.scss new file mode 100644 index 000000000..4ff94bb71 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/openJamTrackDialog.css.scss @@ -0,0 +1,44 @@ +@import "client/common"; + +#open-jam-track-dialog { + table.open-jam-tracks { + tbody { + tr:hover { + background-color: #777; + cursor:pointer; + } + + tr[data-local-state=MISSING], tr[data-local-state=PARTIALLY_MISSING] { + background-color:#777; + color:#aaa; + } + } + } + + .downloaded-jamtrack-help { + margin-left:15px; + } + + .right { + margin-right:10px; + } + + .help-links { + text-align: center; + position: absolute; + margin: 0 auto; + width: 70%; + left: 15%; + font-size: 12px; + padding-top:5px; + + a { + margin:0 10px; + } + } + + .paginator-holder { + padding-top:3px; + } +} + diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index 5ecb29774..cff67d03e 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -13,6 +13,16 @@ class ApiJamTracksController < ApiController render "api_jam_tracks/index", :layout => nil end + def purchased + params[:show_purchased_only] = true + data = JamTrack.index(params, current_user) + @jam_tracks, @next = data[0], data[1] + + response.headers['total-entries'] = @jam_tracks.total_entries.to_s + + render "api_jam_tracks/purchased", :layout => nil + end + def downloads begin render :json => JamTrack.list_downloads(current_user, params[:limit], params[:since]), :status => 200 diff --git a/web/app/views/api_jam_tracks/purchased.rabl b/web/app/views/api_jam_tracks/purchased.rabl new file mode 100644 index 000000000..550ccdaee --- /dev/null +++ b/web/app/views/api_jam_tracks/purchased.rabl @@ -0,0 +1,7 @@ +node :next do |page| + @next +end + +node :jamtracks do |page| + partial "api_jam_tracks/show_for_client", object: @jam_tracks +end \ No newline at end of file diff --git a/web/app/views/api_jam_tracks/show_for_client.rabl b/web/app/views/api_jam_tracks/show_for_client.rabl new file mode 100644 index 000000000..4e708a4ac --- /dev/null +++ b/web/app/views/api_jam_tracks/show_for_client.rabl @@ -0,0 +1,11 @@ +object @jam_track + +attributes :id, :name, :description, :initial_play_silence, :original_artist + +child(:jam_track_tracks => :tracks) { + attributes :id, :part, :instrument +} + +child(:jam_track_tap_ins => :tap_ins) { + attributes :offset_time, :bpm, :tap_in_count +} \ No newline at end of file diff --git a/web/app/views/clients/_help.html.erb b/web/app/views/clients/_help.html.erb deleted file mode 100644 index 102439f70..000000000 --- a/web/app/views/clients/_help.html.erb +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim new file mode 100644 index 000000000..1f0efda08 --- /dev/null +++ b/web/app/views/clients/_help.html.slim @@ -0,0 +1,206 @@ +script type="text/template" id="template-help-pre-processed-track" + | This track has not yet been processed into master form and may include multiple streams from the source musician. + +script type="text/template" id="template-help-refocus-rescore" + | {% if(data.validIOScore) { %} + | We have re-scored latency based on your changes. + | {% } else { %} + | We have re-scored latency and IO based on your changes. + | {% } %} + +script type="text/template" id="template-help-ftue-watch-video" + | Be sure to watch the help video. + +script type="text/template" id="template-help-chat-not-enabled" + | You must first chose this option in order to activate a chat input. + +script type="text/template" id="template-help-select-input" + | You still need to select an input device. + +script type="text/template" id="template-help-select-output" + | Select an output device, because the current input device has no candidate output ports. + +script type="text/template" id="template-help-push-resync-when-done" + | Push 'Resync' when done modifying Framesize, Buffer In, or Buffer Out. + +script type="text/template" id="template-help-can-move-on" + .help-can-move-on + | You can move to the next step now. + +script type="text/template" id="template-help-tweak-asio-settings" + | Click here to try faster ASIO settings. + +script type="text/template" id="template-help-session-plus-musicians" + | Plus any interested JamKazam musicians that I approve. + +script type="text/template" id="template-help-minimum-output-channels" + | To be a valid output audio device, it must have at least 2 output ports. + +script type="text/template" id="template-help-follower-count" + | The number of followers that this {{data.entity_type}} has. + +script type="text/template" id="template-help-friend-count" + | The number of friends that this {{data.entity_type}} has. + +script type="text/template" id="template-help-recording-count" + | The number of recordings that this {{data.entity_type}} has made. + +script type="text/template" id="template-help-session-count" + | The number of sessions that this {{data.entity_type}} has played in. + +script type="text/template" id="template-help-musician-score-count" + .help-musician-score-count + p + | The score shown is the one-way latency (or delay) in milliseconds from you to this user. This score is calculated using the following three values that JamKazam gathers: + ul + li + span.definition + | Your Audio Gear Latency:  + span.measurement.my-gear-latency.partial + span.measurement-value + | {{data.my_gear_latency ? data.my_gear_latency + ' ms': '13 ms*'}} + span.measurement-absent + | {{data.my_gear_latency ? '' : "(you have not qualified any gear, so we picked an average gear latency)"}} + + li + span.definition + | Their Audio Gear Latency:  + span.measurement.their-gear-latency.partial + span.measurement-value + | {{data.their_gear_latency ? data.their_gear_latency + ' ms': '13 ms*'}} + span.measurement-absent + | {{data.their_gear_latency ? '' : "(they have not qualified any gear, so we picked an average gear latency)"}} + li + span.definition + | Round-trip Internet Latency:  + span.measurement.internet-latency.partial + span.measurement-value + | {{data.internet_latency ? data.internet_latency + ' ms': '?'}} + span.measurement-absent + | {{data.internet_latency ? '' : "(we have not scored you with this user yet)"}} + p + span.definition + | Total One-Way Latency:  + span.measurement.my-gear-latency + span.measurement-value + | ( {{data.my_gear_latency ? data.my_gear_latency: '13'}} + {{data.their_gear_latency ? data.their_gear_latency: '13'}} + {{data.internet_latency ? data.internet_latency: '?'}} ) / 2 = {{data.full_score ? data.full_score + ' ms' : "?"}} + span.measurement-absent + | {{data.full_score ? '' : "(when we don't know internet latency, we don't try to guess your one-way latency)"}} + + p + | We categorize this score as good, fair, poor, unacceptable, or unknown. Those categories are defined as follows: + ul + li + span.definition + | Good:  + | 20ms or less  + img src="/assets/content/icon_green_score.png" + li + span.definition + | Fair:  + | 20ms to 35ms + img src="/assets/content/icon_yellow_score.png" + li + span.definition + | Poor:  + | 35ms to 50ms  + img src="/assets/content/icon_red_score.png" + li + span.definition + | Unacceptable:  + | Above 50ms  + img src="/assets/content/icon_blue_score.png" + li + span.definition + | Unknown:  + | No internet score is available between you and them. + img src="/assets/content/icon_purple_score.png" + +script type="text/template" id="template-help-musician-score-self" + .help-musician-score-self + p + | You are looking at your own account. + p + | Try hovering over other user's latency score to find out your one-way latency to them. + p + span.definition + | Your Audio Gear Latency:  + span.measurement.my-gear-latency + span.measurement-value + | {{data.my_gear_latency ? data.my_gear_latency + ' ms': '13 ms*'}} + span.measurement-absent + | {{data.my_gear_latency ? '' : "(you have not qualified any gear, so we picked an average gear latency)"}} + +script type="text/template" id="template-help-gear-wizard-inputs-changed" + .help-inputs-changed + | {% if(data.missingInputDevice) { %} + p + | {{ data.missingInputDevice }} is no longer connected or malfunctioning. + | {% } %} + | {% if(data.newInputDevice) { %} + p + | {{ data.newInputDevice }} is now available. + | {% } %} + + +script type="text/template" id="template-help-gear-wizard-outputs-changed" + .help-outputs-changed + | {% if(data.missingOutputDevice) { %} + p + | {{ data.missingOutputDevice }} is no longer connected or malfunctioning. + | {% } %} + | {% if(data.newOutputDevice) { %} + p + | {{ data.newOutputDevice }} is now available. + | {% } %} + + +script type="text/template" id="template-help-no-audio-profiles" + .help-no-audio-profiles + | Click here to configure new audio gear. + +script type="text/template" id="template-help-file-manager-poke" + .help-file-manager-poke + p + | After the session is over, your recording will synchronize with the server to make a final mix. + p + | You can find out more by clicking + em  File Manager  + | at any time. + +script type="text/template" id="template-help-sync-viewer-paused" + .help-sync-viewer-paused + | JamKazam prevents file uploads, downloads, and recording management while in a session. + +script type="text/template" id="template-help-sync-viewer-retry" + .help-sync-viewer-retry + | Your request will be attempted as soon as possible. + +script type="text/template" id="template-help-recording-discarded-soon" + .help-recording-discarded-soon + | This recording will be discarded {% $.timeago(data.discardTime) %}. If you want to keep this recording, click the (edit) link. + +script type="text/template" id="template-help-command-enqueued" + .help-recording-command-enqueued + | Your request will be executed as soon as possible. + +script type="text/template" id="template-help-file-sync-delayed-deletion" + .file-sync-delayed-deletion + | The files associated with this recording will be deleted as soon as your client has uploaded your tracks and stream mix from this recording. + +script type="text/template" id="template-help-media-controls-disabled" + | {% if(data.mediaTrackOpener) { %} + | Switch + b  MIX:  + | to + b  Master  + | mode to have control over volume levels. + | {% } else { %} + | Only the person who opened the recording can control the volume levels. + | {% } %} + +script type="text/template" id="template-help-downloaded-jamtrack" + .downloaded-jamtrack + p When a JamTrack is first purchased, a user-specific version of it is created on the server. Once it's ready, it's then downloaded to the client. + p However, in some cases, you may need to download the JamTrack again (if you change machines, for instance). + p If you do not currently have it and try to open it now, we will try to download it immediately. diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 7e25ae1e7..900079c8f 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -116,8 +116,10 @@ <%= image_tag "content/icon_folder.png", {width:22, height:20} %> Open: diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 0fb57a248..92d50feda 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -152,6 +152,9 @@ var localRecordingsDialog = new JK.LocalRecordingsDialog(JK.app); localRecordingsDialog.initialize(); + var openJamTrackDialog = new JK.OpenJamTrackDialog(JK.app); + openJamTrackDialog.initialize(); + var configureTracksDialog = new JK.ConfigureTracksDialog(JK.app); configureTracksDialog.initialize(); diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 94a8469d9..b7b15976c 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -31,3 +31,4 @@ = render 'dialogs/changeSearchLocationDialog' = render 'dialogs/allSyncsDialog' = render 'dialogs/adjustGearSpeedDialog' += render 'dialogs/openJamTrackDialog' diff --git a/web/app/views/dialogs/_localRecordingsDialog.html.erb b/web/app/views/dialogs/_localRecordingsDialog.html.erb index f1234de60..2bf71bc03 100644 --- a/web/app/views/dialogs/_localRecordingsDialog.html.erb +++ b/web/app/views/dialogs/_localRecordingsDialog.html.erb @@ -1,4 +1,3 @@ -
diff --git a/web/app/views/dialogs/_openJamTrackDialog.html.slim b/web/app/views/dialogs/_openJamTrackDialog.html.slim new file mode 100644 index 000000000..18ca215a7 --- /dev/null +++ b/web/app/views/dialogs/_openJamTrackDialog.html.slim @@ -0,0 +1,50 @@ +.dialog.openJamTrackDialog-overlay.ftue-overlay.tall#open-jam-track-dialog layout="dialog" layout-id="open-jam-track-dialog" + + .content-head + = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } + h1 + | open a jamtrack + + .dialog-inner + + .recording-wrapper + table.open-jam-tracks cellspacing="0" cellpadding="0" border="0" + thead + tr + th align="left" + | NAME + th align="left" + | ORIGINAL ARTIST + th align="left" + | DOWNLOADED + a.downloaded-jamtrack-help href="#" + | ? + tbody + + br + + .left.paginator-holder + + .help-links + a.what-are-jamtracks href='#' + | What are JamTracks? + a href='/client#/jamtrack' rel="external" + | Shop for JamTracks + .right + a href="#" class="button-grey" layout-action="close" + | CANCEL + + + br clear="all" + + + script#template-jam-track-row type="text/template" + tr data-recording-id="{{data.jamTrackId}}" data-local-state="{{data.jamTrackState}}" + td + | {{data.name}} + td + | {{data.artist}} + td + | {{data.downloaded}} + + diff --git a/web/config/routes.rb b/web/config/routes.rb index e0642c69d..f30e06ff7 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -193,6 +193,7 @@ SampleApp::Application.routes.draw do # Jamtracks match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list' + match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased' match '/jamtracks/downloads' => 'api_jam_tracks#downloads', :via => :get, :as => 'api_jam_tracks_downloads' match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download' match '/jamtracks/keys' => 'api_jam_tracks#keys', :via => :post, :as => 'api_jam_tracks_keys' diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index ddb44ab32..1a9d9b949 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -82,8 +82,27 @@ describe ApiJamTracksController do response.status.should == 202 response.body.should =~ /not available.*/ end + end - + describe "purchased" do + it "can return empty" do + get :purchased + response.should be_success + json = JSON.parse(response.body) + json['jamtracks'].length.should eq(0) + json['next'].should eq(nil) + end + + it "can return single item" do + # purchase the item for the user + right = FactoryGirl.create(:jam_track_right, jam_track: @jam_track, user: @user) + + get :purchased + response.should be_success + json = JSON.parse(response.body) + json['jamtracks'].length.should eq(1) + json['next'].should be_nil + end end describe "with a JamTrack" do