This commit is contained in:
Seth Call 2020-04-29 15:51:50 -05:00
parent 702a39ef3c
commit 10543665ee
21 changed files with 1162 additions and 294 deletions

View File

View File

@ -391,4 +391,5 @@ limit_counter_reminders.sql
amazon_v2.sql amazon_v2.sql
store_backend_details_rate_session.sql store_backend_details_rate_session.sql
invited_user_receiver.sql invited_user_receiver.sql
live_streams.sql live_streams.sql
find_sessions_2020.sql

View File

@ -0,0 +1,3 @@
ALTER TABLE rsvp_requests ADD COLUMN music_session_id VARCHAR(64) REFERENCES music_sessions(id);
ALTER TABLE rsvp_requests ADD COLUMN chosen boolean DEFAULT FALSE NOT NULL;
CREATE INDEX rsvp_request_music_session_id ON rsvp_requests USING btree (music_session_id);

View File

@ -243,6 +243,99 @@ module JamRuby
return query return query
end end
# all sessions that are private and active, yet I can see
def self.friend_active_index(user, options)
session_id = options[:session_id]
genre = options[:genre]
lang = options[:lang]
keyword = options[:keyword]
offset = options[:offset]
limit = options[:limit]
query = MusicSession.select('music_sessions.*')
query = query.joins("INNER JOIN active_music_sessions ON music_sessions.id = active_music_sessions.id")
# one flaw in the join below is that we only consider if the creator of the session has asked you to be your friend, but you have not accepted. While this means you may always see a session by someone you haven't friended, the goal is to match up new users more quickly
query = query.joins(
%Q{
LEFT OUTER JOIN
rsvp_requests
ON rsvp_requests.music_session_id = music_sessions.id and rsvp_requests.user_id = '#{user.id}' AND rsvp_requests.chosen = true
LEFT OUTER JOIN
invitations
ON
active_music_sessions.id = invitations.music_session_id AND invitations.receiver_id = '#{user.id}'
LEFT OUTER JOIN
friendships
ON
active_music_sessions.user_id = friendships.user_id AND friendships.friend_id = '#{user.id}'
LEFT OUTER JOIN
friendships as friendships_2
ON
active_music_sessions.user_id = friendships_2.friend_id AND friendships_2.user_id = '#{user.id}'
}
)
# keep only rsvp/invitation/friend results. Nice tailored active list now!
query = query.where("rsvp_requests.id IS NOT NULL OR invitations.id IS NOT NULL or active_music_sessions.user_id = '#{user.id}' OR (friendships.id IS NOT NULL AND friendships_2.id IS NOT NULL)")
# if not specified, default offset to 0
offset ||= 0
offset = offset.to_i
# if not specified, default limit to 20
limit ||= 20
limit = limit.to_i
query = query.offset(offset)
query = query.limit(limit)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?
query = query.group("music_sessions.id")
query
end
# all sessions that are private and active, yet I can see
def self.public_index(user, options)
session_id = options[:session_id]
genre = options[:genre]
lang = options[:lang]
keyword = options[:keyword]
offset = options[:offset]
limit = options[:limit]
query = MusicSession.select('music_sessions.*')
query = query.joins("INNER JOIN active_music_sessions ON music_sessions.id = active_music_sessions.id")
query = query.where("musician_access = TRUE")
# if not specified, default offset to 0
offset ||= 0
offset = offset.to_i
# if not specified, default limit to 20
limit ||= 20
limit = limit.to_i
query = query.offset(offset)
query = query.limit(limit)
query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
query = query.where('music_sessions.language = ?', lang) unless lang.blank?
query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))", ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?
query = query.group("music_sessions.id")
query
end
# This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true # This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true
# If so, then it's an OR condition. If both are false, you can get sessions with anyone. # If so, then it's an OR condition. If both are false, you can get sessions with anyone.
# note, this is mostly the same as above but includes paging through the result and and scores. # note, this is mostly the same as above but includes paging through the result and and scores.

View File

@ -1,5 +1,5 @@
module JamRuby module JamRuby
class InvitedUser < ActiveRecord::Base class InvitedUser < ActiveRecord::Base
include HtmlSanitize include HtmlSanitize
html_sanitize strict: [:note] html_sanitize strict: [:note]

View File

@ -52,6 +52,7 @@ module JamRuby
has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation" has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation"
has_many :invited_fans, :through => :fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver has_many :invited_fans, :through => :fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :foreign_key => "music_session_id", :dependent => :destroy has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :foreign_key => "music_session_id", :dependent => :destroy
has_many :rsvp_requests, :class_name => "JamRuby::RsvpRequest", :foreign_key => "music_session_id", :dependent => :destroy
has_many :music_notations, :class_name => "JamRuby::MusicNotation", :foreign_key => "music_session_id" has_many :music_notations, :class_name => "JamRuby::MusicNotation", :foreign_key => "music_session_id"
has_many :jam_track_session, :class_name => "JamRuby::JamTrackSession" has_many :jam_track_session, :class_name => "JamRuby::JamTrackSession"
has_many :broadcasts, :class_name => "JamRuby::Broadcast" has_many :broadcasts, :class_name => "JamRuby::Broadcast"
@ -359,6 +360,7 @@ module JamRuby
rsvp = RsvpRequest.find_by_id(rsvp_request_slot.rsvp_request_id) rsvp = RsvpRequest.find_by_id(rsvp_request_slot.rsvp_request_id)
new_rsvp = RsvpRequest.new new_rsvp = RsvpRequest.new
new_rsvp.user_id = rsvp.user_id new_rsvp.user_id = rsvp.user_id
new_rsvp.music_session_id = rsvp.music_session_id
new_rsvp_req_slot = RsvpRequestRsvpSlot.new new_rsvp_req_slot = RsvpRequestRsvpSlot.new
new_rsvp_req_slot.rsvp_request = new_rsvp new_rsvp_req_slot.rsvp_request = new_rsvp
@ -379,6 +381,7 @@ module JamRuby
if rsvp.canceled && !rsvp.cancel_all if rsvp.canceled && !rsvp.cancel_all
new_rsvp = RsvpRequest.new new_rsvp = RsvpRequest.new
new_rsvp.user_id = rsvp.user_id new_rsvp.user_id = rsvp.user_id
new_rsvp.music_session_id = rsvp.music_session_id
new_rsvp_req_slot = RsvpRequestRsvpSlot.new new_rsvp_req_slot = RsvpRequestRsvpSlot.new
new_rsvp_req_slot.rsvp_request = new_rsvp new_rsvp_req_slot.rsvp_request = new_rsvp

