Compare commits

...

15 Commits

Author SHA1 Message Date
Seth Call 8fc54eae06 * fix retry button 2015-08-18 14:21:55 -05:00
Seth Call e6ce9d1d02 * dont clear search results when you leave search page 2015-08-18 10:52:14 -05:00
Seth Call 537e4d3de4 * wip 2015-08-18 10:47:36 -05:00
Seth Call 63369475a3 * wip 2015-08-18 10:14:00 -05:00
Seth Call 9a73bec85b * wip 2015-08-17 16:09:35 -05:00
Seth Call 9402d55d21 * wip 2015-08-17 15:35:03 -05:00
Seth Call 276c9b1a53 * wip 2015-08-15 04:13:53 -05:00
Seth Call 4c1ca84996 * wip 2015-08-14 14:24:44 -05:00
Seth Call 45a8a6897c * wip 2015-08-14 10:35:18 -05:00
Seth Call 303e186eff * wip 2015-08-13 11:26:33 -05:00
Seth Call c1e9e02647 * wip 2015-08-13 11:09:43 -05:00
Seth Call 5f014b4139 * wip 2015-08-12 10:09:15 -05:00
Seth Call ed30cfc921 * wip 2015-08-12 07:43:07 -05:00
Seth Call 76b2710b84 * wip 2015-08-11 10:33:03 -05:00
Seth Call 0bb635cf5d * wip 2015-08-11 05:49:48 -05:00
86 changed files with 2980 additions and 700 deletions

View File

@ -299,4 +299,5 @@ enhance_band_profile.sql
alter_band_profile_rate_defaults.sql
repair_band_profile.sql
jam_track_onboarding_enhancements.sql
jam_track_name_drop_unique.sql
jam_track_name_drop_unique.sql
jam_track_searchability.sql

View File

@ -0,0 +1,28 @@
ALTER TABLE jam_tracks ADD COLUMN search_tsv tsvector;
ALTER TABLE jam_tracks ADD COLUMN artist_tsv tsvector;
ALTER TABLE jam_tracks ADD COLUMN name_tsv tsvector;
CREATE FUNCTION jam_tracks_update_tsv() RETURNS TRIGGER AS $$
BEGIN
new.search_tsv = to_tsvector('public.jamenglish', COALESCE(NEW.original_artist, '') || ' ' || COALESCE(NEW.name, '') || ' ' || COALESCE(NEW.additional_info, ''));
new.artist_tsv = to_tsvector('public.jamenglish', COALESCE(NEW.original_artist, ''));
new.name_tsv = to_tsvector('public.jamenglish', COALESCE(NEW.name, ''));
RETURN NEW;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON jam_tracks FOR EACH ROW EXECUTE PROCEDURE
jam_tracks_update_tsv();
CREATE INDEX jam_tracks_search_tsv_index ON jam_tracks USING gin(search_tsv);
CREATE INDEX jam_tracks_artist_tsv_index ON jam_tracks USING gin(artist_tsv);
CREATE INDEX jam_tracks_name_tsv_index ON jam_tracks USING gin(name_tsv);
CREATE INDEX jam_tracks_name_key ON jam_tracks USING btree (name);
CREATE INDEX jam_tracks_original_artist_key ON jam_tracks USING btree (original_artist);
CREATE INDEX jam_tracks_status_key ON jam_tracks USING btree (status);
UPDATE jam_tracks SET original_artist=original_artist, name=name, additional_info=additional_info;

View File

@ -182,6 +182,10 @@ module JamRuby
finish("success", nil)
end
def add_vendor_metadata(metalocation)
end
def is_tency_storage?
assert_storage_set
@storage_format == 'Tency'

View File

@ -215,6 +215,26 @@ module JamRuby
JamTrack.where("original_artist=?", artist_name).all
end
# special case of index
def autocomplete(options, user)
if options[:match].blank?
return {artists: [], songs: []}
end
options[:limit] = options[:limit] || 5
options[:artist_search] = options[:match]
artists, pager = artist_index(options, user)
options.delete(:artist_search)
options[:song_search] = options[:match]
options[:sort_by] = 'jamtrack'
songs, pager = index(options, user)
{artists: artists, songs:songs}
end
def index(options, user)
if options[:page]
page = options[:page].to_i
@ -252,6 +272,27 @@ module JamRuby
query = query.where("jam_track_rights.user_id = ?", user.id)
end
if options[:search]
tsquery = Search.create_tsquery(options[:search])
if tsquery
query = query.where("(search_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:artist_search]
tsquery = Search.create_tsquery(options[:artist_search])
if tsquery
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:song_search]
tsquery = Search.create_tsquery(options[:song_search])
if tsquery
query = query.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:artist].present?
query = query.where("original_artist=?", options[:artist])
end
@ -266,7 +307,13 @@ module JamRuby
query = query.order('jam_tracks.original_artist')
else
query = query.group("jam_tracks.id")
query = query.order('jam_tracks.original_artist, jam_tracks.name')
if options[:sort_by] == 'jamtrack'
query = query.order('jam_tracks.name')
else
query = query.order('jam_tracks.original_artist, jam_tracks.name')
end
end
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
@ -279,13 +326,14 @@ module JamRuby
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type != 'Master'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
count = query.total_entries
if query.length == 0
[query, nil]
if count == 0
[query, nil, count]
elsif query.length < limit
[query, nil]
[query, nil, count]
else
[query, start + limit]
[query, start + limit, count]
end
end
@ -327,6 +375,14 @@ module JamRuby
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
if options[:artist_search]
tsquery = Search.create_tsquery(options[:artist_search])
if tsquery
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
unless options[:genre].blank?
query = query.joins(:genres)
query = query.where('genre_id = ? ', options[:genre])

View File

@ -139,6 +139,7 @@ module JamRuby
args = args + ":*"
args
end
def order_param(params, keys=M_ORDERING_KEYS)
ordering = params[:orderby]
ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering }

View File

@ -159,8 +159,8 @@ describe JamTrack do
describe "index" do
it "empty query" do
query, pager = JamTrack.index({}, user)
query.size.should == 0
query, pager, count = JamTrack.index({}, user)
count.should == 0
end
it "sorts by name" do
@ -190,24 +190,24 @@ describe JamTrack do
jam_track1.save!
jam_track2.save!
query, pager = JamTrack.index({genre: 'rock'}, user)
query.size.should == 1
query, pager, count = JamTrack.index({genre: 'rock'}, user)
count.should == 1
query[0].should eq(jam_track1)
query, pager = JamTrack.index({genre: 'asian'}, user)
query.size.should == 1
query, pager, count = JamTrack.index({genre: 'asian'}, user)
count.should == 1
query[0].should eq(jam_track2)
query, pager = JamTrack.index({genre: 'african'}, user)
query.size.should == 0
query, pager, count = JamTrack.index({genre: 'african'}, user)
count.should == 0
end
it "supports showing purchased only" do
jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'a')
# no results yet
query, pager = JamTrack.index({show_purchased_only:true}, user)
query.size.should == 0
query, pager, count = JamTrack.index({show_purchased_only:true}, user)
count.should == 0
# but after the user buys it, it is returned
FactoryGirl.create(:jam_track_right, jam_track: jam_track1, user: user)
@ -215,6 +215,25 @@ describe JamTrack do
query.size.should == 1
query[0].should eq(jam_track1)
end
it "full text search" do
jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'Take a Chance On Me', original_artist: 'ABBA')
jam_track2 = FactoryGirl.create(:jam_track_with_tracks, name: 'Nothing Chance', original_artist: 'ABBA')
query, pager = JamTrack.index({search: 'Take'}, user)
query.size.should == 1
query[0].should eq(jam_track1)
query, pager = JamTrack.index({search: 'ABB'}, user)
query.size.should == 2
query, pager = JamTrack.index({search: 'Chance'}, user)
query.size.should == 2
query, pager = JamTrack.index({search: 'Chan'}, user)
query.size.should == 2
end
end
describe "validations" do

View File

@ -97,6 +97,7 @@ gem 'react-rails', '~> 1.0'
source 'https://rails-assets.org' do
gem 'rails-assets-reflux'
gem 'rails-assets-classnames'
gem 'rails-assets-react-select'
end
#group :development, :production do

View File

@ -24,7 +24,6 @@
var $templateOpenSlots = null;
var $templateAccountPendingRsvp = null;
var $templateAccountSessionDetail = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
var invitationDialog = null;
var inviteMusiciansUtil = null;
var friendInput=null;

View File

@ -111,7 +111,7 @@
}
})
.fail(function() {
window.location = '/client#/jamtrackBrowse'
window.location = '/client#/jamtrack/search'
window.location.reload();
})
})

View File

@ -29,7 +29,7 @@ class CheckoutUtils
@logger.debug("deleted preserve billing");
unless $.cookie(@cookie_name)?
if $.cookie(@cookie_name)?
@logger.error("after deleting the preserve billing cookie, it still exists!")

View File

@ -50,7 +50,7 @@
$browserJamTrackBtn.click(function() {
app.layout.closeDialog('getting-started')
window.location = '/client#/jamtrackBrowse'
window.location = '/client#/jamtrack/search'
return false;
})

View File

@ -13,9 +13,58 @@
var $templateOpenJamTrackRow = null;
var $downloadedTrackHelp = null;
var $whatAreJamTracks = null;
var $searchBtn = null;
var sampleRate = null;
var sampleRateForFilename = null;
var searchQuery = null;
var cookieName = 'jamtrack_session_search'
function search(searchType, searchData) {
window.JamTrackSearchInput = searchData;
searchQuery = {searchType: searchType, searchData: searchData}
$.cookie(cookieName, JSON.stringify(searchQuery))
doSearch();
}
function userSearch(e) {
e.preventDefault();
searchQuery = {searchType: 'user-input', searchData: window.JamTrackSearchInput}
$.cookie(cookieName, JSON.stringify(searchQuery))
doSearch();
}
function doSearch() {
emptyList();
resetPagination();
app.user().done(function(user) {
if (user.purchased_jamtracks_count > perPage) {
searchQuery = $.cookie(cookieName)
if (!searchQuery) {
searchQuery = {searchType: 'user-input', searchData: ''}
}
else {
try {
searchQuery = JSON.parse(searchQuery)
}
catch (e) {
searchQuery = {searchType: 'user-input', searchData: ''}
logger.error("unable to parse search query: " + e)
}
}
getPurchasedJamTracks(0)
.done(function (data, textStatus, jqXHR) {
// initialize pagination
var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected, 20)
$paginatorHolder.append($paginator);
});
}
})
}
function emptyList() {
$tbody.empty();
@ -30,19 +79,13 @@
}
function afterShow() {
$dialog.data('result', null)
emptyList();
resetPagination();
showing = true;
sampleRate = context.jamClient.GetSampleRate()
sampleRateForFilename = sampleRate == 48 ? '48' : '44';
doSearch();
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() {
@ -55,7 +98,21 @@
}
function getPurchasedJamTracks(page) {
return rest.getPurchasedJamTracks({page:page + 1, per_page:10})
var query = {page:page + 1, per_page:10}
if (searchQuery && searchQuery.searchData && searchQuery.searchData.length > 0 && searchQuery.searchType && searchQuery.searchType.length > 0) {
if (searchQuery.searchType == 'user-input') {
query.search = searchQuery.searchData
}
else if(searchQuery.searchType == 'artist-select') {
query.artist_search = searchQuery.searchData
}
else if(searchQuery.searchType == 'song-select') {
query.song_search = searchQuery.searchData
}
}
return rest.getPurchasedJamTracks(query)
.done(function(purchasedJamTracks) {
emptyList();
@ -104,6 +161,8 @@
context.JK.helpBubble($whatAreJamTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog})
$whatAreJamTracks.on('click', false) // no help yet
$searchBtn.on('click', userSearch)
}
function initialize(){
@ -121,6 +180,7 @@
$templateOpenJamTrackRow = $('#template-jam-track-row')
$downloadedTrackHelp = $dialog.find('.downloaded-jamtrack-help')
$whatAreJamTracks = $dialog.find('.what-are-jamtracks')
$searchBtn = $dialog.find('.search-btn')
registerStaticEvents();
};
@ -128,6 +188,7 @@
this.initialize = initialize;
this.isShowing = function isShowing() { return showing; }
this.search = search;
}
return this;

View File

@ -243,6 +243,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
@logger.debug "downloadCheck"
retry: () =>
@logger.debug "user initiated retry"
@path = []
@path.push('retry')
this.clear()

View File

@ -43,6 +43,23 @@
context.JK.dropdown($('select', parentSelector));
}
function render2($select, notSelectedString) {
if(!notSelectedString) {
notSelectedString = 'Any Genre'
}
$select.empty();
$select.append('<option value="">' + notSelectedString + '</option>');
var template = $('#template-genre-option').html();
$.each(_genres, function(index, value) {
// value will be a dictionary entry from _genres:
// { value: xxx, label: yyy }
var genreOptionHtml = context.JK.fillTemplate(template, value);
$select.append(genreOptionHtml);
});
context.JK.dropdown($select);
}
function getSelectedGenres(parentSelector) {
var selectedGenres = [];
var selectedVal = $('select', parentSelector).val();
@ -112,6 +129,10 @@
render: function() {
var _args = arguments;
context.JK.GenreSelectorDeferred.done(function(){render.apply(self, _args)})
},
render2: function() {
var _args = arguments;
context.JK.GenreSelectorDeferred.done(function(){render2.apply(self, _args)})
}
};

View File

@ -9,6 +9,7 @@
var logger = context.JK.logger;
var rest = new context.JK.Rest();
var _instruments = []; // will be list of structs: [ {label:xxx, value:yyy}, {...}, ... ]
var _instrumentsSorted = [];
var _rsvp = false;
var _noICheck = false;
if (typeof(_parentSelector)=="undefined") {_parentSelector=null}
@ -35,6 +36,17 @@
label: this.description
});
});
_instrumentsSorted = _instruments.slice().sort(sortAlpha)
}
function sortAlpha(a, b) {
if (a.value == b.value)
return 0;
if (a.value < b.value)
return -1;
else
return 1;
}
function render(parentSelector, userInstruments) {
@ -85,6 +97,23 @@
}
function renderDropdown($select, notSelectedString) {
if(!notSelectedString) {
notSelectedString = 'Any Instrument'
}
$select.empty();
$select.append('<option value="">' + notSelectedString + '</option>');
var template = $('#template-instrument-option-simple').html();
$.each(_instrumentsSorted, function(index, value) {
// value will be a dictionary entry from _genres:
// { value: xxx, label: yyy }
var instrumentOptionHtml = context.JK.fillTemplate(template, value);
$select.append(instrumentOptionHtml);
});
context.JK.dropdown($select);
}
function getSelectedInstruments() {
var selectedInstruments = [];
var $selectedVal = $('input[type="checkbox"]:checked', _parentSelector);
@ -152,6 +181,10 @@
var _args = arguments;
context.JK.InstrumentSelectorDeferred.done(function(){render.apply(self, _args)})
}
this.renderDropdown = function() {
var _args = arguments;
context.JK.InstrumentSelectorDeferred.done(function(){renderDropdown.apply(self, _args)})
}
});
})(window,jQuery);

