This commit is contained in:
Seth Call 2020-05-06 14:42:52 -05:00
parent 77232d9e0a
commit 4d9e8d2847
35 changed files with 1388 additions and 339 deletions

View File

@ -1,3 +1,21 @@
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;
UPDATE rsvp_requests set CHOSEN = TRUE, music_session_id = b.music_session_id FROM (
SELECT music_sessions.id as music_session_id, rsvp_requests.id as rsvp_request_id FROM music_sessions JOIN
rsvp_slots
ON
music_sessions.id = rsvp_slots.music_session_id
JOIN
rsvp_requests_rsvp_slots
ON
rsvp_requests_rsvp_slots.rsvp_slot_id = rsvp_slots.id
JOIN
rsvp_requests
ON rsvp_requests.id = rsvp_requests_rsvp_slots.rsvp_request_id
WHERE rsvp_requests_rsvp_slots.chosen = TRUE
) b WHERE rsvp_requests.id = rsvp_request_id;
ALTER TABLE music_sessions ADD COLUMN friends_can_join boolean DEFAULT FALSE NOT NULL;
CREATE INDEX rsvp_request_music_session_id ON rsvp_requests USING btree (music_session_id);

View File

@ -243,7 +243,6 @@ module JamRuby
return query
end
# all sessions that are private and active, yet I can see
def self.friend_active_index(user, options)
@ -264,7 +263,7 @@ module JamRuby
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
ON rsvp_requests.music_session_id = active_music_sessions.id and rsvp_requests.user_id = '#{user.id}' AND rsvp_requests.chosen = true
LEFT OUTER JOIN
invitations
@ -297,7 +296,8 @@ module JamRuby
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 = query.group("music_sessions.id, active_music_sessions.created_at")
query = query.order("active_music_sessions.created_at DESC")
query
end
@ -331,7 +331,8 @@ module JamRuby
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 = query.group("music_sessions.id, active_music_sessions.created_at")
query = query.order("active_music_sessions.created_at DESC")
query
end
@ -766,6 +767,10 @@ module JamRuby
self.save
end
def friends_can_join
music_session.friends_can_join
end
def invitations
music_session.invitations
end

View File

@ -63,6 +63,7 @@ module JamRuby
validates :fan_access, :inclusion => {:in => [true, false]}
validates :approval_required, :inclusion => {:in => [true, false]}
validates :musician_access, :inclusion => {:in => [true, false]}
validates :friends_can_join, :inclusion => {:in => [true, false]}
validates :is_unstructured_rsvp, :inclusion => {:in => [true, false]}
validates :legal_terms, :inclusion => {:in => [true]}, :on => :create
validates :creator, :presence => true
@ -333,6 +334,7 @@ module JamRuby
new_session.scheduled_duration = self.scheduled_duration
new_session.musician_access = self.musician_access
new_session.approval_required = self.approval_required
new_session.friends_can_join = self.friends_can_join
new_session.fan_chat = self.fan_chat
new_session.genre_id = self.genre_id
new_session.legal_policy = self.legal_policy
@ -463,6 +465,13 @@ module JamRuby
tracks
end
# is the viewer 'friensd with the session?'
# practically, to be a friend with the session, you have to be a friend with the creator
# we could loosen to be friend of friends or friends with anyone in the session
def friends_with_session(user)
self.creator.friends?(user)
end
def can_join? user, as_musician
if as_musician
unless user.musician
@ -475,10 +484,9 @@ module JamRuby
else
return true
end
else
# the creator can always join, and the invited users can join
return self.creator == user || self.invited_musicians.exists?(user.id) || self.approved_rsvps.include?(user)
return self.creator == user || self.invited_musicians.exists?(user.id) || self.approved_rsvps.include?(user) || (self.friends_can_join && self.friends_with_session(user))
end
else
# it's a fan, and the only way a fan can join is if fan_access is true
@ -490,7 +498,7 @@ module JamRuby
if self.musician_access || self.fan_access
true
else
self.creator == user || self.invited_musicians.exists?(user.id) || self.approved_rsvps.include?(user) || self.has_lesson_access?(user)
self.creator == user || self.invited_musicians.exists?(user.id) || self.approved_rsvps.include?(user) || self.creator.friends?(user) || self.has_lesson_access?(user)
end
end
@ -637,6 +645,7 @@ module JamRuby
ms.description = options[:description]
ms.genre_id = (options[:genres].length > 0 ? options[:genres][0] : nil) if options[:genres]
ms.musician_access = options[:musician_access]
ms.friends_can_join = options[:friends_can_join]
ms.approval_required = options[:approval_required]
ms.fan_access = options[:fan_access]
ms.fan_chat = options[:fan_chat]
@ -1115,6 +1124,99 @@ SQL
[music_sessions, user_scores]
end
def self.scheduled_index(user, options)
session_id = options[:session_id]
genre = options[:genre]
lang = options[:lang]
keyword = options[:keyword]
offset = options[:offset]
limit = options[:limit]
day = options[:day]
timezone_offset = options[:timezone_offset]
query = MusicSession.select('music_sessions.*')
query = query.where("old = FALSE")
query = query.where("scheduled_start IS NULL OR scheduled_start > (NOW() - (interval '15 minute'))")
query = query.where("music_sessions.canceled = FALSE")
query = query.where("description != 'Jam Track Session'")
query = query.where("music_sessions.id NOT IN (SELECT id FROM active_music_sessions)")
# 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
music_sessions.id = invitations.music_session_id AND invitations.receiver_id = '#{user.id}'
LEFT OUTER JOIN
friendships
ON
music_sessions.user_id = friendships.user_id AND friendships.friend_id = '#{user.id}'
LEFT OUTER JOIN
friendships as friendships_2
ON
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("open_rsvps = TRUE OR rsvp_requests.id IS NOT NULL OR invitations.id IS NOT NULL or 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, music_sessions.scheduled_start")
query = query.order("music_sessions.scheduled_start DESC")
if !day.blank? && !timezone_offset.blank?
begin
day = Date.parse(day)
next_day = day + 1
timezone_offset = timezone_offset.to_i
if timezone_offset == 0
timezone_offset = '' # no offset to specify in this case
elsif timezone_offset > 0
timezone_offset = "+#{timezone_offset}"
end
query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}'
AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'")
rescue Exception => e
# do nothing. bad date probably
@@log.warn("unable to parse day=#{day}, timezone_offset=#{timezone_offset}, e=#{e}")
end
else
sql =<<SQL
music_sessions.started_at IS NULL AND
(music_sessions.created_at > NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_SKIP} days' OR
music_sessions.scheduled_start > NOW() - interval '#{UNSTARTED_INTERVAL_DAYS_SKIP} days')
SQL
query = query.where(sql)
end
#FROM music_sessions
# WHERE old = FALSE AND (scheduled_start IS NULL OR scheduled_start > (NOW() - (interval '15 minute')))
# AND canceled = FALSE AND description != 'Jam Track Session'
# AND id NOT IN (SELECT id FROM active_music_sessions);
query
end
# returns a single session, but populates any other user info with latency scores, so that show_history.rabl can do it's business
def self.session_with_scores(current_user, music_session_id, include_pending=false)
MusicSession.sms_init(current_user, {session_id: music_session_id}, include_pending)

View File

@ -672,9 +672,11 @@ module JamRuby
@@mq_router.server_publish_to_session(active_music_session, msg)
end
# tell all your friends you joined a session
def send_musician_session_join(music_session, user)
if music_session.musician_access || music_session.fan_access
# tell the creator's friends that they joined a session, because we changed Find session to show your friend's sessions
if music_session.creator.id == user.id || music_session.musician_access || music_session.fan_access
friends = Friendship.where(:friend_id => user.id)
user_followers = user.followers
@ -689,6 +691,7 @@ module JamRuby
notification_msg = format_msg(NotificationTypes::MUSICIAN_SESSION_JOIN, {:user => user})
friends_and_followers.each do |ff|
if ff.online?
notification = Notification.new
notification.description = NotificationTypes::MUSICIAN_SESSION_JOIN
notification.source_user_id = user.id
@ -696,7 +699,6 @@ module JamRuby
notification.session_id = music_session.id
notification.save
if ff.online?
msg = @@message_factory.musician_session_join(
ff.id,
music_session.id,
@ -710,15 +712,6 @@ module JamRuby
)
@@mq_router.publish_to_user(ff.id, msg)
else
# if APP_CONFIG.send_join_session_email_notifications
# begin
# UserMailer.musician_session_join(ff, notification_msg, music_session.id).deliver_now
# rescue => e
# @@log.error("Unable to send MUSICIAN_SESSION_JOIN email to user #{ff.email} #{e}")
# end
# end
end
end
end