View File

@ -2,6 +2,7 @@ module JamRuby
class RsvpRequest < ActiveRecord::Base class RsvpRequest < ActiveRecord::Base
belongs_to :user, :class_name => "JamRuby::User" belongs_to :user, :class_name => "JamRuby::User"
belongs_to :music_session, :class_name => "JamRuby::MusicSession"
has_many :rsvp_requests_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot", :foreign_key => "rsvp_request_id" has_many :rsvp_requests_rsvp_slots, :class_name => "JamRuby::RsvpRequestRsvpSlot", :foreign_key => "rsvp_request_id"
has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :through => :rsvp_requests_rsvp_slots has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :through => :rsvp_requests_rsvp_slots
@ -69,6 +70,8 @@ module JamRuby
RsvpRequest.transaction do RsvpRequest.transaction do
@rsvp = RsvpRequest.new @rsvp = RsvpRequest.new
@rsvp.user = user @rsvp.user = user
@rsvp.music_session = music_session
@rsvp.chosen = true if params[:autoapprove] == true
slot_ids = params[:rsvp_slots] slot_ids = params[:rsvp_slots]
@ -165,6 +168,8 @@ module JamRuby
end end
RsvpRequest.transaction do RsvpRequest.transaction do
rsvp_request.chosen = true
rsvp_responses = params[:rsvp_responses] rsvp_responses = params[:rsvp_responses]
if !rsvp_responses.blank? if !rsvp_responses.blank?
instruments = [] instruments = []
@ -234,6 +239,8 @@ module JamRuby
else else
raise StateError, "Invalid request." raise StateError, "Invalid request."
end end
rsvp_request.save
end end
end end

View File

@ -657,7 +657,8 @@ module JamRuby
end end
def recording_count def recording_count
self.recordings.size #self.recordings.size
0
end end
def age def age
@ -666,7 +667,8 @@ module JamRuby
end end
def session_count def session_count
MusicSession.where("user_id = ? AND started_at IS NOT NULL", self.id).size 0
#MusicSession.where("user_id = ? AND started_at IS NOT NULL", self.id).size
end end
# count up any session you are RSVP'ed to # count up any session you are RSVP'ed to

View File

