jam-cloud/web/app/assets/javascripts/react-components/JamTrackSearchScreen.js.jsx...

525 lines
18 KiB
CoffeeScript

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 context.JK.currentUserFreeJamTrack then 'free' else 'non-free'
trackRow.is_free = trackRow.free_state == '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}</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>`
getInitialState: () ->
{search: '', type: 'user-input', artists:[], jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, first_search: true, count: 0}
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})
clearResults:() ->
#@content.empty()
#@noMoreJamtracks.hide()
@setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null})
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: () ->
#@clearResults();
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)
})