View File

@ -1576,6 +1576,15 @@
});
}
function autocompleteJamTracks(options) {
return $.ajax({
type: "GET",
url: '/api/jamtracks/autocomplete?' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
}
function getJamTrackArtists(options) {
return $.ajax({
type: "GET",
@ -1649,14 +1658,15 @@
type: "POST",
url: '/api/shopping_carts/add_jamtrack?' + $.param(options),
dataType: "json",
contentType: 'applications/json'
contentType: 'application/json'
});
}
function getShoppingCarts() {
// the need for the time de-duplicator indicates we are doing something wrong on the server
return $.ajax({
type: "GET",
url: '/api/shopping_carts',
url: '/api/shopping_carts?time=' + new Date().getTime(),
dataType: "json",
contentType: 'application/json'
});
@ -1966,6 +1976,7 @@
this.getJamTrack = getJamTrack;
this.getJamTrackWithArtistInfo = getJamTrackWithArtistInfo;
this.getJamTracks = getJamTracks;
this.autocompleteJamTracks = autocompleteJamTracks;
this.getJamTrackArtists = getJamTrackArtists;
this.getPurchasedJamTracks = getPurchasedJamTracks;
this.getPaymentHistory = getPaymentHistory;

View File

@ -109,9 +109,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@sound.unload()
removeNowPlaying: () =>
context.JK.JamTrackPreview.nowPlaying.splice(this)
if context.JK.JamTrackPreview.nowPlaying.length > 0
@logger.warn("multiple jamtrack previews playing")
context.JamTrackPreviewActions.stoppedPlaying(this)
onHowlerEnd: () =>
@ -153,10 +151,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@logger.debug("play issued for jam track preview")
@sound.play()
for playingSound in context.JK.JamTrackPreview.nowPlaying
playingSound.issueStop()
context.JK.JamTrackPreview.nowPlaying = []
context.JK.JamTrackPreview.nowPlaying.push(this)
context.JamTrackPreviewActions.startedPlaying(this)
@playButton.addClass('hidden')
@stopButton.removeClass('hidden')
@ -182,7 +177,4 @@ context.JK.JamTrackPreview = class JamTrackPreview
return false
context.JK.JamTrackPreview.nowPlaying = []

View File

@ -180,7 +180,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
else
@availability.val('')
if window.history.replaceState #ie9 proofing
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrackBrowse")
getParams:() =>
@ -448,6 +448,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
this.handleExpanded(jamtrackRecord)
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow
'afterShow': this.afterShow

View File

@ -17,7 +17,8 @@ context.JK.JamTrackLanding = class JamTrackLanding
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrackLanding', screenBindings)
#@app.bindScreen('jamtrackLanding', screenBindings)
@screen = $('#jamtrackLanding')
@noFreeJamTrack = @screen.find('.no-free-jamtrack')
@freeJamTrack = @screen.find('.free-jamtrack')
@ -64,14 +65,14 @@ context.JK.JamTrackLanding = class JamTrackLanding
# client#/jamtrack
for artist in artists
artistLink = "<a href='client?artist=#{encodeURIComponent(artist.original_artist)}#/jamtrackBrowse' class='artist-link' artist='#{artist.original_artist}'>#{artist.original_artist} (#{artist.song_count})</a>"
artistLink = "<a href='client?artist=#{encodeURIComponent(artist.original_artist)}#/jamtrack/search' class='artist-link' artist='#{artist.original_artist}'>#{artist.original_artist} (#{artist.song_count})</a>"
@bandList.append("<li>#{artistLink}</li>")
# We don't want to do a full page load if this is clicked on here:
bindArtistLinks:() =>
that=this
@bandList.on "click", "a.artist-link", (event)->
context.location="client#/jamtrackBrowse"
context.location="client#/jamtrack/search"
if window.history.replaceState # ie9 proofing
window.history.replaceState({}, "", this.href)
event.preventDefault()

View File

@ -577,6 +577,8 @@
$(document).triggerHandler(EVENTS.SCREEN_CHANGED, {previousScreen: previousScreen, newScreen: currentScreen})
context.JamTrackPreviewActions.screenChange()
screenEvent(currentScreen, 'beforeShow', data);
// For now -- it seems we want it open always.

View File

@ -18,7 +18,7 @@
* @param onPageSelected when a new page is selected. receives one argument; the page number.
* the function should return a deferred object (whats returned by $.ajax), and that response has to have a 'total-entries' header set
*/
create:function(totalEntries, perPage, currentPage, onPageSelected) {
create:function(totalEntries, perPage, currentPage, onPageSelected, maxPages) {
if(this.$templatePaginator === null) {
this.$templatePaginator = $('#template-paginator')
@ -100,6 +100,13 @@
var pages = calculatePages(totalEntries, perPage);
if(maxPages) {
if((totalEntries / perPage) > maxPages) {
pages = calculatePages(maxPages * perPage, perPage);
}
}
var options = { pages: pages,
currentPage: currentPage };

View File

@ -1,3 +1,5 @@
//= require react-input-autosize
//= require react-select
//= require_directory ./react-components/helpers
//= require_directory ./react-components/actions
//= require ./react-components/stores/AppStore
@ -13,4 +15,5 @@
//= require_directory ./react-components/stores
//= require_directory ./react-components/mixins
//= require_directory ./react-components
//= require_directory ./react-components/landing
//= require_directory ./react-components/landing

View File

@ -0,0 +1,106 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackAutoComplete = React.createClass({
EVENTS: context.JK.EVENTS
rest: context.JK.Rest()
logger: context.JK.logger
render: () ->
window.JamTrackSearchInput = '' unless window.JamTrackSearchInput? # can't pass null to react-select
searchValue = if @state.search == 'SEPARATOR' then '' else window.JamTrackSearchInput
`<Select
placeholder="Search for JamTracks"
name="search-field"
asyncOptions={this.getOptions}
autoload={false}
value={searchValue}
onChange={this.onSelectChange}
onBlur={this.onSelectBlur}
onFocus={this.onSelectFocus}
className="autocompleter"
cacheAsyncResults={false}
filterOption={this.filterOption}
clearable={false}
/>`
getInitialState: () ->
({search: ''})
filterOption:() ->
true
onSelectChange: (val) ->
#@logger.debug("CHANGE #{val}")
return false unless val?
if typeof @props.onSearch is 'string'
searchFunction = eval(@props.onSearch)
else
searchFunction = @props.onSearch
search_type
if val.indexOf('ARTIST=') == 0
search_type = 'artist-select'
artist = val['ARTIST='.length..-1]
searchFunction(search_type, artist)
else if val.indexOf('SONG=') == 0
search_type = 'song-select'
song = val['SONG='.length..-1]
searchFunction(search_type, song)
else
@logger.debug("user selected separator")
# this is to signal to the component that the separator was selected, and it has code in render to negate the selection
setTimeout((() =>
@setState({search:val})
), 1)
return false
onSelectFocus: (e) ->
e.preventDefault()
window.JamTrackSearchInput = ''
@setState({search:''})
onSelectBlur: (e) ->
#@logger.debug("blur time")
#@search()
getOptions: (input, callback) =>
#@logger.debug("getOptions input #{input}", this)
# sigh. ugly global
window.JamTrackSearchInput = input
if !input? || input.length == 0
callback(null, {options: [], complete: false})
return
@rest.autocompleteJamTracks({match:input, limit:5})
.done((autocomplete) =>
options = []
for artist in autocomplete.artists
options.push { value: "ARTIST=#{artist.original_artist}", label: "Artist: #{artist.original_artist}" }
if options.length > 0 && autocomplete.songs.length > 0
options.push { value: 'SEPARATOR', label: "---------------"}
for jamtrack in autocomplete.songs
options.push { value: "SONG=#{jamtrack.name}", label: "Song: #{jamtrack.name}" }
callback(null, {options: options, complete: false})
)
})

View File

@ -0,0 +1,398 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackFilterScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
LIMIT: 20
instrument_logo_map: context.JK.getInstrumentIconMap24()
computeWeight: (jam_track_track, instrument) ->
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position
else 10000 + jam_track_track.position
render: () ->
searchText = if @state.first_search then 'SEARCH' else 'SEARCH AGAIN'
uiJamTracks = []
for jamtrack in @state.jamtracks
trackRow = context._.clone(jamtrack)
trackRow.track_cnt = jamtrack.tracks.length
trackRow.tracks = []
# if an instrument is selected by the user, then re-order any jam tracks with a matching instrument to the top
instrument = @instrument.val() if @instrument?
if instrument?
jamtrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a, instrument)
bWeight = @computeWeight(b, instrument)
return aWeight - bWeight
)
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
trackRow.free_state = if context.JK.currentUserFreeJamTrack then 'free' else 'non-free'
trackRow.is_free = trackRow.free_state == 'free'
uiJamTracks.push trackRow
jamtracks = []
for jamtrack in uiJamTracks
jamtrackPricesClasses = { "jamtrack-price" : true }
jamtrackPricesClasses[jamtrack.free_state] = true
jamtrackPricesClasses = classNames(jamtrackPricesClasses)
tracks = []
for track in jamtrack.tracks
tracks.push `<div className="jamtrack-track hidden" key={track.id} data-jamtrack-track-id={track.id}>
<div className="jamtrack-preview">
<JamTrackPreview jamTrack={jamtrack} jamTrackTrack={track} options={{master_shows_duration: true, color:'gray'}} />
<div className="clearall" />
</div>
</div>`
actionBtn = null
if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
actionBtn = `<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id}>ADD TO CART</a>`
availabilityNotice = null
if jamtrack.sales_region==context.JK.AVAILABILITY_US
availabilityNotice =
`<div className="jamtrack-license">
This JamTrack available only to US customers. &nbsp;&nbsp;&nbsp;&nbsp;
<a className="license-us-why" href="#">why?</a>
</div>`
jamtracks.push `<tr className="jamtrack-record" key={jamtrack.id} data-jamtrack-id={jamtrack.id}>
<td className="jamtrack-detail">
<div className="jamtrack-name">"{jamtrack.name}"</div>
<div className="jamtrack-original-artist">by {jamtrack.original_artist}</div>
<br className="clearall"/>
<div className="clearall detail-label extra hidden song-writer">Songwriters:</div>
<div className="detail-value extra hidden">{jamtrack.songwriter}</div>
<div className="clearall detail-label extra hidden">Publishers:</div>
<div className="detail-value extra hidden">{jamtrack.publisher}</div>
<div className="clearall detail-label extra hidden">Genres:</div>
<div className="detail-value extra hidden">{jamtrack.genres.join(', ')}</div>
<div className="clearall detail-label extra hidden">Version:</div>
<div className="detail-value extra hidden">{jamtrack.recording_type}</div>
</td>
<td className="jamtrack-tracks">
<div className="detail-arrow">
<div className="jamtrack-detail-btn">
show all tracks <a className="details-arrow arrow-down"/>
</div>
</div>
{tracks}
</td>
<td className="jamtrack-action">
<div className="jamtrack-action-container">
<div className="jamtrack-actions">
<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>
{actionBtn}
{availabilityNotice}
</div>
</div>
</td>
</tr>`
if @state.searching
jamtracksHeader = "searching..."
else
jamtracksHeader = "search results: #{@state.count} jamtracks"
jamTracksSection =
`<div>
<h2 className="jamtrack-results-header">{jamtracksHeader} <a className="back-to-jamtracks-home" href="/client#/jamtrack">back to jamtracks home</a></h2>
<table className="generaltable jamtrack-table">
<thead>
<tr>
<th className="jamtrack-detail">JAMTRACK</th>
<th className="jamtrack-tracks">TRACKS INCLUDED / PREVIEW</th>
<th className="jamtrack-shop">SHOP</th>
</tr>
</thead>
<tbody className="jamtrack-content">
{jamtracks}
</tbody>
</table>
<div className="end-of-jamtrack-list end-of-list">No more JamTracks</div>
</div>`
options = {}
`<div className="JamTrackFilterScreen">
<div className="content-body-scroller">
{jamTracksSection}
</div>
</div>`
getInitialState: () ->
{search: '', type: 'user-input', jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, count: 0}
clearResults:() ->
#@content.empty()
#@noMoreJamtracks.hide()
@setState({currentPage: 0, next: null, jamtracks:[], type: 'user-input', searching:false})
defaultQuery:(extra) ->
query =
per_page: @LIMIT
page: @state.currentPage + 1
if @state.next
query.since = @state.next
instrument = @instrument.val()
if instrument?
query.instrument = instrument
genre = @genre.val()
if genre?
query.genre = genre
$.extend(query, extra)
userSearch: (e) ->
e.preventDefault()
@search()
setFilterState: (state) ->
if state
@genre.easyDropDown('enable').removeAttr('disabled')
@instrument.easyDropDown('enable').removeAttr('disabled')
else
@genre.easyDropDown('disable').attr('disabled', 'disabled')
@instrument.easyDropDown('disable').attr('disabled', 'disabled')
search: (search_type, input) ->
return if @state.searching
$root = $(@getDOMNode())
# disable scroll watching now that we've started a new search
$root.find('.content-body-scroller').off('scroll')
$root.find('.end-of-jamtrack-list').hide()
# we have to make sure the query starts from page 1, and no 'next' from previous causes a 'since' to show up
query = @defaultQuery({page: 1})
delete query.since
@rest.getJamTracks(query)
.done((response) =>
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
)
.fail(() =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@setState({currentPage: 0, next: null, jamtracks:[], searching: true, count: 0})
componentDidMount: () ->
$screen = $('#jamtrackFilter')
@genre = $screen.find('#jamtrack_genre')
@instrument = $screen.find('#jamtrack_instrument')
@genre.on 'change', this.userSearch
@instrument.on 'change', this.userSearch
# increase dropdown size
context.JK.dropdown(@genre, {cutOff:15})
context.JK.dropdown(@instrument, {cutOff:15})
componentDidUpdate: ( ) ->
$root = $(this.getDOMNode())
$scroller = $root.find('.content-body-scroller')
@setFilterState(!@state.searching)
for jamTrack in @state.jamtracks
jamtrackElement = $root.find("tbody .jamtrack-record[data-jamtrack-id=\"#{jamTrack.id}\"]")
jamtrackElement.data('jamTrack', jamTrack)
jamtrackElement.data('expanded', true)
@handleExpanded(jamtrackElement)
@registerEvents(jamtrackElement)
if @state.next == null
$scroller = $root.find('.content-body-scroller')
# if we less results than asked for, end searching
#$scroller.infinitescroll 'pause'
$scroller.off('scroll')
if @state.currentPage == 1 and @state.jamtracks.length == 0
#@content.append '<td colspan="3" class="no-jamtracks-msg\'>No JamTracks found.</div>'
$root.find('.end-of-jamtrack-list').text('No JamTracks found matching your search').show()
@logger.debug("JamTrackSearch: empty search")
else if @state.currentPage > 0
@logger.debug("end of search")
$noMoreJamtracks = $root.find('.end-of-jamtrack-list').text('No more JamTracks').show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
else
@registerInfiniteScroll($scroller)
registerInfiniteScroll:($scroller) ->
$scroller.off('scroll')
$scroller.on('scroll', () =>
# be sure to not fire off many refreshes when user hits the bottom
return if @refreshing
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more JamTracks ...</div>')
@refreshing = true
@logger.debug("refreshing more jamtracks for infinite scroll")
@setState({searching:true})
@rest.getJamTracks(@defaultQuery())
.done((json) =>
@setState({jamtracks: @state.jamtracks.concat(json.jamtracks), next: json.next, currentPage: @state.currentPage + 1, count: json.count})
)
.always(() =>
$scroller.find('.infinite-scroll-loader-2').remove()
@refreshing = false
@setState({searching: false})
)
)
playJamtrack:(e) ->
e.preventDefault()
addToCartJamtrack:(e) ->
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail(() => @app.ajaxError)
licenseUSWhy:(e) ->
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:($parent) ->
$parent.find('.play-button').on 'click', @playJamtrack
$parent.find('.jamtrack-add-cart').on 'click', @addToCartJamtrack
$parent.find('.license-us-why').on 'click', @licenseUSWhy
$parent.find('.jamtrack-detail-btn').on 'click', @toggleExpanded
toggleExpanded:(e) ->
e.preventDefault()
jamtrackRecord = $(e.target).parents('.jamtrack-record')
@handleExpanded(jamtrackRecord)
handleExpanded:(trackElement) ->
jamTrack = trackElement.data('jamTrack')
expanded = trackElement.data('expanded')
expand = !expanded
trackElement.data('expanded', expand)
detailArrow = trackElement.find('.jamtrack-detail-btn')
if expand
trackElement.find('.extra').removeClass('hidden')
detailArrow.html('hide tracks <a class="details-arrow arrow-up"></a>')
for track in jamTrack.tracks
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find('.extra').addClass('hidden')
detailArrow.html('show all tracks <a class="details-arrow arrow-down"></a>')
count = 0
for track in jamTrack.tracks
if count < 6
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find("[data-jamtrack-track-id='#{track.id}']").addClass('hidden')
count++
afterShow: (data) ->
@setFilterFromURL()
beforeShow: () ->
setFilterFromURL:() ->
performSearch = false
if $.QueryString['genre']?
performSearch = true
@genre.easyDropDown('select', $.QueryString['genre'], true)
if $.QueryString['instrument']?
performSearch = true
@instrument.easyDropDown('select', $.QueryString['instrument'], true)
unless performSearch
search = context.JamTrackStore.checkRequestedFilter()
if search?
performSearch = true
@genre.easyDropDown('select', search.genre, true)
@instrument.easyDropDown('select', search.instrument, true)
if performSearch
@search()
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrack/filter")
onAppInit: (@app) ->
window.JamTrackSearchInput = '' # need to be not null; otherwise react-select chokes
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack/filter', screenBindings)
})

View File

@ -0,0 +1,150 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackLandingScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
getInitialState: () ->
{user: null}
render: () ->
howTo = null
if @state.user?.free_jamtrack
howTo =
`<div className="free-jamtrack">
<span>
For a limited time, get one JamTrack free. Search JamTracks below, add one to your shopping cart, and we'll make it free during the checkout process.
</span>
</div>`
else
howTo = `<div className="no-free-jamtrack">
<span>
To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or <a href="/client#/account/jamtracks">visit the JamTracks section of your account.</a>
</span>
</div>`
`<div className="content-body-scroller">
<div className="list-columns">
<div className="browse">
<h2>my jamtracks</h2>
<div className="howto">
{howTo}
</div>
<h2 className="browse-jamtracks">search jamtracks</h2>
<div className="search-area">
<div className="search-help para"> To search by the name of the original artist, band, or song name, enter your search words below:</div>
<div className="search-controls">
<JamTrackAutoComplete onSearch={this.search} /><button onClick={this.searchByString} className="search-by-string-btn button-orange ">SEARCH</button>
</div>
<div className="filter-help para">To search by genre and instrument, make your selections below:</div>
<div className="search-controls">
<select className="genre-list easydropdown" name="genres">
<option value="">Any Genre</option>
</select>
<select className="instrument-list easydropdown" name="insruments">
<option value="">Any Instrument</option>
</select>
<button className="search-by-filter-btn button-orange" onClick={this.searchByFilter}>SEARCH</button>
</div>
</div>
</div>
<div className="about">
<h2>what are jamtracks?</h2>
<div className="what">
<div className="details">
JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each part of the master mix. Used with the free JamKazam app & Internet service, you can:
</div>
<ul>
<li>Solo just the part you want to play in order to hear and learn it</li>
<li>Mute just the part you want to play and play along with the rest</li>
<li>Slow down playback to practice without changing the pitch</li>
<li>Change the song key by raising or lowering pitch in half steps</li>
<li>Make audio recordings and share them via Facebook or URL</li>
<li>Make video recordings and share them via YouTube</li>
<li>And even go online to play with others live &amp; in sync</li>
</ul>
<a className="video-thumbnail" href="https://www.youtube.com/watch?v=askHvcCoNfw" rel="external">
<img className="play" src="/assets/content/icon_youtube_play.png" />
</a>
</div>
</div>
</div>
</div>`
componentDidMount: () ->
$root = $(@getDOMNode())
search: (searchType, searchData) ->
context.JamTrackActions.requestSearch(searchType, searchData)
searchByString: (e) ->
e.preventDefault()
context.JamTrackActions.requestSearch('user-input', window.JamTrackSearchInput)
searchByFilter: (e) ->
e.preventDefault()
$root = $(@getDOMNode())
genre = $root.find('select.genre-list').val()
instrument = $root.find('select.instrument-list').val()
context.JamTrackActions.requestFilter(genre, instrument)
afterShow: (data) ->
if context.JK.currentUserId
@app.user().done(@onUser)
else
@onUser({free_jamtrack: gon.global.one_free_jamtrack_per_user})
beforeShow: () ->
@setState({user: null})
onUser:(user) ->
@setState({user: user})
# Get artist names and build links
#@rest.getJamTrackArtists({group_artist: true, per_page:100})
#.done(this.buildArtistLinks)
#.fail(this.handleFailure)
# Bind links to action that will open the jam_tracks list view filtered to given artist_name:
# artist_name
#@bindArtistLinks()
onAppInit: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@screen = null
@noFreeJamTrack = null
@freeJamTrack = null
@bandList = null
@noBandsFound = null
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack', screenBindings)
@screen = $('#jamtrackLanding')
@noFreeJamTrack = @screen.find('.no-free-jamtrack')
@freeJamTrack = @screen.find('.free-jamtrack')
@bandList = @screen.find('#band_list')
@noBandsFound = @screen.find('#no_bands_found')
$root = $(@getDOMNode())
context.JK.GenreSelectorHelper.render2($root.find('select.genre-list'))
@instrumentSelector = new context.JK.InstrumentSelector(@app)
@instrumentSelector.initialize(false, true)
@instrumentSelector.renderDropdown($root.find('select.instrument-list'))
})

View File

@ -0,0 +1,184 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
@JamTrackPreview = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
EVENTS: context.JK.EVENTS
logger: context.JK.logger
propTypes: { options: React.PropTypes.object }
getDefaultProps: () ->
{ options: {master_shows_duration: false, color: 'gray', add_line_break: false, preload_master: false}}
getInitialState: () ->
{ loaded: false, loading: false, playing: false, no_audio: false }
render: () ->
playButtonClasses = { "play-button": true, disabled: @state.no_audio}
playButtonClasses[@props.options.color] = @props.options.color?
playButtonClasses = classNames(playButtonClasses)
stopButtonClasses = { "stop-button": true, disabled: @state.no_audio }
stopButtonClasses[@props.options.color] = @props.options.color?
stopButtonClasses = classNames(stopButtonClasses)
partClasses = {part: true}
partClasses['adds-line-break'] = true if @props.options.master_adds_line_break
partClasses = classNames(partClasses)
if @state.playing
activeButton = `<a className={stopButtonClasses} onClick={this.stop} />`
else
activeButton = `<a className={playButtonClasses} onClick={this.play} />`
loaders = []
if @state.loading
loaders.push `<div key="spinner" className="loading spinner-small"></div>`
loaders.push `<div key="text" className="loading-text">preview loading</div>`
`<div className="jam-track-preview" data-track-type={this.props.jamTrackTrack.track_type} data-id={this.props.jamTrackTrack.id}>
<div className="actions">
{activeButton}
</div>
<img className="instrument-icon" data-hoveraction="instrument" data-instrument-id={this.state.instrumentId} src={this.state.instrumentSrc} width="24" height="24" />
<div className="instrument-name">{this.state.instrumentDescription}</div>
<div className="part">{this.state.part}</div>
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{loaders}
</ReactCSSTransitionGroup>
</div>`
componentWillMount: () ->
instrumentId = null
instrumentDescription = '?'
if @props.jamTrackTrack.track_type == 'Track'
if @props.jamTrackTrack.instrument
instrumentId = @props.jamTrackTrack.instrument.id
instrumentDescription = @props.jamTrackTrack.instrument.description
else
instrumentId = 'other'
instrumentDescription= 'Master Mix'
instrumentSrc = context.JK.getInstrumentIcon24(instrumentId)
part = ''
if @props.jamTrackTrack.track_type == 'Track'
part = "(#{@props.jamTrackTrack.part})" if @props.jamTrackTrack.part? && @props.jamTrackTrack.part != instrumentDescription
else
if @props.options.master_adds_line_break
part = '"' + @props.jamTrack.name + '"' + ' by ' + @props.jamTrack.original_artist
else
if @props.options.master_shows_duration
duration = 'entire song'
if @props.jamTrack.duration
duration = "#{context.JK.prettyPrintSeconds(@props.jamTrack.duration)}"
part = duration
else
part = @props.jamTrack.name + ' by ' + @props.jamTrack.original_artist
part = "(#{part})" unless part?
part = '' unless part?
urls = null
no_audio = null
if @props.jamTrackTrack.preview_mp3_url?
urls = [@props.jamTrackTrack.preview_mp3_url]
if @props.jamTrackTrack.preview_ogg_url?
urls.push(@props.jamTrackTrack.preview_ogg_url)
urls = urls
no_audio = false
else
no_audio = true
@setState({
instrumentId: instrumentId,
instrumentDescription: instrumentDescription,
instrumentSrc: instrumentSrc,
part: part
urls: urls,
no_audio: no_audio})
componentDidMount: () ->
$root = $(@getDOMNode());
if @props.options.preload_master && @props.jamTrackTrack.track_type == 'Master' && !@state.no_audio
@sound = new Howl({
src: @state.urls,
autoplay: false,
loop: false,
volume: 1.0,
preload: true,
onload: @onHowlerLoad
onend: @onHowlerEnd})
componentWillUnmount: () ->
@sound.unload() if @sound?
removeNowPlaying: () ->
context.JamTrackPreviewActions.stoppedPlaying(this)
onHowlerEnd: () ->
@logger.debug("on end")
@removeNowPlaying()
@setState(playing: false)
onHowlerLoad: () ->
@setState(loaded: true, loading: false)
play: (e) ->
if e?
e.stopPropagation()
e.preventDefault()
$root = $(@getDOMNode())
$root.triggerHandler(@EVENTS.PREVIEW_PLAYED)
$playButton = $root.find('.play-button')
if @state.no_audio
context.JK.prodBubble($playButton, 'There is no preview available for this track.', {}, {duration:2000})
else
unless @sound?
@sound = new Howl({
src: @state.urls,
autoplay: false,
loop: false,
volume: 1.0,
preload: true,
onload: @onHowlerLoad
onend: @onHowlerEnd})
@logger.debug("play issued for jam track preview")
@sound.play()
context.JamTrackPreviewActions.startedPlaying(this)
@setState({playing: true, loading: !@state.loaded})
issueStop: () ->
@logger.debug("pause issued for jam track preview")
@sound.pause() if @sound? # stop does not actually stop in windows client
@setState({playing: false})
stop: (e) ->
if e?
e.stopPropagation()
e.preventDefault()
if @state.no_audio
context.JK.helpBubble(@playButton, 'There is no preview available for this track.', {}, {duration:2000})
else
@issueStop()
@removeNowPlaying()
return false
})