@ -389,7 +389,174 @@ describe ActiveMusicSession do
end end
end end
describe "ams_index", no_transaction: true do
describe "public index", no_transaction: false do
it "public_index" do
creator = FactoryGirl.create(:user)
creator2 = FactoryGirl.create(:user)
earlier_session = FactoryGirl.create(:active_music_session, :creator => creator, :description => "Earlier Session")
later_session = FactoryGirl.create(:active_music_session, :creator => creator2, :description => "Later Session")
user = FactoryGirl.create(:user)
earlier_session.music_session.musician_access = true
earlier_session.music_session.save!
music_sessions = ActiveMusicSession.public_index(creator, {}).take(100)
music_sessions.should_not be_nil
music_sessions.length.should == 2
end
end
describe "friend_active_index", no_transaction: false do
it "does not crash" do
creator = FactoryGirl.create(:user)
creator2 = FactoryGirl.create(:user)
earlier_session = FactoryGirl.create(:active_music_session, :creator => creator, :description => "Earlier Session")
later_session = FactoryGirl.create(:active_music_session, :creator => creator2, :description => "Later Session")
user = FactoryGirl.create(:user)
earlier_session.music_session.musician_access = false
earlier_session.music_session.save!
Friendship.save_using_models(creator, creator2)
music_sessions = ActiveMusicSession.friend_active_index(creator, {}).take(100)
music_sessions.should_not be_nil
music_sessions.length.should == 2
end
describe "parameters" do
let(:creator_1) { FactoryGirl.create(:user, last_jam_locidispid: 4, last_jam_audio_latency: 8) }
let(:creator_conn_1) { FactoryGirl.create(:connection, user: creator_1, ip_address: '4.4.4.4', locidispid: 4, addr:4) }
let(:creator_2) { FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 10) }
let(:creator_conn_2) { FactoryGirl.create(:connection, user: creator_2, ip_address: '4.4.4.4', locidispid: 1, addr:1) }
let(:creator_3) { FactoryGirl.create(:user, last_jam_locidispid: 2, last_jam_audio_latency: 12) }
let(:creator_conn_3) { FactoryGirl.create(:connection, user: creator_3, ip_address: '5.5.5.5', locidispid: 2, addr:2) }
let(:searcher_1) { FactoryGirl.create(:user, last_jam_locidispid: 5, last_jam_audio_latency: 6) }
let(:searcher_conn_1) { FactoryGirl.create(:connection, user: searcher_1, ip_address: '8.8.8.8', locidispid: 5, addr:5) }
let(:searcher_2) { FactoryGirl.create(:user, last_jam_locidispid: 3, last_jam_audio_latency: 14) }
let(:searcher_conn_2) { FactoryGirl.create(:connection, user: searcher_2, ip_address: '9.9.9.9', locidispid: 3, addr:3) }
let!(:music_session_1) { FactoryGirl.create(:active_music_session, :creator => creator_1, genre: Genre.find('african'), language: 'eng', description: "Bunny Jumps" ) }
let!(:music_session_2) { FactoryGirl.create(:active_music_session, :creator => creator_2, genre: Genre.find('ambient'), language: 'spa', description: "Play with us as we jam to beatles and bunnies") }
let(:tracks) { [{'sound' => 'mono', 'client_track_id' => 'abc', 'instrument_id' => 'piano'}] }
it "as invited" do
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 0
Friendship.save_using_models(searcher_1, creator_1)
FactoryGirl.create(:invitation, music_session: music_session_1.music_session, sender: creator_1, receiver: searcher_1)
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 1
music_sessions[0].should == music_session_1.music_session
end
it "as rsvp" do
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 0
rsvp_slot = FactoryGirl.create(:rsvp_slot, music_session: music_session_1.music_session, instrument: Instrument.find('piano'))
rsvp_request = FactoryGirl.create(:rsvp_request, user: searcher_1, chosen:true, music_session: music_session_1.music_session)
rsvp_request_rsvp_slot = FactoryGirl.create(:rsvp_request_rsvp_slot, chosen:true, rsvp_request: rsvp_request, rsvp_slot:rsvp_slot)
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 1
music_sessions[0].should == music_session_1.music_session
end
describe "as friends" do
before(:each) {
Friendship.save_using_models(searcher_1, creator_1)
Friendship.save_using_models(searcher_1, creator_2)
}
it "offset/limit" do
# put creators in the session
creator_conn_1.join_the_session(music_session_1.music_session, true, tracks, creator_1, 10)
creator_conn_1.errors.any?.should be_false
creator_conn_2.join_the_session(music_session_2.music_session, true, tracks, creator_2, 10)
creator_conn_2.errors.any?.should be_false
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 2
music_sessions[0].should == music_session_1.music_session
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, offset:0, limit:1)
music_sessions.length.should == 1
music_sessions[0].should == music_session_1.music_session
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, offset:1, limit:2)
music_sessions.length.should == 1
music_sessions[0].should == music_session_2.music_session
end
it "genre" do
# verify we can get all 2 sessions
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 2
# get only african
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, genre: 'african')
music_sessions.length.should == 1
music_sessions[0].genre.should == Genre.find('african')
# get only ambient
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, genre: 'ambient')
music_sessions.length.should == 1
music_sessions[0].genre.should == Genre.find('ambient')
end
it "language" do
# verify we can get all 2 sessions
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, {})
music_sessions.length.should == 2
# get only english
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, lang: 'eng')
music_sessions.length.should == 1
music_sessions[0].language.should == 'eng'
# get only ambient
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, lang: 'spa')
music_sessions.length.should == 1
music_sessions[0].language.should == 'spa'
end
it "keyword" do
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, keyword: 'Jump')
music_sessions.length.should == 1
music_sessions[0].should == music_session_1.music_session
music_sessions = ams(searcher_1, keyword: 'Bunny')
music_sessions.length.should == 2
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, keyword: 'play')
music_sessions.length.should == 1
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, keyword: 'bun')
music_sessions.length.should == 2
music_sessions = ActiveMusicSession.friend_active_index(searcher_1, keyword: 'bunny play')
music_sessions.length.should == 1
end
end
end
end
describe "ams_index", no_transaction: true do
it "does not crash" do it "does not crash" do
creator = FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 5) creator = FactoryGirl.create(:user, last_jam_locidispid: 1, last_jam_audio_latency: 5)

View File

@ -145,6 +145,20 @@
}); });
} }
function findFriendSessions(query) {
return $.ajax({
type: "GET",
url: "/api/sessions/friends?" + $.param(query)
});
}
function findPublicSessions(query) {
return $.ajax({
type: "GET",
url: "/api/sessions/public?" + $.param(query)
});
}
function findActiveSessions(query) { function findActiveSessions(query) {
return $.ajax({ return $.ajax({
type: "GET", type: "GET",
@ -3097,6 +3111,8 @@
this.listOnboardings = listOnboardings; this.listOnboardings = listOnboardings;
this.getOnboarding = getOnboarding; this.getOnboarding = getOnboarding;
this.updateOnboarding = updateOnboarding; this.updateOnboarding = updateOnboarding;
this.findFriendSessions = findFriendSessions;
this.findPublicSessions = findPublicSessions;
return this; return this;
}; };
})(window, jQuery); })(window, jQuery);

View File

@ -0,0 +1,133 @@
context = window
@FindSessionFriends = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
getInitialState: () ->
{sessions: [], currentPage: 0, next: null, searching: false, count: 0}
search: () ->
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-sessions-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: 0})
delete query.since
@rest.findFriendSessions(query)
.done((response) =>
@setState({sessions: response.sessions, searching: false, first_search: false, currentPage: 1})
)
.fail((jqXHR) =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@setState({currentPage: 0, sessions:[], searching: true})
sessionResults: () ->
results = []
for session in @state.sessions
results.push(`<FindSessionRow session={session} />`)
results
render: () ->
results = @sessionResults()
className = "sessions-for-me"
if(@props.active)
className = className + " active"
`<div className={className}>
<div className="content-body-scroller">
<div className="content-wrapper">
<div id="sessions-active" className="session-container">
<h2>sessions for me</h2>
<br/>
<div className="findsession-container">
<table id="sessions-active" className="findsession-table" cellspacing="0"
cellpadding="0" border="0">
<tr>
<th align="left" width="30%">SESSION</th>
<th align="left" width="35%">MUSICIANS</th>
<th align="left" width="30%" style={{textAlign:'center'}}>ACTIONS</th>
</tr>
{results}
</table>
</div>
</div>
</div>
</div>
</div>`
defaultQuery: (extra) ->
query =
limit: @LIMIT
offset: @state.currentPage * @LIMIT
$.extend(query, extra)
componentDidMount: () ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
@search()
componentDidUpdate: () ->
$root = $(this.getDOMNode())
$scroller = $root.find('.content-body-scroller')
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.sessions.length == 0
$root.find('.end-of-sessions-list').text('No friend sessions found').show()
@logger.debug("FindSessions: empty search")
else if @state.currentPage > 0
$noMoreSessions = $root.find('.end-of-sessions-list').text('No more sessions').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 Sessions ...</div>')
@refreshing = true
@logger.debug("refreshing more sessions for infinite scroll")
@setState({searching: true})
@rest.findFriendSessions(@defaultQuery())
.done((json) =>
@setState({
sessions: @state.sessions.concat(json.sessions),
currentPage: @state.currentPage + 1
})
)
.always(() =>
$scroller.find('.infinite-scroll-loader-2').remove()
@refreshing = false
@setState({searching: false})
)
)
onAppInit: (@app) ->
return
})