View File

@ -495,6 +495,105 @@ describe MusicSession do
end
end
describe "sms_index_2" do
describe "simple" do
let(:conn) { FactoryGirl.create(:connection, user: creator, locidispid: creator.last_jam_locidispid) }
let(:searcher) { FactoryGirl.create(:user, last_jam_locidispid: 2) }
let(:searcher_conn) { FactoryGirl.create(:connection, user: searcher, ip_address: '2.2.2.2', locidispid: searcher.last_jam_locidispid) }
let(:default_opts) { {client_id: searcher_conn.client_id} }
let(:network_score) { 20 }
it "no results" do
music_sessions = MusicSession.scheduled_index(searcher, {})
end
it "one session shows/hides based on open_rsvps" do
music_session = FactoryGirl.create(:music_session, creator: creator, scheduled_start: nil)
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
music_session.open_rsvps = false
music_session.save!
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 0
end
it "one session, one RSVP (creator)" do
music_session = FactoryGirl.create(:music_session, creator: creator)
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
end
it "skip session with past due scheduled_start time" do
interval = MusicSession::UNSTARTED_INTERVAL_DAYS_SKIP
dd = Time.now - (interval.to_i + 1).days
Timecop.travel(dd)
msess1 = FactoryGirl.create(:music_session, creator: creator, scheduled_start: dd)
msess2 = FactoryGirl.create(:music_session, creator: creator)
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
expect(music_sessions[0].id).to eq(msess2.id)
end
it "filters sessions in the past" do
music_session = FactoryGirl.create(:music_session, creator: creator)
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
# 15 minutes is the edge of forgiveness
music_session.scheduled_start = 16.minutes.ago
music_session.save!
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 0
# this should still fall in time
music_session.scheduled_start = 14.minutes.ago
music_session.save!
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
end
it "one session, one RSVP (creator), one invitation" do
# create an invitee, and friend them with the creator (you have to be friends to send an invite)
invitee = FactoryGirl.create(:user)
FactoryGirl.create(:friendship, user: creator, friend: invitee)
FactoryGirl.create(:friendship, user: invitee, friend: creator)
music_session = FactoryGirl.create(:music_session, creator: creator)
FactoryGirl.create(:invitation, receiver:invitee, sender:creator, music_session: music_session)
music_sessions = MusicSession.scheduled_index(searcher, default_opts)
music_sessions.length.should == 1
#search with the invitee this time.
invitee_conn = FactoryGirl.create(:connection, user: invitee)
music_sessions = MusicSession.scheduled_index(invitee, {})
music_sessions.length.should == 1
end
it "does not show when it goes active" do
# we create a scheduled session--it should return
music_session = FactoryGirl.create(:music_session, creator: creator, scheduled_start: nil)
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
# but then make an active session for this scheduled session
ams = FactoryGirl.create(:active_music_session, music_session: music_session, creator: creator, musician_access: true)
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 0
# finally, delete the active session, and see results go back to one
ams.delete
music_sessions = MusicSession.scheduled_index(searcher, {})
music_sessions.length.should == 1
end
end
end
describe "sms_index", no_transaction: true do
describe "simple" do

View File

@ -66,6 +66,11 @@
$('#session-settings-fan-access').val('listen-chat-band');
}
// friends can join
var friendsCanJoinValue = currentSession.friends_can_join ? "true" : "false"
$('#session-settings-friends-can-join').val(friendsCanJoinValue)
var $controllerSelect = $('#session-settings-master-mix-controller')
$controllerSelect.empty()
@ -98,12 +103,14 @@
context.JK.dropdown($('#session-settings-language'));
context.JK.dropdown($('#session-settings-musician-access'));
context.JK.dropdown($('#session-settings-fan-access'));
context.JK.dropdown($('#session-settings-friends-can-join'));
context.JK.dropdown($('#session-settings-master-mix-controller'));
var easyDropDownState = canPlayWithOthers.canPlay ? 'enable' : 'disable'
$('#session-settings-musician-access').easyDropDown(easyDropDownState)
$('#session-settings-fan-access').easyDropDown(easyDropDownState)
}
function addNotation(notation) {
@ -165,6 +172,14 @@
data.fan_chat = true;
}
var friendsCanJoin = $('#session-settings-friends-can-join').val();
if (friendsCanJoin == 'true') {
data.friends_can_join = true
}
else {
data.friends_can_join = false
}
rest.updateSession($('#session-settings-id').val(), data).done(settingsSaved)
.done(function(response) {
context.SessionActions.updateSession.trigger(response);

View File

@ -346,6 +346,7 @@
$action_btn.click(function() {
openTerms(payload);
});
context.SessionsActions.updateSession(payload.session_id)
}
else if (type === context.JK.MessageType.JOIN_REQUEST_REJECTED) {
@ -740,6 +741,8 @@
handleNotification(payload, header.type);
context.SessionsActions.updateSession(payload.session_id)
app.notify({
"title": "Session Invitation",
"text": payload.msg
@ -810,19 +813,13 @@
var showNotification = false;
var callback;
if (context.JK.currentUserMusician) {
// user is MUSICIAN; musician_access = TRUE
if (payload.musician_access) {
context.SessionsActions.updateSession(payload.session_id)
showNotification = true;
okText = "JOIN";
callback = joinSession;
}
// user is MUSICIAN; fan_access = TRUE
else if (payload.fan_access) {
showNotification = true;
okText = "LISTEN";
callback = listenToSession;
}
}
else {
// user is FAN; fan_access = TRUE
if (payload.fan_access) {

View File

@ -1,37 +1,18 @@
context = window
SessionsActions = @SessionsActions
@FindSessionFriends = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
mixins: [Reflux.listenTo(@AppStore, "onAppInit")
Reflux.listenTo(@SessionsStore, "onSessionsChanged")]
LIMIT: 50
TYPE_SESSION: 'my'
searched_ever: false
registeredInfiniteScroll: false
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})
{active: false, sessions: [], searching: false, count: 0, currentPage: 0, end:false}
sessionResults: () ->
results = []
@ -39,16 +20,51 @@ context = window
results.push(`<FindSessionRow session={session} />`)
results
help: () ->
`<div className="find-session-help">
<h3>How This Page Works</h3>
<p>This page will show sessions tailored for you:</p>
<ul>
<li>sessions created by a friend (<a href='#' onClick={this.inviteFriends.bind(this)}>invite others to JamKazam!</a>)</li>
<li>sessions for which you have an invitation</li>
<li>sessions for which you have a RSVP</li>
</ul>
<br/>
<h3>Sit Back and Relax</h3>
<p>This page will automatically update when a friend creates a session, or you are invited to one.</p>
<p>So if your friend is creating a session soon, just sit tight and watch for sessions to show up!</p>
</div>`
refresh: () ->
if @state.searching
return
SessionsActions.clearSessions.trigger(@TYPE_SESSION, @query())
render: () ->
results = @sessionResults()
className = "sessions-for-me"
className = "content-body-scroller sessions-for-me"
if(@props.active)
className = className + " active"
firstTime = null
if results.length == 0
if @state.searching
results = `<tr key="single"><td colSpan="3" className="none-found">... Searching ...</td></tr>`
else
# help = @help()
results = `<tr key="single"><td colSpan="3" className="none-found">None Found</td></tr>`
help = @help()
refreshButtonClasses = "button-grey btn-refresh"
if @state.searching
refreshButtonClasses = refreshButtonClasses += " disabled"
`<div className={className}>
<div className="content-body-scroller">
<div className="content-wrapper">
<div id="sessions-active" className="session-container">
<div className="header-zone">
@ -56,8 +72,8 @@ context = window
<div className="refresh-btn">
<a href="/client#/findSession" style={{textDecoration:'none'}}
className="button-grey btn-refresh">REFRESH<span class="extra"></span></a>
<a href="/client#/findSession" style={{textDecoration:'none'}} disabled={this.state.searching}
className={refreshButtonClasses} onClick={this.refresh.bind(this)}>REFRESH<span class="extra"></span></a>
</div>
<div className="search-box">
<input className="session-keyword-srch" type="text" name="search"
@ -70,79 +86,77 @@ context = window
<div className="findsession-container">
<table id="sessions-active" className="findsession-table" cellspacing="0"
cellpadding="0" border="0">
<thead>
<tr>
<th align="left" width="40%">SESSION</th>
<th align="left" width="45%">MUSICIANS</th>
<th align="left" width="10%" style={{textAlign:'center'}}>ACTIONS</th>
</tr>
</thead>
<tbody>
{results}
</tbody>
</table>
</div>
</div>
{help}
</div>
</div>
</div>`
query: () ->
defaultQuery: (extra) ->
$root = $(this.getDOMNode())
keyword = $root.find('input.session-keyword-srch').val()
if keyword == ''
keyword = undefined
query =
limit: @LIMIT
offset: @state.currentPage * @LIMIT
keyword: keyword
query
$.extend(query, extra)
componentDidMount: () ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
@search()
componentDidUpdate: () ->
componentDidUpdate: (prevProps, prevState) ->
if @props.active && !prevProps.active && !@searched_ever
SessionsActions.updateSessions.trigger('my')
@searched_ever = true
$root = $(this.getDOMNode())
$scroller = $root.find('.content-body-scroller')
$scroller = $root
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
if !@registeredInfiniteScroll
@registerInfiniteScroll($scroller)
registerInfiniteScroll: ($scroller) ->
@registeredInfiniteScroll = true
$scroller.off('scroll')
$scroller.on('scroll', () =>
# be sure to not fire off many refreshes when user hits the bottom
return if @refreshing
return if @state.searching
if @state.end
return
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})
)
SessionsActions.updateSessions.trigger(@TYPE_SESSION, @query())
)
inviteFriends:() ->
JK.InvitationDialogInstance.showEmailDialog();
onAppInit: (@app) ->
return
onSessionsChanged: (sessionsChanged) ->
if sessionsChanged.type == @TYPE_SESSION
@setState(sessionsChanged)
})

