move views to their own frontend folder
This commit is contained in:
parent
fe3012136d
commit
30894a313f
13 changed files with 851 additions and 856 deletions
|
@ -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_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
|
||||||
Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
||||||
Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.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.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
||||||
Metamaps.VERSION = '<%= METAMAPS_VERSION %>'
|
Metamaps.VERSION = '<%= METAMAPS_VERSION %>'
|
||||||
|
|
|
@ -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: "<div class='chat-message'>" +
|
|
||||||
"<div class='chat-message-user'><img src='{{ user_image }}' title='{{user_name }}'/></div>" +
|
|
||||||
"<div class='chat-message-text'>{{ message }}</div>" +
|
|
||||||
"<div class='chat-message-time'>{{ timestamp }}</div>" +
|
|
||||||
"<div class='clearfloat'></div>" +
|
|
||||||
"</div>",
|
|
||||||
participantHTML: "<div class='participant participant-{{ id }} {{ selfClass }}'>" +
|
|
||||||
"<div class='chat-participant-image'><img src='{{ image }}' style='border: 2px solid {{ color }};' /></div>" +
|
|
||||||
"<div class='chat-participant-name'>{{ username }} {{ selfName }}</div>" +
|
|
||||||
"<button type='button' class='button chat-participant-invite-call' onclick='Metamaps.Realtime.inviteACall({{ id}});'></button>" +
|
|
||||||
"<button type='button' class='button chat-participant-invite-join' onclick='Metamaps.Realtime.inviteToJoin({{ id}});'></button>" +
|
|
||||||
"<span class='chat-participant-participating'><div class='green-dot'></div></span>" +
|
|
||||||
"<div class='clearfloat'></div>" +
|
|
||||||
"</div>",
|
|
||||||
templates: function() {
|
|
||||||
_.templateSettings = {
|
|
||||||
interpolate: /\{\{(.+?)\}\}/g
|
|
||||||
};
|
|
||||||
this.messageTemplate = _.template(Private.messageHTML);
|
|
||||||
|
|
||||||
this.participantTemplate = _.template(Private.participantHTML);
|
|
||||||
},
|
|
||||||
createElements: function() {
|
|
||||||
this.$unread = $('<div class="chat-unread"></div>');
|
|
||||||
this.$button = $('<div class="chat-button"><div class="tooltips">Chat</div></div>');
|
|
||||||
this.$messageInput = $('<textarea placeholder="Send a message..." class="chat-input"></textarea>');
|
|
||||||
this.$juntoHeader = $('<div class="junto-header">PARTICIPANTS</div>');
|
|
||||||
this.$videoToggle = $('<div class="video-toggle"></div>');
|
|
||||||
this.$cursorToggle = $('<div class="cursor-toggle"></div>');
|
|
||||||
this.$participants = $('<div class="participants"></div>');
|
|
||||||
this.$conversationInProgress = $('<div class="conversation-live">LIVE <span class="call-action leave" onclick="Metamaps.Realtime.leaveCall();">LEAVE</span><span class="call-action join" onclick="Metamaps.Realtime.joinCall();">JOIN</span></div>');
|
|
||||||
this.$chatHeader = $('<div class="chat-header">CHAT</div>');
|
|
||||||
this.$soundToggle = $('<div class="sound-toggle"></div>');
|
|
||||||
this.$messages = $('<div class="chat-messages"></div>');
|
|
||||||
this.$container = $('<div class="chat-box"></div>');
|
|
||||||
},
|
|
||||||
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;
|
|
||||||
|
|
||||||
})();
|
|
|
@ -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;
|
|
||||||
})();
|
|
|
@ -1,207 +0,0 @@
|
||||||
Metamaps.Views = Metamaps.Views || {};
|
|
||||||
|
|
||||||
Metamaps.Views.videoView = (function () {
|
|
||||||
|
|
||||||
var videoView;
|
|
||||||
|
|
||||||
var Private = {
|
|
||||||
addControls: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.$audioControl = $('<div class="video-audio"></div>');
|
|
||||||
this.$videoControl = $('<div class="video-video"></div>');
|
|
||||||
|
|
||||||
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 = $('<div></div>');
|
|
||||||
this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : ''));
|
|
||||||
this.$container.attr('id', 'container_' + id);
|
|
||||||
|
|
||||||
|
|
||||||
var $vidContainer = $('<div></div>');
|
|
||||||
$vidContainer.addClass('video-cutoff');
|
|
||||||
$vidContainer.append(this.video);
|
|
||||||
|
|
||||||
this.avatar = config.avatar;
|
|
||||||
this.$avatar = $('<img draggable="false" class="collaborator-video-avatar" src="' + config.avatar + '" width="150" height="150" />');
|
|
||||||
$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;
|
|
||||||
})();
|
|
|
@ -77,13 +77,13 @@ const Realtime = {
|
||||||
var $video = $('<video></video>').attr('id', self.videoId)
|
var $video = $('<video></video>').attr('id', self.videoId)
|
||||||
self.localVideo = {
|
self.localVideo = {
|
||||||
$video: $video,
|
$video: $video,
|
||||||
view: new Views.videoView($video[0], $('body'), 'me', true, {
|
view: new Views.VideoView($video[0], $('body'), 'me', true, {
|
||||||
DOUBLE_CLICK_TOLERANCE: 200,
|
DOUBLE_CLICK_TOLERANCE: 200,
|
||||||
avatar: Active.Mapper ? Active.Mapper.get('image') : ''
|
avatar: Active.Mapper ? Active.Mapper.get('image') : ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
self.room = new Views.room({
|
self.room = new Views.Room({
|
||||||
webrtc: self.webrtc,
|
webrtc: self.webrtc,
|
||||||
socket: self.socket,
|
socket: self.socket,
|
||||||
username: Active.Mapper ? Active.Mapper.get('name') : '',
|
username: Active.Mapper ? Active.Mapper.get('name') : '',
|
||||||
|
@ -104,26 +104,26 @@ const Realtime = {
|
||||||
addJuntoListeners: function () {
|
addJuntoListeners: function () {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
|
|
||||||
$(document).on(Views.chatView.events.openTray, function () {
|
$(document).on(Views.ChatView.events.openTray, function () {
|
||||||
$('.main').addClass('compressed')
|
$('.main').addClass('compressed')
|
||||||
self.chatOpen = true
|
self.chatOpen = true
|
||||||
self.positionPeerIcons()
|
self.positionPeerIcons()
|
||||||
})
|
})
|
||||||
$(document).on(Views.chatView.events.closeTray, function () {
|
$(document).on(Views.ChatView.events.closeTray, function () {
|
||||||
$('.main').removeClass('compressed')
|
$('.main').removeClass('compressed')
|
||||||
self.chatOpen = false
|
self.chatOpen = false
|
||||||
self.positionPeerIcons()
|
self.positionPeerIcons()
|
||||||
})
|
})
|
||||||
$(document).on(Views.chatView.events.videosOn, function () {
|
$(document).on(Views.ChatView.events.videosOn, function () {
|
||||||
$('#wrapper').removeClass('hideVideos')
|
$('#wrapper').removeClass('hideVideos')
|
||||||
})
|
})
|
||||||
$(document).on(Views.chatView.events.videosOff, function () {
|
$(document).on(Views.ChatView.events.videosOff, function () {
|
||||||
$('#wrapper').addClass('hideVideos')
|
$('#wrapper').addClass('hideVideos')
|
||||||
})
|
})
|
||||||
$(document).on(Views.chatView.events.cursorsOn, function () {
|
$(document).on(Views.ChatView.events.cursorsOn, function () {
|
||||||
$('#wrapper').removeClass('hideCursors')
|
$('#wrapper').removeClass('hideCursors')
|
||||||
})
|
})
|
||||||
$(document).on(Views.chatView.events.cursorsOff, function () {
|
$(document).on(Views.ChatView.events.cursorsOff, function () {
|
||||||
$('#wrapper').addClass('hideCursors')
|
$('#wrapper').addClass('hideCursors')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -611,7 +611,7 @@ const Realtime = {
|
||||||
var sendNewMessage = function (event, data) {
|
var sendNewMessage = function (event, data) {
|
||||||
self.sendNewMessage(data)
|
self.sendNewMessage(data)
|
||||||
}
|
}
|
||||||
$(document).on(Views.room.events.newMessage + '.map', sendNewMessage)
|
$(document).on(Views.Room.events.newMessage + '.map', sendNewMessage)
|
||||||
},
|
},
|
||||||
attachMapListener: function () {
|
attachMapListener: function () {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
|
|
|
@ -49,11 +49,11 @@ const _Router = Backbone.Router.extend({
|
||||||
|
|
||||||
GlobalUI.showDiv('#explore')
|
GlobalUI.showDiv('#explore')
|
||||||
|
|
||||||
Views.exploreMaps.setCollection(Metamaps.Maps.Active)
|
Views.ExploreMaps.setCollection(Metamaps.Maps.Active)
|
||||||
if (Metamaps.Maps.Active.length === 0) {
|
if (Metamaps.Maps.Active.length === 0) {
|
||||||
Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render
|
Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render
|
||||||
} else {
|
} else {
|
||||||
Views.exploreMaps.render(navigate)
|
Views.ExploreMaps.render(navigate)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// logged out home page
|
// logged out home page
|
||||||
|
@ -108,7 +108,7 @@ const _Router = Backbone.Router.extend({
|
||||||
Metamaps.Maps.Mapper.mapperId = id
|
Metamaps.Maps.Mapper.mapperId = id
|
||||||
}
|
}
|
||||||
|
|
||||||
Views.exploreMaps.setCollection(Metamaps.Maps[capitalize])
|
Views.ExploreMaps.setCollection(Metamaps.Maps[capitalize])
|
||||||
|
|
||||||
var navigate = function () {
|
var navigate = function () {
|
||||||
var path = '/explore/' + this.currentPage
|
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
|
}, 300) // wait 300 milliseconds till the other animations are done to do the fetch
|
||||||
} else {
|
} else {
|
||||||
if (id) {
|
if (id) {
|
||||||
Views.exploreMaps.fetchUserThenRender(navigateTimeout)
|
Views.ExploreMaps.fetchUserThenRender(navigateTimeout)
|
||||||
} else {
|
} else {
|
||||||
Views.exploreMaps.render(navigateTimeout)
|
Views.ExploreMaps.render(navigateTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
337
frontend/src/Metamaps/Views/ChatView.js
Normal file
337
frontend/src/Metamaps/Views/ChatView.js
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
/* global Autolinker, $ */
|
||||||
|
var linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false });
|
||||||
|
|
||||||
|
var Private = {
|
||||||
|
messageHTML: "<div class='chat-message'>" +
|
||||||
|
"<div class='chat-message-user'><img src='{{ user_image }}' title='{{user_name }}'/></div>" +
|
||||||
|
"<div class='chat-message-text'>{{ message }}</div>" +
|
||||||
|
"<div class='chat-message-time'>{{ timestamp }}</div>" +
|
||||||
|
"<div class='clearfloat'></div>" +
|
||||||
|
"</div>",
|
||||||
|
participantHTML: "<div class='participant participant-{{ id }} {{ selfClass }}'>" +
|
||||||
|
"<div class='chat-participant-image'><img src='{{ image }}' style='border: 2px solid {{ color }};' /></div>" +
|
||||||
|
"<div class='chat-participant-name'>{{ username }} {{ selfName }}</div>" +
|
||||||
|
"<button type='button' class='button chat-participant-invite-call' onclick='Metamaps.Realtime.inviteACall({{ id}});'></button>" +
|
||||||
|
"<button type='button' class='button chat-participant-invite-join' onclick='Metamaps.Realtime.inviteToJoin({{ id}});'></button>" +
|
||||||
|
"<span class='chat-participant-participating'><div class='green-dot'></div></span>" +
|
||||||
|
"<div class='clearfloat'></div>" +
|
||||||
|
"</div>",
|
||||||
|
templates: function() {
|
||||||
|
_.templateSettings = {
|
||||||
|
interpolate: /\{\{(.+?)\}\}/g
|
||||||
|
};
|
||||||
|
this.messageTemplate = _.template(Private.messageHTML);
|
||||||
|
|
||||||
|
this.participantTemplate = _.template(Private.participantHTML);
|
||||||
|
},
|
||||||
|
createElements: function() {
|
||||||
|
this.$unread = $('<div class="chat-unread"></div>');
|
||||||
|
this.$button = $('<div class="chat-button"><div class="tooltips">Chat</div></div>');
|
||||||
|
this.$messageInput = $('<textarea placeholder="Send a message..." class="chat-input"></textarea>');
|
||||||
|
this.$juntoHeader = $('<div class="junto-header">PARTICIPANTS</div>');
|
||||||
|
this.$videoToggle = $('<div class="video-toggle"></div>');
|
||||||
|
this.$cursorToggle = $('<div class="cursor-toggle"></div>');
|
||||||
|
this.$participants = $('<div class="participants"></div>');
|
||||||
|
this.$conversationInProgress = $('<div class="conversation-live">LIVE <span class="call-action leave" onclick="Metamaps.Realtime.leaveCall();">LEAVE</span><span class="call-action join" onclick="Metamaps.Realtime.joinCall();">JOIN</span></div>');
|
||||||
|
this.$chatHeader = $('<div class="chat-header">CHAT</div>');
|
||||||
|
this.$soundToggle = $('<div class="sound-toggle"></div>');
|
||||||
|
this.$messages = $('<div class="chat-messages"></div>');
|
||||||
|
this.$container = $('<div class="chat-box"></div>');
|
||||||
|
},
|
||||||
|
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
|
86
frontend/src/Metamaps/Views/ExploreMaps.js
Normal file
86
frontend/src/Metamaps/Views/ExploreMaps.js
Normal file
|
@ -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
|
198
frontend/src/Metamaps/Views/Room.js
Normal file
198
frontend/src/Metamaps/Views/Room.js
Normal file
|
@ -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
|
202
frontend/src/Metamaps/Views/VideoView.js
Normal file
202
frontend/src/Metamaps/Views/VideoView.js
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
/* global $ */
|
||||||
|
|
||||||
|
var Private = {
|
||||||
|
addControls: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.$audioControl = $('<div class="video-audio"></div>');
|
||||||
|
this.$videoControl = $('<div class="video-video"></div>');
|
||||||
|
|
||||||
|
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 = $('<div></div>');
|
||||||
|
this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : ''));
|
||||||
|
this.$container.attr('id', 'container_' + id);
|
||||||
|
|
||||||
|
|
||||||
|
var $vidContainer = $('<div></div>');
|
||||||
|
$vidContainer.addClass('video-cutoff');
|
||||||
|
$vidContainer.append(this.video);
|
||||||
|
|
||||||
|
this.avatar = config.avatar;
|
||||||
|
this.$avatar = $('<img draggable="false" class="collaborator-video-avatar" src="' + config.avatar + '" width="150" height="150" />');
|
||||||
|
$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
|
6
frontend/src/Metamaps/Views/index.js
Normal file
6
frontend/src/Metamaps/Views/index.js
Normal file
|
@ -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
|
|
@ -28,7 +28,7 @@ import SynapseCard from './SynapseCard'
|
||||||
import Topic from './Topic'
|
import Topic from './Topic'
|
||||||
import TopicCard from './TopicCard'
|
import TopicCard from './TopicCard'
|
||||||
import Util from './Util'
|
import Util from './Util'
|
||||||
import Views from './Views'
|
import * as Views from './Views'
|
||||||
import Visualize from './Visualize'
|
import Visualize from './Visualize'
|
||||||
import ReactComponents from './ReactComponents'
|
import ReactComponents from './ReactComponents'
|
||||||
|
|
||||||
|
@ -83,18 +83,18 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||||
if (Metamaps.currentSection === "explore") {
|
if (Metamaps.currentSection === "explore") {
|
||||||
const capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1)
|
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") {
|
if (Metamaps.currentPage === "mapper") {
|
||||||
Views.exploreMaps.fetchUserThenRender()
|
Views.ExploreMaps.fetchUserThenRender()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Views.exploreMaps.render()
|
Views.ExploreMaps.render()
|
||||||
}
|
}
|
||||||
GlobalUI.showDiv('#explore')
|
GlobalUI.showDiv('#explore')
|
||||||
}
|
}
|
||||||
else if (Metamaps.currentSection === "" && Active.Mapper) {
|
else if (Metamaps.currentSection === "" && Active.Mapper) {
|
||||||
Views.exploreMaps.setCollection(Metamaps.Maps.Active)
|
Views.ExploreMaps.setCollection(Metamaps.Maps.Active)
|
||||||
Views.exploreMaps.render()
|
Views.ExploreMaps.render()
|
||||||
GlobalUI.showDiv('#explore')
|
GlobalUI.showDiv('#explore')
|
||||||
}
|
}
|
||||||
else if (Active.Map || Active.Topic) {
|
else if (Active.Map || Active.Topic) {
|
||||||
|
|
Loading…
Reference in a new issue