View File

@ -0,0 +1,272 @@
context = window
rest = window.JK.Rest()
logger = context.JK.logger
@FindSessionRow = React.createClass({
createInstrument: (participant) ->
instruments = []
existingTracks = []
for j in [0 .. participant.tracks.length]
track = participant.tracks[j]
if existingTracks.indexOf(track.instrument_id) < 0
existingTracks.push(track.instrument_id)
logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks)
inst = context.JK.getInstrumentIcon24(track.instrument_id)
instruments.push(`<img title={context.JK.getInstrumentId(track.instrument_id)} hoveraction="instrument" data-instrument-id={track.instrument_id} src={inst} width="24" height="24" />`)
instruments
createInSessionUser: (participant) ->
instruments = @createInstrument(participant)
id = participant.user.id;
name = participant.user.name;
userId = id
avatar_url = context.JK.resolveAvatarUrl(participant.user.photo_url)
profile_url = "/client#/profile/" + id
musician_name = name
more_link = ''
`<tr>
<td width="24">
<a user-id={userId} hoveraction="musician" href={profile_url} className="avatar-tiny">
<img src={avatar_url} />
</a>
</td>
<td>
<a user-id={userId} hoveraction="musician" href={profile_url}>{musician_name}</a>
</td>
<td>
<div className="instruments nowrap">{instruments}</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>`
createOpenSlot:(slot) ->
` <tr>
<td width="24">
<img src="{instrument_url}" />
</td>
<td>
<div className="instruments nowrap">{instrument} ({proficiency})</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>`
createRsvpUser: (user, session, isLast) ->
instrumentLogoHtml = []
if user.instrument_list
for j in [0 .. user.instrument_list.length]
instrument = user.instrument_list[j];
inst = context.JK.getInstrumentIcon24(instrument.id);
instrumentLogoHtml.push(`<img title={context.JK.getInstrumentId(instrument.id)} hoveraction="instrument" data-instrument-id={instrument.id} src={inst} width="24" height="24" />`)
moreLinkHtml = '';
if isLast
moreLinkHtml = `<span><a class="rsvps more">more</a><a className="details-arrow arrow-down-orange"></a></span>`
instruments = @createInstrument(user)
id = participant.user.id;
name = participant.user.name;
userId = id
avatar_url = context.JK.resolveAvatarUrl(participant.user.photo_url)
profile_url = "/client#/profile/" + id
musician_name = name
more_link = ''
`<tr>
<td width="24">
<a user-id={userId} hoveraction="musician" href={profile_url} className="avatar-tiny">
<img src={avatar_url} />
</a>
</td>
<td>
<a user-id={userId} hoveraction="musician" href={profile_url}>{musician_name}</a>
</td>
<td>
<div className="instruments nowrap">{instruments}</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>`
inSessionUsersHtml: (session) ->
inSessionUsers = []
result = []
if session.active_music_session && "participants" in session.active_music_session && session.active_music_session.participants.length > 0
for i in [0 .. session.active_music_session.participants.length]
participant = session.active_music_session.participants[i]
inSessionUsers.push(participant.user.id);
result.push(@createInSessionUser(participant))
return [result, inSessionUsers]
createRsvpUsers:( ) ->
session = @props.session
firstResults = []
lastResults = []
approvedRsvpCount = session.approved_rsvps.length
if session.approved_rsvps
first = session.approved_rsvps.slice(0, 3)
last = session.approved_rsvps.slice(3)
for i in [0..first]
user = first[i]
firstResults.push(@createRsvpUser(user, session, approvedRsvpCount > 3 && i == 2))
for i in [0..last]
user = last[i]
lastResults.push(@createRsvpUser(user, session, false))
[firstResults, lastResults]
createOpenSlots: () ->
session = @prop.session
firstResults = []
remainingResults = []
if session['is_unstructured_rsvp?']
firstResults.push(@createOpenSlot({description: 'Any Instrument'}))
if session.open_slots
openSlotCount = session.open_slots.length
for i in [0 .. openSlotCount]
openSlot = session.open_slots[i]
if i < 3
firstResults.push(@createOpenSlot(session.open_slots[i], openSlotCount > 3 && i == 2))
else
remainingResults.push(@createOpenSlot(session.open_slots[i], false))
return [firstResults, remainingResults]
showJoinLinks: (inSessionUsers) ->
session = @prop.session
showJoinLink = session.musician_access
if session.approved_rsvps
approvedRsvpCount = session.approved_rsvps.length
for i in [0 .. approvedRsvpCount]
# do not show the user in this section if he is already in the session
if $.inArray(session.approved_rsvps[i].id, inSessionUsers) == -1
if session.approved_rsvps[i].id == context.JK.currentUserId
showJoinLink = true
else
showJoinLink = true
showJoinLink
render: () ->
session = @props.session
id = session.id
name = session.name
description = session.description || "(No description)"
genres = '(' + session.genres.join (', ') + ')'
[in_session_musicians, inSessionUsers] = @inSessionUsersHtml()
[rsvp_musicians_first_3, rsvp_musicians_remaining] = @createRsvpUsers()
[open_slots_first_3, open_slots_remaining] = @createOpenSlots()
showJoinLink = @showJoinLink(inSessionUsers)
showListenLink = session.fan_access && session.active_music_session && session.active_music_session.mount
join_link_display_style = {display: "none"}
listen_link_display_style = {display: "none"}
if showJoinLink
join_link_display_style = {display: "block"}
if showListenLink
listen_link_display_style = {display: "inline-block"}
listen_link_text = ''
if !session.fan_access
listen_link_text = ''
else if session.active_music_session && session.active_music_session.mount
listen_link_text = 'Listen'
else
listen_link_text = '';
`<tr data-session-id={id} className="found-session">
<td width="30%">
<table className="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td className="bold"><a href={"/sessions/" + id} rel="external">{name}</a></td>
<td align="right" width="75">{genres}</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
</table>
</td>
<td width="35%">
<table className="musicians" cellpadding="0" cellspacing="0">
<tr>
<td>In Session:</td>
<td>
<table className="musicians" cellpadding="0" cellspacing="0" width="100%">
{in_session_musicians}
</table>
</td>
</tr>
<tr>
<td>RSVPs:</td>
<td>
<table className="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_first_3}
</table>
<div style="display:none;">
<table className="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_remaining}
</table>
</div>
</td>
</tr>
<tr>
<td>Still Needed:</td>
<td>
<table className="musicians" cellpadding="0" cellspacing="0">
{open_slots_first_3}
</table>
<div style="display:none">
<table className="musicians" cellpadding="0" cellspacing="0">
{open_slots_remaining}
</table>
</div>
</td>
</tr>
</table>
</td>
<td width="30%" className="latency">
<div className="center">
<a className="listen-link" style="display:{listen_link_display_style};">
<img className="listen-icon" />
</a>
<br/>
<span className="listen-link-details"><span className="listen-link-text">{listen_link_text}</span><a href="#" className="listen-detail-hover">?</a></span>
</div>
<div className="center">
<a className="join-link" style="display:{join_link_display_style};">
<img className="join-icon" />
</a>
<span className="join-link-text">Join</span>
</div>
</td>
</tr>`
})

