diff --git a/admin/app/admin/chat_messages.rb b/admin/app/admin/chat_messages.rb new file mode 100644 index 000000000..29dfaed52 --- /dev/null +++ b/admin/app/admin/chat_messages.rb @@ -0,0 +1,23 @@ +ActiveAdmin.register JamRuby::ChatMessage, :as => 'ChatMessage' do + # Note: a lame thing is it's not obvious how to make it search on email instead of user_id. + filter :music_session_id + filter :user_id + + menu :parent => 'Misc' + + config.per_page = 200 + + config.sort_order = 'created_at DESC' + + index do + column 'User' do |oo| link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) end + column "Timestamp" do |post| + (post.created_at).strftime('%b %d %Y, %H:%M') + end + column "Message" do |post| + post.message + end + actions + + end +end 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..ea0cbba75 --- /dev/null +++ b/db/up/chat_channel.sql @@ -0,0 +1,4 @@ +ALTER TABLE chat_messages ADD COLUMN channel VARCHAR(128) NOT NULL DEFAULT 'session'; +CREATE INDEX chat_messages_idx_channels ON chat_messages(channel); +CREATE INDEX chat_messages_idx_created_at ON chat_messages(created_at); +CREATE INDEX chat_messages_idx_music_session_id ON chat_messages(music_session_id); \ 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..8e650bad7 100644 --- a/ruby/lib/jam_ruby/models/chat_message.rb +++ b/ruby/lib/jam_ruby/models/chat_message.rb @@ -29,10 +29,14 @@ module JamRuby start = params[:start].presence start = start.to_i || 0 - music_session_id = params[:music_session] + query = ChatMessage.where('channel = ?', params[:channel]) - query = ChatMessage.where('music_session_id = ?', music_session_id) - .offset(start).limit(limit) + if params.has_key? (:music_session) + music_session_id = params[:music_session] + query = ChatMessage.where('music_session_id = ?', music_session_id) + end + + query = query.offset(start).limit(limit).order('created_at DESC') if query.length == 0 [query, nil] @@ -43,21 +47,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/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index b8e99e6cd..c99b36bcd 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1230,7 +1230,11 @@ module JamRuby user.errors.add(:email, "is not real") end elsif result == "risky" || result == "unknown" - user.email_needs_verification = true + if response.body["disposable"] + user.errors.add(:email, "is disposable address") + else + user.email_needs_verification = true + end end else user.email_needs_verification = false 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/JamServer.js b/web/app/assets/javascripts/JamServer.js index 0f6c48cbe..139f86a89 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -295,11 +295,15 @@ function markAway() { logger.debug("sleep again!") active = false; + context.UserActivityActions.setActive(active) + var userStatus = msg_factory.userStatus(false, null); + server.send(userStatus); } function activityCheck() { var timeoutTime = 300000; // 5 * 1000 * 60 , 5 minutes active = true; + context.UserActivityActions.setActive(active) activityTimeout = setTimeout(markAway, timeoutTime); $(document).ready(function() { $('body').bind('mousedown keydown touchstart focus', function(event) { @@ -316,6 +320,7 @@ } } active = true; + context.UserActivityActions.setActive(active) activityTimeout = setTimeout(markAway, timeoutTime); }); }); diff --git a/web/app/assets/javascripts/chatPanel.js b/web/app/assets/javascripts/chatPanel.js index 45a13014b..32aaf043c 100644 --- a/web/app/assets/javascripts/chatPanel.js +++ b/web/app/assets/javascripts/chatPanel.js @@ -1,8 +1,8 @@ -(function(context,$) { +(function (context, $) { "use strict"; context.JK = context.JK || {}; - context.JK.ChatPanel = function(app) { + context.JK.ChatPanel = function (app) { var logger = context.JK.logger; var rest = context.JK.Rest(); var $panel = null; @@ -18,7 +18,7 @@ var $errorMsg = null; var sendingMessage = false; var showing = false; - var fullyInitialized = false; + var fullyInitialized = true; var renderQueue = []; var sidebar = null; var user = null; @@ -27,11 +27,11 @@ var next = null; function reset() { - fullyInitialized = false; + fullyInitialized = true; renderQueue = []; sendingMessage = false; - $chatMessages.empty(); - $textBox.val(''); + //$chatMessages.empty(); + //$textBox.val(''); } function buildMessage() { @@ -45,28 +45,28 @@ } function sendMessage() { - if(!context.JK.JamServer.connected) { + if (!context.JK.JamServer.connected) { return false; } var msg = $textBox.val(); - if(!msg || msg == '') { + if (!msg || msg == '') { // don't bother the server with empty messages return false; } - if(!sendingMessage) { + if (!sendingMessage) { sendingMessage = true; rest.createChatMessage(buildMessage()) - .done(function() { + .done(function () { $textBox.val(''); renderMessage(msg, user.id, user.name, new Date().toISOString(), true); }) - .fail(function(jqXHR) { + .fail(function (jqXHR) { app.notifyServerError(jqXHR, 'Unable to Send Chat Message'); }) - .always(function() { + .always(function () { sendingMessage = false; }) @@ -84,9 +84,9 @@ sender: senderId == user.id ? 'me' : senderName, sent: sent }; - var txt = $(context._.template($('#template-chat-message').html(), options, { variable: 'data' })); + var txt = $(context._.template($('#template-chat-message').html(), options, {variable: 'data'})); txt.find('.timeago').timeago(); - if(append) { + if (append) { $chatMessages.append(txt); scrollToBottom(); } @@ -96,7 +96,7 @@ } function drainQueue() { - context._.each(renderQueue, function(msg) { + context._.each(renderQueue, function (msg) { renderMessage(msg.message, msg.user_id, msg.user_name, msg.sent, true); }); renderQueue = []; @@ -128,51 +128,46 @@ evt.preventDefault(); pasteIntoInput(this, "\n"); } - else if(evt.keyCode == 13 && !evt.shiftKey){ + else if (evt.keyCode == 13 && !evt.shiftKey) { sendMessage(); return false; } } function events(bind) { - if (bind) { + /**if (bind) { $form.submit(sendMessage); $textBox.keydown(handleEnter); $sendChatMessageBtn.click(sendMessage); } - else { + else { $form.submit(null); $textBox.keydown(null); $sendChatMessageBtn.click(null); - } + }*/ - registerChatMessage(bind); } // called from sidebar when messages come in function chatMessageReceived(payload) { - if(fullyInitialized) { + 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}); - context.jamClient.UserAttention(true); } } - else { - renderQueue.push({message: payload.msg, user_id: payload.sender_id, user_name: payload.sender_name, sent: payload.created_at}); - } } function registerChatMessage(bind) { if (bind && bind == true) { - context.JK.JamServer.registerMessageCallback(context.JK.MessageType.CHAT_MESSAGE, function(header, payload) { + 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); }); @@ -185,45 +180,38 @@ function opened() { lowlightCount(); setCount(0); - drainQueue(); } + function fullyOpened() { + context.ChatActions.fullyOpened() + } + function sessionStarted(e, data) { $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); $panel.find('.btn-next-pager').attr('href', '/api/sessions/' + $sessionId + '/chats?page=1'); reset(); - // load previous chat messages - rest.getChatMessages(buildQuery()) - .done(function (response) { - handleChatResponse(response); - - scrollToBottom(true); - showing = true; - fullyInitialized = true; - drainQueue(); - }) - .fail(function (jqXHR) { - app.notifyServerError(jqXHR, 'Unable to Load Session Conversations') - }); + context.ChatActions.sessionStarted($sessionId); + showing = true + fullyInitialized = true; + drainQueue(); events(true); } function sessionStopped(e, data) { // open chat panel - $chatSender.hide(); - $chatMessagesScroller.hide(); - $errorMsg.show(); + //$chatSender.hide(); + //$chatMessagesScroller.hide(); + //$errorMsg.show(); reset(); events(false); @@ -251,9 +239,9 @@ } function buildQuery() { - var query = {type: 'CHAT_MESSAGE', music_session: $sessionId, limit:LIMIT, page:currentPage}; + var query = {type: 'CHAT_MESSAGE', music_session: $sessionId, limit: LIMIT, page: currentPage}; - if(next) { + if (next) { query.start = next; } @@ -271,11 +259,11 @@ renderChats(response.chats); - if(response.next == null) { + if (response.next == null) { // if we less results than asked for, end searching $chatMessagesScroller.infinitescroll('pause'); - if(currentPage > 0) { + if (currentPage > 0) { // there are bugs with infinitescroll not removing the 'loading'. // it's most noticeable at the end of the list, so whack all such entries $('.infinite-scroll-loader').remove(); @@ -303,10 +291,10 @@ msg: $('
Loading ...
'), img: '/assets/shared/spinner-32.gif' }, - path: function(page) { + path: function (page) { return '/api/sessions/' + $sessionId + '/chats?' + $.param(buildQuery()); } - },function(json, opts) { + }, function (json, opts) { handleChatResponse(json); }); $chatMessagesScroller.infinitescroll('resume'); @@ -318,28 +306,26 @@ $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'); + $errorMsg = $panel.find('.chat-status'); - $errorMsg.show(); - $chatSender.hide(); - $chatMessagesScroller.hide(); + $panel.on('open', opened); + $panel.on('fullyOpen', fullyOpened); app.user() .done(function (userDetail) { user = userDetail; + + registerChatMessage(true); + }); } this.initialize = initialize; this.sessionStarted = sessionStarted; - this.sessionStopped = sessionStopped; + this.sessionStopped = sessionStopped; this.registerChatMessage = registerChatMessage; }; return this; -})(window,jQuery); \ No newline at end of file +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 953b9e0a7..b45cf5325 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1711,11 +1711,9 @@ } function getChatMessages(options) { - var musciSessionId = options["music_session"]; - delete options["music_session"]; return $.ajax({ type: "GET", - url: '/api/sessions/' + musciSessionId + '/chats?' + $.param(options), + url: '/api/chat?' + $.param(options), dataType: "json", contentType: 'application/json' }) diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index 3ea51c8be..19a25056c 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -290,7 +290,9 @@ $('[layout-panel="contents"]').css({"height": "1px"}); $expandedPanelContents.show(); $expandedPanel.triggerHandler('open') - $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration); + $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration, function() { + $expandedPanel.triggerHandler('fullyOpen') + }); } function layoutHeader(screenWidth, screenHeight) { diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index 8c4e9b8db..aa06843e3 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -3,18 +3,20 @@ //= require_directory ./react-components/helpers //= require_directory ./react-components/actions //= require ./react-components/stores/AppStore +//= require ./react-components/stores/UserStore +//= require ./react-components/stores/UserActivityStore //= require ./react-components/stores/InstrumentStore //= require ./react-components/stores/LanguageStore //= require ./react-components/stores/GenreStore //= require ./react-components/stores/SubjectStore //= require ./react-components/stores/ProfileStore //= require ./react-components/stores/PlatformStore -//= require ./react-components/stores/ChatStore //= require ./react-components/stores/BrowserMediaStore //= require ./react-components/stores/RecordingStore //= require ./react-components/stores/VideoStore //= require ./react-components/stores/SessionStore //= require ./react-components/stores/SessionStatsStore +//= require ./react-components/stores/ChatStore //= require ./react-components/stores/MixerStore //= require ./react-components/stores/ConfigureTracksStore //= require ./react-components/stores/JamTrackStore 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..3aa8172dc 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,146 @@ 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() + 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) -> + 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() + + 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) + + 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() }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee index 0a0b8922f..c5d09b153 100644 --- a/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/JamTrackLandingScreen.js.jsx.coffee @@ -37,7 +37,7 @@ rest = context.JK.Rest() playJamTracks = [] for jamTrack in @state.purchasedJamTracks - playJamTracks.push ` + playJamTracks.push ` {jamTrack.name} by {jamTrack.original_artist}` if @state.purchasedJamTracks.length < 5 diff --git a/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee b/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee index 0177cb7ed..e6f720de5 100644 --- a/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/TeacherExperienceEditableList.js.jsx.coffee @@ -78,7 +78,7 @@ logger = context.JK.logger errors = [] if this.state.errors? for error in this.state.errors - errors.push(`{error}`) + errors.push(`{error}`) `
diff --git a/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee b/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee index 483eb347e..9b543533e 100644 --- a/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/YearSelect.js.jsx.coffee @@ -10,7 +10,7 @@ logger = context.JK.logger now = new Date().getFullYear() for yr in [now..1916] - options.push `` + options.push `` `