View File

@ -0,0 +1,529 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackSearchScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
LIMIT: 10
instrument_logo_map: context.JK.getInstrumentIconMap24()
input: null
MAX_ARTIST_SHOW: 3
filterOption:() ->
true
render: () ->
searchText = if @state.first_search then 'SEARCH' else 'SEARCH AGAIN'
uiJamTracks = []
for jamtrack in @state.jamtracks
trackRow = context._.clone(jamtrack)
trackRow.track_cnt = jamtrack.tracks.length
trackRow.tracks = []
# if an instrument is selected by the user, then re-order any jam tracks with a matching instrument to the top
###instrument = @instrument.val()
if instrument?
jamtrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a, instrument)
bWeight = @computeWeight(b, instrument)
return aWeight - bWeight
)
###
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
trackRow.free_state = if @state.is_free then 'free' else 'non-free'
trackRow.is_free = @state.is_free == 'free'
uiJamTracks.push trackRow
artists = []
artistsShown = 0
for artist in @state.artists
if @state.show_all_artists || artistsShown < @MAX_ARTIST_SHOW
artists.push `<div key={artist.original_artist}><a className="show-artist" onClick={this.artistNavSelected} data-artist={artist.original_artist}>{artist.original_artist}</a></div>`
artistsShown += 1
artists.push `<div key="no-results" className="no-results">No matching artists</div>` if artists.length == 0
if !@state.show_all_artists && @state.artists.length > @MAX_ARTIST_SHOW
artists.push `<div key="show-hide-artists"><a onClick={this.showAllArtists} className="show-hide-artists">show all <div className="details-arrow arrow-down" /></a></div>`
else if @state.show_all_artists
artists.push `<div key="show-hide-artists"><a onClick={this.hideExtraArtists} className="show-hide-artists">hide artists <div className="details-arrow arrow-up" /></a></div>`
jamtracks = []
for jamtrack in uiJamTracks
jamtrackPricesClasses = { "jamtrack-price" : true }
jamtrackPricesClasses[jamtrack.free_state] = true
jamtrackPricesClasses = classNames(jamtrackPricesClasses)
tracks = []
for track in jamtrack.tracks
tracks.push `<div className="jamtrack-track hidden" key={track.id} data-jamtrack-track-id={track.id}>
<div className="jamtrack-preview">
<JamTrackPreview jamTrack={jamtrack} jamTrackTrack={track} options={{master_shows_duration: true, color:'gray'}} />
<div className="clearall" />
</div>
</div>`
actionBtn = null
if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
actionBtn = `<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id}>ADD TO CART</a>`
availabilityNotice = null
if jamtrack.sales_region==context.JK.AVAILABILITY_US
availabilityNotice =
`<div className="jamtrack-license">
This JamTrack available only to US customers. &nbsp;&nbsp;&nbsp;&nbsp;
<a className="license-us-why" href="#">why?</a>
</div>`
jamtracks.push `<tr className="jamtrack-record" key={jamtrack.id} data-jamtrack-id={jamtrack.id}>
<td className="jamtrack-detail">
<div className="jamtrack-name">"{jamtrack.name}"</div>
<div className="jamtrack-original-artist">by {jamtrack.original_artist}</div>
<br className="clearall"/>
<div className="clearall detail-label extra hidden song-writer">Songwriters:</div>
<div className="detail-value extra hidden">{jamtrack.songwriter}</div>
<div className="clearall detail-label extra hidden">Publishers:</div>
<div className="detail-value extra hidden">{jamtrack.publisher}</div>
<div className="clearall detail-label extra hidden">Genres:</div>
<div className="detail-value extra hidden">{jamtrack.genres.join(', ')}</div>
<div className="clearall detail-label extra hidden">Version:</div>
<div className="detail-value extra hidden">{jamtrack.recording_type}</div>
</td>
<td className="jamtrack-tracks">
<div className="detail-arrow">
<div className="jamtrack-detail-btn">
show all tracks <a className="details-arrow arrow-down"/>
</div>
</div>
{tracks}
</td>
<td className="jamtrack-action">
<div className="jamtrack-action-container">
<div className="jamtrack-actions">
<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>
{actionBtn}
{availabilityNotice}
</div>
</div>
</td>
</tr>`
#jamtracks.push `<div className="no-results">No matching JamTracks</div>` if jamtracks.length == 0
searchClasses = classNames({
"button-orange" : true,
"search-btn" : true,
"disabled" : @state.searching
})
artistSection = null
jamTracksSection = null
if @state.type == 'user-input'
if @state.searching
jamtracksHeader = "searching..."
else
jamtracksHeader = "search results: #{@state.count} jamtracks"
else if @state.type == 'artist-select'
jamtracksHeader = "search results: jamtracks for artist \"#{@state.artist}\""
else if @state.type == 'song-select'
jamtracksHeader = "search results: jamtrack \"#{@state.song}\""
else
throw "unknown search type #{@state.type}"
if !@state.first_search
# only show the artists links if the user typed the results
if @state.type == 'user-input'
artistSection =
`<div>
<h2>search results: artists</h2>
<div className="artist-results">
{artists}
</div>
</div>`
jamTracksSection =
`<div>
<h2 className="jamtrack-results-header">{jamtracksHeader} <a className="back-to-jamtracks-home" href="/client#/jamtrack">back to jamtracks home</a></h2>
<table className="generaltable jamtrack-table">
<thead>
<tr>
<th className="jamtrack-detail">JAMTRACK</th>
<th className="jamtrack-tracks">TRACKS INCLUDED / PREVIEW</th>
<th className="jamtrack-shop">SHOP</th>
</tr>
</thead>
<tbody className="jamtrack-content">
{jamtracks}
</tbody>
</table>
<div className="end-of-jamtrack-list end-of-list">No more JamTracks</div>
</div>`
options = {}
searchValue = if @state.search == 'SEPARATOR' then '' else window.JamTrackSearchInput
`<div className="JamTrackSearchScreen">
<div className="controls">
<JamTrackAutoComplete onSearch={this.search} />
<button className={searchClasses} name="search" onClick={this.userSearch}>{searchText}</button>
</div>
<div className="content-body-scroller">
{artistSection}
{jamTracksSection}
</div>
</div>`
clearResults:() ->
@setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null, is_free: context.JK.currentUserFreeJamTrack, first_search: true})
getInitialState: () ->
{search: '', type: 'user-input', artists:[], jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, first_search: true, count: 0, is_free: context.JK.currentUserFreeJamTrack}
onSelectChange: (val) ->
#@logger.debug("CHANGE #{val}")
return false unless val?
search_type
if val.indexOf('ARTIST=') == 0
search_type = 'artist-select'
artist = val['ARTIST='.length..-1]
@search(search_type, artist)
else if val.indexOf('SONG=') == 0
search_type = 'song-select'
song = val['SONG='.length..-1]
@search(search_type, song)
else
@logger.debug("user selected separator")
# this is to signal to the component that the separator was selected, and it has code in render to negate the selection
setTimeout((() =>
@setState({search:val})
), 1)
return false
onSelectBlur: (e) ->
#@logger.debug("blur time")
#@search()
showAllArtists: () ->
@setState({show_all_artists: true})
hideExtraArtists: () ->
@setState({show_all_artists: false})
defaultQuery:(extra) ->
query =
per_page: @LIMIT
page: @state.currentPage + 1
sort_by: 'jamtrack'
if @state.next
query.since = @state.next
$.extend(query, extra)
userSearch: (e) ->
e.preventDefault()
@search('user-input', window.JamTrackSearchInput)
search: (search_type, input) ->
return if @state.searching
return unless input?
window.JamTrackSearchInput = input
$root = $(@getDOMNode())
# disable scroll watching now that we've started a new search
#@logger.debug("disabling infinite scroll")
$root.find('.content-body-scroller').off('scroll')
$root.find('.end-of-jamtrack-list').hide()
if input?
@rest.getJamTrackArtists({artist_search: input, limit:100})
.done((response) =>
@setState({artists:response.artists})
# we have to make sure the query starts from page 1, and no 'next' from previous causes a 'since' to show up
query = @defaultQuery({page: 1})
delete query.since
@logger.debug("Search type", search_type)
if search_type == 'artist-select'
query.artist_search = input # works with ilike
else if search_type == 'song-select'
query.song_search = input # works with ilike
else
query.search = input # works with tsv
@rest.getJamTracks(query)
.done((response) =>
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
)
.fail(() =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
)
.fail(() =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@setState({currentPage: 0, next: null, artists: [], jamtracks:[], searching: true, artist: input, song: input, type: search_type, search:input, count:0})
getOptions: (input, callback) =>
#@logger.debug("getOptions input #{input}", this)
# sigh. ugly global
window.JamTrackSearchInput = input
if !input? || input.length == 0
callback(null, {options: [], complete: false})
return
@rest.autocompleteJamTracks({match:input, limit:5})
.done((autocomplete) =>
options = []
for artist in autocomplete.artists
options.push { value: "ARTIST=#{artist.original_artist}", label: "Artist: #{artist.original_artist}" }
if options.length > 0 && autocomplete.songs.length > 0
options.push { value: 'SEPARATOR', label: "---------------"}
for jamtrack in autocomplete.songs
options.push { value: "SONG=#{jamtrack.name}", label: "Song: #{jamtrack.name}" }
callback(null, {options: options, complete: false})
)
artistNavSelected: (e) ->
e.preventDefault()
@search('artist-select', $(e.target).attr('data-artist'))
componentDidMount: () ->
#@logger.debug("componentDidMount")
componentDidUpdate: ( ) ->
$root = $(this.getDOMNode())
$scroller = $root.find('.content-body-scroller')
for jamTrack in @state.jamtracks
jamtrackElement = $root.find("tbody .jamtrack-record[data-jamtrack-id=\"#{jamTrack.id}\"]")
jamtrackElement.data('jamTrack', jamTrack)
jamtrackElement.data('expanded', true)
@handleExpanded(jamtrackElement)
@registerEvents(jamtrackElement)
if @state.next == null
$scroller = $root.find('.content-body-scroller')
# if we less results than asked for, end searching
#$scroller.infinitescroll 'pause'
#@logger.debug("disabling infinite scroll")
$scroller.off('scroll')
if @state.currentPage == 1 and @state.jamtracks.length == 0
#@content.append '<td colspan="3" class="no-jamtracks-msg\'>No JamTracks found.</div>'
$root.find('.end-of-jamtrack-list').text('No JamTracks found matching your search').show()
@logger.debug("JamTrackSearch: empty search")
else if @state.currentPage > 0
@logger.debug("end of search")
$noMoreJamtracks = $root.find('.end-of-jamtrack-list').text('No more JamTracks').show()
$noMoreJamtracks = $root.find('.end-of-jamtrack-list')
$noMoreJamtracks.show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
else
@registerInfiniteScroll($scroller)
registerInfiniteScroll:($scroller) ->
@logger.debug("registering infinite scroll")
$scroller.off('scroll')
$scroller.on('scroll', () =>
# be sure to not fire off many refreshes when user hits the bottom
return if @refreshing
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more JamTracks ...</div>')
@refreshing = true
@setState({searching: true})
@logger.debug("refreshing more jamtracks for infinite scroll")
@rest.getJamTracks(@defaultQuery({search:@state.search}))
.done((json) =>
@setState({jamtracks: @state.jamtracks.concat(json.jamtracks), next: json.next, first_search: false, currentPage: @state.currentPage + 1, count: json.count})
)
.always(() =>
$scroller.find('.infinite-scroll-loader-2').remove()
@refreshing = false
@setState({searching: false})
)
)
playJamtrack:(e) ->
e.preventDefault()
addToCartJamtrack:(e) ->
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail(() => @app.ajaxError)
licenseUSWhy:(e) ->
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:($parent) ->
$parent.find('.play-button').on 'click', @playJamtrack
$parent.find('.jamtrack-add-cart').on 'click', @addToCartJamtrack
$parent.find('.license-us-why').on 'click', @licenseUSWhy
$parent.find('.jamtrack-detail-btn').on 'click', @toggleExpanded
toggleExpanded:(e) ->
e.preventDefault()
jamtrackRecord = $(e.target).parents('.jamtrack-record')
@handleExpanded(jamtrackRecord)
handleExpanded:(trackElement) ->
jamTrack = trackElement.data('jamTrack')
expanded = trackElement.data('expanded')
expand = !expanded
trackElement.data('expanded', expand)
detailArrow = trackElement.find('.jamtrack-detail-btn')
if expand
trackElement.find('.extra').removeClass('hidden')
detailArrow.html('hide tracks <a class="details-arrow arrow-up"></a>')
for track in jamTrack.tracks
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find('.extra').addClass('hidden')
detailArrow.html('show all tracks <a class="details-arrow arrow-down"></a>')
count = 0
for track in jamTrack.tracks
if count < 6
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find("[data-jamtrack-track-id='#{track.id}']").addClass('hidden')
count++
afterShow: (data) ->
@setFilterFromURL()
setFilterFromURL:() ->
performSearch = false
if $.QueryString['artist']?
performSearch = true
@search('artist-select', $.QueryString['artist'])
else if $.QueryString['song']?
performSearch = true
@search('song-select', $.QueryString['song'])
else if $.QueryString['search']?
performSearch = true
@search('user-input', $.QueryString['search'])
else
# check if someone has requested a search for us as we transition to this screen
search = context.JamTrackStore.checkRequestedSearch()
if search?
performSearch = true
@search(search.searchType, search.searchData)
if performSearch
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrack/search")
beforeShow: () ->
if !@state.first_search
@setState({is_free: context.JK.currentUserFreeJamTrack})
@search(@state.type, window.JamTrackSearchInput)
onAppInit: (@app) ->
window.JamTrackSearchInput = '' # need to be not null; otherwise react-select chokes
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack/search', screenBindings)
})