View File

@ -0,0 +1,193 @@
context = window
SessionsActions = @SessionsActions
@FindSessionOpen = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")
Reflux.listenTo(@SessionsStore, "onSessionsChanged")]
searched_ever: false
registeredInfiniteScroll: false
getInitialState: () ->
{active: false, sessions: [], searching: false, count: 0, currentPage: 0, end: false}
sessionResults: () ->
results = []
for session in @state.sessions
results.push(`<FindSessionRow mode={this.props.mode} session={session} />`)
results
help: () ->
if this.props.mode == 'open'
`<div className="find-session-help">
<h3>How This Page Works</h3>
<p>This page will show only public jam sessions.</p>
<p>Most recent sessions are shown an the top.</p>
<p>This list of public sessions does not auto-update. You will have to push the REFRESH button to see new sessions.</p>
</div>`
else if this.props.mode == 'my'
`<div className="find-session-help">
<h3>How This Page Works</h3>
<p>This page will show sessions tailored for you:</p>
<ul>
<li>sessions created by a friend (<a href='#' onClick={this.inviteFriends.bind(this)}>invite others to JamKazam</a>)</li>
<li>sessions for which you have an invitation</li>
<li>sessions for which you have a RSVP</li>
</ul>
<br/>
<h3>Sit Back and Relax</h3>
<p>This page will automatically update when a friend creates a session, or you are invited to one.</p>
<p>So if your friend is creating a session soon, just sit tight and watch for sessions to show up!</p>
</div>`
else
`<div className="find-session-help">
<h3>How This Page Works</h3>
<p>This page will show these scheduled sessions:</p>
<ul>
<li>public jam sessions</li>
<li>sessions created by a friend (<a href='#' onClick={this.inviteFriends.bind(this)}>invite others to JamKazam</a>)</li>
<li>sessions for which you have an invitation</li>
<li>sessions for which you have a RSVP</li>
</ul>
<br/>
<h3>Reserve a spot</h3>
<p>If you find a session you are interested in attending later, then click the RSVP icon to reserve your spot.</p><p>An email will be sent to the creator of the session, and they can then approve your request.</p>
<h3>Schedule your Own</h3>
<p>Don't see a session you like? You can schedule your own session for others to RSVP to at the <a href="/client#/createSession">create session</a> screen. You will receive emails when others RSVP.</p>
</div>`
refresh: () ->
if @state.searching
return
SessionsActions.clearSessions.trigger(@props.mode, @query())
render: () ->
results = @sessionResults()
className = "content-body-scroller sessions-for-open"
if(@props.active)
className = className + " active"
firstTime = null
if results.length == 0
if @state.searching
results = `<tr key="single"><td colSpan="3" className="none-found">... Searching ...</td></tr>`
else
# help = @help()
results = `<tr key="single"><td colSpan="3" className="none-found">None Found</td></tr>`
if @state.end && results.length > 5
results.push(`<tr><td colSpan="3" className="end-of-results">End of Search Results<div className="spacer"></div></td></tr>`)
help = @help()
refreshButtonClasses = "button-grey btn-refresh"
if @state.searching
refreshButtonClasses = refreshButtonClasses += " disabled"
`<div className={className}>
<div className="content-wrapper">
<div id="sessions-active" className="session-container">
<div className="header-zone">
<h2>sessions for me</h2>
<div className="refresh-btn">
<a href="/client#/findSession" style={{textDecoration:'none'}} disabled={this.state.searching}
className={refreshButtonClasses} onClick={this.refresh.bind(this)}>REFRESH<span class="extra"></span></a>
</div>
<div className="search-box">
<input className="session-keyword-srch" type="text" name="search"
placeholder="Search by Keyword"/>
</div>
</div>
<br/>
<div className="findsession-container">
<table id="sessions-active" className="findsession-table" cellspacing="0"
cellpadding="0" border="0">
<thead>
<tr>
<th align="left" width="40%">SESSION</th>
<th align="left" width="45%">MUSICIANS</th>
<th align="left" width="10%" style={{textAlign:'center'}}>ACTIONS</th>
</tr>
</thead>
<tbody>
{results}
</tbody>
</table>
</div>
{help}
</div>
</div>
</div>`
query: () ->
$root = $(this.getDOMNode())
keyword = $root.find('input.session-keyword-srch').val()
if keyword == ''
keyword = undefined
query =
keyword: keyword
query
componentDidMount: () ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
componentDidUpdate: (prevProps, prevState) ->
if !@props.screenActive && prevProps.screenActive
@searched_ever = false
if @props.screenActive && @props.active && !@searched_ever
SessionsActions.updateSessions.trigger(@props.mode)
@searched_ever = true
#if @props.active && !prevProps.active && !@searched_ever
$root = $(this.getDOMNode())
$scroller = $root
if !@registeredInfiniteScroll
@registerInfiniteScroll($scroller)
registerInfiniteScroll: ($scroller) ->
@registeredInfiniteScroll = true
$scroller.off('scroll')
$scroller.on('scroll', () =>
# be sure to not fire off many refreshes when user hits the bottom
return if @state.searching
return if @state.end
if $scroller.scrollTop() + $scroller.innerHeight() + 400 >= $scroller[0].scrollHeight
SessionsActions.updateSessions.trigger(@props.mode, @query())
)
inviteFriends:() ->
JK.InvitationDialogInstance.showEmailDialog();
onAppInit: (@app) ->
return
onSessionsChanged: (sessionsChanged) ->
if sessionsChanged.type == @props.mode
@setState(sessionsChanged)
})

View File