View File

@ -0,0 +1,108 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@FindSessionScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
LIMIT: 20
instrument_logo_map: context.JK.getInstrumentIconMap24()
getInitialState: () ->
{activeTab: 'friends', search: '', type: 'user-input'}
generateProperties: (tab) ->
properties = {}
properties['active'] = @state.activeTab == tab
properties
generateTabClasses: (tab) ->
classes = {}
classes['button-orange'] = true
classes['find-tab'] = true
classes[tab] = true
if @state.activeTab == tab
classes['active'] = true
classNames(classes)
render: () ->
friendTabClasses = @generateTabClasses('friends')
publicTabClasses = @generateTabClasses('public')
scheduledTabClasses = @generateTabClasses('scheduled')
friendProperties = @generateProperties('friends')
publicProperties = @generateProperties('public')
scheduledProperties = @generateProperties('scheduled')
search = ''
`<div className="">
<div className="">
<form id="find-session-form">
<div className="session-filter">
<div style={{minWidth:'770px'}}>
<div className="tabs">
<a className={friendTabClasses}>
Friends
</a>
<a className={publicTabClasses}>
Public
</a>
<a className={scheduledTabClasses}>
Scheduled
</a>
<div className="search-box">
<input className="session-keyword-srch" type="text" name="search"
placeholder="Search by Keyword"/>
</div>
<div className="right mr10">
<a href="/client#/findSession" style={{textDecoration:'none'}}
className="button-grey btn-refresh">REFRESH<span class="extra"></span></a>
</div>
</div>
</div>
</div>
</form>
<FindSessionFriends active={friendProperties.active} />
</div>
</div>`
componentDidMount: () ->
return
componentDidUpdate: () ->
return
beforeShow: () ->
return
afterShow: () ->
return
onAppInit: (@app) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('findSession', screenBindings)
onUserChanged: (userState) ->
@user = userState?.user
})

View File

@ -2,6 +2,49 @@
#findSession { #findSession {
.btn-refresh {
position: absolute;
right: 2%;
top: 13px;
margin-right: 0;
}
.session-keyword-srch {
}
.tabs {
padding-left: 25px;
}
.find-tab {
float: left;
margin: 0 10px;
&.active {
}
}
.sessions-for-me {
display:none;
&.active {
display:block;
}
}
.join-icon {
background: url('/assets/content/join-icon.jpg') no-repeat;
height: 37px;
width: 40px;
}
.listen-icon {
background: url('/assets/content/listen-icon.jpg') no-repeat;
height: 40px;
width: 40px;
}
.paginate-wait { .paginate-wait {
margin:auto; margin:auto;
text-align:center; text-align:center;
@ -45,6 +88,7 @@
min-height:20px; min-height:20px;
overflow-x:visible; overflow-x:visible;
vertical-align:middle; vertical-align:middle;
position:relative;
} }
.session-filter select { .session-filter select {

View File

@ -62,6 +62,14 @@ class ApiMusicSessionsController < ApiController
limit: params[:limit]) limit: params[:limit])
end end
def friend_active_index
@music_sessions = ActiveMusicSession.friend_active_index(current_user, params)
end
def public_index
@music_sessions = ActiveMusicSession.public_index(current_user, params)
end
def ams_index def ams_index
# returns a relation which will produce a list of music_sessions which are active and augmented with attributes # returns a relation which will produce a list of music_sessions which are active and augmented with attributes
# tag and latency, then sorted by tag, latency, and finally music_sessions.id (for stability). the list is # tag and latency, then sorted by tag, latency, and finally music_sessions.id (for stability). the list is

View File

@ -0,0 +1,3 @@
child @music_sessions => :sessions do
extends "api_music_sessions/show_history"
end