View File

@ -210,7 +210,6 @@ ChannelGroupIds = context.JK.ChannelGroupIds
<img src="/assets/content/icon_close.png" width="18" height="20" />
Close JamTrack
</a>
<div className="download-jamtrack-holder"></div>
</div>`
contents = closeOptions
@ -236,6 +235,9 @@ ChannelGroupIds = context.JK.ChannelGroupIds
</a>`
if this.state.downloadJamTrack?
mediaTracks.push `<div key="download-jamtrack-holder" className="download-jamtrack-holder"></div>`
if this.state.mediaSummary.backingTrackOpen
for backingTrack in @state.backingTracks

View File

@ -3,5 +3,7 @@ context = window
@JamTrackActions = Reflux.createActions({
open: {}
close: {}
requestSearch: {}
requestFilter: {}
})

View File

@ -0,0 +1,8 @@
context = window
@JamTrackPreviewActions = Reflux.createActions({
startedPlaying: {}
stoppedPlaying: {}
screenChange: {}
})

View File

@ -606,7 +606,7 @@ MIX_MODES = context.JK.MIX_MODES;
# sanity check
if mixer && mixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
logger.warn("master: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, mixer)
vuMixer = mixer
muteMixer = mixer
@ -618,7 +618,7 @@ MIX_MODES = context.JK.MIX_MODES;
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
if !oppositeMixer
logger.error("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer )
logger.warn("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer )
when MIX_MODES.PERSONAL
mixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
@ -632,9 +632,9 @@ MIX_MODES = context.JK.MIX_MODES;
# now grab the PeerAudioInputMusicGroup in master mode to satisfy the 'opposite' mixer
oppositeMixer = @getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
if !oppositeMixer
logger.debug("unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", client_id, track.client_track_id)
logger.debug("personal: unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", client_id, track.client_track_id)
else if oppositeMixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
logger.error("personaol: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, mixer)
#vuMixer = oppositeMixer; # for personal mode, use the PeerAudioInputMusicGroup's VUs

View File

@ -39,11 +39,11 @@ rest = context.JK.Rest()
{processing: false}
render: () ->
bandBrowseUrl = "/client?artist=#{this.props.jam_track.original_artist}#/jamtrackBrowse"
bandBrowseUrl = "/client?artist=#{this.props.jam_track.original_artist}#/jamtrack/search"
`<div className="cta-holder">
<div className="checkout">
<a href="/client#/jamtrackBrowse" onClick={this.redeem} className="cta-free-jamtrack" alt="ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!">
<a href="/client#/jamtrack/search" onClick={this.redeem} className="cta-free-jamtrack" alt="ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!">
<img src="/assets/web/button_cta_jamtrack_free.png" />
</a>
<span className="value-indicator">$1.99 value</span>
@ -54,7 +54,7 @@ rest = context.JK.Rest()
</div>
<br/>
<div className="browse-all">
<a href="/client#/jamtrackBrowse">or browse all {this.props.all_track_count} backing tracks!</a>
<a href="/client#/jamtrack/search">or browse all {this.props.all_track_count} backing tracks!</a>
</div>
</div>`
})

View File

@ -0,0 +1,40 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
EVENTS = context.JK.EVENTS
JamTrackPreviewActions = @JamTrackPreviewActions
@JamTrackPreviewStore = Reflux.createStore(
{
listenables: JamTrackPreviewActions
logger: context.JK.logger
nowPlaying: []
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (app) ->
@app = app
onStartedPlaying: (preview) ->
for playingSound in @nowPlaying
playingSound.issueStop()
@nowPlaying = []
@nowPlaying.push(preview)
onStoppedPlaying: (preview) ->
@nowPlaying.splice(preview)
if @nowPlaying.length > 0
@logger.warn("multiple jamtrack previews playing")
onScreenChange: () ->
for playingSound in @nowPlaying
playingSound.issueStop()
@nowPlaying = []
}
)

View File

@ -11,6 +11,8 @@ JamTrackActions = @JamTrackActions
{
listenables: JamTrackActions
jamTrack: null
requestedSearch: null
requestedFilter: null
init: ->
# Register with the app store to get @app
@ -30,5 +32,26 @@ JamTrackActions = @JamTrackActions
onClose: () ->
@jamTrack = null
this.trigger(@jamTrack)
onRequestSearch:(searchType, searchData) ->
@requestedSearch = {searchType: searchType, searchData: searchData}
window.location.href = '/client#/jamtrack/search'
# needed by the JamTrackSearchScreen
checkRequestedSearch:() ->
requested = @requestedSearch
@requestedSearch = null
requested
onRequestFilter:(genre, instrument) ->
@requestedFilter = {genre: genre, instrument:instrument}
window.location.href = '/client#/jamtrack/filter'
# needed by the JamTrackSearchScreen
checkRequestedFilter:() ->
requested = @requestedFilter
@requestedFilter = null
requested
}
)

View File

@ -59,7 +59,7 @@
if(!checkoutUtils.hasOneFreeItemInShoppingCart(carts)) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemComplete page")
window.location = '/client#/jamtrackBrowse'
window.location = '/client#/jamtrack/search'
}
else {
// ok, we have one, free item. save it for
@ -226,7 +226,7 @@
$backBtn.on('click', function(e) {
e.preventDefault();
context.location = '/client#/jamtrackBrowse'
context.location = '/client#/jamtrack/search'
})
}

