From 0f4be8867726365c25ea70c339d58c2a78fbd63e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 30 Jan 2016 16:08:54 -0600 Subject: [PATCH] * wip --- db/manifest | 3 +- db/up/chat_channel.sql | 2 + ruby/lib/jam_ruby/jam_track_importer.rb | 7 +- ruby/lib/jam_ruby/models/chat_message.rb | 18 ++- ruby/lib/jam_ruby/mq_router.rb | 6 + web/app/assets/javascripts/chatPanel.js | 59 ++++---- .../react-components/ChatWindow.js.jsx.coffee | 137 +++++++++++++++++- .../actions/ChatActions.js.coffee | 4 + .../stores/ChatStore.js.coffee | 90 +++++++++++- .../react-components/ChatWindow.css.scss | 104 +++++++++++++ .../stylesheets/client/sidebar.css.scss | 63 -------- web/app/controllers/api_chats_controller.rb | 19 ++- web/app/views/api_chats/show.rabl | 2 +- web/app/views/clients/_sidebar.html.erb | 6 +- 14 files changed, 401 insertions(+), 119 deletions(-) create mode 100644 db/up/chat_channel.sql create mode 100644 web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss diff --git a/db/manifest b/db/manifest index 1250059b1..89fd895c4 100755 --- a/db/manifest +++ b/db/manifest @@ -327,4 +327,5 @@ populate_languages.sql populate_subjects.sql reviews.sql download_tracker_fingerprints.sql -connection_active.sql \ No newline at end of file +connection_active.sql +chat_channel.sql \ No newline at end of file diff --git a/db/up/chat_channel.sql b/db/up/chat_channel.sql new file mode 100644 index 000000000..1e94e7e16 --- /dev/null +++ b/db/up/chat_channel.sql @@ -0,0 +1,2 @@ +ALTER TABLE chat_messages ADD COLUMN channel VARCHAR(128) NOT NULL DEFAULT 'session'; +ADD INDEX \ No newline at end of file diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index 01fed9be0..36b1a8655 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -2603,8 +2603,13 @@ module JamRuby def generate_jmeps importers = [] + if is_tency_storage? + licensor = JamTrackLicensor.find_by_name!('Tency Music') + elsif is_paris_storage? + licensor = JamTrackLicensor.find_by_name!('Paris Music') + end - JamTrack.all.each do |jam_track| + JamTrack.where(licensor_id: licensor).each do |jam_track| importers << generate_jmep(jam_track) end diff --git a/ruby/lib/jam_ruby/models/chat_message.rb b/ruby/lib/jam_ruby/models/chat_message.rb index 005187415..fc7b51929 100644 --- a/ruby/lib/jam_ruby/models/chat_message.rb +++ b/ruby/lib/jam_ruby/models/chat_message.rb @@ -32,7 +32,7 @@ module JamRuby music_session_id = params[:music_session] query = ChatMessage.where('music_session_id = ?', music_session_id) - .offset(start).limit(limit) + .offset(start).limit(limit) if query.length == 0 [query, nil] @@ -43,21 +43,25 @@ module JamRuby end end - def send_chat_msg(music_session, chat_msg, user, client_id) + def send_chat_msg(music_session, chat_msg, user, client_id, channel) + music_session_id = music_session.id if music_session + msg = @@message_factory.chat_message( - music_session.id, + music_session_id, user.name, user.id, chat_msg.message, chat_msg.id, chat_msg.created_at.utc.iso8601, - 'session' + channel ) - @@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id}) + if channel == 'session' + @@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id}) + elsif channel == 'global' + @@mq_router.publish_to_active_clients(msg) + end end - end - end end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/mq_router.rb b/ruby/lib/jam_ruby/mq_router.rb index c7a9ba417..be4ee21c6 100644 --- a/ruby/lib/jam_ruby/mq_router.rb +++ b/ruby/lib/jam_ruby/mq_router.rb @@ -70,6 +70,12 @@ class MQRouter publish_to_client(MessageFactory::ALL_NATIVE_CLIENTS, client_msg) end + + # sends a message to all clients + def publish_to_active_clients(client_msg) + publish_to_client(MessageFactory::ALL_ACTIVE_CLIENTS, client_msg) + end + # sends a message to a session with no checking of permissions (RAW USAGE) # this method deliberately has no database interactivity/active_record objects def publish_to_session(music_session_id, client_ids, client_msg, sender = {:client_id => nil}) diff --git a/web/app/assets/javascripts/chatPanel.js b/web/app/assets/javascripts/chatPanel.js index 45a13014b..fa6033939 100644 --- a/web/app/assets/javascripts/chatPanel.js +++ b/web/app/assets/javascripts/chatPanel.js @@ -30,8 +30,8 @@ fullyInitialized = false; renderQueue = []; sendingMessage = false; - $chatMessages.empty(); - $textBox.val(''); + //$chatMessages.empty(); + //$textBox.val(''); } function buildMessage() { @@ -135,7 +135,7 @@ } function events(bind) { - if (bind) { + /**if (bind) { $form.submit(sendMessage); $textBox.keydown(handleEnter); $sendChatMessageBtn.click(sendMessage); @@ -144,28 +144,27 @@ $form.submit(null); $textBox.keydown(null); $sendChatMessageBtn.click(null); - } + }*/ - registerChatMessage(bind); } // called from sidebar when messages come in function chatMessageReceived(payload) { - if(fullyInitialized) { - if (isChatPanelVisible()) { - renderMessage(payload.msg, payload.sender_id, payload.sender_name, payload.created_at, true); - } - else { + //if(fullyInitialized) { + //if (isChatPanelVisible()) { + // renderMessage(payload.msg, payload.sender_id, payload.sender_name, payload.created_at, true); + //} + //else { highlightCount(); incrementChatCount(); - renderQueue.push({message: payload.msg, user_id: payload.sender_id, user_name: payload.sender_name, sent: payload.created_at}); + // renderQueue.push({message: payload.msg, user_id: payload.sender_id, user_name: payload.sender_name, sent: payload.created_at}); context.jamClient.UserAttention(true); - } - } - else { - renderQueue.push({message: payload.msg, user_id: payload.sender_id, user_name: payload.sender_name, sent: payload.created_at}); - } + //} + //} + //else { + // renderQueue.push({message: payload.msg, user_id: payload.sender_id, user_name: payload.sender_name, sent: payload.created_at}); + //} } function registerChatMessage(bind) { @@ -173,6 +172,7 @@ context.JK.JamServer.registerMessageCallback(context.JK.MessageType.CHAT_MESSAGE, function(header, payload) { logger.debug("Handling CHAT_MESSAGE msg " + JSON.stringify(payload)); chatMessageReceived(payload); + context.ChatActions.msgReceived(payload); handledNotification(payload); }); @@ -193,8 +193,8 @@ $sessionId = data.session.id; // open chat panel - $chatSender.show(); - $chatMessagesScroller.show(); + //$chatSender.show(); + //$chatMessagesScroller.show(); $errorMsg.hide(); $panel.find('.panel-header').trigger('click'); $panel.on('open', opened); @@ -202,12 +202,15 @@ reset(); + context.ChatActions.sessionStarted($sessionId); + // load previous chat messages rest.getChatMessages(buildQuery()) .done(function (response) { - handleChatResponse(response); + //handleChatResponse(response); - scrollToBottom(true); + context.ChatActions.loadMessages('session', response) + //scrollToBottom(true); showing = true; fullyInitialized = true; drainQueue(); @@ -318,20 +321,22 @@ $contents = $panel.find('.chatcontents'); $chatMessagesScroller = $panel.find('.chat-list-scroller'); $count = $panel.find('#sidebar-chat-count'); - $chatMessages = $panel.find('.previous-chat-list'); - $sendChatMessageBtn = $panel.find('.btn-send-chat-message'); - $chatSender = $panel.find('.chat-sender'); - $form = $panel.find('.chat-message-form'); - $textBox = $form.find('textarea'); + //$chatMessages = $panel.find('.previous-chat-list'); + //$sendChatMessageBtn = $panel.find('.btn-send-chat-message'); + //$chatSender = $panel.find('.chat-sender'); + //$form = $panel.find('.chat-message-form'); + //$textBox = $form.find('textarea'); $errorMsg = $panel.find('.chat-status'); $errorMsg.show(); - $chatSender.hide(); - $chatMessagesScroller.hide(); + //$chatSender.hide(); + //$chatMessagesScroller.hide(); app.user() .done(function (userDetail) { user = userDetail; + + registerChatMessage(true); }); } diff --git a/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee b/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee index 415eaae52..d0e739387 100644 --- a/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ChatWindow.js.jsx.coffee @@ -1,16 +1,145 @@ context = window -MIX_MODES = context.JK.MIX_MODES +rest = new context.JK.Rest() +ChatActions = @ChatActions @ChatWindow = React.createClass({ - mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@ChatStore, "onChatChanged")] + mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), + Reflux.listenTo(@ChatStore, "onChatChanged")] + + lastChannel: null getInitialState: () -> - {channels:['global', 'session']} + state = context.ChatStore.getState() + state + + onChatChanged: (chatState) -> + @setState(chatState) + + onUserChanged: (userState) -> + @setState(userState) + + onAppInit: (app) -> + @app = app + + activateChannel: (channel, e) -> + e.preventDefault() + ChatActions.activateChannel(channel); render: () -> + 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? + display = 'Global' + else + display = 'Unknown' + + tabs.push(`
{display}
`) + + msgs = [] + activeMsgs = @state.msgs[@state.channel] || [] + + for msg in activeMsgs + + timeago = $.timeago(msg.created_at) + if msg.sender_id == context.JK.currentUserId + sender = "me" + else + sender = msg.sender_name + + msgs.push(`
+ {sender} + {msg.msg} + +
`) + `
-
+
+ {tabs} +
+
+
+ {msgs}
+
+
+
+ + SEND +
+ +
` + + sendMessage:()-> + if !context.JK.JamServer.connected + return false + + msg = @textBox.val() + logger.debug("text box: " + msg) + if !msg? || msg == '' + # don't bother the server with empty messages + return false; + + if !@sendingMessage + @sendingMessage = true + ChatActions.sendMsg(msg, @sendMsgDone, @sendMsgFail) + + sendMsgDone: () -> + @textBox.val('') + @sendingMessage = false + + sendMsgFail: (jqXHR) -> + @app.notifyServerError(jqXHR, 'Unable to Send Chat Message') + @sendingMessage = false + + handleSendMessage: (e) -> + e.preventDefault() + @sendMessage() + + 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') + @root.animate({scrollTop: $scroller[0].scrollHeight}, speed) + + 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(this, "\n") + else if evt.keyCode == 13 && !evt.shiftKey + @sendMessage() + return false }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/ChatActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ChatActions.js.coffee index 5d99548b0..41b65f36a 100644 --- a/web/app/assets/javascripts/react-components/actions/ChatActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/ChatActions.js.coffee @@ -3,4 +3,8 @@ context = window @ChatActions = Reflux.createActions({ msgReceived: {} sendMsg: {} + loadMessages: {} + emptyChannel: {} + sessionStarted: {} + activateChannel: {} }) diff --git a/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee index 207136212..9e504ca2c 100644 --- a/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee @@ -6,14 +6,57 @@ logger = context.JK.logger { listenables: @ChatActions - channel: null, - msgs: {} + channel: 'global', + msgs: {global:[], session:[]} - init: -> + init: () -> # Register with the app store to get @app this.listenTo(context.AppStore, this.onAppInit) onAppInit: (@app) -> + #$(document).on(EVENTS.SESSION_STARTED, ((e, data) => + #@sessionStarted(e, data); + # return false + #)) + + # $(context.AppStore).on('SessionStarted', @onSessionStarted) + + # called from ChatPanel + onSessionStarted: () -> + logger.debug("ChatStore: onSessionStarted") + @msgs['session'] = [] + @channel = 'session' + @textBox.val('') + @onEmptyChannel(@channel) + + onEmptyChannel: (channel) -> + @msgs[channel] = [] + @changed() + + convertServerMessages: (chats) -> + converted = [] + for chat in chats + convert = {} + convert.sender_name = chat.user?.name + convert.sender_id = chat.user_id + convert.msg = chat.message + convert.msg_id = chat.id + convert.created_at = chat.created_at + convert.channel = chat.channel + converted + + # called from ChatPanel + onLoadMessages: (channel, msgs) -> + channelMsgs = @msgs[channel] + + if !channelMsgs? + channelMsgs = [] + @msgs[channel] = channelMsgs + + history = @convertServerMessages(msgs.chats) + + @msgs[channel] = history.concat(channelMsgs) + @changed() onMsgReceived: (msg) -> logger.debug("ChatStore.msgReceived", msg) @@ -27,12 +70,49 @@ logger = context.JK.logger channelMsgs.push(msg) @changed() - onSendMsg: (msg) -> + buildMessage:(msg) -> + payload = {message: msg} + if @channel == 'session' + payload.music_session = SessionStore.currentSessionId + payload.channel = @channel + payload.client_id = @app.clientId + payload + + onActivateChannel: (channel) -> + @channel = channel + @changed() + + onSendMsg: (msg, done, fail) -> + rest.createChatMessage(@buildMessage(msg)) + .done((response) => + + done(response) + + console.log("ON SEND MESSAGE SIMULATE", response) + + @onMsgReceived({ + sender_name: context.JK.currentUserName + sender_id: context.JK.currentuserId + msg: msg, + msg_id: response.id, + created_at: response.created_at, + channel: @channel + }) + ) + .fail((jqXHR) => + fail(jqXHR) + ) + + # unused/untested. send direct to gateway + onSendMsgInstant: (msg) -> logger.debug("ChatStore.sendMsg", msg) window.JK.JamServer.sendChatMessage(@channel, msg) + getState: () -> + return {msgs: @msgs, channel: @channel} + changed: () -> - @trigger(msgs) + @trigger(@getState()) } ) diff --git a/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss b/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss new file mode 100644 index 000000000..f17b2a68a --- /dev/null +++ b/web/app/assets/stylesheets/client/react-components/ChatWindow.css.scss @@ -0,0 +1,104 @@ +div[data-react-class="ChatWindow"] { + height:100%; + + .ChatWindow { + height:100%; + } + + .chat-tabs { + text-align:center; + } + .chat-tab { + + margin:0 2px; + display:inline-block; + + padding:2px; + color: #ED3618; + background-color:#09525b; + border-style:solid; + border-color:#09525b; + border-width:1px; + + a { + //color:white; + text-decoration: underline; + } + + &.active { + a { + text-decoration: none; + } + background-color:transparent; + border-color:#09525b; + border-width:0; + } + } + + + .chat-list-scroller { + position: relative; + display: block; + overflow: auto; + margin: 0px 15px; + /*height: 210px;*/ + height: 73%; + } + .chart-text-section { + + } + + .btn-send-chat-message { + float:right; + margin: 3px 0 0; + } + + #new-chat-message { + width: calc(100% - 70px); + height: 20px; + } + + .chat-status { + line-height: 20px; + text-align: center; + padding: 10px; + } + + .chat-message { + margin:5px 0; + + .chat-message-sender { + font-weight:bold; + margin-right:10px; + color: #ED3618; + + &:after { + content:':' + } + } + + .chat-message-text { + line-height:18px; + white-space:pre-line; + color: #D5E2E4; + } + + .chat-message-timestamp { + margin-top:4px; + color:#AAA; + display:block; + font-size:12px; + } + } + + .chat-sender { + width: 100%; + position: absolute; + bottom: 7px; + padding: 0px 7px; + /* height: 20%; */ + /* min-height: 69px; */ + box-sizing: border-box; + } + +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/sidebar.css.scss b/web/app/assets/stylesheets/client/sidebar.css.scss index 59a3bdfb5..58c058f8f 100644 --- a/web/app/assets/stylesheets/client/sidebar.css.scss +++ b/web/app/assets/stylesheets/client/sidebar.css.scss @@ -180,69 +180,6 @@ position: relative; } - .chat-list-scroller { - position: relative; - display: block; - overflow: auto; - margin: 0px 15px; - /*height: 210px;*/ - height: 73%; - } - .chart-text-section { - - } - - .btn-send-chat-message { - margin-top: 5px; - margin-right: 30px; - } - - #new-chat-message { - width: 90%; - height: 40px; - } - - .chat-status { - line-height: 20px; - text-align: center; - padding: 10px; - } - - .chat-message { - margin:5px 0; - - .chat-message-sender { - font-weight:bold; - margin-right:10px; - color: #ED3618; - - &:after { - content:':' - } - } - - .chat-message-text { - line-height:18px; - white-space:pre-line; - color: #D5E2E4; - } - - .chat-message-timestamp { - margin-top:4px; - color:#AAA; - display:block; - font-size:12px; - } - } - - .chat-sender { - width: 100%; - position: absolute; - bottom: 10px; - padding: 0px 15px; - height: 20%; - min-height: 69px; - } em { color:#9DB8AF diff --git a/web/app/controllers/api_chats_controller.rb b/web/app/controllers/api_chats_controller.rb index 70988b845..26bd1bac9 100644 --- a/web/app/controllers/api_chats_controller.rb +++ b/web/app/controllers/api_chats_controller.rb @@ -7,11 +7,11 @@ class ApiChatsController < ApiController def create @chat_msg = ChatMessage.new @chat_msg.user_id = current_user.id - @chat_msg.music_session_id = @music_session.id + @chat_msg.music_session_id = @music_session.id if @music_session @chat_msg.message = params[:message] if @chat_msg.save - ChatMessage.send_chat_msg @music_session, @chat_msg, current_user, params[:client_id] + ChatMessage.send_chat_msg @music_session, @chat_msg, current_user, params[:client_id], params[:channel] end respond_with_model(@chat_msg) @@ -25,14 +25,17 @@ class ApiChatsController < ApiController end def check_session - @music_session = ActiveMusicSession.find(params[:music_session]) - if @music_session.nil? - raise ArgumentError, 'specified session not found' + if params.has_key?(:music_session) + @music_session = ActiveMusicSession.find(params[:music_session]) + if @music_session.nil? + raise ArgumentError, 'specified session not found' + end + + unless @music_session.access? current_user + raise JamPermissionError, 'not allowed to join the specified session' + end end - unless @music_session.access? current_user - raise JamPermissionError, 'not allowed to join the specified session' - end end end \ No newline at end of file diff --git a/web/app/views/api_chats/show.rabl b/web/app/views/api_chats/show.rabl index d6ff4caa3..828e09f21 100644 --- a/web/app/views/api_chats/show.rabl +++ b/web/app/views/api_chats/show.rabl @@ -1,6 +1,6 @@ object @chat -attributes :message, :user_id, :session_id, :created_at +attributes :id, :message, :user_id, :session_id, :created_at, :channel node :user do |c| user_data = {} diff --git a/web/app/views/clients/_sidebar.html.erb b/web/app/views/clients/_sidebar.html.erb index b8d44dc68..8c8efaa7f 100644 --- a/web/app/views/clients/_sidebar.html.erb +++ b/web/app/views/clients/_sidebar.html.erb @@ -101,7 +101,9 @@

chat

-
+ <%= react_component 'ChatWindow', {} %> + +