* wip
This commit is contained in:
parent
303e186eff
commit
45a8a6897c
|
|
@ -1,6 +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
|
||||
tsvector_update_trigger(search_tsv, 'public.jamenglish', original_artist, name, additional_info);
|
||||
CREATE INDEX jam_track_search_tsv_index ON jam_tracks USING gin(search_tsv);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -280,11 +280,17 @@ module JamRuby
|
|||
end
|
||||
|
||||
if options[:artist_search]
|
||||
query = query.where('original_artist ilike ?', "%#{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]
|
||||
query = query.where('name ilike ?', "%#{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?
|
||||
|
|
@ -369,7 +375,10 @@ module JamRuby
|
|||
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
|
||||
|
||||
if options[:artist_search]
|
||||
query = query.where('original_artist ilike ?', "%#{options[:artist_search]}%")
|
||||
tsquery = Search.create_tsquery(options[:artist_search])
|
||||
if tsquery
|
||||
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ module JamRuby
|
|||
@search_type = :musicians
|
||||
User.musicians
|
||||
end
|
||||
@results = rel.where("(name_c @@ to_tsquery('jamenglish', ?))", tsquery).limit(10)
|
||||
@results = rel.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery).limit(10)
|
||||
@results
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
input: null
|
||||
MAX_ARTIST_SHOW: 3
|
||||
|
||||
filterOption:() ->
|
||||
true
|
||||
|
||||
render: () ->
|
||||
|
||||
|
|
@ -161,6 +163,7 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
|
||||
if !@state.first_search
|
||||
|
||||
|
||||
# only show the artists links if the user typed the results
|
||||
if @state.type == 'user-input'
|
||||
artistSection =
|
||||
|
|
@ -186,20 +189,27 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
{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">
|
||||
<Select
|
||||
placeholder="Search for JamTracks"
|
||||
name="search-field"
|
||||
asyncOptions={this.getOptions}
|
||||
autoload={false}
|
||||
value={window.JamTrackSearchInput}
|
||||
value={searchValue}
|
||||
onChange={this.onSelectChange}
|
||||
onBlur={this.onSelectBlur}
|
||||
className="autocompleter"
|
||||
cacheAsyncResults={true}
|
||||
cacheAsyncResults={false}
|
||||
filterOption={this.filterOption}
|
||||
/>
|
||||
|
||||
<button className={searchClasses} name="search" onClick={this.userSearch}>{searchText}</button>
|
||||
|
|
@ -215,9 +225,9 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
{search: '', type: 'user-input', artists:[], jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, first_search: true}
|
||||
|
||||
onSelectChange: (val) ->
|
||||
@logger.debug("CHANGE #{val}")
|
||||
#@logger.debug("CHANGE #{val}")
|
||||
|
||||
return unless val?
|
||||
return false unless val?
|
||||
|
||||
search_type
|
||||
if val.indexOf('ARTIST=') == 0
|
||||
|
|
@ -230,6 +240,11 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
@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
|
||||
|
||||
|
||||
|
|
@ -251,13 +266,15 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
@setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null})
|
||||
|
||||
|
||||
defaultQuery:() ->
|
||||
defaultQuery:(extra) ->
|
||||
query =
|
||||
per_page: @LIMIT
|
||||
page: @state.currentPage + 1
|
||||
sort_by: 'jamtrack'
|
||||
if @state.next
|
||||
query.since = @state.next
|
||||
query
|
||||
$.extend(query, extra)
|
||||
|
||||
|
||||
|
||||
userSearch: (e) ->
|
||||
|
|
@ -270,20 +287,31 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
|
||||
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})
|
||||
query = @defaultQuery()
|
||||
|
||||
# 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
|
||||
query.artist_search = input # works with ilike
|
||||
else if search_type == 'song-select'
|
||||
query.song_search = input
|
||||
query.song_search = input # works with ilike
|
||||
else
|
||||
query.search = input # works with tsv
|
||||
@rest.getJamTracks(query)
|
||||
.done((response) =>
|
||||
@next = response.next
|
||||
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false})
|
||||
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1})
|
||||
)
|
||||
.fail(() =>
|
||||
@app.notifyServerError jqXHR, 'Search Unavailable'
|
||||
|
|
@ -295,7 +323,7 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
@setState({searching: false, first_search: false})
|
||||
)
|
||||
|
||||
@setState({artists: [], jamtracks:[], searching: true, artist: input, song: input, type: search_type})
|
||||
@setState({currentPage: 0, next: null, artists: [], jamtracks:[], searching: true, artist: input, song: input, type: search_type, search:input})
|
||||
|
||||
getOptions: (input, callback) =>
|
||||
|
||||
|
|
@ -305,7 +333,7 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
window.JamTrackSearchInput = input
|
||||
|
||||
if !input? || input.length == 0
|
||||
callback(null, {options: [], complete: true})
|
||||
callback(null, {options: [], complete: false})
|
||||
return
|
||||
|
||||
@rest.autocompleteJamTracks({match:input, limit:5})
|
||||
|
|
@ -316,13 +344,12 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
options.push { value: "ARTIST=#{artist.original_artist}", label: "Artist: #{artist.original_artist}" }
|
||||
|
||||
if options.length > 0 && autocomplete.songs.length > 0
|
||||
options.push { value: null, label: "-------"}
|
||||
options.push { value: 'SEPARATOR', label: "---------------"}
|
||||
|
||||
for jamtrack in autocomplete.songs
|
||||
options.push { value: "SONG=#{jamtrack.name}", label: "Song: #{jamtrack.name}" }
|
||||
|
||||
@logger.debug("OPTIONS", options)
|
||||
callback(null, {options: options, complete: true})
|
||||
callback(null, {options: options, complete: false})
|
||||
)
|
||||
|
||||
artistNavSelected: (e) ->
|
||||
|
|
@ -335,6 +362,7 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
|
||||
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}\"]")
|
||||
|
|
@ -350,6 +378,49 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
@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 == 0 and @state.jamtracks.length == 0
|
||||
#@content.append '<td colspan="3" class="no-jamtracks-msg\'>No JamTracks found.</div>'
|
||||
@logger.debug("JamTrackSearch: empty search")
|
||||
if @state.currentPage > 0
|
||||
@logger.debug("end of search")
|
||||
$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
|
||||
@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, searching: false, first_search: false, currentPage: @state.currentPage + 1})
|
||||
)
|
||||
.always(() =>
|
||||
$scroller.find('.infinite-scroll-loader-2').remove()
|
||||
@refreshing = false
|
||||
)
|
||||
)
|
||||
|
||||
playJamtrack:(e) ->
|
||||
e.preventDefault()
|
||||
|
||||
|
|
@ -414,14 +485,15 @@ MIX_MODES = context.JK.MIX_MODES
|
|||
|
||||
|
||||
afterShow: (data) ->
|
||||
@logger.debug("afterShow")
|
||||
#@logger.debug("afterShow")
|
||||
|
||||
beforeShow: () ->
|
||||
@clearResults();
|
||||
#@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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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;
|
||||
})();
|
||||
Loading…
Reference in New Issue