View File

@ -0,0 +1,3 @@
child @music_sessions => :sessions do
extends "api_music_sessions/show_history"
end

View File

@ -10,294 +10,10 @@
<%= render "screen_navigation" %> <%= render "screen_navigation" %>
</div> </div>
<div class="content-body"> <div class="content-body">
<div class="content-body-scroller"> <%= react_component 'FindSessionScreen', {} %>
<form id="find-session-form">
<div class="session-filter">
<div style="min-width:770px;">
<div class="left ml35" style="padding-top:3px;">Filter Session List:</div>
<!-- genre filter -->
<div id="find-session-genre" class="left ml10">
<%= render "genreSelector" %>
</div>
<!-- date filter -->
<div class="search-box">
<input type="text" id="session-date-filter" placeholder="Any Date" />
</div>
<!-- language filter -->
<div class="language">
<select id="session-language-filter">
<option value="">All Languages</option>
<% music_session_languages.each do |language| %>
<option value="<%= language[:id] %>"><%= language[:label] %></option>
<% end %>
</select>
</div>
<!-- keyword filter -->
<div class="search-box">
<input id="session-keyword-srch" type="text" name="search" placeholder="Search by Keyword" />
</div>
<div class="right mr10">
<a id="btn-refresh" href="/client#/findSession" style="text-decoration:none;" class="button-grey">REFRESH<span class="extra"></span></a>
</div>
</div>
</div>
<div>
<div class="content-wrapper">
<div id="sessions-active" class="session-container">
<%= render :partial => "sessionList", :locals => {:title => "current, active sessions", :category => "sessions-active"} %>
<br />
<div class="paginate-wait">Fetching results...<div class="spinner-small"></div></div>
<div id="no-active-sessions" class="end-of-list">
End of list.
</div>
</div>
<br />
<div id="sessions-scheduled" class="session-container">
<%= render :partial => "sessionList", :locals => {:title => "future, scheduled sessions", :category => "sessions-scheduled"} %>
<br />
<div class="paginate-wait">Fetching results...<div class="spinner-small"></div></div>
<div id="no-scheduled-sessions" class="end-of-list">
End of list.
</div>
<span class="btn-next-wrapper"><a href="/api/sessions/inactive?page=1" class="btn-next">Next</a></span>
</div>
</div>
</div>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- active session template -->
<script type="text/template" id="template-active-session-row">
<tr data-session-id="{id}" class="found-session">
<td width="30%">
<table class="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="bold"><a href="/sessions/{id}" rel="external">{name}</a></td>
<td align="right" width="75">({genres})</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td>Notation Files:</td>
<td>{notation_files}</td>
</tr>
<tr class="action-links">
<td>
<a class="listen-link" style="display:{listen_link_display_style};">
<%= image_tag "content/listen-icon.jpg", :size => "40x40" %>
</a>
<br/>
<span class="listen-link-details"><span class="listen-link-text">{listen_link_text}</span><a href="#" class="listen-detail-hover">?</a></span>
</td>
<td>
<a class="join-link" style="display:{join_link_display_style};">
<%= image_tag "content/join-icon.jpg", :size => "37x40" %>
</a>
<span class="join-link-text">Join</span>
</td>
</tr>
</table>
</td>
<td width="35%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr>
<td>In Session:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{in_session_musicians}
</table>
</td>
</tr>
<tr>
<td>RSVPs:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_first_3}
</table>
<div style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_remaining}
</table>
</div>
</td>
</tr>
<tr>
<td>Still Needed:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_first_3}
</table>
<div style="display:none">
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_remaining}
</table>
</div>
</td>
</tr>
</table>
</td>
<td width="10%" class="latency">
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_in_session}
</table>
</div>
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_first_3}
</table>
</div>
<div id="latency-extra-{id}" class="center" style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_remaining}
</table>
</div>
</td>
<td width="20%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr><td><span class="bold">Chat Language:</span><br/>{language}</td></tr>
<tr><td><span class="bold">Musician Access:</span><br/>{musician_access}</td></tr>
<tr><td><span class="bold">Fan Access:</span><br/>{fan_access}</td></tr>
<tr><td><span class="bold">Legal Policy:</span><br/>{legal_policy}</td></tr>
</table>
</td>
</tr>
</script>
<!-- inactive session template -->
<script type="text/template" id="template-inactive-session-row">
<tr data-session-id="{id}" class="found-session">
<td width="30%">
<table class="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="bold"><a href="/sessions/{id}/details" rel="external">{name}</a></td></td>
<td align="right" width="75">({genres})</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td>Notation Files:</td>
<td>{notation_files}</td>
</tr>
<tr>
<td colspan="2">{scheduled_start}</td>
</tr>
<tr>
<td>
</td>
<td class="nowrap">
<span class="rsvp-msg" style="display:none;">You cannot RSVP to this session.</span>
<a class="rsvp-link">
<%= image_tag "content/rsvp-icon.jpg", :size => "40x40" %>
</a>
<br/>
<span class="rsvp-link-text">RSVP</span>
</td>
</tr>
</table>
</td>
<td width="35%">
<table class="musician-groups" cellpadding="0" cellspacing="0">
<tr>
<td>RSVPs:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_first_3}
</table>
<div style="display:none">
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_remaining}
</table>
</div>
</td>
</tr>
<tr>
<td>Still Needed:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_first_3}
</table>
<div style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_remaining}
</table>
</div>
</td>
</tr>
</table>
</td>
<td width="10%" class="latency pt10">
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_first_3}
</table>
</div>
<div id="latency-extra-{id}" class="center" style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_remaining}
</table>
</div>
</td>
<td width="20%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr><td><span class="bold">Chat Language:</span><br/>{language}</td></tr>
<tr><td><span class="bold">Musician Access:</span><br/>{musician_access}</td></tr>
<tr><td><span class="bold">Fan Access:</span><br/>{fan_access}</td></tr>
<tr><td><span class="bold">Legal Policy:</span><br/>{legal_policy}</td></tr>
</table>
</td>
</tr>
</script>
<script type="text/template" id="template-notation-files">
<a class="{link_class}" data-notation-id="{notation_id}" href="{file_url}" rel="external">{file_name}</a><br />
</script>
<script type="text/template" id="template-musician-info">
<tr>
<td width="24">
<a user-id="{userId}" hoveraction="musician" href="{profile_url}" class="avatar-tiny">
<img src="{avatar_url}" />
</a>
</td>
<td>
<a user-id="{userId}" hoveraction="musician" href="{profile_url}">{musician_name}</a>
</td>
<td>
<div class="instruments" class="nowrap">{instruments}</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>
</script>
<script type="text/template" id="template-open-slots">
<tr>
<td width="24">
<img src="{instrument_url}" />
</td>
<td>
<div class="instruments" class="nowrap">{instrument} ({proficiency})</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>
</script>
<script type="text/template" id="template-latency">
<tr class="mb15">
<td class="{{data.latency_style}} latency-value" data-user-id="{{data.id}}" data-audio-latency="{{data.audio_latency || ''}}" data-full-score="{{data.full_score || ''}}" data-internet-score="{{data.internet_score || ''}}">
{{data.latency_text}}
</td>
</tr>
<tr><td><div style='height:5px;'>&nbsp;</div></td></tr>
</script>