@ -1,14 +1,24 @@
context = window
rest = window.JK.Rest()
logger = context.JK.logger
EVENTS = context.JK.EVENTS
SessionsActions = context.SessionsActions
AppStore = context.AppStore
MAX_MINUTES_SHOW_START = 15
@FindSessionRow = React.createClass({
mixins: [Reflux.listenTo(AppStore, "onAppInit")]
ui: null
getInitialState: () ->
{rsvpToggle: false, openSlotToggle: false}
createInstrument: (participant) ->
instruments = []
existingTracks = []
logger.debug("Find:Finding instruments. Participant tracks:", participant.tracks)
for track in participant.tracks
if existingTracks.indexOf(track.instrument_id) < 0
@ -22,31 +32,10 @@ logger = context.JK.logger
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>`
`<HoverUser more={null} user={participant.user} instruments={instruments} />`
createOpenSlot:(slot) ->
createOpenSlot:(slot, isLast) ->
inst = context.JK.getInstrumentIcon24(slot.instrument_id);
@ -55,9 +44,20 @@ logger = context.JK.logger
# this is to allow unstructured RSVPs to not specify proficiency_desc
proficiency_desc = "Any Skill Level"
moreLinkHtml = '';
toggle = @state.openSlotToggle
if isLast
moreLinkHtml = `<span><a className="slots more">more</a><a className="details-arrow arrow-down-orange"></a></span>`
remainingStyles = {}
text = null
computedClass = "details-arrow"
if toggle
text = 'less'
computedClass += " arrow-up-orange"
else
text = 'more'
computedClass += " arrow-down-orange"
moreLinkHtml = `<td><span><a onClick={this.toggleOpenSlot.bind(this)} className="rsvps more">{text}</a><a className={computedClass}></a></span></td>`
instrument_url = inst
instrument = slot.description
@ -65,9 +65,9 @@ logger = context.JK.logger
more_link = moreLinkHtml
`<tr>
`<tr key={slot.id}>
<td width="24">
<img src="{instrument_url}" />
<img src={instrument_url} />
</td>
<td>
<div className="instruments nowrap">{instrument} ({proficiency})</div>
@ -86,31 +86,23 @@ logger = context.JK.logger
moreLinkHtml = '';
if isLast
moreLinkHtml = `<span><a class="rsvps more">more</a><a className="details-arrow arrow-down-orange"></a></span>`
# false means hide, true means show
toggle = @state.rsvpToggle
#instruments = @createInstrument(user)
id = user.id;
name = user.name;
userId = id
avatar_url = context.JK.resolveAvatarUrl(user.photo_url)
profile_url = "/client#/profile/" + id
musician_name = name
more_link = ''
remainingStyles = {}
text = null
computedClass = "details-arrow"
if toggle
text = 'less'
computedClass += " arrow-up-orange"
else
text = 'more'
computedClass += " arrow-down-orange"
`<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>`
moreLinkHtml = `<td><span><a onClick={this.toggleRsvp.bind(this)} className="rsvps more">{text}</a><a className={computedClass}></a></span></td>`
`<HoverUser user={user} instruments={instruments} more={moreLinkHtml} />`
@ -118,12 +110,17 @@ logger = context.JK.logger
inSessionUsers = []
result = []
console.log("render active", session)
if session.active_music_session && session.active_music_session.participants && session.active_music_session.participants.length > 0
for participant in session.active_music_session.participants
inSessionUsers.push(participant.user.id);
result.push(@createInSessionUser(participant))
if result.length == 0
result = `<span>Abandoned</span>`
else
result = `<table className="musicians musicians-category" cellpadding="0" cellspacing="0" width="100%">
{result}
</table>`
return [result, inSessionUsers]
@ -139,8 +136,12 @@ logger = context.JK.logger
last = session.approved_rsvps.slice(3)
for approved_rsvp in first
firstResults.push(@createRsvpUser(approved_rsvp, session, approvedRsvpCount > 3 && i == 2))
if approved_rsvp.id == session.user_id
continue
firstResults.push(@createRsvpUser(approved_rsvp, session, approvedRsvpCount > 3 && i == 0))
for approved_rsvp in last
if approved_rsvp.id == session.user_id
continue
lastResults.push(@createRsvpUser(approved_rsvp, session, false))
[firstResults, lastResults]
@ -155,48 +156,248 @@ logger = context.JK.logger
if session['is_unstructured_rsvp?']
firstResults.push(@createOpenSlot({description: 'Any Instrument'}))
i = 0
if session.open_slots
openSlotCount = session.open_slots.length
for openSlot in session.open_slots
if i < 3
firstResults.push(@createOpenSlot(openSlot, openSlotCount > 3 && i == 2))
else
remainingResults.push(@createOpenSlot(openSlot, false))
i++
return [firstResults, remainingResults]
showJoinLink: (session, inSessionUsers) ->
showJoinLink = session.musician_access
if session.approved_rsvps
for approved_rsvps in session.approved_rsvps
# do not show the user in this section if he is already in the session
if $.inArray(approved_rsvps.id, inSessionUsers) == -1
if approved_rsvps.id == context.JK.currentUserId
showJoinLink = true
else
showJoinLink = true
joinLink: (session, inSessionUsers) ->
#showJoinLink = session.musician_access
#if session.approved_rsvps
# for approved_rsvps in session.approved_rsvps
# # do not show the user in this section if he is already in the session
# if $.inArray(approved_rsvps.id, inSessionUsers) == -1
# if approved_rsvps.id == context.JK.currentUserId
# showJoinLink = true
# else
# showJoinLink = true
showJoinLink
rsvps: (session, rsvp_musicians_first_3, rsvp_musicians_remaining) ->
if session.create_type == 'quick-start' || !rsvp_musicians_first_3 || rsvp_musicians_first_3.length == 0
if @props.mode == 'upcoming'
return null
` <tr>
<td>RSVPs:</td>
joinText = 'Join'
if session.highlight
highlight = session.highlight
if highlight.updated
joinText = 'Ready!'
`<div className="center">
<a className="join-link" onClick={this.joinLinkClicked.bind(this, session)}>
<div className="join-icon"></div>
</a>
<div className="join-link-text">{joinText}</div>
</div>`
rsvpLink: (session) ->
pendingRsvpId = null
approvedRsvpId = null
hasInvitation = false
for pending_rsvp_request in session.pending_rsvp_requests
if pending_rsvp_request.user_id == context.JK.currentUserId
pendingRsvpId = pending_rsvp_request.id
break
for approved_rsvp in session.approved_rsvps
if approved_rsvp.id == context.JK.currentUserId
approvedRsvpId = approved_rsvp.rsvp_request_id
break
if session.invitations
for pending_invitation in session.invitations
if context.JK.currentUserId == pending_invitation.receiver_id
hasInvitation = true
break
errorMsg = null
error = false
if error
errorMsg = `<span className="rsvp-msg" style="display:none;">You cannot RSVP to this session.</span>`
# if this is your own session, let you start it immediately
if context.JK.currentUserId == session.user_id
result = `<div className="center"><span className="text"><a onClick={this.startSessionNow.bind(this, session)} className="start" style={{color: '#fc0'}}>Start session now?</a><br/><br/>This is your session.</span></div>`
return result
# if you are approved RSVP person, let you cancel it
if approvedRsvpId
if session.scheduled_start && @showStartSessionButton(session.scheduled_start)
# give user both option to start session, and also cancel RSVP
result = `<div className="center"><span className="text"><a onClick={this.startSessionNow.bind(this, session)} className="start">Start session now?</a>&nbsp;|&nbsp;<a onClick={this.cancelRsvpClicked.bind(this, session, approvedRsvpId)} className="cancel">Cancel RSVP</a></span></div>`
return result
else
# user can just cancel their RSVP
result = `<div className="center"><span className="text"><a onClick={this.cancelRsvpClicked.bind(this, session, approvedRsvpId)} className="cancel">Cancel RSVP</a></span></div>`
return result
else if hasInvitation
if session.scheduled_start && @showStartSessionButton(session.scheduled_start)
# give user both option to start session, and also cancel RSVP
result = `<div className="center"><span className="text"><a onClick={this.startSessionNow.bind(this, session)} className="start">Start session now?</a>&nbsp;|&nbsp;You have an invite to this session.<br/><br/>You can join it when it starts.</span></div>`
return result
else
# user can just cancel their RSVP
result = `<div className="center"><span className="text">You have an invite to this session.<br/><br/>You can join it when it starts.</span></div>`
return result
else if pendingRsvpId
result = `<div className="center"><span className="text"><a onClick={this.cancelRsvpClicked.bind(this, session, pendingRsvpId)} className="cancel">Cancel RSVP</a></span></div>`
return result
else if !session['is_unstructured_rsvp?'] && session.open_slots.length == 0
result = `<div className="center"><span className="text">No more open positions.</span></div>`
return result
else if !session.open_rsvps && !hasInvitation
result = `<div className="center"><span className="text">You need an invitation to RSVP to this session.</span></div>`
return result
`<div className="center">
{errorMsg}
<a className="rsvp-link" onClick={this.rsvpLinkClicked.bind(this, session)}>
<div className="rsvp-icon"></div>
</a>
<div className="rsvp-link-text">RSVP</div>
</div>`
openSlots: (session, open_slots_first_3, open_slots_remaining) ->
# false means hide, true means show
openSlotToggle = @state.openSlotToggle
remainingStyles = {}
if openSlotToggle
remainingStyles.display = 'block'
else
remainingStyles.display = 'none'
`<tr className="musicians-detail">
<td className="musicians-header"><span>Still Needed:</span></td>
<td>
<table className="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_first_3}
<table className="musicians musicians-category" cellpadding="0" cellspacing="0" width="100%">
<tbody>
{open_slots_first_3}
</tbody>
</table>
<div style={{display:'none'}}>
<table className="musicians" cellpadding="0" cellspacing="0" width="100%">
{rsvp_musicians_remaining}
<div style={remainingStyles}>
<table className="musicians remaining" cellpadding="0" cellspacing="0" width="100%">
<tbody>
{open_slots_remaining}
</tbody>
</table>
</div>
</td>
</tr>`
rsvps: (session, rsvp_musicians_first_3, rsvp_musicians_remaining, open_slots_first_3) ->
if session.create_type == 'quick-start' || ((!rsvp_musicians_first_3 || rsvp_musicians_first_3.length == 0) && (!open_slots_first_3 || open_slots_first_3.length == 0))
return null
# if no rsvps yet some open slots
if (!rsvp_musicians_first_3 || rsvp_musicians_first_3.length == 0) && (open_slots_first_3 && open_slots_first_3.length > 0)
return `<tr className="musicians-detail">
<td className="musicians-header"><span>RSVPs:</span></td>
<td>
<div className="none-yet">
None yet
</div>
</td>
</tr>`
# false means hide, true means show
rsvpToggle = @state.rsvpToggle
remainingStyles = {}
if rsvpToggle
remainingStyles.display = 'block'
else
remainingStyles.display = 'none'
`<tr className="musicians-detail">
<td className="musicians-header"><span>RSVPs:</span></td>
<td>
<table className="musicians musicians-category" cellpadding="0" cellspacing="0" width="100%">
<tbody>
{rsvp_musicians_first_3}
</tbody>
</table>
<div style={remainingStyles}>
<table className="musicians" cellpadding="0" cellspacing="0" width="100%">
<tbody>
{rsvp_musicians_remaining}
</tbody>
</table>
</div>
</td>
</tr>`
componentDidMount: () ->
@ui = new context.JK.UIHelper(AppStore.app)
ensuredCallback: (sessionId) ->
context.JK.SessionUtils.joinSession(sessionId)
joinLinkClicked: (session) ->
context.JK.SessionUtils.ensureValidClient(@app, context.JK.GearUtils, @ensuredCallback.bind(this, session.id))
rsvpLinkClicked: (session) ->
@ui.launchRsvpSubmitDialog(session.id)
.one(EVENTS.RSVP_SUBMITTED, () -> SessionsActions.updateSession.trigger(session.id))
.one(EVENTS.DIALOG_CLOSED, () ->
$(this).unbind(EVENTS.RSVP_SUBMITTED);
)
return false
toggleRsvp: () ->
@setState(rsvpToggle: !@state.rsvpToggle)
toggleOpenSlot: (sessionId) ->
@setState(openSlotToggle: !@state.openSlotToggle)
startSessionNow: (session) ->
@ui.launchSessionStartDialog(session)
showStartSessionButton: (scheduledStart) ->
now = new Date()
scheduledDate = new Date(scheduledStart)
minutesFromStart = (scheduledDate.getTime() - now.getTime()) / (1000 * 60)
minutesFromStart <= MAX_MINUTES_SHOW_START
cancelRsvpClicked: (session, approvedRsvpId) ->
@ui.launchRsvpCancelDialog(session.id, approvedRsvpId)
.one(EVENTS.RSVP_CANCELED, () -> SessionsActions.updateSession.trigger(session.id))
.one(EVENTS.DIALOG_CLOSED, () ->
$(this).unbind(EVENTS.RSVP_CANCELED);
)
return false
inSessionMusicians: (in_session_musicians) ->
if @props.mode == 'upcoming'
return null
`<tr className="musicians-detail">
<td className="musicians-header"><span>In Session:</span></td>
<td>
{in_session_musicians}
</td>
</tr>`
render: () ->
session = @props.session
@ -209,12 +410,29 @@ logger = context.JK.logger
[in_session_musicians, inSessionUsers] = @inSessionUsersHtml(session)
[rsvp_musicians_first_3, rsvp_musicians_remaining] = @createRsvpUsers(session)
[open_slots_first_3, open_slots_remaining] = @createOpenSlots(session)
showJoinLink = @showJoinLink(session, inSessionUsers)
rsvps = @rsvps(session, rsvp_musicians_first_3, rsvp_musicians_remaining, open_slots_first_3)
joinLink = @joinLink(session, inSessionUsers)
showListenLink = session.fan_access && session.active_music_session && session.active_music_session.mount
join_link_display_style = {display: "none"}
showListenLink = false # for now... XXX
openSlots = null
scheduled_start = null
rsvpLink = null
if @props.mode == 'upcoming'
openSlots = @openSlots(session, open_slots_first_3, open_slots_remaining)
scheduled_start = ` <tr>
<td colspan="2">{session.pretty_scheduled_start_with_timezone}</td>
</tr>`
rsvpLink = @rsvpLink(session)
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 = ''
@ -226,11 +444,21 @@ logger = context.JK.logger
else
listen_link_text = '';
rsvps = @rsvps(rsvp_musicians_first_3, rsvp_musicians_remaining)
remark = null
if session.highlight
highlight = session.highlight
if highlight.new
remark = `<div className="highlight new">NEW!</div>`
inSessionMusicians = @inSessionMusicians(in_session_musicians)
`<tr data-session-id={id} className="found-session">
<td width="40%" className="session-cell">
{remark}
<table className="musician-groups" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td className="bold"><a className="session-name" href={"/sessions/" + id} rel="external">{name}</a></td>
<td align="right" width="75" className="session-genre">{genres}</td>
@ -238,20 +466,20 @@ logger = context.JK.logger
<tr>
<td colspan="2" className="session-description">{description}</td>
</tr>
{scheduled_start}
</tbody>
</table>
<div className="spacer"></div>
</td>
<td width="45%" className="session-musicians">
<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>
<tbody>
{inSessionMusicians}
{rsvps}
{openSlots}
</tbody>
</table>
<div className="spacer"></div>
</td>
@ -263,12 +491,8 @@ logger = context.JK.logger
<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={join_link_display_style}>
<div className="join-icon"></div>
</a>
<span className="join-link-text">Join</span>
</div>
{joinLink}
{rsvpLink}
<div className="spacer"></div>
</td>

View File

@ -1,6 +1,7 @@
context = window
MIX_MODES = context.JK.MIX_MODES
SessionsActions = @SessionsActions
@FindSessionScreen = React.createClass({
@ -10,7 +11,7 @@ MIX_MODES = context.JK.MIX_MODES
instrument_logo_map: context.JK.getInstrumentIconMap24()
getInitialState: () ->
{activeTab: 'friends', search: '', type: 'user-input'}
{activeTab: '', search: '', type: 'user-input', screenActive: false}
generateProperties: (tab) ->
@ -28,33 +29,35 @@ MIX_MODES = context.JK.MIX_MODES
classNames(classes)
tabActivate: (tab) ->
@setState({activeTab: tab})
render: () ->
friendTabClasses = @generateTabClasses('friends')
publicTabClasses = @generateTabClasses('public')
scheduledTabClasses = @generateTabClasses('scheduled')
myTabClasses = @generateTabClasses('my')
openTabClasses = @generateTabClasses('open')
upcomingTabClasses = @generateTabClasses('upcoming')
friendProperties = @generateProperties('friends')
publicProperties = @generateProperties('public')
scheduledProperties = @generateProperties('scheduled')
myProperties = @generateProperties('my')
openProperties = @generateProperties('open')
upcomingProperties = @generateProperties('upcoming')
search = ''
`<div className="">
<div className="">
`<div className="FindSessionScreen">
<form id="find-session-form">
<div className="session-filter">
<div style={{minWidth:'770px'}}>
<div className="tabs">
<a className={friendTabClasses}>
Friends
<a className={myTabClasses} onClick={this.tabActivate.bind(this, 'my')}>
For Me
</a>
<a className={publicTabClasses}>
Public
<a className={openTabClasses} onClick={this.tabActivate.bind(this, 'open')}>
Open Jams
</a>
<a className={scheduledTabClasses}>
<a className={upcomingTabClasses} onClick={this.tabActivate.bind(this, 'upcoming')}>
Upcoming
</a>
</div>
@ -62,8 +65,9 @@ MIX_MODES = context.JK.MIX_MODES
</div>
</form>
<FindSessionFriends active={friendProperties.active} />
</div>
<FindSessionOpen mode='my' active={myProperties.active} screenActive={this.state.screenActive}/>
<FindSessionOpen mode='open' active={openProperties.active} screenActive={this.state.screenActive} />
<FindSessionOpen mode='upcoming' active={upcomingProperties.active} screenActive={this.state.screenActive} />
</div>`
@ -71,14 +75,23 @@ MIX_MODES = context.JK.MIX_MODES
componentDidMount: () ->
return
componentDidUpdate: () ->
componentDidUpdate: (prevProps, prevState) ->
return
beforeShow: () ->
return
afterShow: () ->
return
SessionsActions.watching.trigger(true)
if @state.activeTab == ''
@setState({activeTab: 'my'})
@setState({screenActive:true})
beforeHide: () ->
SessionsActions.watching.trigger(false)
SessionsActions.resetSessions.trigger()
@setState({screenActive:false})
onAppInit: (@app) ->
@EVENTS = context.JK.EVENTS
@ -88,6 +101,7 @@ MIX_MODES = context.JK.MIX_MODES
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
'beforeHide': @beforeHide
@app.bindScreen('findSession', screenBindings)

View File

@ -0,0 +1,39 @@
context = window
rest = window.JK.Rest()
logger = context.JK.logger
EVENTS = context.JK.EVENTS
SessionsActions = context.SessionsActions
AppStore = context.AppStore
MAX_MINUTES_SHOW_START = 15
@HoverUser = React.createClass({
render: () ->
user = this.props.user
userId = user.id
name = user.name
avatar_url = context.JK.resolveAvatarUrl(user.photo_url)
profile_url = "/client#/profile/" + userId
musician_name = name
`<tr key={userId} >
<td width="24">
<a user-id={userId} data-hoveraction="musician" href={profile_url} className="avatar-tiny">
<img src={avatar_url} />
</a>
</td>
<td>
<a user-id={userId} data-hoveraction="musician" href={profile_url}>{musician_name}</a>
</td>
<td>
<div className="instruments nowrap">{this.props.instruments}</div>
</td>
{this.props.more}
</tr>`
componentDidMount: () ->
$root = $(this.getDOMNode())
context.JK.bindHoverEvents($root, "data-hoveraction")
})

View File

@ -0,0 +1,5 @@
context = window
@FriendActions = Reflux.createActions({
updateFriends: {}
})

View File

@ -0,0 +1,9 @@
context = window
@SessionsActions = Reflux.createActions({
updateSession: {}
updateSessions: {}
resetSessions: {}
clearSessions: {}
watching: {}
})

View File

@ -6,9 +6,12 @@ logger = context.JK.logger
{
listenables: @AppActions
app: null
onAppInit: (app) ->
@app = app
@trigger(app)
onOpenExternalUrl: (href) ->
logger.debug("opening external url #{href}")

View File

@ -0,0 +1,35 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
FriendActions = @FriendActions
@FriendStore = Reflux.createStore(
{
listenables: FriendActions
friends: []
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
return
updateFriends: (friends) ->
@friends = friends
issueChange: (type) ->
@trigger({friends: @friends})
isFriend: (userId) ->
found = false
for friend in @friends
if friend.id == userId
found = true
break
found
}
)

View File

@ -0,0 +1,169 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
SessionActions = @SessionActions
@SessionsStore = Reflux.createStore(
{
listenables: SessionsActions
my: {sessions: [], currentPage: 0, next: null, searching: false, count: 0}
open: {sessions: [], currentPage: 0, next: null, searching: false, count: 0}
upcoming: {sessions: [], currentPage: 0, next: null, searching: false, count: 0}
highlight: null
LIMITS: {my: 50, open: 50, upcoming: 20}
TYPE_MY: 'my'
TYPE_OPEN: 'open'
TYPE_UPCOMING: 'upcoming'
# if the Find Session screen isn't active, don't 'watch' automatically
watchingState: false
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
return
defaultQuery: (extra, type) ->
query =
limit: @LIMITS[type],
offset: this[type].currentPage * @LIMITS[type]
$.extend(query, extra)
updateSessionState:(session, type) ->
state = this[type]
# so callers can realize this is a pinpointed session update
session.highlight = {new: false, updated: false}
@highlight = session
foundIndex = -1
console.log("STATE", state)
if state.sessions.length > 0
for x in [0..state.sessions.length - 1]
if state.sessions[x].id == session.id
foundIndex = x
break
# session did not exist already. Add it to front of list because that's where interesting stuff should be?
if foundIndex == -1
session.highlight.new = true
console.log("SessionStore.updateSession: adding a new one")
state.sessions.unshift(session)
else
session.highlight.updated = true
console.log("SessionStore.updateSession: updating existing")
state.sessions[x] = session
updateSession: (sessionId) ->
if !@watchingState
return
rest.getSessionHistory(sessionId)
.done((response) =>
determined_type = null
if response.active_music_session
if response.can_join || context.FriendStore.isFriend(response.user_id)
determined_type = @TYPE_MY
else
determined_type = @TYPE_OPEN
else
determined_type = @TYPE_UPCOMING
@updateSessionState(response, determined_type)
@issueChange(determined_type)
)
.fail((jqXHR) =>
@app.notifyServerError jqXHR, 'SessionsStore: Unable to fetch session information'
)
resetSessions:() ->
@my.sessions = []
@my.currentPage = 0
@my.end = false
@open.sessions = []
@open.currentPage = 0
@open.end = false
@upcoming.sessions = []
@upcoming.currentPage = 0
@upcoming.end = false
@issueChange(@TYPE_MY)
@issueChange(@TYPE_OPEN)
@issueChange(@TYPE_UPCOMING)
clearSessions: (type, query) ->
this[type].sessions = []
this[type].currentPage = 0
this[type].end = false
@updateSessions(type, query)
handleSessionResponse: (type, response) ->
state = this[type]
state.sessions = state.sessions.concat(response.sessions)
state.searching = false
state.currentPage = state.currentPage + 1
state.first_search = false
state.error = null
state.end = response.sessions.length < @LIMITS[type]
@issueChange(type)
handleSessionError: (type, response) ->
#@app.notifyServerError jqXHR, 'Search Unavailable'
state = this[type]
state.searching = false
state.error = jqXHR
@issueChange(type)
updateSessions: (type, query) ->
query = @defaultQuery(query, type)
this[type].searching = true
@issueChange(type)
if type == 'my'
rest.findFriendSessions(query)
.done((response) =>
@handleSessionResponse(type, response)
)
.fail((jqXHR) =>
@handleSessionError(type, jqXHR)
)
else if type == 'open'
rest.findPublicSessions(query)
.done((response) =>
@handleSessionResponse(type, response)
)
.fail((jqXHR) =>
@handleSessionError(type, jqXHR)
)
else if type == 'upcoming'
rest.findInactiveSessions(query)
.done((response) =>
@handleSessionResponse(type, response)
)
.fail((jqXHR) =>
@handleSessionError(type, jqXHR)
)
watching:(watching) ->
@watchingState = watching
issueChange: (type) ->
sessions = this[type]
@trigger({type: type, sessions: sessions.sessions, highlight: @highlight, searching: sessions.searching, currentPage: sessions.currentPage, end: sessions.end})
@highlight = null
}
)

View File

@ -24,7 +24,9 @@
band: {},
musician_access: {},
fans_access: {},
open_rsvps: false
open_rsvps: false,
friends_can_join: false
};
var friendInput = null;
@ -49,6 +51,7 @@
var $selectedFilenames = null;
var $uploadSpinner = null;
var $quickStartSoloBtn = null;
var $quickStartFriendsBtn = null;
var $quickStartOpenBtn = null;
var $startOrScheduledBtn = null;
var $featureSessions = null;
@ -245,6 +248,16 @@
logger.debug("user clicked quick start solo")
createSessionSettings.createType = '<%= MusicSession::CREATE_TYPE_QUICK_START %>'
createSessionSettings.friends_can_join = false
next();
return false;
}
function clickQuickStartFriends () {
logger.debug("user clicked quick start solo")
createSessionSettings.createType = '<%= MusicSession::CREATE_TYPE_QUICK_START %>'
createSessionSettings.friends_can_join = true
next();
return false;
}
@ -253,6 +266,7 @@
logger.debug("user clicked quick start public")
createSessionSettings.createType = '<%= MusicSession::CREATE_TYPE_QUICK_PUBLIC %>'
createSessionSettings.friends_can_join = false
next();
return false;
}
@ -725,6 +739,10 @@
createSessionSettings.fans_access.value = $fansAccess.val();
createSessionSettings.fans_access.label = $fansAccess.get(0).options[$fansAccess.get(0).selectedIndex].text;
var $friendsCanJoin = $screen.find('#session-friends-can-join');
createSessionSettings.friends_can_join = $friendsCanJoin.val() == 'true'
//createSessionSettings.friends_can_join.label = $friendsCanJoin.get(0).options[$friendsCanJoin.get(0).selectedIndex].text;
return isValid;
}
@ -832,6 +850,7 @@
data.timezone = createSessionSettings.timezone.value;
data.open_rsvps = createSessionSettings.open_rsvps;
data.create_type = createSessionSettings.createType;
data.friends_can_join = createSessionSettings.friends_can_join;
data.rsvp_slots = [];
$.each(getCreatorInstruments(), function(index, instrument) {
@ -893,7 +912,7 @@
joinSession(newSessionId);
}
else {
app.notifyAlert("Session is successfully published.");
app.notifyAlert("Session published! It can be now found on the Find Session screen.");
context.location = '/client#/home';
}
})
@ -1244,6 +1263,7 @@
context.JK.dropdown($screen.find('#session-musician-access'));
context.JK.dropdown($screen.find('#session-fans-access'));
context.JK.dropdown($screen.find('#session-friends-can-join'));
context.JK.dropdown($timezoneList);
context.JK.dropdown($recurringModeList);
context.JK.dropdown($languageList);
@ -1398,6 +1418,11 @@
$('#session-fans-access-info .info-box[fans-access-type="' + $(event.target).val() + '"]').removeClass('hidden');
}
function toggleFriendsCanJoinTypes(event) {
$('#session-friends-can-join-info .info-box').addClass('hidden');
$('#session-friends-can-join-info .info-box[friends-can-join-type="' + $(event.target).val() + '"]').removeClass('hidden');
}
function selectBand() {
var bandId = $bandList.get(0).options[$bandList.get(0).selectedIndex].value;
if (bandId != '') {
@ -1435,8 +1460,10 @@
$createTypes.on("ifChanged", toggleCreateType);
$startTimeList.on('change', function() { toggleStartTime(); });
$policyTypes.on("ifChanged", togglePolicyTypeChanged);
$('#session-step-4 #session-musician-access').on('change', toggleMusicianAccessTypes);
$('#session-step-4 #session-fans-access').on('change', toggleFanAccessTypes);
$('#session-step-5 #session-musician-access').on('change', toggleMusicianAccessTypes);
$('#session-step-5 #session-fans-access').on('change', toggleFanAccessTypes);
$('#session-step-5 #session-friends-can-join').on('change', toggleFriendsCanJoinTypes);
$('div[layout-id="createSession"] .btn-email-invitation').click(function() {
invitationDialog.showEmailDialog();
@ -1457,6 +1484,7 @@
$btnSelectFiles.on('click', toggleSelectFiles);
$quickStartSoloBtn.on('click', clickQuickStartSolo)
$quickStartFriendsBtn.on('click', clickQuickStartFriends)
$quickStartOpenBtn.on('click', clickQuickStartPublic)
$startOrScheduledBtn.on('click', clickStartOrSchedule)
}
@ -1500,6 +1528,7 @@
$noSessionFound = $screen.find("#scheduled-session-not-found");
$sessionHeader = $screen.find('.session-header')
$quickStartSoloBtn = $screen.find('.quick-start-solo')
$quickStartFriendsBtn = $screen.find('.quick-start-friends')
$quickStartOpenBtn = $screen.find('.quick-start-open')
$startOrScheduledBtn = $screen.find('.start-or-schedule')
$featureSessions = $screen.find('.featured-sessions tbody')

View File

@ -196,6 +196,10 @@
openJoinSessionTerms(sessionId);
}
}
else {
// if it's private, yet we can see it, presumably we are friends (better there was a front-end check here)
openJoinRequestAlert(sessionId);
}
}
}
})