View File

@ -27,11 +27,11 @@
var $signinLink = null;
function beforeShow(data) {
renderLoggedInState();
}
function afterShow(data) {
renderLoggedInState();
}
@ -67,13 +67,13 @@
if(carts.length == 0) {
// nothing is in the user's shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
logger.error("invalid access of redeemJamTrack page; none")
window.location = '/client#/jamtrack/search'
}
else if(carts.length > 1) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
logger.error("invalid access of redeemJamTrack page; multiple")
window.location = '/client#/jamtrack/search'
}
else {
var item = carts[0];
@ -86,8 +86,8 @@
}
else {
// the user has a non-free, single item in their basket. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
logger.error("invalid access of redeemJamTrack page, non-free/item")
window.location = '/client#/jamtrack/search'
}
}

View File

@ -224,7 +224,7 @@
var instrumentLogoHtml = '';
if (instruments !== undefined) {
for (var i=0; i < instruments.length; i++) {
var inst = '../assets/content/icon_instrument_default24.png';
var inst = '/assets/content/icon_instrument_default24.png';
if (instruments[i].instrument_id in instrument_logo_map) {
inst = instrument_logo_map[instruments[i].instrument_id].asset;
instrumentLogoHtml += '<img src="' + inst + '" width="24" height="24" />&nbsp;';

View File

@ -1081,8 +1081,11 @@
});
}
context.JK.dropdown = function ($select) {
context.JK.dropdown = function ($select, options) {
var opts = options || {}
opts = $.extend({}, {nativeTouch: !(context.jamClient && context.jamClient.IsNativeClient()) && gon.global.env != "test", cutOff: 7}, opts)
$select.each(function (index) {
var $item = $(this);
@ -1090,7 +1093,7 @@
// if this has already been initialized, re-init it so it picks up any new <options>
$item.easyDropDown('destroy')
}
$item.easyDropDown({nativeTouch: !(context.jamClient && context.jamClient.IsNativeClient()) && gon.global.env != "test", cutOff: 7});
$item.easyDropDown(opts);
})
}

View File

@ -23,9 +23,9 @@
logger.debug("jam_track", jam_track)
$jamtrack_band.text(jam_track.original_artist)
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/search')
$jamTracksButton.removeClass('hidden').text("Preview all " + jam_track.band_jam_track_count + " of our " + jam_track.original_artist + " JamTracks")
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/searche')
context._.each(jam_track.tracks, function (track) {

View File

@ -24,7 +24,7 @@
if(!gon.just_previews) {
if (gon.generic) {
$genericHeader.removeClass('hidden');
$jamTracksButton.attr('href', '/client#/jamtrackBrowse')
$jamTracksButton.attr('href', '/client#/jamtrack/search')
$jamTracksButton.removeClass('hidden').text("Check out all 100+ JamTracks")
}
@ -32,9 +32,9 @@
$individualizedHeader.removeClass('hidden')
$jamtrack_name.text('"' + jam_track.name + '"');
$jamtrack_band.text(jam_track.original_artist)
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/search')
$jamTracksButton.removeClass('hidden').text("Preview all " + jam_track.band_jam_track_count + " of our " + jam_track.original_artist + " JamTracks")
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/search')
}
}

View File

@ -58,6 +58,7 @@
*= require ./feed
*= require ./jamtrack
*= require ./jamtrack_landing
*= require ./jamtrackSearch
*= require ./shoppingCart
*= require ./checkout
*= require ./checkout_signin
@ -85,4 +86,5 @@
*= require users/signinCommon
*= require landings/partner_agreement_v1
*= require_directory ./react-components
*/

View File

@ -521,6 +521,15 @@ textarea {
text-align: center;
margin:auto;
width:100%;
@include border_box_sizing;
}
.infinite-scroll-loader-2 {
height:14px;
text-align: center;
margin:auto;
@include border_box_sizing;
margin-top:10px;
}
// disable text selection for the in-session panel, ftue, and arbitrary elements marked with .no-selection-range
@ -640,4 +649,53 @@ body.jam .icheckbox_minimal {
margin:-46.75% auto 0;
position: relative;
}
}
}
$ReactSelectVerticalPadding: 3px;
.Select-value {
padding-top: $ReactSelectVerticalPadding !important;
padding-bottom: $ReactSelectVerticalPadding !important;
}
.Select-arrow {
top: $ReactSelectVerticalPadding + 5 + 1 !important;
}
// overrides for react-select
.Select-control {
background-color: $ColorTextBoxBackground !important;
border: 0px solid #868686 !important;
border-color: lighten(#868686, 5%) #868686 darken(#868686, 10%) !important;
color: #ccc !important;
border-radius:0 !important;
padding-top: $ReactSelectVerticalPadding !important;
padding-bottom: $ReactSelectVerticalPadding !important;
font-size:15px !important;
&:hover {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
}
.Select-placeholder {
padding-top: $ReactSelectVerticalPadding + 2 !important;
padding-bottom: $ReactSelectVerticalPadding + 2 !important;
}
.Select-menu-outer {
// Unfortunately, having both border-radius and allows scrolling using overflow defined on the same
// element forces the browser to repaint on scroll. However, if these definitions are split into an
// outer and an inner element, the browser is able to optimize the scrolling behavior and does not
// have to repaint on scroll.
border-radius: 0 !important;
//.border-bottom-radius( @select-input-border-radius );
background-color: $ColorTextBoxBackground !important;
border: 1px solid #868686 !important;
border-top-color: mix($ColorTextBoxBackground, #868686, 50%);
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
box-sizing: border-box;
margin-top: -1px;
}

View File

@ -0,0 +1,299 @@
@import 'common';
#jamtrackFilter {
.jamtrack-filter.filter-head {
padding-left:6px;
.easydropdown.dropdown {
width:150px;
}
.filter-element.wrapper:nth-child(2) {
.easydropdown-wrapper {
margin-left:20px;
}
}
.filter-element.wrapper:nth-child(3) {
margin-left:10px;
}
}
}
#jamtrackSearch, #jamtrackFilter {
div[data-react-class="JamTrackSearchScreen"], .JamTrackSearchScreen, div[data-react-class="JamTrackFilterScreen"], .JamTrackFilterScreen {
height:100%;
}
.filter-element.desc {
margin-left:15px;
}
h2 {
font-size: 20px;
color: white;
font-weight: bold;
margin-bottom:10px;
}
.jamtrack-header {
background-color: #4c4c4c;
font-weight: bold;
text-transform: uppercase;
height: 2em;
padding: 4px;
}
a.jamtrack_help {
color: #fff;
text-decoration: none;
margin: 4px 0px 0px 60px;
&:hover {
text-decoration: underline;
}
}
table.jamtrack-table {
table-layout:fixed;
}
.jamtrack-content {
text-align: center;
border: 1px solid #222222;
padding: 2px
}
.no-jamtracks-msg {
margin: 10px 0;
text-align:center;
}
.jamtrack-record {
//border-bottom: 1px solid black;
text-align: left;
}
th.jamtrack-detail {
padding:6px;
}
th.jamtrack-tracks {
padding:6px;
}
th.jamtrack-action {
padding:6px;
text-align:center;
}
td.jamtrack-action {
padding:0;
position:relative;
}
.jamtrack-detail {
@include border_box_sizing;
width: 25%;
padding: 10px 0 0 10px;
.detail-label {
width: 80px;
float: left;
margin-top: 5px;
font-weight: 400;
font-size: 11px;
clear:both;
}
.detail-value {
float: left;
margin-top: 5px;
margin-bottom:15px;
font-size:11px;
}
.copyright-value {
width: 40%;
float: left;
margin-top: 5px;
}
.jamtrack-description {
display: none;
}
}
.jamtrack-detail-btn {
cursor: pointer;
margin-top: 29px;
margin-right: 5px;
padding-top: 5px;
vertical-align: bottom;
color:#fc0;
.arrow-down {
float:none;
margin-left:5px;
margin-top:0;
margin-right:0;
border-top: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
.arrow-up {
float:none;
margin-right:0;
margin-left:5px;
margin-bottom:2px;
border-bottom: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
}
.jamtrack-tracks {
padding-bottom:30px;
position:relative;
@include border_box_sizing;
width:55%;
//padding: 10px 0px;
.tracks-caption {
margin-top: 5px;
margin-bottom: 10px;
}
.track-instrument {
margin-top: 5px;
}
.instrument-image {
float: left;
margin-right: 2px;
}
.instrument-desc {
margin-top: 6px;
float: left;
margin-left: 10px;
}
.instrument-name {
color:white;
}
}
.detail-arrow {
position:absolute;
float: right;
margin-right: 10px;
bottom:14px;
left:12px;
}
.jamtrack-name {
font-size:16px;
white-space: normal;
}
.jamtrack-original-artist {
font-size:16px;
margin-top:10px;
margin-bottom:5px;
white-space: normal;
}
.jamtrack-track {
float:left;
padding:0 0 8px 7px;
width: 250px;
@include border_box_sizing;
&.hidden {
display:none;
}
}
.jam-track-preview {
font-size:11px;
white-space:nowrap;
.loading-text {
right:-115px;
background-color:#262626;
}
}
.jamtrack-action {
@include border_box_sizing;
width: 20%;
text-align: center;
vertical-align: middle;
.jamtrack-action-container {
display:inline-block;
}
.play-button {
margin-top: 5px;
}
.jamtrack-price {
margin-top: 5px;
font-size: 20px;
&.free {
margin-top:0;
display:none;
.free-state {
font-size: 11px;
margin-top: 5px;
display:block;
}
}
}
.jamtrack-add-cart, .jamtrack-add-cart-disabled {
margin: 8px 0px;
}
.jamtrack-license {
margin-left: 20%;
margin-right: 20%;
font-size: 13px;
width: 60%;
}
}
.end-of-jamtrack-list {
margin-top:20px;
}
}
#jamtrack-license-dialog {
.dialog-inner {
height: auto;
.content-body {
max-height: auto;
.content-body-scroller {
height: 350px;
.paragraph {
margin-bottom: 1em;
}
overflow: hidden;
}
border: 1px solid #222;
margin: 4px 4px 8px 4px;
}
}
}
.jamtrack_buttons {
margin: 8px 4px 12px 4px;
}

View File

@ -1,6 +1,53 @@
@import 'common';
#jamtrackLanding {
div[data-react-class="JamTrackLandingScreen"], .content-body-scroller {
height:100%;
}
.search-by-string-btn {
float: right;
margin-top: 2px;
}
.search-by-filter-btn {
float:right;
margin-top:10px;
}
.search-help {
margin:10px 0 10px 0;
line-height:150%;
}
.filter-help {
margin:20px 0 10px 0;
line-height:150%;
}
.autocompleter {
width: calc(100% - 90px);
margin-bottom: 20px;
display:inline-block;
}
.search-area {
.easydropdown-wrapper{
max-width:350px;
@include border_box_sizing;
&:nth-child(1) {
margin-right:10px;
width:calc(50% - 10px);
}
&:nth-child(2) {
margin-left:10px;
width:calc(50% - 10px);
}
}
button {
margin-right:0;
}
}
//font-family: verdana;
.two-column-list-container {
-moz-column-count: 2;
@ -20,14 +67,6 @@
}
}
ul {
li {
margin: 1px 4px 1px 4px;
font-size:12px;
line-height:14px ;
}
}
#band_list {
margin-top:10px;
margin-left:0;
@ -83,7 +122,17 @@
> * {
margin: 4px;
}
ul {
li {
margin: 1px 4px 1px 4px;
font-size:12px;
line-height:14px ;
}
}
}
.browse {
@include border_box_sizing;
float: left;
@ -93,6 +142,11 @@
margin: 4px;
}
.para {
color: #cccccc;
}
h2.browse-jamtracks {
margin-top:30px;
}
@ -102,15 +156,19 @@
}
.video-container {
width:80%;
padding-top:56.25%;
width:100%;
padding-bottom:53.33%;
margin:30px 0 30px 30px;
}
.video-thumbnail {
width:80%;
padding-top:56.25%;
margin:30px 0 30px 30px;
width:100%;
padding-top:53.33%;
margin:25px 0;
background:url(//img.youtube.com/vi/askHvcCoNfw/maxresdefault.jpg) no-repeat center;
img.play {
margin:-35.75% auto 0;
}
}
}