View File

@ -0,0 +1,287 @@
<!-- Find Session Screen -->
<div layout="screen" layout-id="findSession" id="findSession" class="screen secondary">
<div class="content">
<div class="content-head">
<div class="content-icon">
<%= image_tag "content/icon_search.png", :size => "19x19" %>
</div>
<h1>find a session</h1>
<%= render "screen_navigation" %>
</div>
<div class="content-body">
<div class="content-body-scroller">
<form id="find-session-form">
<div class="session-filter">
<div style="min-width:770px;">
<div class="left ml35" style="padding-top:3px;">Filter Session List:</div>
<!-- keyword filter -->
<div class="search-box">
<input id="session-keyword-srch" type="text" name="search" placeholder="Search by Keyword" />
</div>
<div class="right mr10">
<a id="btn-refresh" href="/client#/findSession" style="text-decoration:none;" class="button-grey">REFRESH<span class="extra"></span></a>
</div>
</div>
</div>
<div>
<div class="content-wrapper">
<div id="sessions-active" class="session-container">
<%= render :partial => "sessionList", :locals => {:title => "current, active sessions", :category => "sessions-active"} %>
<br />
<div class="paginate-wait">Fetching results...<div class="spinner-small"></div></div>
<div id="no-active-sessions" class="end-of-list">
End of list.
</div>
</div>
<br />
<div id="sessions-scheduled" class="session-container">
<%= render :partial => "sessionList", :locals => {:title => "future, scheduled sessions", :category => "sessions-scheduled"} %>
<br />
<div class="paginate-wait">Fetching results...<div class="spinner-small"></div></div>
<div id="no-scheduled-sessions" class="end-of-list">
End of list.
</div>
<span class="btn-next-wrapper"><a href="/api/sessions/inactive?page=1" class="btn-next">Next</a></span>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- active session template -->
<script type="text/template" id="template-active-session-row">
<tr data-session-id="{id}" class="found-session">
<td width="30%">
<table class="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="bold"><a href="/sessions/{id}" rel="external">{name}</a></td>
<td align="right" width="75">({genres})</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td>Notation Files:</td>
<td>{notation_files}</td>
</tr>
<tr class="action-links">
<td>
<a class="listen-link" style="display:{listen_link_display_style};">
<%= image_tag "content/listen-icon.jpg", :size => "40x40" %>
</a>
<br/>
<span class="listen-link-details"><span class="listen-link-text">{listen_link_text}</span><a href="#" class="listen-detail-hover">?</a></span>
</td>
<td>
<a class="join-link" style="display:{join_link_display_style};">
<%= image_tag "content/join-icon.jpg", :size => "37x40" %>
</a>
<span class="join-link-text">Join</span>
</td>
</tr>
</table>
</td>
<td width="35%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr>
<td>In Session:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{in_session_musicians}
</table>
</td>
</tr>
<tr>
<td>RSVPs:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_first_3}
</table>
<div style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_remaining}
</table>
</div>
</td>
</tr>
<tr>
<td>Still Needed:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_first_3}
</table>
<div style="display:none">
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_remaining}
</table>
</div>
</td>
</tr>
</table>
</td>
<td width="10%" class="latency">
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_in_session}
</table>
</div>
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_first_3}
</table>
</div>
<div id="latency-extra-{id}" class="center" style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_remaining}
</table>
</div>
</td>
<td width="20%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr><td><span class="bold">Chat Language:</span><br/>{language}</td></tr>
<tr><td><span class="bold">Musician Access:</span><br/>{musician_access}</td></tr>
<tr><td><span class="bold">Fan Access:</span><br/>{fan_access}</td></tr>
<tr><td><span class="bold">Legal Policy:</span><br/>{legal_policy}</td></tr>
</table>
</td>
</tr>
</script>
<!-- inactive session template -->
<script type="text/template" id="template-inactive-session-row">
<tr data-session-id="{id}" class="found-session">
<td width="30%">
<table class="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="bold"><a href="/sessions/{id}/details" rel="external">{name}</a></td></td>
<td align="right" width="75">({genres})</td>
</tr>
<tr>
<td colspan="2">{description}</td>
</tr>
<tr>
<td>Notation Files:</td>
<td>{notation_files}</td>
</tr>
<tr>
<td colspan="2">{scheduled_start}</td>
</tr>
<tr>
<td>
</td>
<td class="nowrap">
<span class="rsvp-msg" style="display:none;">You cannot RSVP to this session.</span>
<a class="rsvp-link">
<%= image_tag "content/rsvp-icon.jpg", :size => "40x40" %>
</a>
<br/>
<span class="rsvp-link-text">RSVP</span>
</td>
</tr>
</table>
</td>
<td width="35%">
<table class="musician-groups" cellpadding="0" cellspacing="0">
<tr>
<td>RSVPs:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_first_3}
</table>
<div style="display:none">
<table class="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_remaining}
</table>
</div>
</td>
</tr>
<tr>
<td>Still Needed:</td>
<td>
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_first_3}
</table>
<div style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0">
{open_slots_remaining}
</table>
</div>
</td>
</tr>
</table>
</td>
<td width="10%" class="latency pt10">
<div class="center">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_first_3}
</table>
</div>
<div id="latency-extra-{id}" class="center" style="display:none;">
<table class="musicians" cellpadding="0" cellspacing="0">
{latency_remaining}
</table>
</div>
</td>
<td width="20%">
<table class="musicians" cellpadding="0" cellspacing="0">
<tr><td><span class="bold">Chat Language:</span><br/>{language}</td></tr>
<tr><td><span class="bold">Musician Access:</span><br/>{musician_access}</td></tr>
<tr><td><span class="bold">Fan Access:</span><br/>{fan_access}</td></tr>
<tr><td><span class="bold">Legal Policy:</span><br/>{legal_policy}</td></tr>
</table>
</td>
</tr>
</script>
<script type="text/template" id="template-notation-files">
<a class="{link_class}" data-notation-id="{notation_id}" href="{file_url}" rel="external">{file_name}</a><br />
</script>
<script type="text/template" id="template-musician-info">
<tr>
<td width="24">
<a user-id="{userId}" hoveraction="musician" href="{profile_url}" class="avatar-tiny">
<img src="{avatar_url}" />
</a>
</td>
<td>
<a user-id="{userId}" hoveraction="musician" href="{profile_url}">{musician_name}</a>
</td>
<td>
<div class="instruments" class="nowrap">{instruments}</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>
</script>
<script type="text/template" id="template-open-slots">
<tr>
<td width="24">
<img src="{instrument_url}" />
</td>
<td>
<div class="instruments" class="nowrap">{instrument} ({proficiency})</div>
</td>
<td>{more_link}&nbsp;</td>
</tr>
</script>
<script type="text/template" id="template-latency">
<tr class="mb15">
<td class="{{data.latency_style}} latency-value" data-user-id="{{data.id}}" data-audio-latency="{{data.audio_latency || ''}}" data-full-score="{{data.full_score || ''}}" data-internet-score="{{data.internet_score || ''}}">
{{data.latency_text}}
</td>
</tr>
<tr><td><div style='height:5px;'>&nbsp;</div></td></tr>
</script>

