jam-cloud/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee

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}>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()
})