305 lines
9.9 KiB
CoffeeScript
305 lines
9.9 KiB
CoffeeScript
context = window
|
|
rest = new context.JK.Rest()
|
|
ChatActions = @ChatActions
|
|
SessionStore = @SessionStore
|
|
|
|
|
|
@ChatWindow = React.createClass({
|
|
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"),
|
|
Reflux.listenTo(@ChatStore, "onChatChanged")]
|
|
|
|
lastChannel: null
|
|
|
|
getInitialState: () ->
|
|
state = context.ChatStore.getState()
|
|
state
|
|
|
|
getInitialProps: () ->
|
|
{newFormat:true}
|
|
|
|
onChatChanged: (chatState) ->
|
|
@setState(chatState)
|
|
|
|
onUserChanged: (userState) ->
|
|
@setState(userState)
|
|
|
|
onAppInit: (app) ->
|
|
@app = app
|
|
|
|
activateChannel: (channel, e) ->
|
|
e.preventDefault()
|
|
ChatActions.activateChannel(channel);
|
|
|
|
convertPurpose: (purpose) ->
|
|
if !purpose?
|
|
return ''
|
|
|
|
switch purpose
|
|
when 'Lesson Requested' then 'requested lesson'
|
|
when 'Lesson Canceled' then 'canceled lesson'
|
|
when 'Lesson Approved' then 'approved lesson'
|
|
when 'All Lesson Times Updated' then 'updated all lesson times'
|
|
when 'Lesson Updated Time Approved' then 'updated lesson time'
|
|
when 'New Time Proposed' then 'proposed new time'
|
|
when 'Lesson Declined' then 'declined lesson'
|
|
when 'Notation File' then 'attached a notation file'
|
|
when 'Audio File' then 'attached an audio file'
|
|
when 'JamKazam Recording' then 'attached a recording'
|
|
when 'Lesson Timeout' then 'canceled by system'
|
|
when 'Video Uploaded' then 'uploaded video'
|
|
else purpose
|
|
|
|
notationClicked: (music_notation, e) ->
|
|
e.preventDefault()
|
|
|
|
rest.getMusicNotation(music_notation.id).done((response) =>
|
|
context.JK.popExternalLink(response.url)
|
|
)
|
|
.fail((jqXHR) =>
|
|
context.JK.Banner.showAlert('Unable to fetch URL for music notation. Error: ' + jqXHR.responseText)
|
|
)
|
|
|
|
audioClicked: (music_notation, e) ->
|
|
e.preventDefault()
|
|
|
|
rest.getMusicNotation(music_notation.id).done((response) =>
|
|
context.JK.popExternalLink(response.url)
|
|
)
|
|
.fail((jqXHR) =>
|
|
context.JK.Banner.showAlert('Unable to fetch URL for audio. Error: ' + jqXHR.responseText)
|
|
)
|
|
|
|
recordingClicked: (recording, e) ->
|
|
e.preventDefault()
|
|
context.JK.popExternalLink("/recordings/#{recording.id}")
|
|
|
|
videoClicked: (recording, e) ->
|
|
e.preventDefault()
|
|
context.JK.popExternalLink("https://www.youtube.com/watch?v=#{recording.video_id}")
|
|
|
|
openMenu: (lesson, e) ->
|
|
$this = $(e.target)
|
|
if !$this.is('.lesson-session-actions-btn')
|
|
$this = $this.closest('.lesson-session-actions-btn')
|
|
$this.btOn()
|
|
|
|
render: () ->
|
|
|
|
if @state.channel == 'lesson'
|
|
chatTabs = `<div className="chat-tabs">
|
|
<a data-lesson-id={this.state.lessonSessionId} className="lesson-session-actions-btn"
|
|
onClick={this.openMenu.bind(this, {id: this.state.lessonSessionId})}>attach file
|
|
<div className="details-arrow arrow-down"/>
|
|
</a>
|
|
</div>`
|
|
else if !this.props.hideHeader
|
|
tabs = []
|
|
for channel of @state.msgs
|
|
classes = {}
|
|
classes[channel] = true
|
|
classes['chat-tab'] = true
|
|
classes['active'] = channel == @state.channel
|
|
|
|
if channel == 'global'
|
|
display = 'Global'
|
|
else if channel == 'session'
|
|
display = 'Session'
|
|
else if channel == 'lesson'
|
|
display = 'Lesson'
|
|
else if !channel?
|
|
display = 'Global'
|
|
else
|
|
display = 'Unknown'
|
|
|
|
if display == 'Unknown'
|
|
continue
|
|
|
|
tabs.push(`<div key={channel} className={classNames(classes)}><a onClick={this.activateChannel.bind(this, channel)}>{display}</a></div>`)
|
|
chatTabs = `<div className="chat-tabs">
|
|
{tabs}
|
|
</div>`
|
|
|
|
msgs = []
|
|
|
|
openBig = null
|
|
|
|
if @activeChannelType() == 'lesson'
|
|
activeChannel = @state.lessonSessionId
|
|
else
|
|
activeChannel = @state.channel
|
|
|
|
if activeChannel == 'global'
|
|
openBig = `<a className="make-big" href="#" onClick={this.openChatDialog.bind(this)}>Expand</a>`
|
|
|
|
activeMsgs = @state.msgs[activeChannel] || []
|
|
|
|
for msg in activeMsgs
|
|
|
|
timeago = $.timeago(msg.created_at)
|
|
if msg.sender_id == context.JK.currentUserId
|
|
sender = "me"
|
|
else
|
|
sender = msg.sender_name
|
|
|
|
if msg.purpose
|
|
purpose = `<div className="chat-message-purpose">{this.convertPurpose(msg.purpose)}</div>`
|
|
else
|
|
purpose = null
|
|
|
|
additional = null
|
|
if msg.purpose == 'Notation File'
|
|
additional = `<a className="additional" onClick={this.notationClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
|
|
else if msg.purpose == 'Audio File'
|
|
additional = `<a className="additional" onClick={this.audioClicked.bind(this, msg.music_notation)}>{msg.music_notation.file_name}</a>`
|
|
else if msg.purpose == 'JamKazam Recording'
|
|
additional = `<a className="additional" onClick={this.recordingClicked.bind(this, msg.claimed_recording)}>{msg.claimed_recording.name}</a>`
|
|
else if msg.purpose == 'Video Uploaded'
|
|
additional = `<a className="additional" onClick={this.videoClicked.bind(this, msg.claimed_recording)}>Watch on YouTube</a>`
|
|
|
|
msgs.push(`<div key={msg.msg_id} className="chat-message">
|
|
<span className="chat-message-sender">{sender}</span>{purpose}<time className="chat-message-timestamp timeago">{timeago}</time>
|
|
<span className="chat-message-text">{msg.msg}</span>
|
|
{additional}
|
|
</div>`)
|
|
|
|
|
|
|
|
if this.props?.showEmailNotice
|
|
otherName = this.props?.other?.name
|
|
emailSentNotice = `<div className="email-notice">an email will be sent if {otherName} is offline</div>`
|
|
if this.props?.showClose
|
|
closeBtn = `<a className="button-grey btn-close-chat" onClick={this.handleCloseMessage}>CLOSE</a>`
|
|
|
|
topClasses = {ChatWindow: true}
|
|
if this.props?.rootClass?
|
|
topClasses[this.props.rootClass] = true
|
|
|
|
if this.props.rootClass == 'ChatDialog'
|
|
attachFiles = `<a id="attach-files-chat-dialog" data-lesson-id={this.state.lessonSessionId} className="lesson-session-actions-btn"
|
|
onClick={this.openMenu.bind(this, {id: this.state.lessonSessionId})}>attach file
|
|
<div className="details-arrow arrow-down"/>
|
|
</a>`
|
|
|
|
`<div className={classNames(topClasses)}>
|
|
{openBig}
|
|
{chatTabs}
|
|
<div className="active-tab">
|
|
<div className="chat-list-scroller">
|
|
{msgs}
|
|
</div>
|
|
</div>
|
|
<div className="chat-sender">
|
|
<form onSubmit={this.handleSendMessage} className="chat-message-form">
|
|
<textarea onKeyDown={this.handleEnter} name="chat-message" id="new-chat-message" placeholder="enter message"></textarea>
|
|
|
|
<a className="button-orange btn-send-chat-message" onClick={this.handleSendMessage}>SEND</a>
|
|
{closeBtn}
|
|
{emailSentNotice}
|
|
{attachFiles}
|
|
</form>
|
|
|
|
</div>
|
|
</div>`
|
|
|
|
openChatDialog: () ->
|
|
@app.layout.showDialog('chat-dialog', {d1: 'global'})
|
|
|
|
activeChannelType: () ->
|
|
@state.channelType
|
|
|
|
sendMessage:()->
|
|
if !context.JK.JamServer.connected
|
|
return false
|
|
|
|
msg = @textBox.val()
|
|
if !msg? || msg == ''
|
|
# don't bother the server with empty messages
|
|
return false;
|
|
|
|
if !@sendingMessage
|
|
@sendingMessage = true
|
|
target_user_id = this.props?.other?.id
|
|
ChatActions.sendMsg(msg, @sendMsgDone, @sendMsgFail, target_user_id, @activeChannelType())
|
|
|
|
sendMsgDone: () ->
|
|
@textBox.val('')
|
|
@sendingMessage = false
|
|
|
|
sendMsgFail: (jqXHR) ->
|
|
if jqXHR.status == 404
|
|
@app.notifyServerError(jqXHR, 'Session chat is only available while in session.')
|
|
else
|
|
@app.notifyServerError(jqXHR, 'Unable to Send Chat Message')
|
|
@sendingMessage = false
|
|
|
|
handleSendMessage: (e) ->
|
|
e.preventDefault()
|
|
@sendMessage()
|
|
|
|
|
|
handleCloseMessage: (e) ->
|
|
e.preventDefault()
|
|
this.props.onCloseClicked()
|
|
|
|
componentDidMount: () ->
|
|
@root = $(@getDOMNode())
|
|
@textBox = @root.find('textarea')
|
|
|
|
componentDidUpdate: () ->
|
|
if @lastChannel != @state.channel
|
|
speed = 0
|
|
else
|
|
speed = 'slow'
|
|
@lastChannel = @state.channel
|
|
|
|
#speed = 0 #slow
|
|
$scroller = @root.find('.chat-list-scroller')
|
|
$scroller.animate({scrollTop: $scroller[0].scrollHeight}, speed)
|
|
|
|
items = @root.find('.chat-tabs .lesson-session-actions-btn')
|
|
@hookupMenu(items)
|
|
|
|
items = @root.find('#attach-files-chat-dialog')
|
|
@hookupMenu(items)
|
|
|
|
|
|
hookupMenu: (items) ->
|
|
$.each(items, (i, node) => (
|
|
$node = $(node)
|
|
|
|
chat_dialog = this.props.rootClass == 'ChatDialog'
|
|
lesson = {id: $node.attr('data-lesson-id'), attachments_only: true, chat_dialog: chat_dialog}
|
|
|
|
$node.lessonSessionActions(lesson).off(context.JK.EVENTS.LESSON_SESSION_ACTION).on(context.JK.EVENTS.LESSON_SESSION_ACTION, @lessonSessionActionSelected)
|
|
))
|
|
|
|
lessonSessionActionSelected: (e, data) ->
|
|
lessonId = data.options.id
|
|
|
|
if data.lessonAction == 'attach-recording'
|
|
window.AttachmentActions.startAttachRecording.trigger(lessonId, SessionStore.id())
|
|
else if data.lessonAction == 'attach-notation'
|
|
window.AttachmentActions.startAttachNotation.trigger(lessonId, SessionStore.id())
|
|
else if data.lessonAction == 'attach-audio'
|
|
window.AttachmentActions.startAttachAudio.trigger(lessonId, SessionStore.id())
|
|
|
|
pasteIntoInput: (el, text) ->
|
|
el.focus();
|
|
if typeof el.selectionStart == "number" && typeof el.selectionEnd == "number"
|
|
val = el.value
|
|
selStart = el.selectionStart
|
|
el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd)
|
|
el.selectionEnd = el.selectionStart = selStart + text.length
|
|
else if typeof document.selection != "undefined"
|
|
textRange = document.selection.createRange()
|
|
textRange.text = text
|
|
textRange.collapse(false)
|
|
textRange.select()
|
|
|
|
handleEnter: (evt) ->
|
|
if evt.keyCode == 13 && evt.shiftKey
|
|
evt.preventDefault()
|
|
@pasteIntoInput($(evt.target).get(0), "\n")
|
|
else if evt.keyCode == 13 && !evt.shiftKey
|
|
@sendMessage()
|
|
}) |