View File

@ -9,7 +9,7 @@ script type="text/template" id="client-download-blurb-contents"
h5 SYSTEM REQUIREMENTS: h5 SYSTEM REQUIREMENTS:
| {% if(data.platform == "Win32") { %} | {% if(data.platform == "Win32") { %}
ul.windows-requirements ul.windows-requirements
li Windows 7 or 8, 64-bit (32-bit not supported) li Windows 7, 8, or 10 - 64-bit (32-bit not supported)
li Dual core processor or higher li Dual core processor or higher
li 75MB hard disk space for app li 75MB hard disk space for app
li External audio interface recommended (but you can start with built-in mic and & headphone jack) li External audio interface recommended (but you can start with built-in mic and & headphone jack)
@ -18,7 +18,7 @@ script type="text/template" id="client-download-blurb-contents"
li Broadband Internet service with 1Mbps uplink bandwidth for real-time online sessions li Broadband Internet service with 1Mbps uplink bandwidth for real-time online sessions
| {% } else if(data.platform == "MacOSX") { %} | {% } else if(data.platform == "MacOSX") { %}
ul.mac-requirements ul.mac-requirements
li Mac OS X 10.7 or higher, 64-bit li Mac OS X 10.8 or higher, 64-bit
li Dual-core processor or higher li Dual-core processor or higher
li 75MB hard disk space for app li 75MB hard disk space for app
li External audio interface recommended (but you can start with built-in mic and & headphone jack) li External audio interface recommended (but you can start with built-in mic and & headphone jack)
@ -45,4 +45,4 @@ script type="text/template" id="client-download-select-others"
br br
| Click here for to get JamKazam | Click here for to get JamKazam
br br
| for {{data.platformDisplay1}} | for {{data.platformDisplay1}} ' ''

View File

@ -244,6 +244,8 @@ Rails.application.routes.draw do
match '/sessions/scheduled' => 'api_music_sessions#scheduled', :via => :get match '/sessions/scheduled' => 'api_music_sessions#scheduled', :via => :get
match '/sessions/scheduled_rsvp' => 'api_music_sessions#scheduled_rsvp', :via => :get match '/sessions/scheduled_rsvp' => 'api_music_sessions#scheduled_rsvp', :via => :get
match '/sessions/legacy' => 'api_music_sessions#create_legacy', :via => :post match '/sessions/legacy' => 'api_music_sessions#create_legacy', :via => :post
match '/sessions/friends' => 'api_music_sessions#friend_active_index', :via => :get
match '/sessions/public' => 'api_music_sessions#public_index', :via => :get
match '/sessions/active' => 'api_music_sessions#ams_index', :via => :get match '/sessions/active' => 'api_music_sessions#ams_index', :via => :get
match '/sessions/inactive' => 'api_music_sessions#sms_index', :via => :get match '/sessions/inactive' => 'api_music_sessions#sms_index', :via => :get
match '/sessions/:id' => 'api_music_sessions#show', :via => :get, :as => 'api_session_detail' match '/sessions/:id' => 'api_music_sessions#show', :via => :get, :as => 'api_session_detail'