View File

@ -0,0 +1,16 @@
.JamTrackFilterScreen {
.content-body-scroller {
height:calc(100% - 30px) ! important; // 15px top and bottom padding, and 48px used by .controls
padding:15px 30px;
}
.back-to-jamtracks-home {
font-weight: normal;
margin-right: 2px;
margin-top: 6px;
font-size:12px;
float:right;
}
}

View File

@ -0,0 +1,81 @@
.JamTrackSearchScreen {
.content-body-scroller {
height:calc(100% - 70px) !important; // 15px top and bottom padding, and 40px used by .controls
padding:15px 30px;
}
// react-select overridse
.Select-menu-outer {
max-height: 400px !important;
}
.Select-menu {
max-height: 400px !important;
}
.controls {
background-color: #4c4c4c;
min-height: 40px;
.autocompleter {
display: inline-block;
min-width: 250px;
width: 33%;
top: 8px;
margin-left: 30px;
}
.search-btn {
margin-left: 15px;
margin-top: 10px;
vertical-align: top;
}
}
.back-to-jamtracks-home {
font-weight: normal;
margin-right: 2px;
margin-top: 6px;
font-size:12px;
float:right;
}
.jamtrack-results-header {
margin-top:10px;
}
.artist-results {
div {
margin-bottom:5px;
}
a {
color:#fc0;
}
a.show-hide-artists {
margin-top:10px;
font-size:11px;
}
margin-bottom:30px;
}
.details-arrow.arrow-down{
margin:0;
color:#fc0;
display:inline-block;
float:none;
border-top: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
}
.details-arrow.arrow-up{
margin:0;
color:#fc0;
display:inline-block;
float:none;
border-bottom: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
}
}

View File

@ -0,0 +1,10 @@
/*
*
*
# comes from the gem 'rails-assets-react-select'
*= require react-select/dist/default.scss
*/
@import "client/common.css.scss";
.Select-control {
background-color:$ColorTextBoxBackground;
}

View File

@ -1,6 +1,23 @@
@import "client/common";
#open-jam-track-dialog {
height:auto;
min-height:400px;
div[data-react-class="JamTrackAutoComplete"] {
width: calc(98% - 90px);
margin-bottom: 20px;
display:inline-block;
}
.search-btn {
margin-left: 15px;
margin-top: 2px;
vertical-align: top;
float: right;
margin-right: 17px;
}
table.open-jam-tracks {
tbody {
tr:hover {

View File

@ -1,8 +1,8 @@
class ApiJamTracksController < ApiController
# have to be signed in currently to see this screen
before_filter :api_signed_in_user, :except => [:index, :show, :show_with_artist_info, :artist_index]
before_filter :api_any_user, :only => [:index, :show, :show_with_artist_info, :artist_index]
before_filter :api_signed_in_user, :except => [:index, :show, :autocomplete, :show_with_artist_info, :artist_index]
before_filter :api_any_user, :only => [:index, :show, :autocomplete, :show_with_artist_info, :artist_index]
before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right]
respond_to :json
@ -21,11 +21,19 @@ class ApiJamTracksController < ApiController
def index
data = JamTrack.index(params, any_user)
@jam_tracks, @next = data[0], data[1]
@jam_tracks, @next, @count = data[0], data[1], data[2]
render "api_jam_tracks/index", :layout => nil
end
def autocomplete
autocomplete = JamTrack.autocomplete(params, any_user)
@artists = autocomplete[:artists]
@songs = autocomplete[:songs]
render "api_jam_tracks/autocomplete", :layout => nil
end
def artist_index
data = JamTrack.artist_index(params, any_user)
@artists, @next = data[0], data[1]

View File

@ -4,6 +4,10 @@ class ApiShoppingCartsController < ApiController
respond_to :json
def log
@log || Logging.logger[VanillaForumsController ]
end
def index
@carts = any_user.shopping_carts
end
@ -22,7 +26,7 @@ class ApiShoppingCartsController < ApiController
response.status = :unprocessable_entity
respond_with @cart
else
respond_with @cart, responder: ApiResponder, :statue => 201
respond_with @cart, responder: ApiResponder, :status => 201
end
end

View File

@ -0,0 +1,7 @@
child(@artists => :artists) {
attributes :original_artist
}
child(@songs => :songs) {
attributes :id, :name, :original_artist
}

View File

@ -2,6 +2,9 @@ node :next do |page|
@next
end
node :count do |page|
@count
end
node :jamtracks do |page|
partial "api_jam_tracks/show", object: @jam_tracks
end

View File

@ -1,7 +1,7 @@
object @user
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count,
:recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :purchased_jamtracks_count
:recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card
if @user.musician?
node :location do @user.location end

View File

@ -25,9 +25,9 @@
tr.no-jamtracks-found.hidden
td colspan="3"
| You don't currently own any JamTracks.
a.orange href="/client#/jamtrackBrowse" Browse JamTracks Now
a.orange href="/client#/jamtrack/search" Browse JamTracks Now
.right
a.button-orange href="/client#/jamtrackBrowse" JAMTRACKS
a.button-orange href="/client#/jamtrack/search" JAMTRACKS
a.button-grey href="javascript:history.go(-1)" BACK
script#template-account-jamtrack type='text/template'

View File

@ -11,7 +11,7 @@ div layout="screen" layout-id="checkoutComplete" id="checkoutCompleteScreen" cla
.checkout-complete-wrapper
.no-purchases-prompt.hidden
| You have not made any purchases recently. Why not go go&nbsp;
a href="/client#/jamtrackBrowse" browse our collection of JamTracks
a href="/client#/jamtrack/search" browse our collection of JamTracks
| ?
.thanks-panel

View File

@ -14,7 +14,7 @@ div layout="screen" layout-id="checkoutOrder" id="checkoutOrderScreen" class="sc
| Please review your order, and if everything looks correct, click the PLACE YOUR ORDER button. Thank you!
p.empty-cart-prompt.hidden
| You have nothing in your cart. You can go browse for JamTracks&nbsp;
a href="/client#/jamtrackBrowse" here
a href="/client#/jamtrack/search" here
| .
p.no-account-info-prompt.hidden
| You have no billing info. Please go back to the&nbsp;

View File

@ -32,7 +32,7 @@
.homebox-info
/! 1 session invitation, 19 public sessions active
-if jamtracks
.homecard.jamtrack layout-grid-columns=small_tile_size layout-grid-position=column_positions[2] layout-grid-rows="1" layout-link="jamtrackLanding"
.homecard.jamtrack layout-grid-columns=small_tile_size layout-grid-position=column_positions[2] layout-grid-rows="1" layout-link="jamtrack"
h2 jamtracks
.homebox-info
/! 5 followers, 3 following
@ -67,7 +67,7 @@
.homebox-info
-if jamtracks
/! 1 session invitation, 19 public sessions active
.homecard.jamtrack layout-grid-columns=small_tile_size layout-grid-position=column_positions[2] layout-grid-rows="1" layout-link="jamtrackLanding"
.homecard.jamtrack layout-grid-columns=small_tile_size layout-grid-position=column_positions[2] layout-grid-rows="1" layout-link="jamtrack"
h2 jamtracks
.homebox-info
/! 5 followers, 3 following

View File

@ -0,0 +1,11 @@
#jamtrackFilter.screen.secondary.no-login-required layout='screen' layout-id='jamtrack/filter'
.content
.content-head
.content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19)
h1 search jamtracks
= render "screen_navigation"
.content-body
=form_tag('', {:id => 'jamtrack-filter-form', :class => 'inner-content'}) do
=render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_JAMTRACK})
.filter-body
= react_component 'JamTrackFilterScreen', {}

View File

@ -1,37 +1,8 @@
#jamtrackLanding.screen.secondary.no-login-required layout='screen' layout-id='jamtrackLanding'
#jamtrackLanding.screen.secondary.no-login-required layout='screen' layout-id='jamtrack'
.content
.content-head
.content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19)
h1 jamtracks
= render "screen_navigation"
.content-body
.content-body-scroller
.list-columns
.about
h2 what are jamtracks?
.what
.details JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each and every part of the master mix. Used with the free JamKazam app & Internet service, you can:
ul
li Solo just the part you want to play in order to hear and learn it
li Mute just the part you want to play and play along with the rest
li Make audio recordings and share them via Facebook or URL
li Make video recordings and share them via YouTube
li And even go online to play with others in real time -- for example, you can play the electric guitar lead, while someone else plays the bass, and all other parts play from the recorded tracks in your session
a.video-thumbnail href="https://www.youtube.com/watch?v=askHvcCoNfw" rel="external"
img.play src="/assets/content/icon_youtube_play.png"
.browse
h2 my jamtracks
.howto
.no-free-jamtrack.details.hidden
span="To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or "
a href="client#/account/jamtracks" visit the JamTracks Section of your account.
.free-jamtrack.orange-fill.details.hidden
| For a limited time, get one JamTrack free. Browse JamTracks below, add one to your shopping cart, and we'll make it free during the checkout process.
h2.browse-jamtracks browse jamtracks
.browse-header
| browse by band &nbsp;&nbsp;&nbsp;
a href="client#/jamtrackBrowse" or browse all jamtracks
.band-browse.two-column-list-container
ul#band_list
li#no_bands_found.hidden No bands found
= react_component 'JamTrackLandingScreen', {}

View File

@ -0,0 +1,8 @@
#jamtrackSearch.screen.secondary.no-login-required layout='screen' layout-id='jamtrack/search'
.content
.content-head
.content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19)
h1 search jamtracks
= render "screen_navigation"
.content-body
= react_component 'JamTrackSearchScreen', {}

View File

@ -11,7 +11,7 @@ div layout="screen" layout-id="redeemComplete" id="redeemCompleteScreen" class="
.checkout-complete-wrapper
.no-purchases-prompt.hidden
| You have not made any purchases recently. Why not go go&nbsp;
a href="/client#/jamtrackBrowse" browse our collection of JamTracks
a href="/client#/jamtrack/search" browse our collection of JamTracks
| ?
.thanks-panel

View File

@ -14,7 +14,7 @@ div layout="screen" layout-id="redeemSignup" id="redeemSignupScreen" class="scre
p.carry-on-prompt
| You can go back to browsing.
.actions
a.btnNext.button-orange href="/client#/jamtrackBrowse" BROWSE JAMTRACKS
a.btnNext.button-orange href="/client#/jamtrack/search" BROWSE JAMTRACKS
.not-signed-in.prompt
| To get your free&nbsp;
span.jamtrack-name

View File

@ -519,4 +519,4 @@
</div>
</div>
</div>
</script>
</script>

View File

@ -1,167 +0,0 @@
#session-screen-old.screen.secondary[layout="screen" layout-id="session_old" layout-arg="id"]
.content-head
.content-icon
= image_tag "shared/icon_session.png", {:height => 19, :width => 19}
h1
| session
.content-body
#session-controls
a#session-resync.button-grey.resync.left
= image_tag "content/icon_resync.png", {:align => "texttop", :height => 12, :width => 12}
| RESYNC
a#session-settings-button.button-grey.left[layout-link="session-settings"]
= image_tag "content/icon_settings_sm.png", {:align => "texttop", :height => 12, :width => 12}
| SETTINGS
a.button-grey.left[layout-link="share-dialog"]
= image_tag "content/icon_share.png", {:align => "texttop", :height => 12, :width => 12}
| SHARE
- if (Rails.application.config.video_available && Rails.application.config.video_available!="none")
a#session-webcam.button-grey-toggle.video.left
= image_tag "content/icon_cam.png", {:align => "texttop", :height => 12, :width => 12}
| VIDEO
.block
.label
| VOLUME:
#volume.fader.lohi[mixer-id=""]
.block.monitor-mode-holder
.label
| MIX:
select.monitor-mode.easydropdown
option.label[value="personal"]
| Personal
option[value="master"]
| Master
a#session-leave.button-grey.right.leave[href="/client#/home"]
| X  LEAVE
#tracks
.content-scroller
.content-wrapper
.session-mytracks
h2
| my tracks
#track-settings.session-add[style="display:block;" layout-link="configure-tracks"]
= image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18}
span
| Settings
.session-tracks-scroller
#session-mytracks-notracks
p.notice
| You have not set up any inputs for your instrument or vocals.&nbsp;
| If you want to hear yourself play through the JamKazam app,&nbsp;
| and let the app mix your live playing with JamTracks, or with other musicians in online sessions,&nbsp;
a.open-ftue-no-tracks href='#' click here now.
#session-mytracks-container
#voice-chat.voicechat[style="display:none;" mixer-id=""]
.voicechat-label
| CHAT
.voicechat-gain
.voicechat-mute.enabled[control="mute" mixer-id=""]
.session-fluidtracks
.session-livetracks
h2
| live tracks
.session-add[layout-link="select-invites"]
a#session-invite-musicians[href="#"]
= image_tag "content/icon_add.png", {:width => 19, :height => 19, :align => "texttop"}
|   Invite Musicians
.session-tracks-scroller
#session-livetracks-container
.when-empty.livetracks
| No other musicians
br
| are in your session
br[clear="all"]
#recording-start-stop.recording
a
= image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"}
|   
span#recording-status
| Make Recording
.session-recordings
h2
| other audio
.session-recording-name-wrapper
.session-recording-name.left
| (No audio loaded)
.session-add.right
a#close-playback-recording[href="#"]
= image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"}
|   Close
.session-tracks-scroller
#session-recordedtracks-container
.when-empty.recordings
span.open-media-file-header
= image_tag "content/icon_folder.png", {width:22, height:20}
| Open:
ul.open-media-file-options
li
a#open-a-recording[href="#"]
| Recording
- if Rails.application.config.jam_tracks_available || (current_user && current_user.admin)
li.open-a-jamtrack
a#open-a-jamtrack[href="#"]
| JamTrack
- if Rails.application.config.backing_tracks_available
li
a#open-a-backingtrack[href="#"]
| Audio File
.when-empty.use-metronome-header
- if Rails.application.config.metronome_available
= image_tag "content/icon_metronome.png", {width:22, height:20}
a#open-a-metronome[href="#"]
| Use Metronome
br[clear="all"]
.play-controls-holder
= render "play_controls"
.webcam-container.hidden
/ Webcam is actually in another window.
= render 'webcam'
= render "configureTrack"
= render "addTrack"
= render "addNewGear"
= render "error"
= render "sessionSettings"
script#template-session-track[type="text/template"]
.session-track.track client-id="{clientId}" track-id="{trackId}"
.track-vu-left.mixer-id="{vuMixerId}_vul"
.track-vu-right.mixer-id="{vuMixerId}_vur"
.track-label[title="{name}"]
span.name-text="{name}"
#div-track-close.track-close.op30 track-id="{trackId}"
=image_tag("content/icon_closetrack.png", {width: 12, height: 12})
div class="{avatarClass}"
img src="{avatar}"
.track-instrument class="{preMasteredClass}"
img height="45" src="{instrumentIcon}" width="45"
.track-gain mixer-id="{mixerId}"
.track-icon-mute class="{muteClass}" control="mute" mixer-id="{muteMixerId}"
.track-icon-loop.hidden control="loop"
input#loop-button type="checkbox" value="loop" Loop
.track-connection.grey mixer-id="{mixerId}_connection"
CONNECTION
.disabled-track-overlay
.metronome-selects.hidden
select.metronome-select.metro-sound title="Metronome Sound"
option.label value="Beep" Knock
option.label value="Click" Tap
option.label value="Snare" Snare
option.label value="Kick" Kick
br
select.metronome-select.metro-tempo title="Metronome Tempo"
- metronome_tempos.each do |t|
option.label value=t
=t
script#template-option type="text/template"
option value="{value}" title="{label}" selected="{selected}"
="{label}"
script#template-genre-option type="text/template"
option value="{value}"
="{label}"
script#template-pending-metronome type="text/template"
.pending-metronome
.spinner-large
p Your metronome is synchronizing.

