516 lines
17 KiB
CoffeeScript
516 lines
17 KiB
CoffeeScript
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
|
|
SessionUtils = context.JK.SessionUtils
|
|
|
|
@FindSessionRow = React.createClass({
|
|
|
|
mixins: [Reflux.listenTo(@LatencyStore, "onLatencyUpdate")]
|
|
|
|
ui: null
|
|
|
|
getInitialState: () ->
|
|
{ rsvpToggle: false, openSlotToggle: false, userLatencies: [] }
|
|
|
|
createInstrument: (participant) ->
|
|
|
|
instruments = []
|
|
existingTracks = []
|
|
for track in participant.tracks
|
|
|
|
if existingTracks.indexOf(track.instrument_id) < 0
|
|
existingTracks.push(track.instrument_id)
|
|
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
|
|
|
|
createLatencyBadge: (participant) ->
|
|
logger.debug(">>>userLatencies", @state.userLatencies)
|
|
latency = @state.userLatencies.find((latency) ->
|
|
latency.user_id == participant.id;
|
|
)
|
|
# latencyData = SessionUtils.changeLatencyDataStructure(latency)
|
|
# data = $.extend(latencyData, SessionUtils.createLatency(latencyData))
|
|
# latency_text = data.latency_text
|
|
# latency_style = data.latency_text
|
|
|
|
`<span class="latency {latency_style}">{latency_text}</span>`
|
|
|
|
|
|
createInSessionUser: (participant) ->
|
|
|
|
instruments = @createInstrument(participant)
|
|
|
|
latencyBadge = @createLatencyBadge(participant)
|
|
|
|
`<HoverUser more={null} user={participant.user} instruments={instruments} latency={latencyBadge} />`
|
|
|
|
createOpenSlot:(slot, isLast) ->
|
|
|
|
inst = context.JK.getInstrumentIcon24(slot.instrument_id);
|
|
|
|
proficiency_desc = slot.proficiency_desc
|
|
if !proficiency_desc
|
|
# this is to allow unstructured RSVPs to not specify proficiency_desc
|
|
proficiency_desc = "Any Skill Level"
|
|
|
|
toggle = @state.openSlotToggle
|
|
|
|
if isLast
|
|
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
|
|
proficiency = proficiency_desc
|
|
more_link = moreLinkHtml
|
|
|
|
|
|
`<tr key={slot.id}>
|
|
<td width="24">
|
|
<img src={instrument_url} />
|
|
</td>
|
|
<td>
|
|
<div className="instruments nowrap">{instrument} ({proficiency})</div>
|
|
</td>
|
|
<td>{more_link} </td>
|
|
</tr>`
|
|
|
|
createRsvpUser: (user, session, isLast) ->
|
|
instruments = []
|
|
|
|
if user.instrument_list
|
|
for instrument in user.instrument_list
|
|
inst = context.JK.getInstrumentIcon24(instrument.id);
|
|
instruments.push(`<img title={context.JK.getInstrumentId(instrument.id)} hoveraction="instrument" data-instrument-id={instrument.id} key={instrument.id} src={inst} width="24" height="24" />`)
|
|
|
|
moreLinkHtml = '';
|
|
if isLast
|
|
# false means hide, true means show
|
|
toggle = @state.rsvpToggle
|
|
|
|
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.toggleRsvp.bind(this)} className="rsvps more">{text}</a><a className={computedClass}></a></span></td>`
|
|
|
|
latencyBadge = @createLatencyBadge(user)
|
|
|
|
`<HoverUser user={user} instruments={instruments} more={moreLinkHtml} latency={latencyBadge} />`
|
|
|
|
inSessionUsersHtml: (session) ->
|
|
inSessionUsers = []
|
|
|
|
result = []
|
|
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]
|
|
|
|
createRsvpUsers:(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 approved_rsvp in first
|
|
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]
|
|
|
|
createOpenSlots: (session) ->
|
|
|
|
firstResults = []
|
|
remainingResults = []
|
|
|
|
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]
|
|
|
|
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
|
|
|
|
if @props.mode == 'upcoming'
|
|
return null
|
|
|
|
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> | <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> | 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 musicians-category" cellpadding="0" cellspacing="0" width="100%">
|
|
<tbody>
|
|
{open_slots_first_3}
|
|
</tbody>
|
|
</table>
|
|
<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(AppStore.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>`
|
|
|
|
createListenLink: () ->
|
|
null
|
|
|
|
onLatencyUpdate: (userLatencies) ->
|
|
@setState(userLatencies: userLatencies)
|
|
|
|
render: () ->
|
|
|
|
session = @props.session
|
|
|
|
id = session.id
|
|
name = session.name
|
|
description = session.description || "(No description)"
|
|
genres = session.genres.join (', ')
|
|
if session.genres.length > 1
|
|
genres = `<span><span className="bold">Genres: </span>{genres}</span>`
|
|
else
|
|
genres = `<span><span className="bold">Genre: </span>{genres}</span>`
|
|
|
|
[in_session_musicians, inSessionUsers] = @inSessionUsersHtml(session)
|
|
[rsvp_musicians_first_3, rsvp_musicians_remaining] = @createRsvpUsers(session)
|
|
[open_slots_first_3, open_slots_remaining] = @createOpenSlots(session)
|
|
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
|
|
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 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 = '';
|
|
|
|
remark = null
|
|
if session.highlight
|
|
highlight = session.highlight
|
|
if highlight.new
|
|
remark = `<div className="highlight new">NEW</div>`
|
|
|
|
|
|
inSessionMusicians = @inSessionMusicians(in_session_musicians)
|
|
listenLink = @createListenLink(session)
|
|
|
|
`<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>
|
|
|
|
</tr>
|
|
<tr>
|
|
<td colspan="2" className="session-description">{description}</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="session-genre">{genres}</td>
|
|
</tr>
|
|
|
|
{scheduled_start}
|
|
|
|
</tbody>
|
|
</table>
|
|
<div className="spacer"></div>
|
|
</td>
|
|
<td width="45%" className="session-musicians">
|
|
<table className="musicians" cellpadding="0" cellspacing="0">
|
|
<tbody>
|
|
{inSessionMusicians}
|
|
{rsvps}
|
|
{openSlots}
|
|
</tbody>
|
|
</table>
|
|
<div className="spacer"></div>
|
|
</td>
|
|
<td width="10%" className="actions">
|
|
{listenLink}
|
|
{joinLink}
|
|
{rsvpLink}
|
|
<div className="spacer"></div>
|
|
|
|
</td>
|
|
</tr>`
|
|
}) |