From 30894a313fbde92faabcc5ed37124cf995a34967 Mon Sep 17 00:00:00 2001 From: Devin Howard Date: Fri, 23 Sep 2016 00:07:30 +0800 Subject: [PATCH] move views to their own frontend folder --- .../javascripts/src/Metamaps.Erb.js.erb | 2 + .../javascripts/src/views/chatView.js.erb | 343 ------------------ app/assets/javascripts/src/views/room.js | 195 ---------- app/assets/javascripts/src/views/videoView.js | 207 ----------- frontend/src/Metamaps/Realtime.js | 18 +- frontend/src/Metamaps/Router.js | 10 +- frontend/src/Metamaps/Views.js | 91 ----- frontend/src/Metamaps/Views/ChatView.js | 337 +++++++++++++++++ frontend/src/Metamaps/Views/ExploreMaps.js | 86 +++++ frontend/src/Metamaps/Views/Room.js | 198 ++++++++++ frontend/src/Metamaps/Views/VideoView.js | 202 +++++++++++ frontend/src/Metamaps/Views/index.js | 6 + frontend/src/Metamaps/index.js | 12 +- 13 files changed, 851 insertions(+), 856 deletions(-) delete mode 100644 app/assets/javascripts/src/views/chatView.js.erb delete mode 100644 app/assets/javascripts/src/views/room.js delete mode 100644 app/assets/javascripts/src/views/videoView.js delete mode 100644 frontend/src/Metamaps/Views.js create mode 100644 frontend/src/Metamaps/Views/ChatView.js create mode 100644 frontend/src/Metamaps/Views/ExploreMaps.js create mode 100644 frontend/src/Metamaps/Views/Room.js create mode 100644 frontend/src/Metamaps/Views/VideoView.js create mode 100644 frontend/src/Metamaps/Views/index.js diff --git a/app/assets/javascripts/src/Metamaps.Erb.js.erb b/app/assets/javascripts/src/Metamaps.Erb.js.erb index 90eba5e5..60b64e46 100644 --- a/app/assets/javascripts/src/Metamaps.Erb.js.erb +++ b/app/assets/javascripts/src/Metamaps.Erb.js.erb @@ -14,5 +14,7 @@ Metamaps.Erb['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>' Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>' Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>' Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>' +Metamaps.Erb['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>' +Metamaps.Erb['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>' Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %> Metamaps.VERSION = '<%= METAMAPS_VERSION %>' diff --git a/app/assets/javascripts/src/views/chatView.js.erb b/app/assets/javascripts/src/views/chatView.js.erb deleted file mode 100644 index 7a1e7f8e..00000000 --- a/app/assets/javascripts/src/views/chatView.js.erb +++ /dev/null @@ -1,343 +0,0 @@ -Metamaps.Views = Metamaps.Views || {}; - -Metamaps.Views.chatView = (function () { - var - chatView, - linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false }); - - var Private = { - messageHTML: "
" + - "
" + - "
{{ message }}
" + - "
{{ timestamp }}
" + - "
" + - "
", - participantHTML: "
" + - "
" + - "
{{ username }} {{ selfName }}
" + - "" + - "" + - "
" + - "
" + - "
", - templates: function() { - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; - this.messageTemplate = _.template(Private.messageHTML); - - this.participantTemplate = _.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: ["<%= asset_path 'sounds/MM_sounds.mp3' %>", "<%= asset_path '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); - } - }; - - 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' - }; - - return chatView; - -})(); diff --git a/app/assets/javascripts/src/views/room.js b/app/assets/javascripts/src/views/room.js deleted file mode 100644 index 4595c3cb..00000000 --- a/app/assets/javascripts/src/views/room.js +++ /dev/null @@ -1,195 +0,0 @@ -Metamaps.Views = Metamaps.Views || {}; - -Metamaps.Views.room = (function () { - - var ChatView = Metamaps.Views.chatView; - var VideoView = Metamaps.Views.videoView; - - var room = function(opts) { - var self = this; - - this.isActiveRoom = false; - this.socket = opts.socket; - this.webrtc = opts.webrtc; - //this.roomRef = opts.firebase; - this.room = opts.room; - this.config = opts.config; - this.peopleCount = 0; - - this.$myVideo = opts.$video; - this.myVideo = opts.myVideoView; - - this.messages = new Backbone.Collection(); - this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image }); - this.chat = new ChatView(this.messages, this.currentMapper, this.room); - - this.videos = {}; - - this.init(); - }; - - room.prototype.join = function(cb) { - this.isActiveRoom = true; - this.webrtc.joinRoom(this.room, cb); - this.chat.conversationInProgress(true); // true indicates participation - } - - room.prototype.conversationInProgress = function() { - this.chat.conversationInProgress(false); // false indicates not participating - } - - room.prototype.conversationEnding = function() { - this.chat.conversationEnded(); - } - - room.prototype.leaveVideoOnly = function() { - this.chat.leaveConversation(); // the conversation will carry on without you - for (var id in this.videos) { - this.removeVideo(id); - } - this.isActiveRoom = false; - this.webrtc.leaveRoom(); - } - - room.prototype.leave = function() { - for (var id in this.videos) { - this.removeVideo(id); - } - this.isActiveRoom = false; - this.webrtc.leaveRoom(); - this.chat.conversationEnded(); - this.chat.removeParticipants(); - this.chat.clearMessages(); - this.messages.reset(); - } - - room.prototype.setPeopleCount = function(count) { - this.peopleCount = count; - } - - room.prototype.init = function () { - var self = this; - - $(document).on(VideoView.events.audioControlClick, function (event, videoView) { - if (!videoView.audioStatus) self.webrtc.mute(); - else if (videoView.audioStatus) self.webrtc.unmute(); - }); - $(document).on(VideoView.events.videoControlClick, function (event, videoView) { - if (!videoView.videoStatus) self.webrtc.pauseVideo(); - else if (videoView.videoStatus) self.webrtc.resumeVideo(); - }); - - this.webrtc.webrtc.off('peerStreamAdded'); - this.webrtc.webrtc.off('peerStreamRemoved'); - this.webrtc.on('peerStreamAdded', function (peer) { - var mapper = Metamaps.Realtime.mappersOnMap[peer.nick]; - peer.avatar = mapper.image; - peer.username = mapper.name; - if (self.isActiveRoom) { - self.addVideo(peer); - } - }); - - this.webrtc.on('peerStreamRemoved', function (peer) { - if (self.isActiveRoom) { - self.removeVideo(peer); - } - }); - - this.webrtc.on('mute', function (data) { - var v = self.videos[data.id]; - if (!v) return; - - if (data.name === 'audio') { - v.audioStatus = false; - } - else if (data.name === 'video') { - v.videoStatus = false; - v.$avatar.show(); - } - if (!v.audioStatus && !v.videoStatus) v.$container.hide(); - }); - this.webrtc.on('unmute', function (data) { - var v = self.videos[data.id]; - if (!v) return; - - if (data.name === 'audio') { - v.audioStatus = true; - } - else if (data.name === 'video') { - v.videoStatus = true; - v.$avatar.hide(); - } - v.$container.show(); - }); - - var sendChatMessage = function (event, data) { - self.sendChatMessage(data); - }; - $(document).on(ChatView.events.message + '-' + this.room, sendChatMessage); - } - - room.prototype.videoAdded = function (callback) { - this._videoAdded = callback; - } - - room.prototype.addVideo = function (peer) { - var - id = this.webrtc.getDomId(peer), - video = attachMediaStream(peer.stream); - - var - v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar, username: peer.username }); - - this.videos[peer.id] = v; - if (this._videoAdded) this._videoAdded(v, peer.nick); - } - - room.prototype.removeVideo = function (peer) { - var id = typeof peer == 'string' ? peer : peer.id; - if (this.videos[id]) { - this.videos[id].remove(); - delete this.videos[id]; - } - } - - room.prototype.sendChatMessage = function (data) { - var self = this; - //this.roomRef.child('messages').push(data); - if (self.chat.alertSound) self.chat.sound.play('sendchat'); - var m = new Metamaps.Backbone.Message({ - message: data.message, - resource_id: Metamaps.Active.Map.id, - resource_type: "Map" - }); - m.save(null, { - success: function (model, response) { - self.addMessages(new Metamaps.Backbone.MessageCollection(model), false, true); - $(document).trigger(room.events.newMessage, [model]); - }, - error: function (model, response) { - console.log('error!', response); - } - }); - } - - // they should be instantiated as backbone models before they get - // passed to this function - room.prototype.addMessages = function (messages, isInitial, wasMe) { - var self = this; - - messages.models.forEach(function (message) { - self.chat.addMessage(message, isInitial, wasMe); - }); - } - - /** - * @class - * @static - */ - room.events = { - newMessage: "Room:newMessage" - }; - - return room; -})(); diff --git a/app/assets/javascripts/src/views/videoView.js b/app/assets/javascripts/src/views/videoView.js deleted file mode 100644 index b9d39c06..00000000 --- a/app/assets/javascripts/src/views/videoView.js +++ /dev/null @@ -1,207 +0,0 @@ -Metamaps.Views = Metamaps.Views || {}; - -Metamaps.Views.videoView = (function () { - - var videoView; - - var Private = { - addControls: function() { - var self = this; - - this.$audioControl = $('
'); - this.$videoControl = $('
'); - - this.$audioControl.on('click', function () { - Handlers.audioControlClick.call(self); - }); - - this.$videoControl.on('click', function () { - Handlers.videoControlClick.call(self); - }); - - this.$container.append(this.$audioControl); - this.$container.append(this.$videoControl); - }, - cancelClick: function() { - this.mouseIsDown = false; - - if (this.hasMoved) { - - } - - $(document).trigger(videoView.events.dragEnd); - } - }; - - var Handlers = { - mousedown: function(event) { - this.mouseIsDown = true; - this.hasMoved = false; - this.mouseMoveStart = { - x: event.pageX, - y: event.pageY - }; - this.posStart = { - x: parseInt(this.$container.css('left'), '10'), - y: parseInt(this.$container.css('top'), '10') - } - - $(document).trigger(videoView.events.mousedown); - }, - mouseup: function(event) { - $(document).trigger(videoView.events.mouseup, [this]); - - var storedTime = this.lastClick; - var now = Date.now(); - this.lastClick = now; - - if (now - storedTime < this.config.DOUBLE_CLICK_TOLERANCE) { - $(document).trigger(videoView.events.doubleClick, [this]); - } - }, - mousemove: function(event) { - var - diffX, - diffY, - newX, - newY; - - if (this.$parent && this.mouseIsDown) { - this.manuallyPositioned = true; - this.hasMoved = true; - diffX = event.pageX - this.mouseMoveStart.x; - diffY = this.mouseMoveStart.y - event.pageY; - newX = this.posStart.x + diffX; - newY = this.posStart.y - diffY; - this.$container.css({ - top: newY, - left: newX - }); - } - }, - audioControlClick: function() { - if (this.audioStatus) { - this.audioOff(); - } else { - this.audioOn(); - } - $(document).trigger(videoView.events.audioControlClick, [this]); - }, - videoControlClick: function() { - if (this.videoStatus) { - this.videoOff(); - } else { - this.videoOn(); - } - $(document).trigger(videoView.events.videoControlClick, [this]); - }, - }; - - var videoView = function(video, $parent, id, isMyself, config) { - var self = this; - - this.$parent = $parent; // mapView - - this.video = video; - this.id = id; - - this.config = config; - - this.mouseIsDown = false; - this.mouseDownOffset = { x: 0, y: 0 }; - this.lastClick = null; - this.hasMoved = false; - - this.audioStatus = true; - this.videoStatus = true; - - this.$container = $('
'); - this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : '')); - this.$container.attr('id', 'container_' + id); - - - var $vidContainer = $('
'); - $vidContainer.addClass('video-cutoff'); - $vidContainer.append(this.video); - - this.avatar = config.avatar; - this.$avatar = $(''); - $vidContainer.append(this.$avatar); - - this.$container.append($vidContainer); - - this.$container.on('mousedown', function (event) { - Handlers.mousedown.call(self, event); - }); - - if (isMyself) { - Private.addControls.call(this); - } - - // suppress contextmenu - this.video.oncontextmenu = function () { return false; }; - - if (this.$parent) this.setParent(this.$parent); - }; - - videoView.prototype.setParent = function($parent) { - var self = this; - this.$parent = $parent; - this.$parent.off('.video' + this.id); - this.$parent.on('mouseup.video' + this.id, function (event) { - Handlers.mouseup.call(self, event); - Private.cancelClick.call(self); - }); - this.$parent.on('mousemove.video' + this.id, function (event) { - Handlers.mousemove.call(self, event); - }); - } - - videoView.prototype.setAvatar = function (src) { - this.$avatar.attr('src', src); - this.avatar = src; - } - - videoView.prototype.remove = function () { - this.$container.off(); - if (this.$parent) this.$parent.off('.video' + this.id); - this.$container.remove(); - } - - videoView.prototype.videoOff = function () { - this.$videoControl.addClass('active'); - this.$avatar.show(); - this.videoStatus = false; - } - - videoView.prototype.videoOn = function () { - this.$videoControl.removeClass('active'); - this.$avatar.hide(); - this.videoStatus = true; - } - - videoView.prototype.audioOff = function () { - this.$audioControl.addClass('active'); - this.audioStatus = false; - } - - videoView.prototype.audioOn = function () { - this.$audioControl.removeClass('active'); - this.audioStatus = true; - } - - /** - * @class - * @static - */ - videoView.events = { - mousedown: "VideoView:mousedown", - mouseup: "VideoView:mouseup", - doubleClick: "VideoView:doubleClick", - dragEnd: "VideoView:dragEnd", - audioControlClick: "VideoView:audioControlClick", - videoControlClick: "VideoView:videoControlClick", - }; - - return videoView; -})(); diff --git a/frontend/src/Metamaps/Realtime.js b/frontend/src/Metamaps/Realtime.js index 80143f25..355e73f8 100644 --- a/frontend/src/Metamaps/Realtime.js +++ b/frontend/src/Metamaps/Realtime.js @@ -77,13 +77,13 @@ const Realtime = { var $video = $('').attr('id', self.videoId) self.localVideo = { $video: $video, - view: new Views.videoView($video[0], $('body'), 'me', true, { + view: new Views.VideoView($video[0], $('body'), 'me', true, { DOUBLE_CLICK_TOLERANCE: 200, avatar: Active.Mapper ? Active.Mapper.get('image') : '' }) } - self.room = new Views.room({ + self.room = new Views.Room({ webrtc: self.webrtc, socket: self.socket, username: Active.Mapper ? Active.Mapper.get('name') : '', @@ -104,26 +104,26 @@ const Realtime = { addJuntoListeners: function () { var self = Realtime - $(document).on(Views.chatView.events.openTray, function () { + $(document).on(Views.ChatView.events.openTray, function () { $('.main').addClass('compressed') self.chatOpen = true self.positionPeerIcons() }) - $(document).on(Views.chatView.events.closeTray, function () { + $(document).on(Views.ChatView.events.closeTray, function () { $('.main').removeClass('compressed') self.chatOpen = false self.positionPeerIcons() }) - $(document).on(Views.chatView.events.videosOn, function () { + $(document).on(Views.ChatView.events.videosOn, function () { $('#wrapper').removeClass('hideVideos') }) - $(document).on(Views.chatView.events.videosOff, function () { + $(document).on(Views.ChatView.events.videosOff, function () { $('#wrapper').addClass('hideVideos') }) - $(document).on(Views.chatView.events.cursorsOn, function () { + $(document).on(Views.ChatView.events.cursorsOn, function () { $('#wrapper').removeClass('hideCursors') }) - $(document).on(Views.chatView.events.cursorsOff, function () { + $(document).on(Views.ChatView.events.cursorsOff, function () { $('#wrapper').addClass('hideCursors') }) }, @@ -611,7 +611,7 @@ const Realtime = { var sendNewMessage = function (event, data) { self.sendNewMessage(data) } - $(document).on(Views.room.events.newMessage + '.map', sendNewMessage) + $(document).on(Views.Room.events.newMessage + '.map', sendNewMessage) }, attachMapListener: function () { var self = Realtime diff --git a/frontend/src/Metamaps/Router.js b/frontend/src/Metamaps/Router.js index d5c07e12..6760edcc 100644 --- a/frontend/src/Metamaps/Router.js +++ b/frontend/src/Metamaps/Router.js @@ -49,11 +49,11 @@ const _Router = Backbone.Router.extend({ GlobalUI.showDiv('#explore') - Views.exploreMaps.setCollection(Metamaps.Maps.Active) + Views.ExploreMaps.setCollection(Metamaps.Maps.Active) if (Metamaps.Maps.Active.length === 0) { Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render } else { - Views.exploreMaps.render(navigate) + Views.ExploreMaps.render(navigate) } } else { // logged out home page @@ -108,7 +108,7 @@ const _Router = Backbone.Router.extend({ Metamaps.Maps.Mapper.mapperId = id } - Views.exploreMaps.setCollection(Metamaps.Maps[capitalize]) + Views.ExploreMaps.setCollection(Metamaps.Maps[capitalize]) var navigate = function () { var path = '/explore/' + this.currentPage @@ -130,9 +130,9 @@ const _Router = Backbone.Router.extend({ }, 300) // wait 300 milliseconds till the other animations are done to do the fetch } else { if (id) { - Views.exploreMaps.fetchUserThenRender(navigateTimeout) + Views.ExploreMaps.fetchUserThenRender(navigateTimeout) } else { - Views.exploreMaps.render(navigateTimeout) + Views.ExploreMaps.render(navigateTimeout) } } diff --git a/frontend/src/Metamaps/Views.js b/frontend/src/Metamaps/Views.js deleted file mode 100644 index aee0fdf0..00000000 --- a/frontend/src/Metamaps/Views.js +++ /dev/null @@ -1,91 +0,0 @@ -/* global Metamaps, $ */ - -import Active from './Active' -import ReactComponents from './ReactComponents' -import ReactDOM from 'react-dom' // TODO ensure this isn't a double import - -/* - * Metamaps.Views.js.erb - * - * Dependencies: - * - Metamaps.Loading - */ - -const Views = { - exploreMaps: { - setCollection: function (collection) { - var self = Views.exploreMaps - - if (self.collection) { - self.collection.off('add', self.render) - self.collection.off('successOnFetch', self.handleSuccess) - self.collection.off('errorOnFetch', self.handleError) - } - self.collection = collection - self.collection.on('add', self.render) - self.collection.on('successOnFetch', self.handleSuccess) - self.collection.on('errorOnFetch', self.handleError) - }, - render: function (mapperObj, cb) { - var self = Views.exploreMaps - - if (typeof mapperObj === 'function') { - cb = mapperObj - mapperObj = null - } - - var exploreObj = { - currentUser: Active.Mapper, - section: self.collection.id, - displayStyle: 'grid', - maps: self.collection, - moreToLoad: self.collection.page != 'loadedAll', - user: mapperObj, - loadMore: self.loadMore - } - ReactDOM.render( - React.createElement(ReactComponents.Maps, exploreObj), - document.getElementById('explore') - ) - - if (cb) cb() - Metamaps.Loading.hide() - }, - loadMore: function () { - var self = Views.exploreMaps - - if (self.collection.page != "loadedAll") { - self.collection.getMaps() - } - else self.render() - }, - handleSuccess: function (cb) { - var self = Views.exploreMaps - - if (self.collection && self.collection.id === 'mapper') { - self.fetchUserThenRender(cb) - } else { - self.render(cb) - } - }, - handleError: function () { - console.log('error loading maps!') // TODO - }, - fetchUserThenRender: function (cb) { - var self = Views.exploreMaps - - // first load the mapper object and then call the render function - $.ajax({ - url: '/users/' + self.collection.mapperId + '/details.json', - success: function (response) { - self.render(response, cb) - }, - error: function () { - self.render(cb) - } - }) - } - } -} - -export default Views diff --git a/frontend/src/Metamaps/Views/ChatView.js b/frontend/src/Metamaps/Views/ChatView.js new file mode 100644 index 00000000..5d8f5f65 --- /dev/null +++ b/frontend/src/Metamaps/Views/ChatView.js @@ -0,0 +1,337 @@ +/* global Autolinker, $ */ +var linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false }); + +var Private = { + messageHTML: "
" + + "
" + + "
{{ message }}
" + + "
{{ timestamp }}
" + + "
" + + "
", + participantHTML: "
" + + "
" + + "
{{ username }} {{ selfName }}
" + + "" + + "" + + "
" + + "
" + + "
", + templates: function() { + _.templateSettings = { + interpolate: /\{\{(.+?)\}\}/g + }; + this.messageTemplate = _.template(Private.messageHTML); + + this.participantTemplate = _.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 diff --git a/frontend/src/Metamaps/Views/ExploreMaps.js b/frontend/src/Metamaps/Views/ExploreMaps.js new file mode 100644 index 00000000..4ffbf9fb --- /dev/null +++ b/frontend/src/Metamaps/Views/ExploreMaps.js @@ -0,0 +1,86 @@ +/* global Metamaps, $ */ + +import Active from './Active' +import ReactComponents from './ReactComponents' +import ReactDOM from 'react-dom' // TODO ensure this isn't a double import + +/* + * - Metamaps.Loading + */ + +const ExploreMaps = { + setCollection: function (collection) { + var self = ExploreMaps + + if (self.collection) { + self.collection.off('add', self.render) + self.collection.off('successOnFetch', self.handleSuccess) + self.collection.off('errorOnFetch', self.handleError) + } + self.collection = collection + self.collection.on('add', self.render) + self.collection.on('successOnFetch', self.handleSuccess) + self.collection.on('errorOnFetch', self.handleError) + }, + render: function (mapperObj, cb) { + var self = ExploreMaps + + if (typeof mapperObj === 'function') { + cb = mapperObj + mapperObj = null + } + + var exploreObj = { + currentUser: Active.Mapper, + section: self.collection.id, + displayStyle: 'grid', + maps: self.collection, + moreToLoad: self.collection.page != 'loadedAll', + user: mapperObj, + loadMore: self.loadMore + } + ReactDOM.render( + React.createElement(ReactComponents.Maps, exploreObj), + document.getElementById('explore') + ) + + if (cb) cb() + Metamaps.Loading.hide() + }, + loadMore: function () { + var self = ExploreMaps + + if (self.collection.page != "loadedAll") { + self.collection.getMaps() + } + else self.render() + }, + handleSuccess: function (cb) { + var self = ExploreMaps + + if (self.collection && self.collection.id === 'mapper') { + self.fetchUserThenRender(cb) + } else { + self.render(cb) + } + }, + handleError: function () { + console.log('error loading maps!') // TODO + }, + fetchUserThenRender: function (cb) { + var self = ExploreMaps + + // first load the mapper object and then call the render function + $.ajax({ + url: '/users/' + self.collection.mapperId + '/details.json', + success: function (response) { + self.render(response, cb) + }, + error: function () { + self.render(cb) + } + }) + } +} + +export default ExploreMaps diff --git a/frontend/src/Metamaps/Views/Room.js b/frontend/src/Metamaps/Views/Room.js new file mode 100644 index 00000000..014df61b --- /dev/null +++ b/frontend/src/Metamaps/Views/Room.js @@ -0,0 +1,198 @@ +/* global Metamaps, $ */ +import Active from '../Active' +import Realtime from '../Realtime' + +import ChatView from './ChatView' +import VideoView from './VideoView' + +/* + * Metamaps.Backbone + */ + +const Room = function(opts) { + var self = this + + this.isActiveRoom = false + this.socket = opts.socket + this.webrtc = opts.webrtc + //this.roomRef = opts.firebase + this.room = opts.room + this.config = opts.config + this.peopleCount = 0 + + this.$myVideo = opts.$video + this.myVideo = opts.myVideoView + + this.messages = new Backbone.Collection() + this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image }) + this.chat = new ChatView(this.messages, this.currentMapper, this.room) + + this.videos = {} + + this.init() +} + +Room.prototype.join = function(cb) { + this.isActiveRoom = true + this.webrtc.joinRoom(this.room, cb) + this.chat.conversationInProgress(true) // true indicates participation +} + +Room.prototype.conversationInProgress = function() { + this.chat.conversationInProgress(false) // false indicates not participating +} + +Room.prototype.conversationEnding = function() { + this.chat.conversationEnded() +} + +Room.prototype.leaveVideoOnly = function() { + this.chat.leaveConversation() // the conversation will carry on without you + for (var id in this.videos) { + this.removeVideo(id) + } + this.isActiveRoom = false + this.webrtc.leaveRoom() +} + +Room.prototype.leave = function() { + for (var id in this.videos) { + this.removeVideo(id) + } + this.isActiveRoom = false + this.webrtc.leaveRoom() + this.chat.conversationEnded() + this.chat.removeParticipants() + this.chat.clearMessages() + this.messages.reset() +} + +Room.prototype.setPeopleCount = function(count) { + this.peopleCount = count +} + +Room.prototype.init = function () { + var self = this + + $(document).on(VideoView.events.audioControlClick, function (event, videoView) { + if (!videoView.audioStatus) self.webrtc.mute() + else if (videoView.audioStatus) self.webrtc.unmute() + }) + $(document).on(VideoView.events.videoControlClick, function (event, videoView) { + if (!videoView.videoStatus) self.webrtc.pauseVideo() + else if (videoView.videoStatus) self.webrtc.resumeVideo() + }) + + this.webrtc.webrtc.off('peerStreamAdded') + this.webrtc.webrtc.off('peerStreamRemoved') + this.webrtc.on('peerStreamAdded', function (peer) { + var mapper = Realtime.mappersOnMap[peer.nick] + peer.avatar = mapper.image + peer.username = mapper.name + if (self.isActiveRoom) { + self.addVideo(peer) + } + }) + + this.webrtc.on('peerStreamRemoved', function (peer) { + if (self.isActiveRoom) { + self.removeVideo(peer) + } + }) + + this.webrtc.on('mute', function (data) { + var v = self.videos[data.id] + if (!v) return + + if (data.name === 'audio') { + v.audioStatus = false + } + else if (data.name === 'video') { + v.videoStatus = false + v.$avatar.show() + } + if (!v.audioStatus && !v.videoStatus) v.$container.hide() + }) + this.webrtc.on('unmute', function (data) { + var v = self.videos[data.id] + if (!v) return + + if (data.name === 'audio') { + v.audioStatus = true + } + else if (data.name === 'video') { + v.videoStatus = true + v.$avatar.hide() + } + v.$container.show() + }) + + var sendChatMessage = function (event, data) { + self.sendChatMessage(data) + } + $(document).on(ChatView.events.message + '-' + this.room, sendChatMessage) + } + + Room.prototype.videoAdded = function (callback) { + this._videoAdded = callback + } + + Room.prototype.addVideo = function (peer) { + var + id = this.webrtc.getDomId(peer), + video = attachMediaStream(peer.stream) + + var + v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar, username: peer.username }) + + this.videos[peer.id] = v + if (this._videoAdded) this._videoAdded(v, peer.nick) + } + + Room.prototype.removeVideo = function (peer) { + var id = typeof peer == 'string' ? peer : peer.id + if (this.videos[id]) { + this.videos[id].remove() + delete this.videos[id] + } + } + + Room.prototype.sendChatMessage = function (data) { + var self = this + //this.roomRef.child('messages').push(data) + if (self.chat.alertSound) self.chat.sound.play('sendchat') + var m = new Metamaps.Backbone.Message({ + message: data.message, + resource_id: Active.Map.id, + resource_type: "Map" + }) + m.save(null, { + success: function (model, response) { + self.addMessages(new Metamaps.Backbone.MessageCollection(model), false, true) + $(document).trigger(Room.events.newMessage, [model]) + }, + error: function (model, response) { + console.log('error!', response) + } + }) + } + + // they should be instantiated as backbone models before they get + // passed to this function + Room.prototype.addMessages = function (messages, isInitial, wasMe) { + var self = this + + messages.models.forEach(function (message) { + self.chat.addMessage(message, isInitial, wasMe) + }) + } + +/** + * @class + * @static + */ +Room.events = { + newMessage: "Room:newMessage" +} + +export default Room diff --git a/frontend/src/Metamaps/Views/VideoView.js b/frontend/src/Metamaps/Views/VideoView.js new file mode 100644 index 00000000..401ece54 --- /dev/null +++ b/frontend/src/Metamaps/Views/VideoView.js @@ -0,0 +1,202 @@ +/* global $ */ + +var Private = { + addControls: function() { + var self = this; + + this.$audioControl = $('
'); + this.$videoControl = $('
'); + + this.$audioControl.on('click', function () { + Handlers.audioControlClick.call(self); + }); + + this.$videoControl.on('click', function () { + Handlers.videoControlClick.call(self); + }); + + this.$container.append(this.$audioControl); + this.$container.append(this.$videoControl); + }, + cancelClick: function() { + this.mouseIsDown = false; + + if (this.hasMoved) { + + } + + $(document).trigger(VideoView.events.dragEnd); + } +}; + +var Handlers = { + mousedown: function(event) { + this.mouseIsDown = true; + this.hasMoved = false; + this.mouseMoveStart = { + x: event.pageX, + y: event.pageY + }; + this.posStart = { + x: parseInt(this.$container.css('left'), '10'), + y: parseInt(this.$container.css('top'), '10') + } + + $(document).trigger(VideoView.events.mousedown); + }, + mouseup: function(event) { + $(document).trigger(VideoView.events.mouseup, [this]); + + var storedTime = this.lastClick; + var now = Date.now(); + this.lastClick = now; + + if (now - storedTime < this.config.DOUBLE_CLICK_TOLERANCE) { + $(document).trigger(VideoView.events.doubleClick, [this]); + } + }, + mousemove: function(event) { + var + diffX, + diffY, + newX, + newY; + + if (this.$parent && this.mouseIsDown) { + this.manuallyPositioned = true; + this.hasMoved = true; + diffX = event.pageX - this.mouseMoveStart.x; + diffY = this.mouseMoveStart.y - event.pageY; + newX = this.posStart.x + diffX; + newY = this.posStart.y - diffY; + this.$container.css({ + top: newY, + left: newX + }); + } + }, + audioControlClick: function() { + if (this.audioStatus) { + this.audioOff(); + } else { + this.audioOn(); + } + $(document).trigger(VideoView.events.audioControlClick, [this]); + }, + videoControlClick: function() { + if (this.videoStatus) { + this.videoOff(); + } else { + this.videoOn(); + } + $(document).trigger(VideoView.events.videoControlClick, [this]); + }, +}; + +var VideoView = function(video, $parent, id, isMyself, config) { + var self = this; + + this.$parent = $parent; // mapView + + this.video = video; + this.id = id; + + this.config = config; + + this.mouseIsDown = false; + this.mouseDownOffset = { x: 0, y: 0 }; + this.lastClick = null; + this.hasMoved = false; + + this.audioStatus = true; + this.videoStatus = true; + + this.$container = $('
'); + this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : '')); + this.$container.attr('id', 'container_' + id); + + + var $vidContainer = $('
'); + $vidContainer.addClass('video-cutoff'); + $vidContainer.append(this.video); + + this.avatar = config.avatar; + this.$avatar = $(''); + $vidContainer.append(this.$avatar); + + this.$container.append($vidContainer); + + this.$container.on('mousedown', function (event) { + Handlers.mousedown.call(self, event); + }); + + if (isMyself) { + Private.addControls.call(this); + } + + // suppress contextmenu + this.video.oncontextmenu = function () { return false; }; + + if (this.$parent) this.setParent(this.$parent); +}; + +VideoView.prototype.setParent = function($parent) { + var self = this; + this.$parent = $parent; + this.$parent.off('.video' + this.id); + this.$parent.on('mouseup.video' + this.id, function (event) { + Handlers.mouseup.call(self, event); + Private.cancelClick.call(self); + }); + this.$parent.on('mousemove.video' + this.id, function (event) { + Handlers.mousemove.call(self, event); + }); +} + +VideoView.prototype.setAvatar = function (src) { + this.$avatar.attr('src', src); + this.avatar = src; +} + +VideoView.prototype.remove = function () { + this.$container.off(); + if (this.$parent) this.$parent.off('.video' + this.id); + this.$container.remove(); +} + +VideoView.prototype.videoOff = function () { + this.$videoControl.addClass('active'); + this.$avatar.show(); + this.videoStatus = false; +} + +VideoView.prototype.videoOn = function () { + this.$videoControl.removeClass('active'); + this.$avatar.hide(); + this.videoStatus = true; +} + +VideoView.prototype.audioOff = function () { + this.$audioControl.addClass('active'); + this.audioStatus = false; +} + +VideoView.prototype.audioOn = function () { + this.$audioControl.removeClass('active'); + this.audioStatus = true; +} + +/** + * @class + * @static + */ +VideoView.events = { + mousedown: "VideoView:mousedown", + mouseup: "VideoView:mouseup", + doubleClick: "VideoView:doubleClick", + dragEnd: "VideoView:dragEnd", + audioControlClick: "VideoView:audioControlClick", + videoControlClick: "VideoView:videoControlClick", +}; + +export default VideoView diff --git a/frontend/src/Metamaps/Views/index.js b/frontend/src/Metamaps/Views/index.js new file mode 100644 index 00000000..ca0e751a --- /dev/null +++ b/frontend/src/Metamaps/Views/index.js @@ -0,0 +1,6 @@ +import ExploreMaps from './ExploreMaps' +import ChatView from './ChatView' +import VideoView from './VideoView' +import Room from './Room' + +export ExploreMaps, ChatView, VideoView, Room diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js index 7b431d1f..45283c89 100644 --- a/frontend/src/Metamaps/index.js +++ b/frontend/src/Metamaps/index.js @@ -28,7 +28,7 @@ import SynapseCard from './SynapseCard' import Topic from './Topic' import TopicCard from './TopicCard' import Util from './Util' -import Views from './Views' +import * as Views from './Views' import Visualize from './Visualize' import ReactComponents from './ReactComponents' @@ -83,18 +83,18 @@ document.addEventListener("DOMContentLoaded", function() { if (Metamaps.currentSection === "explore") { const capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1) - Metamaps.Views.exploreMaps.setCollection( Metamaps.Maps[capitalize] ) + Metamaps.Views.ExploreMaps.setCollection( Metamaps.Maps[capitalize] ) if (Metamaps.currentPage === "mapper") { - Views.exploreMaps.fetchUserThenRender() + Views.ExploreMaps.fetchUserThenRender() } else { - Views.exploreMaps.render() + Views.ExploreMaps.render() } GlobalUI.showDiv('#explore') } else if (Metamaps.currentSection === "" && Active.Mapper) { - Views.exploreMaps.setCollection(Metamaps.Maps.Active) - Views.exploreMaps.render() + Views.ExploreMaps.setCollection(Metamaps.Maps.Active) + Views.ExploreMaps.render() GlobalUI.showDiv('#explore') } else if (Active.Map || Active.Topic) {