View File

@ -6,3 +6,56 @@
| session
.content-body
= react_component 'SessionScreen', {}
= render "configureTrack"
= render "addTrack"
= render "addNewGear"
= render "error"
= render "sessionSettings"
script#template-session-track[type="text/template"]
.session-track.track client-id="{clientId}" track-id="{trackId}"
.track-vu-left.mixer-id="{vuMixerId}_vul"
.track-vu-right.mixer-id="{vuMixerId}_vur"
.track-label[title="{name}"]
span.name-text="{name}"
#div-track-close.track-close.op30 track-id="{trackId}"
=image_tag("content/icon_closetrack.png", {width: 12, height: 12})
div class="{avatarClass}"
img src="{avatar}"
.track-instrument class="{preMasteredClass}"
img height="45" src="{instrumentIcon}" width="45"
.track-gain mixer-id="{mixerId}"
.track-icon-mute class="{muteClass}" control="mute" mixer-id="{muteMixerId}"
.track-icon-loop.hidden control="loop"
input#loop-button type="checkbox" value="loop" Loop
.track-connection.grey mixer-id="{mixerId}_connection"
CONNECTION
.disabled-track-overlay
.metronome-selects.hidden
select.metronome-select.metro-sound title="Metronome Sound"
option.label value="Beep" Knock
option.label value="Click" Tap
option.label value="Snare" Snare
option.label value="Kick" Kick
br
select.metronome-select.metro-tempo title="Metronome Tempo"
- metronome_tempos.each do |t|
option.label value=t
=t
script#template-option type="text/template"
option value="{value}" title="{label}" selected="{selected}"
="{label}"
script#template-genre-option type="text/template"
option value="{value}"
="{label}"
script#template-instrument-option-simple type="text/template"
option value="{value}"
="{label}"
script#template-pending-metronome type="text/template"
.pending-metronome
.spinner-large
p Your metronome is synchronizing.

View File

@ -56,6 +56,6 @@
.clearall
.right.actions
%a.button-grey{href: "#"} HELP
%a.button-orange{href: "/client#/jamtrackBrowse"} CONTINUE SHOPPING
%a.button-orange{href: "/client#/jamtrack/search"} CONTINUE SHOPPING
%a.button-orange.proceed-checkout{href: "#"} PROCEED TO CHECKOUT
.clearall

View File

@ -22,8 +22,9 @@
/ =select_tag("#{filter_label}_genre", |
/ options_for_select([['Any', '']].concat(JamRuby::Genre.all.collect { |ii| [ii.description, ii.id] })), {:class => 'easydropdown'}) |
-if :jamtrack==filter_label
=content_tag(:div, 'Filter JamTracks:', :class => 'filter-element desc')
=select_tag("#{filter_label}_artist", options_for_select([['Any Band', '']].concat(JamTrack.all_artists.collect { |ii| [ii, ii] })), {:class => 'easydropdown'})
=content_tag(:div, 'Filter JamTracks By:', :class => 'filter-element desc')
=select_tag("#{filter_label}_genre",
options_for_select([['Any Genre', '']].concat(JamRuby::Genre.order(:description).collect { |ii| [ii.description, ii.id] })), {:class => 'easydropdown'})
=content_tag(:div, :class => 'filter-element wrapper') do
-if :musician==filter_label || :jamtrack==filter_label
/ =content_tag(:div, 'Instrument:', :class => 'filter-element desc instrument-selector')

View File

@ -31,7 +31,6 @@
<%= render "sidebar" %>
<%= render "scheduledSession" %>
<%= render "findSession" %>
<%= render "session" %>
<%= render "session2" %>
<%= render "profile" %>
<%= render "bandProfile" %>
@ -39,7 +38,8 @@
<%= render "band_setup_photo" %>
<%= render "users/feed_music_session_ajax" %>
<%= render "users/feed_recording_ajax" %>
<%= render "jamtrack_browse" %>
<%= render "jamtrack_search" %>
<%= render "jamtrack_filter" %>
<%= render "jamtrack_landing" %>
<%= render "shopping_cart" %>
<%= render "checkout_signin" %>
@ -167,6 +167,7 @@
localRecordingsDialog.initialize();
var openJamTrackDialog = new JK.OpenJamTrackDialog(JK.app);
JK.OpenJamTrackDialogInstance = openJamTrackDialog;
openJamTrackDialog.initialize();
var openBackingTrackDialog = new JK.OpenBackingTrackDialog(JK.app);
@ -284,11 +285,11 @@
// }
// findSessionScreen.initialize(sessionLatency);
var jamtrackScreen = new JK.JamTrackScreen(JK.app);
jamtrackScreen.initialize();
//var jamtrackScreen = new JK.JamTrackScreen(JK.app);
//jamtrackScreen.initialize();
var jamtrackLanding = new JK.JamTrackLanding(JK.app);
jamtrackLanding.initialize();
//var jamtrackLanding = new JK.JamTrackLanding(JK.app);
//jamtrackLanding.initialize();
var shoppingCartScreen = new JK.ShoppingCartScreen(JK.app);
shoppingCartScreen.initialize();

View File

@ -11,7 +11,7 @@
| &nbsp;to access most functionality on this page.
p
| However, you can browse for&nbsp;
a class="go-to-jamtracks" href='/client#/jamtrackBrowse' JamTracks
a class="go-to-jamtracks" href='/client#/jamtrack/search' JamTracks
| &nbsp;without logging in.
br
.clearall

View File

@ -7,6 +7,8 @@
.dialog-inner
= react_component 'JamTrackAutoComplete', {:onSearch => 'window.JK.OpenJamTrackDialogInstance.search'}
button.search-btn.button-orange SEARCH
.recording-wrapper
table.open-jam-tracks cellspacing="0" cellpadding="0" border="0"
thead
@ -28,7 +30,7 @@
.help-links
a.what-are-jamtracks href='#'
| What are JamTracks?
a href='/client#/jamtrackBrowse' rel="external"
a href='/client#/jamtrack/search' rel="external"
| Shop for JamTracks
.right
a href="#" class="button-grey" layout-action="cancel"

View File

@ -22,7 +22,7 @@
p Click the GET A JAMTRACK FREE button below. Browse to find the one you want, click the Add to cart, and we'll apply a credit during checkout to make the first one free! We're confident you'll be back for more.
.cta-big-button
a.white-bordered-button href="/client#/jamtrackBrowse" GET A JAMTRACK FREE!
a.white-bordered-button href="/client#/jamtrack/search" GET A JAMTRACK FREE!
.column
h1 Why are JamTracks Better than Backing Tracks?
p

View File

@ -48,7 +48,7 @@
<% if @show_cta_free_jamtrack %>
<div class="cta-free-jamtrack">
<%= link_to image_tag("web/free-jamtrack-cta.png", :alt => "ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!"), "/client#/jamtrackBrowse", class: "cta-free-jamtrack" %>
<%= link_to image_tag("web/free-jamtrack-cta.png", :alt => "ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!"), "/client#/jamtrack/search", class: "cta-free-jamtrack" %>
<span class="value-indicator">$1.99 value</span>
</div>
<% end %>

View File

@ -49,7 +49,7 @@
.video-container
iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen="allowfullscreen"
a.go-jamtrack-shopping href="/client#/jamtrackBrowse" rel="external"
a.go-jamtrack-shopping href="/client#/jamtrack/search" rel="external"
| Shop for free
br
| JamTrack now!

View File

@ -9,7 +9,7 @@
strong JamTracks
| &nbsp;are the best way to play with your favorite music. Unlike traditional backing tracks, JamTracks are complete multitrack recordings, with fully isolated tracks for each part.
= link_to image_tag("web/button_cta_jamtrack.png", width: 234, height:57), '/client#/jamtrackBrowse', class: 'cta-button jamtracks'
= link_to image_tag("web/button_cta_jamtrack.png", width: 234, height:57), '/client#/jamtrack/search', class: 'cta-button jamtracks'
br clear="all"
.extra-links
.learn-more

View File

@ -41,7 +41,7 @@ SampleApp::Application.routes.draw do
match '/affiliate/links/:id', to: 'affiliates#links', via: :get, as: 'affilate_links'
# redirect /jamtracks to jamtracks browse page
get '/jamtracks', to: redirect('/client#/jamtrackBrowse')
get '/jamtracks', to: redirect('/client#/jamtrack/search')
# product pages
match '/products/jamblaster', to: 'landings#product_jamblaster', via: :get, as: 'product_jamblaster'
@ -233,6 +233,7 @@ SampleApp::Application.routes.draw do
match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list'
# Jamtracks
match '/jamtracks/autocomplete' => 'api_jam_tracks#autocomplete', :via => :get, :as => 'api_jam_tracks_autocomplete'
match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased'
match '/jamtracks/artists' => 'api_jam_tracks#artist_index', :via => :get, :as => 'api_jam_tracks_list_artists'
match '/jamtracks/:plan_code' => 'api_jam_tracks#show', :via => :get, :as => 'api_jam_tracks_show'

View File

@ -70,6 +70,9 @@ namespace :jam_tracks do
JamTrackImporter.synchronize_all(skip_audio_upload:false)
end
task tency_dups: :environment do |task, args|
end
task onboarding_exceptions: :environment do |task, args|
JamTrackImporter.onboarding_exceptions
end

View File

@ -228,6 +228,7 @@ describe ApiRecordingsController do
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@jam_track = FactoryGirl.create(:jam_track)
@jam_track.reload
# make sure the jam track is opened
@music_session.jam_track = @jam_track

View File

@ -701,8 +701,8 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
describe "Complete Checkout Flow" do
it "for anonymous user" do
visit "/client#/jamtrackBrowse"
find('h1', text: 'jamtracks')
visit "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search"
find('h1', text: 'search jamtracks')
#find('a', text: 'What is a JamTrack?')
find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_acdc_backinblack.id}\"]", text: 'GET IT FREE!').trigger(:click)
@ -756,7 +756,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
# now, go back to checkout flow again, and make sure we are told there are no free jam tracks
visit "/client#/jamtrackBrowse"
visit "/client?song=#{jamtrack_pearljam_evenflow.name}#/jamtrack/search"
find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_pearljam_evenflow.id}\"]", text: 'ADD TO CART').trigger(:click)
find('h1', text: 'shopping cart')
@ -833,9 +833,9 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
affiliate_params = partner.affiliate_query_params
visit '/landing/jamtracks/acdc-backinblack?' + affiliate_params
find('.browse-all a').trigger(:click)
find('.browse-band a').trigger(:click)
find('h1', text: 'jamtracks')
find('h1', text: 'search jamtracks')
#find('a', text: 'What is a JamTrack?')
find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_acdc_backinblack.id}\"]", text: 'GET IT FREE!').trigger(:click)
@ -877,7 +877,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
# now, go back to checkout flow again, and make sure we are told there are no free jam tracks
visit "/client#/jamtrackBrowse"
visit "/client?song=#{jamtrack_pearljam_evenflow.name}#/jamtrack/search"
find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_pearljam_evenflow.id}\"]", text: 'ADD TO CART').trigger(:click)
find('h1', text: 'shopping cart')
@ -947,7 +947,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
it "for existing user with a freebie available (already logged in)" do
fast_signin(user, "/client#/jamtrackBrowse")
fast_signin(user, "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search")
find('h1', text: 'jamtracks')
#find('a', text: 'What is a JamTrack?')
@ -967,7 +967,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
user.has_redeemable_jamtrack = false
user.save!
fast_signin(user, "/client#/jamtrackBrowse")
fast_signin(user, "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search")
find('h1', text: 'jamtracks')
#find('a', text: 'What is a JamTrack?')
@ -1025,7 +1025,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
user.save!
# the point of this is to also prove that the free jamtrack does not carry over to the existing user, once they log in
visit "/client#/jamtrackBrowse"
visit "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search"
find('h1', text: 'jamtracks')
#find('a', text: 'What is a JamTrack?')
@ -1087,7 +1087,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
set_cookie('redeemed_jamtrack', 'true')
# the point of this is to also prove that the free jamtrack does not carry over to the existing user, once they log in
visit "/client#/jamtrackBrowse"
visit "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search"
find('h1', text: 'jamtracks')
#find('a', text: 'What is a JamTrack?')
@ -1152,7 +1152,7 @@ describe "Checkout", :js => true, :type => :feature, :capybara_feature => true d
set_cookie('redeemed_jamtrack', 'true')
# the point of this is to also prove that the free jamtrack does not carry over to the existing user, once they log in
visit "/client#/jamtrackBrowse"
visit "/client?song=#{jamtrack_acdc_backinblack.name}#/jamtrack/search"
find('h1', text: 'jamtracks')
#find('a', text: 'What is a JamTrack?')

