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 = $('');
- this.$messageInput = $('');
- this.$juntoHeader = $('');
- this.$videoToggle = $('');
- this.$cursorToggle = $('');
- this.$participants = $('');
- this.$conversationInProgress = $('LIVE LEAVEJOIN
');
- this.$chatHeader = $('');
- 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 = $('');
+ this.$messageInput = $('');
+ this.$juntoHeader = $('');
+ this.$videoToggle = $('');
+ this.$cursorToggle = $('');
+ this.$participants = $('');
+ this.$conversationInProgress = $('LIVE LEAVEJOIN
');
+ this.$chatHeader = $('');
+ 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) {