/* global Metamaps, $, Howl */ /* * Dependencies: * Metamaps.Erb */ import Backbone from 'backbone' import Autolinker from 'autolinker' import _ from 'lodash' import underscore from 'underscore' // TODO is this line good or bad // Backbone.$ = window.$ const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false }); var Private = { messageHTML: "
" + "
" + "
{{ message }}
" + "
{{ timestamp }}
" + "
" + "
", participantHTML: "
" + "
" + "
{{ username }} {{ selfName }}
" + "" + "" + "
" + "
" + "
", templates: function() { underscore.templateSettings = { interpolate: /\{\{(.+?)\}\}/g }; this.messageTemplate = underscore.template(Private.messageHTML); this.participantTemplate = underscore.template(Private.participantHTML); }, createElements: function() { this.$unread = $('
'); this.$button = $('
Chat
'); this.$messageInput = $(''); this.$juntoHeader = $('
PARTICIPANTS
'); this.$videoToggle = $('
'); this.$cursorToggle = $('
'); this.$participants = $('
'); this.$conversationInProgress = $('
LIVE LEAVEJOIN
'); this.$chatHeader = $('
CHAT
'); this.$soundToggle = $('
'); this.$messages = $('
'); this.$container = $('
'); }, attachElements: function() { this.$button.append(this.$unread); this.$juntoHeader.append(this.$videoToggle); this.$juntoHeader.append(this.$cursorToggle); this.$chatHeader.append(this.$soundToggle); this.$participants.append(this.$conversationInProgress); this.$container.append(this.$juntoHeader); this.$container.append(this.$participants); this.$container.append(this.$chatHeader); this.$container.append(this.$button); this.$container.append(this.$messages); this.$container.append(this.$messageInput); }, addEventListeners: function() { var self = this; this.participants.on('add', function (participant) { Private.addParticipant.call(self, participant); }); this.participants.on('remove', function (participant) { Private.removeParticipant.call(self, participant); }); this.$button.on('click', function () { Handlers.buttonClick.call(self); }); this.$videoToggle.on('click', function () { Handlers.videoToggleClick.call(self); }); this.$cursorToggle.on('click', function () { Handlers.cursorToggleClick.call(self); }); this.$soundToggle.on('click', function () { Handlers.soundToggleClick.call(self); }); this.$messageInput.on('keyup', function (event) { Handlers.keyUp.call(self, event); }); this.$messageInput.on('focus', function () { Handlers.inputFocus.call(self); }); this.$messageInput.on('blur', function () { Handlers.inputBlur.call(self); }); }, initializeSounds: function() { this.sound = new Howl({ urls: [Metamaps.Erb['sounds/MM_sounds.mp3'], Metamaps.Erb['sounds/MM_sounds.ogg']], sprite: { joinmap: [0, 561], leavemap: [1000, 592], receivechat: [2000, 318], sendchat: [3000, 296], sessioninvite: [4000, 5393, true] } }); }, incrementUnread: function() { this.unreadMessages++; this.$unread.html(this.unreadMessages); this.$unread.show(); }, addMessage: function(message, isInitial, wasMe) { if (!this.isOpen && !isInitial) Private.incrementUnread.call(this); function addZero(i) { if (i < 10) { i = "0" + i; } return i; } var m = _.clone(message.attributes); var today = new Date(); m.timestamp = new Date(m.created_at); var date = (m.timestamp.getMonth() + 1) + '/' + m.timestamp.getDate(); date += " " + addZero(m.timestamp.getHours()) + ":" + addZero(m.timestamp.getMinutes()); m.timestamp = date; m.image = m.user_image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; // TODO: remove m.message = linker.link(m.message); var $html = $(this.messageTemplate(m)); this.$messages.append($html); if (!isInitial) this.scrollMessages(200); if (!wasMe && !isInitial && this.alertSound) this.sound.play('receivechat'); }, initialMessages: function() { var messages = this.messages.models; for (var i = 0; i < messages.length; i++) { Private.addMessage.call(this, messages[i], true); } }, handleInputMessage: function() { var message = { message: this.$messageInput.val(), }; this.$messageInput.val(''); $(document).trigger(ChatView.events.message + '-' + this.room, [message]); }, addParticipant: function(participant) { var p = _.clone(participant.attributes); if (p.self) { p.selfClass = 'is-self'; p.selfName = '(me)'; } else { p.selfClass = ''; p.selfName = ''; } var html = this.participantTemplate(p); this.$participants.append(html); }, removeParticipant: function(participant) { this.$container.find('.participant-' + participant.get('id')).remove(); } }; var Handlers = { buttonClick: function() { if (this.isOpen) this.close(); else if (!this.isOpen) this.open(); }, videoToggleClick: function() { this.$videoToggle.toggleClass('active'); this.videosShowing = !this.videosShowing; $(document).trigger(this.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff); }, cursorToggleClick: function() { this.$cursorToggle.toggleClass('active'); this.cursorsShowing = !this.cursorsShowing; $(document).trigger(this.cursorsShowing ? ChatView.events.cursorsOn : ChatView.events.cursorsOff); }, soundToggleClick: function() { this.alertSound = !this.alertSound; this.$soundToggle.toggleClass('active'); }, keyUp: function(event) { switch(event.which) { case 13: // enter Private.handleInputMessage.call(this); break; } }, inputFocus: function() { $(document).trigger(ChatView.events.inputFocus); }, inputBlur: function() { $(document).trigger(ChatView.events.inputBlur); } }; const ChatView = function(messages, mapper, room) { var self = this; this.room = room; this.mapper = mapper; this.messages = messages; // backbone collection this.isOpen = false; this.alertSound = true; // whether to play sounds on arrival of new messages or not this.cursorsShowing = true; this.videosShowing = true; this.unreadMessages = 0; this.participants = new Backbone.Collection(); Private.templates.call(this); Private.createElements.call(this); Private.attachElements.call(this); Private.addEventListeners.call(this); Private.initialMessages.call(this); Private.initializeSounds.call(this); this.$container.css({ right: '-300px' }); }; ChatView.prototype.conversationInProgress = function (participating) { this.$conversationInProgress.show(); this.$participants.addClass('is-live'); if (participating) this.$participants.addClass('is-participating'); this.$button.addClass('active'); // hide invite to call buttons } ChatView.prototype.conversationEnded = function () { this.$conversationInProgress.hide(); this.$participants.removeClass('is-live'); this.$participants.removeClass('is-participating'); this.$button.removeClass('active'); this.$participants.find('.participant').removeClass('active'); this.$participants.find('.participant').removeClass('pending'); } ChatView.prototype.leaveConversation = function () { this.$participants.removeClass('is-participating'); } ChatView.prototype.mapperJoinedCall = function (id) { this.$participants.find('.participant-' + id).addClass('active'); } ChatView.prototype.mapperLeftCall = function (id) { this.$participants.find('.participant-' + id).removeClass('active'); } ChatView.prototype.invitationPending = function (id) { this.$participants.find('.participant-' + id).addClass('pending'); } ChatView.prototype.invitationAnswered = function (id) { this.$participants.find('.participant-' + id).removeClass('pending'); } ChatView.prototype.addParticipant = function (participant) { this.participants.add(participant); } ChatView.prototype.removeParticipant = function (username) { var p = this.participants.find(function (p) { return p.get('username') === username; }); if (p) { this.participants.remove(p); } } ChatView.prototype.removeParticipants = function () { this.participants.remove(this.participants.models); } ChatView.prototype.open = function () { this.$container.css({ right: '0' }); this.$messageInput.focus(); this.isOpen = true; this.unreadMessages = 0; this.$unread.hide(); this.scrollMessages(0); $(document).trigger(ChatView.events.openTray); } ChatView.prototype.addMessage = function(message, isInitial, wasMe) { this.messages.add(message); Private.addMessage.call(this, message, isInitial, wasMe); } ChatView.prototype.scrollMessages = function(duration) { duration = duration || 0; this.$messages.animate({ scrollTop: this.$messages[0].scrollHeight }, duration); } ChatView.prototype.clearMessages = function () { this.unreadMessages = 0; this.$unread.hide(); this.$messages.empty(); } ChatView.prototype.close = function () { this.$container.css({ right: '-300px' }); this.$messageInput.blur(); this.isOpen = false; $(document).trigger(ChatView.events.closeTray); } ChatView.prototype.remove = function () { this.$button.off(); this.$container.remove(); } /** * @class * @static */ ChatView.events = { message: 'ChatView:message', openTray: 'ChatView:openTray', closeTray: 'ChatView:closeTray', inputFocus: 'ChatView:inputFocus', inputBlur: 'ChatView:inputBlur', cursorsOff: 'ChatView:cursorsOff', cursorsOn: 'ChatView:cursorsOn', videosOff: 'ChatView:videosOff', videosOn: 'ChatView:videosOn' }; export default ChatView