View File

@ -52,9 +52,9 @@ describe "Individual JamTrack", :js => true, :type => :feature, :capybara_featur
find('.jam-track-preview-holder[data-id="' + track.id + '"] .instrument-name', text:track.instrument.description)
end
end
find('.browse-band a')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrackBrowse")
find('.browse-all a')['href'].should eq("/client#/jamtrackBrowse")
find('a.cta-free-jamtrack')['href'].should eq("/client#/jamtrackBrowse")
find('.browse-band a')['href'].should eq("/client?artist=#{jamtrack_acdc_backinblack.original_artist}#/jamtrack/search")
find('.browse-all a')['href'].should eq("/client#/jamtrack/search")
find('a.cta-free-jamtrack')['href'].should eq("/client#/jamtrack/search")
find('a.cta-free-jamtrack').trigger(:click)
find('h1', text: 'check out')
find('h3', text: 'OR SIGN UP USING YOUR EMAIL')

View File

@ -0,0 +1,71 @@
require 'spec_helper'
describe "JamTrack Search", :js => true, :type => :feature, :capybara_feature => true do
let(:user) { FactoryGirl.create(:user, has_redeemable_jamtrack: true) }
let(:jt_us) { FactoryGirl.create(:jam_track, :name=>'jt_us', sales_region: 'United States', make_track: true, original_artist: "foobar") }
let(:jt_ww) { FactoryGirl.create(:jam_track, :name=>'jt_ww', sales_region: 'Worldwide', make_track: true, original_artist: "barfoo") }
let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genres: [JamRuby::Genre.find('rock')], make_track: true, original_artist: "badfood") }
let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genres: [JamRuby::Genre.find('blues')], make_track: true, original_artist: "foodbart") }
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 30 # these tests are SLOOOOOW
end
before(:each) do
ShoppingCart.delete_all
JamTrackRight.delete_all
JamTrack.delete_all
JamTrackTrack.delete_all
JamTrackLicensor.delete_all
stub_const("APP_CONFIG", web_config)
end
it "search repeatedly" do
fast_signin user, "/client#/jamtrack/search"
find('h1', text: 'search jamtracks')
find('#jamtrackSearch .Select-control').trigger(:mousedown)
# wait for the 'Type to search' prompt to show
find('#jamtrackSearch .Select-noresults')
send_key_sequence('#jamtrackSearch .Select-control', 'abc')
jt_us.touch
find('.Select-input').trigger(:blur)
find('#jamtrackSearch .search-btn').trigger(:click)
find('#jamtrackSearch .Select-control').trigger(:mousedown)
send_key_sequence('#jamtrackSearch .Select-control', 'foo')
#screenshot_and_open_image
find('.jamtrack-record[data-jamtrack-id="' + jt_us.id + '"]')
find('.show-artist', text: jt_us.original_artist).trigger(:click)
find('h2.jamtrack-results-header', text: "search results: jamtracks for artist \"#{jt_us.original_artist}\"")
find('.jamtrack-record[data-jamtrack-id="' + jt_us.id + '"]')
# TODO: do a second search. Unfortunately, i Can't figure out how to simulate events to cause the search bar to reset
#jt_blues.touch
#find('.Select-input').trigger(:blur)
#find('#jamtrackSearch .Select-control').trigger(:mousedown)
#send_key_sequence('#jamtrackSearch .Select-control', 'foo')
#find('.jamtrack-record[data-jamtrack-id="' + jt_blues.id + '"]')
#screenshot_and_open_image
#jt_ww.touch
#jt_rock.touch
#jt_blues.touch
end
end

View File

@ -5,8 +5,8 @@ describe "JamTrack Landing", :js => true, :type => :feature, :capybara_feature =
let(:user) { FactoryGirl.create(:user, has_redeemable_jamtrack: true) }
let(:jt_us) { FactoryGirl.create(:jam_track, :name=>'jt_us', sales_region: 'United States', make_track: true, original_artist: "foobar") }
let(:jt_ww) { FactoryGirl.create(:jam_track, :name=>'jt_ww', sales_region: 'Worldwide', make_track: true, original_artist: "barfoo") }
let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genre: JamRuby::Genre.find('rock'), make_track: true, original_artist: "badfood") }
let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genre: JamRuby::Genre.find('blues'), make_track: true, original_artist: "foodbart") }
let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genres: [JamRuby::Genre.find('avante-garde')], make_track: true, original_artist: "badfood") }
let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genres: [JamRuby::Genre.find('blues')], make_track: true, original_artist: "foodbart") }
before(:all) do
Capybara.javascript_driver = :poltergeist
@ -27,10 +27,12 @@ describe "JamTrack Landing", :js => true, :type => :feature, :capybara_feature =
end
it "not logged in" do
jt_us.touch
jt_ww.touch
jt_rock.touch
jt_rock.reload
jt_blues.touch
jt_blues.reload
visit '/client#/jamtrackLanding'
visit '/client#/jamtrack'
find('h2', text: 'what are jamtracks?')
if web_config.one_free_jamtrack_per_user
@ -39,20 +41,24 @@ describe "JamTrack Landing", :js => true, :type => :feature, :capybara_feature =
find('.no-free-jamtrack')
end
find("a[artist='#{jt_us.original_artist}']", text: 'foobar (1)')
find("a[artist='#{jt_ww.original_artist}']", text: 'barfoo (1)')
# and go ahead and try out some searches
jk_select(jt_rock.genres[0].description, '#jamtrackLanding .genre-list')
jk_select(jt_rock.jam_track_tracks[0].instrument.description, '#jamtrackLanding .instrument-list')
find('.search-by-filter-btn').trigger(:click)
# should have transitioned to the filter screen
find('.JamTrackFilterScreen')
# and the search should have been kicked off and already showing our track
find('.jamtrack-record[data-jamtrack-id="' + jt_rock.id + '"]')
end
it "logged in and has redeemable track" do
jt_us.touch
jt_ww.touch
fast_signin(user, '/client#/jamtrackLanding')
fast_signin(user, '/client#/jamtrack')
find('h2', text: 'what are jamtracks?')
find('.free-jamtrack')
find("a[artist='#{jt_us.original_artist}']", text: 'foobar (1)')
find("a[artist='#{jt_ww.original_artist}']", text: 'barfoo (1)')
end
it "logged in and does not have redeemable track" do
@ -61,11 +67,9 @@ describe "JamTrack Landing", :js => true, :type => :feature, :capybara_feature =
user.has_redeemable_jamtrack = false
user.save!
fast_signin(user, '/client#/jamtrackLanding')
fast_signin(user, '/client#/jamtrack')
find('h2', text: 'what are jamtracks?')
find('.no-free-jamtrack')
find("a[artist='#{jt_us.original_artist}']", text: 'foobar (1)')
find("a[artist='#{jt_ww.original_artist}']", text: 'barfoo (1)')
end
end

View File

@ -11,7 +11,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
before(:all) do
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver
Capybara.default_wait_time = 30 # these tests are SLOOOOOW
Capybara.default_wait_time = 10 # these tests are SLOOOOOW
end
@ -34,7 +34,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
end
def find_jamtrack jamtrack, options = {}
jamtrack_record = find(".jamtrack-record[jamtrack-id=\"#{jamtrack.id}\"]")
jamtrack_record = find(".jamtrack-record[data-jamtrack-id=\"#{jamtrack.id}\"]")
#jamtrack_record.find('.detail-value', text: jamtrack.name)
#jamtrack_record.find('.detail-value', text: jamtrack.recording_type)
#jamtrack_record.find('.detail-value', text: jamtrack.original_artist)
@ -58,68 +58,24 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
end
def not_find_jamtrack jamtrack
should_not have_selector(".jamtrack-record[jamtrack-id=\"#{jamtrack.id}\"]")
end
describe "Shopping" do
before(:each) do
visit "/client#/jamtrackBrowse"
find('h1', text: 'jamtracks')
jk_select('Any', '#jamtrack-find-form #jamtrack_artist')
end
it "shows all JamTracks" do
find_jamtrack jt_us
find_jamtrack jt_ww
find_jamtrack jt_rock
end
=begin
# removed for now
it "filters with availability" do
jk_select('Worldwide', '#jamtrack-find-form #jamtrack_artist')
find_jamtrack jt_ww
not_find_jamtrack jt_us
not_find_jamtrack jt_rock
end
=end
it "filters with artist" do
jk_select("foobar", '#jamtrack-find-form #jamtrack_artist')
find_jamtrack jt_us
not_find_jamtrack jt_blues
not_find_jamtrack jt_rock
not_find_jamtrack jt_ww
end
it "filters with instrument" do
jk_select('Electric Guitar', '#jamtrack-find-form #jamtrack_instrument')
find_jamtrack jt_us
find_jamtrack jt_ww
find_jamtrack jt_rock
end
end
it "sets artist filter" do
pending "The item is clearly present, so not currently sure why capybar can't find it"
visit "/client?artist=foobar#/jamtrackBrowse"
art = find('#jamtrack_artist')
puts "art: #{art}"
should_not have_selector(".jamtrack-record[data-jamtrack-id=\"#{jamtrack.id}\"]")
end
describe "Shopping Carts" do
before(:each) do
visit "/client#/jamtrackBrowse"
find('h1', text: 'jamtracks')
visit "/client#/jamtrack/search"
find('h1', text: 'jamtracks')
jk_select('Any', '#jamtrack-find-form #jamtrack_artist')
find('#jamtrackSearch .Select-control').trigger(:mousedown)
# wait for the 'Type to search' prompt to show
find('#jamtrackSearch .Select-noresults')
send_key_sequence('#jamtrackSearch .Select-control', jt_us.name)
find('#jamtrackSearch .search-btn').trigger(:click)
end
it "adds/deletes JamTrack to/from Cart" do
find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jt_us.id}\"]").trigger(:click)
find('h1', text: 'shopping cart')
@ -127,19 +83,22 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
find('.cart-item-price', text: "$ #{jt_us.price}")
find('a.button-orange', text: 'CONTINUE SHOPPING').trigger(:click)
jk_select('Any', '#jamtrack-find-form #jamtrack_artist')
find('button.disabled.search-btn')
find_jamtrack jt_us, {added_cart: true}
find('a.header-shopping-cart').trigger(:click)
find("a.remove-cart").trigger(:click)
find('a.button-orange', text: 'CONTINUE SHOPPING').trigger(:click)
jk_select('Any', '#jamtrack-find-form #jamtrack_artist')
find('button.disabled.search-btn')
find_jamtrack jt_us
find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jt_us.id}\"]").trigger(:click)
find('.shopping-sub-total', text: "Subtotal:$ #{jt_us.price}")
find('a.button-orange', text: 'CONTINUE SHOPPING').trigger(:click)
find('button.disabled.search-btn')
find_jamtrack jt_ww

View File

@ -137,7 +137,7 @@ bputs "before load poltergeist"
require 'capybara/poltergeist'
bputs "before register capybara"
Capybara.register_driver :poltergeist do |app|
driver = Capybara::Poltergeist::Driver.new(app, { debug: false, phantomjs_logger: File.open('log/phantomjs.out', 'w'), phantomjs_options: ['--ignore-ssl-errors=yes'] })
driver = Capybara::Poltergeist::Driver.new(app, { debug: false, phantomjs_logger: File.open('log/phantomjs.out', 'w'), phantomjs_options: ['--ignore-ssl-errors=yes', '--load-images=no'] })
end
Capybara.javascript_driver = :poltergeist
Capybara.default_wait_time = 10

View File

@ -739,9 +739,23 @@ end
def send_key(selector, keycode = 13)
keypress_script = "var e = $.Event('keyup', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
page.driver.execute_script(keypress_script)
end
def send_key_sequence(selector, text)
text.each_char do |char|
keycode = char.ord - 32
keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
page.driver.execute_script(keypress_script)
end
end
def send_keydown(selector, keycode = 13)
keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); jQuery('#{selector}').trigger(e);"
page.driver.execute_script(keypress_script)
end
def special_characters
["?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}"]
end

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,73 @@
function topPosition(domElt) {
if (!domElt) {
return 0;
}
return domElt.offsetTop + topPosition(domElt.offsetParent);
}
(function () {
if (React.addons && React.addons.InfiniteScroll) {
return React.addons.InfiniteScroll;
}
React.addons = React.addons || {};
var InfiniteScroll = React.addons.InfiniteScroll = React.createClass({
getDefaultProps: function () {
return {
pageStart: 0,
hasMore: false,
loadMore: function () {},
threshold: 250,
scrollNode: null
};
},
componentDidMount: function () {
this.pageLoaded = this.props.pageStart;
this.attachScrollListener();
},
shouldComponentUpdate: function(nextProps, nextState) {
return !_.isEqual(this.props.children, nextProps.children);
},
componentDidUpdate: function () {
this.attachScrollListener();
},
render: function () {
var props = this.props;
return React.DOM.tbody(null, props.children, props.hasMore && (props.loader || InfiniteScroll._defaultLoader));
},
scrollListener: function () {
var el = this.props.scrollNode ? $(this.getDOMNode()).closest(this.props.scrollNode).get(0) : this.getDOMNode();
var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
console.log("scrollTop", scrollTop)
if (topPosition(el) + el.offsetHeight - scrollTop - window.innerHeight < Number(this.props.threshold)) {
this.detachScrollListener();
// call loadMore after detachScrollListener to allow
// for non-async loadMore functions
this.props.loadMore(this.pageLoaded += 1);
}
},
attachScrollListener: function () {
if (!this.props.hasMore) {
return;
}
console.log("attachScrollListener")
window.addEventListener('scroll', this.scrollListener);
window.addEventListener('resize', this.scrollListener);
setTimeout(
this.scrollListener,
1
);
},
detachScrollListener: function () {
window.removeEventListener('scroll', this.scrollListener);
window.removeEventListener('resize', this.scrollListener);
},
componentWillUnmount: function () {
this.detachScrollListener();
}
});
InfiniteScroll.setDefaultLoader = function (loader) {
InfiniteScroll._defaultLoader = loader;
};
return InfiniteScroll;
})();