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

559 lines
19 KiB
CoffeeScript

context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackSearchScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@UserStore,"onUserChanged")]
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
if track.track_type == 'Master' || track.track_type == 'Track'
trackRow.tracks.push(track)
if track.track_type == 'Master'
track.instrument_desc = "Master"
else if track.track_type == 'Track'
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 && jamtrack.allow_free) then 'free' else 'non-free'
trackRow.is_free = @state.is_free && jamtrack.allow_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.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else 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.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"><a onClick={this.onArtistClick.bind(this, jamtrack.original_artist)}>by {jamtrack.original_artist}</a></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 className="download-all">Download JamTracks catalog<br/><JamTrackCSVLink/> or <JamTrackPdfLink/></div>
</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: @user.show_free_jamtrack, 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}
onArtistClick: (artist, e) ->
e.preventDefault()
@search('artist-select', artist)
onSelectChange: (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()
artistSearch = {limit:100}
if search_type == 'artist-select'
# the user wants to see just artists matching thes exact name
artistSearch.artist = input
else
# the user wants to see anything sort of matching input
artistSearch.artist_search = input
if input?
@rest.getJamTrackArtists(artistSearch)
.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 = input # works like exact match
else if search_type == 'song-select'
query.song = input # works as exact match
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((jqXHR) =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
)
.fail((jqXHR) =>
@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}\"]")
alreadyRegistered = jamtrackElement.data('registered')
unless alreadyRegistered
jamtrackElement.data('jamTrack', jamTrack)
jamtrackElement.data('registered', true)
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
$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) ->
@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) =>
console.log("added item to shopping cart. fast_redeem? " + response.fast_redeem)
if response.fast_reedem
if context.JK.currentUserId?
context.location = '/client#/redeemComplete'
else
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail(((jqxhr) =>
handled = false
if jqxhr.status == 422
body = JSON.parse(jqxhr.responseText)
if body.errors && body.errors.base
handled = true
context.JK.Banner.showAlert("You can not have a mix of free and non-free items in your shopping cart.<br/><br/>If you want to add this new item to your shopping cart, then clear out all current items by clicking on the shopping cart icon and clicking 'delete' next to each item.")
if !handled
@app.ajaxError(arguments[0], arguments[1], arguments[2])
))
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)
else
if !@state.first_search
@search(@state.type, window.JamTrackSearchInput)
if performSearch
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrack/search")
beforeShow: () ->
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)
onUserChanged: (userState) ->
@user = userState?.user
@setState({is_free: @user?.show_free_jamtrack})
})