View File

@ -73,6 +73,9 @@
return 0;
});
context.FriendActions.updateFriends.trigger(response)
$.each(response, function(index, val) {
var css = val.online ? '' : 'offline';

View File

@ -466,12 +466,16 @@
return css;
}
context.JK.bindHoverEvents = function ($parent) {
context.JK.bindHoverEvents = function ($parent, hoveractionAttr) {
var timeout = 300;
var fadeoutValue = 100;
var sensitivity = 3;
var interval = 500;
if(!hoveractionAttr) {
hoveractionAttr = 'hoveraction'
}
if (!$parent) {
$parent = $('body');
}
@ -505,7 +509,7 @@
}
// MUSICIAN
$("[hoveraction='musician']", $parent).hoverIntent({
$("[" + hoveractionAttr + "='musician']", $parent).hoverIntent({
over: function(e) {
var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), e.pageX, e.pageY);
@ -522,7 +526,7 @@
});
// FAN
$("[hoveraction='fan']", $parent).hoverIntent({
$("[" + hoveractionAttr + "='fan']", $parent).hoverIntent({
over: function(e) {
var bubble = new JK.FanHoverBubble($(this).attr('user-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
@ -536,7 +540,7 @@
});
// BAND
$("[hoveraction='band']", $parent).hoverIntent({
$("[" + hoveractionAttr +"='band']", $parent).hoverIntent({
over: function(e) {
var bubble = new JK.BandHoverBubble($(this).attr('band-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
@ -550,7 +554,7 @@
});
// SESSION
$("[hoveraction='session']", $parent).hoverIntent({
$("[" + hoveractionAttr + "='session']", $parent).hoverIntent({
over: function(e) {
var bubble = new JK.SessionHoverBubble($(this).attr('session-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));
@ -564,7 +568,7 @@
});
// RECORDING
$("[hoveraction='recording']", $parent).hoverIntent({
$("[" + hoveractionAttr + "='recording']", $parent).hoverIntent({
over: function(e) {
var bubble = new JK.RecordingHoverBubble($(this).attr('recording-id'), e.pageX, e.pageY);
showBubble(bubble, $(this));

View File

@ -62,7 +62,7 @@
.quick-options {
margin: 0 0 35px 0;
a {
width:130px;
width:135px;
margin-left:0px;
position:absolute;
top:5px;
@ -385,7 +385,7 @@
}
}
.session-musician-access-header, .session-fans-access-header {
.session-musician-access-header, .session-fans-access-header, .session-friends-can-join-header {
margin: 0 0 5px 0;
}

View File

@ -3,12 +3,92 @@
#findSession {
.FindSessionScreen {
height:100%;
}
[data-react-class="FindSessionScreen"] {
height:100%;
}
.session-keyword-srch {
}
.musicians-category {
}
.musicians-detail > td {
padding-bottom:20px;
}
.musicians-header {
span {
display:block;
height:100%;
margin:10px;
}
}
.find-session-help {
background-color: #262626;
max-width: 66%;
border: 2px solid #333;
color: white;
font-size: 14px;
text-align: left;
/* margin-top: 20px; */
margin: 30px auto;
padding: 10px;
width: 400px;
a {
}
li {
margin-bottom: 7px;
}
h3 {
text-align:center;
font-weight:bold;
margin: 0 0 10px 0;
text-decoration: underline;
}
p {
line-height:125%;
margin: 0 0 10px 0;
font-size:14px;
}
p + h3 {
margin-top:20px;
}
}
.none-found {
text-align:center;
padding:10px;
}
.tabs {
padding-left: 25px;
padding-left: 35px;
}
.highlight {
writing-mode: vertical-rl;
text-orientation: upright;
position: absolute;
height: calc(100% - 10px); // 10px comes from spacer height
width: 20px;
left: -21px;
background-color: #ed3618;
top: 0;
border: 0px solid black;
font-size: 11px;
text-align: center;
padding: 0 5px;
margin: 0;
@include border_box_sizing;
}
.header-zone {
@ -65,7 +145,16 @@
padding-bottom:20px;
}
.sessions-for-me {
.actions {
vertical-align:middle;
}
.end-of-results {
position:relative;
padding-bottom:20px;
text-align:center;
}
.sessions-for-me, .sessions-for-open {
clear:both;
display:none;
&.active {
@ -73,6 +162,12 @@
}
}
.rsvp-icon {
display: inline-block;
background: url('/assets/content/rsvp-icon.jpg') no-repeat;
height: 40px;
width: 40px;
}
.join-icon {
display: inline-block;
background: url('/assets/content/join-icon.jpg') no-repeat;
@ -123,8 +218,8 @@
.content-body-scroller {
height:inherit;
position:relative;
display:block;
overflow:auto
height:calc(100% - 123px) ! important;
overflow:auto;
}
.session-filter {
@ -173,6 +268,10 @@
margin-top:0;
}
.none-yet {
margin-left:36px;
}
.latency-value {
@include border-radius(2px);
}
@ -190,6 +289,10 @@
}
}
a.start, a.cancel {
color:#fc0;
}
.listen-detail-hover {
margin-left:5px;
margin-top:0 !important;
@ -203,17 +306,11 @@
}
.join-link-text {
font-weight:bold;
#position:relative;
#left:6px;
}
.rsvp-link {
#position:relative;
#left:0px;
}
.rsvp-link-text {
font-weight:bold;
#position:relative;
#left:6px;
}
}

View File

@ -2,7 +2,7 @@
#session-settings {
width:500px;
width:550px;
.dropdown-wrapper {
width:100%;
@ -38,7 +38,7 @@
}
.input-holder {
width:350px;
width:400px;
}
.notation-entry {

View File

@ -93,6 +93,11 @@ class ApiMusicSessionsController < ApiController
end
end
def sms_index_2
@music_sessions = MusicSession.scheduled_index(current_user, params)
@user_scores = {}
end
def scheduled
@music_sessions = MusicSession.scheduled(current_user)
end
@ -192,7 +197,8 @@ class ApiMusicSessionsController < ApiController
params[:approval_required],
params[:fan_chat],
params[:fan_access],
params[:session_controller])
params[:session_controller],
params[:friends_can_join])
if @music_session.errors.any?
# we have to do this because api_session_detail_url will fail with a bad @music_session
@ -234,6 +240,7 @@ class ApiMusicSessionsController < ApiController
@music_session.approval_required = params[:approval_required] if params.include? :approval_required
@music_session.fan_chat = params[:fan_chat] if params.include? :fan_chat
@music_session.fan_access = params[:fan_access] if params.include? :fan_access
@music_session.friends_can_join = params[:friends_can_join] if params.include? :friends_can_join
@music_session.genre = Genre.find_by_id(params[:genres][0]) if params.include?(:genres) && params[:genres] && params[:genres].length > 0
@music_session.legal_policy = params[:legal_policy] if params.include? :legal_policy
@music_session.language = params[:language] if params.include? :language

View File

@ -13,7 +13,7 @@ if !current_user
}
else
attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id, :jam_track_id
attributes :id, :name, :description, :musician_access, :approval_required, :friends_can_join, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id, :jam_track_id
node :can_join do |session|
session.can_join?(current_user, true)

View File

@ -19,7 +19,7 @@ else
attributes :id, :music_session_id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :create_type,
:band_id, :user_id, :genre_id, :created_at, :like_count, :comment_count, :play_count, :scheduled_duration,
:language, :recurring_mode, :language_description, :scheduled_start_date, :access_description, :timezone, :timezone_id, :timezone_description,
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps, :is_unstructured_rsvp?
:musician_access_description, :fan_access_description, :session_removed_at, :legal_policy, :open_rsvps, :is_unstructured_rsvp?, :friends_can_join
node :can_join do |session|
session.can_join?(current_user, true)

View File

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

View File

@ -1,60 +1,3 @@
<!-- 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">

View File

@ -26,6 +26,7 @@
<div class="quick-options">
<div class="quick-option"><a class="button-orange quick-start-solo" href="#" >QUICK START SOLO</a><p>Use this button to quick start a private session just for yourself. Good for solo practice and gear testing.</p></div>
<div class="quick-option"><a class="button-orange quick-start-open" href="#" >QUICK START OPEN</a><p>Use this button to quick start an open session that anyone can join to play with you, and that fans can listen to.</p></div>
<div class="quick-option"><a class="button-orange quick-start-friends" href="#" >QUICK START FRIENDS</a><p>Use this button to quick start a friends-only session. No invitations necessary for any of your friends. Good for those with select friends they always play with.</p></div>
<div class="quick-option"><a class="button-orange start-or-schedule" href="#" >START OR SCHEDULE</a><p>Use this button start a wizard that will let you have more control over your session. Schedule future sessions. Start private sessions. Invite other musicians. And lots more...</p><a class="schedule-learn-more" href="https://forum.jamkazam.com/showthread.php?tid=4" rel="external">learn more</a></div>
</div>
<h2>featured sessions</h2>
@ -390,6 +391,20 @@
</div>
</div>
<br>
<h3 class="session-friends-can-join-header">How will you handle friend access?</h3>
<select id="session-friends-can-join">
<option value="true" selected="selected">Friends Can Always Join</option>
<option value="false">Friends Follow Musician Access Rules</option>
</select>
<div id="session-friends-can-join-info" class="mt5">
<div class="info-box" friends-can-join-type="true">
Friends can join your session without any invitation.
</div>
<div class="info-box hidden" friends-can-join-type="false">
Friends have to follow 'Musician Access' rules.
</div>
</div>
<br>
<h3 class="session-fans-access-header">How will you handle fan access?</h3>
<select id="session-fans-access">
<!--<option value="listen-chat-band">Fans may listen, chat with the band</option>-->

View File

@ -48,6 +48,13 @@
%option{:value => "musicians"} Musicians may join at will
%option{:value => "musicians-approval"} Musicians may join by approval
.clearall.left.w20.ib.mb10
Friends Can Join:
.right.w75.ib.mb10
%select{:name => "friends-can-join", :id => "session-settings-friends-can-join"}
%option{:value => "true"} Friends Can Always Join
%option{:value => "false"} Friends Follow Musician Access Rules
.clearall.left.w25.ib.mb10
Fan Access:
.right.w75.ib.mb10

View File

@ -34,6 +34,7 @@
<%= render "sidebar" %>
<%= render "scheduledSession" %>
<%= render "findSession" %>
<%= render "findSession2" %>
<%= render "session2" %>
<%= render "profile" %>
<%= render "bandProfile" %>

View File

@ -23,7 +23,7 @@ SampleApp::Application.configure do
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_controller.perform_caching = true
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
@ -41,7 +41,7 @@ SampleApp::Application.configure do
config.assets.compress = false
# Expands the lines which load the assets
config.assets.debug = true
config.assets.debug = false
# Set the logging destination(s)
config.log_to = %w[stdout file]

View File

@ -241,7 +241,7 @@ Rails.application.routes.draw do
# music sessions
match '/sessions/:id/participants/legacy' => 'api_music_sessions#participant_create_legacy', :via => :post # can be removed when new Create Session comes in
match '/sessions/:id/participants' => 'api_music_sessions#participant_create', :via => :post
match '/participants/:id' => 'api_music_sessions#participant_show', :via => :tt, :as => 'api_session_participant_detail'
match '/participants/:id' => 'api_music_sessions#participant_show', :via => :get, :as => 'api_session_participant_detail'
match '/participants/:id' => 'api_music_sessions#participant_delete', :via => :delete
match '/sessions/scheduled' => 'api_music_sessions#scheduled', :via => :get
match '/sessions/scheduled_rsvp' => 'api_music_sessions#scheduled_rsvp', :via => :get
@ -249,7 +249,7 @@ Rails.application.routes.draw do
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/inactive' => 'api_music_sessions#sms_index', :via => :get
match '/sessions/inactive' => 'api_music_sessions#sms_index_2', :via => :get
match '/sessions/:id' => 'api_music_sessions#show', :via => :get, :as => 'api_session_detail'
match '/sessions/:id' => 'api_music_sessions#update', :via => :put
match '/sessions/:id' => 'api_music_sessions#session_update', :via => :post

View File

@ -70,7 +70,7 @@ class MusicSessionManager < BaseManager
end
# Update the session. If a field is left out (meaning, it's set to nil), it's not updated.
def update(current_user, music_session, name, description, genre, language, musician_access, approval_required, fan_chat, fan_access, session_controller_id)
def update(current_user, music_session, name, description, genre, language, musician_access, approval_required, fan_chat, fan_access, session_controller_id, friends_can_join)
music_session.name = name unless name.nil?
music_session.description = description unless description.nil?
@ -80,6 +80,7 @@ class MusicSessionManager < BaseManager
music_session.approval_required = approval_required unless approval_required.nil?
music_session.fan_chat = fan_chat unless fan_chat.nil?
music_session.fan_access = fan_access unless fan_access.nil?
music_session.friends_can_join = friends_can_join unless friends_can_join.nil?
session_controller = User.find(session_controller_id) if session_controller_id.present?
should_tick = music_session.set_session_controller(current_user, session_controller)
music_session.save