temp
BIN
app/assets/images/audio_sprite.png
Normal file
After Width: | Height: | Size: 854 B |
BIN
app/assets/images/camera_sprite.png
Normal file
After Width: | Height: | Size: 780 B |
BIN
app/assets/images/chat32.png
Normal file
After Width: | Height: | Size: 466 B |
BIN
app/assets/images/cursor_sprite.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
app/assets/images/default_profile.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
app/assets/images/junto.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/assets/images/sound_sprite.png
Normal file
After Width: | Height: | Size: 717 B |
BIN
app/assets/images/sounds/sounds.mp3
Normal file
BIN
app/assets/images/sounds/sounds.ogg
Normal file
BIN
app/assets/images/tray_tab.png
Normal file
After Width: | Height: | Size: 331 B |
BIN
app/assets/images/video_sprite.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -20,6 +20,9 @@
|
|||
//= require ./src/Metamaps.Router
|
||||
//= require ./src/Metamaps.Backbone
|
||||
//= require ./src/Metamaps.Views
|
||||
//= require ./src/views/chatView
|
||||
//= require ./src/views/videoView
|
||||
//= require ./src/views/room
|
||||
//= require ./src/JIT
|
||||
//= require ./src/Metamaps
|
||||
//= require ./src/Metamaps.JIT
|
||||
|
|
2756
app/assets/javascripts/lib/Autolinker.js
Normal file
39
app/assets/javascripts/lib/attachMediaStream.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
var attachMediaStream = function (stream, el, options) {
|
||||
var URL = window.URL;
|
||||
var opts = {
|
||||
autoplay: true,
|
||||
mirror: false,
|
||||
muted: false
|
||||
};
|
||||
var element = el || document.createElement('video');
|
||||
var item;
|
||||
|
||||
if (options) {
|
||||
for (item in options) {
|
||||
opts[item] = options[item];
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.autoplay) element.autoplay = 'autoplay';
|
||||
if (opts.muted) element.muted = true;
|
||||
if (opts.mirror) {
|
||||
['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) {
|
||||
var styleName = prefix ? prefix + 'Transform' : 'transform';
|
||||
element.style[styleName] = 'scaleX(-1)';
|
||||
});
|
||||
}
|
||||
|
||||
// this first one should work most everywhere now
|
||||
// but we have a few fallbacks just in case.
|
||||
if (URL && URL.createObjectURL) {
|
||||
element.src = URL.createObjectURL(stream);
|
||||
} else if (element.srcObject) {
|
||||
element.srcObject = stream;
|
||||
} else if (element.mozSrcObject) {
|
||||
element.mozSrcObject = stream;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
1353
app/assets/javascripts/lib/howler.js
Normal file
9802
app/assets/javascripts/lib/simplewebrtc.bundle.js
Normal file
23
app/assets/javascripts/lib/socketIoConnection.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
function SocketIoConnection(config) {
|
||||
this.connection = io.connect(config.url, config.socketio);
|
||||
}
|
||||
|
||||
SocketIoConnection.prototype.on = function (ev, fn) {
|
||||
this.connection.on(ev, fn);
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.emit = function () {
|
||||
this.connection.emit.apply(this.connection, arguments);
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.removeAllListeners = function () {
|
||||
this.connection.removeAllListeners();
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.getSessionid = function () {
|
||||
return this.connection.socket.sessionid;
|
||||
};
|
||||
|
||||
SocketIoConnection.prototype.disconnect = function () {
|
||||
return this.connection.disconnect();
|
||||
};
|
|
@ -1910,11 +1910,15 @@ Metamaps.Realtime = {
|
|||
stringForLocalhost: 'http://localhost:5001',
|
||||
stringForMetamaps: 'http://metamaps.cc:5001',
|
||||
stringForHeroku: 'http://gentle-savannah-1303.herokuapp.com',
|
||||
videoId: 'video-wrapper',
|
||||
socket: null,
|
||||
webrtc: null,
|
||||
readyToCall: false,
|
||||
isOpen: false,
|
||||
changing: false,
|
||||
mappersOnMap: {},
|
||||
status: true, // stores whether realtime is True/On or False/Off
|
||||
status: true, // stores whether realtime is True/On or False/Off,
|
||||
localVideo: null,
|
||||
init: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
|
@ -1935,10 +1939,54 @@ Metamaps.Realtime = {
|
|||
|
||||
var railsEnv = $('body').data('env');
|
||||
var whichToConnect = railsEnv === 'development' ? self.stringForLocalhost : self.stringForHeroku;
|
||||
self.socket = io.connect(whichToConnect);
|
||||
self.socket = new SocketIoConnection({ url: whichToConnect });
|
||||
self.socket.on('connect', function () {
|
||||
self.startActiveMap();
|
||||
});
|
||||
self.webrtc = new SimpleWebRTC({
|
||||
connection: self.socket,
|
||||
localVideoEl: self.videoId,
|
||||
remoteVideosEl: '',
|
||||
detectSpeakingEvents: true,
|
||||
autoAdjustMic: true,
|
||||
autoRequestMedia: false,
|
||||
localVideo: {
|
||||
autoplay: true,
|
||||
mirror: true,
|
||||
muted: true
|
||||
},
|
||||
media: {
|
||||
video: true,
|
||||
audio: true
|
||||
}
|
||||
});
|
||||
self.webrtc.on('readyToCall', function () {
|
||||
self.readyToCall = true;
|
||||
if (self.localVideo && self.status) {
|
||||
$('#wrapper').append(self.localVideo.view.$container);
|
||||
}
|
||||
});
|
||||
|
||||
var
|
||||
$video = $('<video></video>').attr('id', self.videoId);
|
||||
self.localVideo = {
|
||||
$video: $video,
|
||||
view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, { DOUBLE_CLICK_TOLERANCE: 200 }),
|
||||
};
|
||||
|
||||
self.room = new Metamaps.Views.room({
|
||||
webrtc: self.webrtc,
|
||||
socket: self.socket,
|
||||
username: 'dude',//getUsername(opts.user, provider),
|
||||
image: 'https://pbs.twimg.com/profile_images/436050101539065856/QMGlzCUn_400x400.jpeg', //getImage(opts.user, provider),
|
||||
room: 'global',
|
||||
$video: self.localVideo.$video,
|
||||
myVideoView: self.localVideo.view,
|
||||
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
||||
});
|
||||
|
||||
self.createChat();
|
||||
self.webrtc.startLocalVideo();
|
||||
},
|
||||
toggleBox: function (event) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
@ -2019,6 +2067,31 @@ Metamaps.Realtime = {
|
|||
self.status = true;
|
||||
$(".sidebarCollaborateIcon").addClass("blue");
|
||||
$(".collabCompass").show();
|
||||
if (self.localVideo) $('#wrapper').append(self.localVideo.view.$container);
|
||||
self.room.room = 'map-' + Metamaps.Active.Map.id;
|
||||
self.room.join(function (err, roomDesc) {
|
||||
console.log('joining');
|
||||
attachMediaStream(self.webrtc.webrtc.localStream, self.localVideo.$video[0]);
|
||||
|
||||
function addVideo(v) {
|
||||
// random position for now
|
||||
var top = Math.floor((Math.random() * ($('#wrapper').height() - 100)) + 1);
|
||||
var left = Math.floor((Math.random() * ($('#wrapper').width() - 100)) + 1);
|
||||
//var right = Math.floor((Math.random() * (468 - 100)) + 1);
|
||||
v.setParent($('#wrapper'));
|
||||
$('#wrapper').append(v.$container);
|
||||
v.$container.css({
|
||||
top: top + 'px',
|
||||
left: left + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
self.room.videoAdded(addVideo);
|
||||
|
||||
for (peer in self.room.videos) {
|
||||
addVideo(self.room.videos[peer]);
|
||||
}
|
||||
});
|
||||
},
|
||||
turnOff: function (silent) {
|
||||
var self = Metamaps.Realtime;
|
||||
|
@ -2031,6 +2104,7 @@ Metamaps.Realtime = {
|
|||
self.status = false;
|
||||
$(".sidebarCollaborateIcon").removeClass("blue");
|
||||
$(".collabCompass").hide();
|
||||
$('#' + self.videoId).remove();
|
||||
}
|
||||
},
|
||||
setupSocket: function () {
|
||||
|
@ -2154,6 +2228,12 @@ Metamaps.Realtime = {
|
|||
|
||||
socket.on('mapChangeFromServer', self.mapChange);
|
||||
},
|
||||
createChat: function() {
|
||||
var self = Metamaps.Realtime;
|
||||
|
||||
$('#wrapper').append(self.room.chat.$container);
|
||||
//self.room.chat.open();
|
||||
},
|
||||
sendRealtimeOn: function () {
|
||||
var self = Metamaps.Realtime;
|
||||
var socket = Metamaps.Realtime.socket;
|
||||
|
|
272
app/assets/javascripts/src/views/chatView.js
Normal file
|
@ -0,0 +1,272 @@
|
|||
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='<%= image %>' title='<%= user %>'/></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-<%= username %>'>" +
|
||||
"<div class='chat-participant-image'><img src='<%= image %>' /></div>" +
|
||||
"<div class='chat-participant-name'><%= username %></div>" +
|
||||
"<div class='clearfloat'></div>" +
|
||||
"</div>",
|
||||
templates: function() {
|
||||
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>');
|
||||
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.$chatHeader = $('<div class="chat-header">CHAT</div>');
|
||||
this.$soundToggle = $('<div class="sound-toggle active"></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.$container.append(this.$juntoHeader);
|
||||
this.$container.append(this.$participants);
|
||||
this.$container.append(this.$chatHeader);
|
||||
this.$container.append(this.$messageInput);
|
||||
this.$container.append(this.$button);
|
||||
this.$container.append(this.$messages);
|
||||
},
|
||||
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);
|
||||
});
|
||||
|
||||
// add the event listener so that when
|
||||
// the realtime module adds messages to the collection
|
||||
// from other mappers, it will update the UI
|
||||
this.messages.on('add', function (message) {
|
||||
Private.addMessage.call(self, message);
|
||||
});
|
||||
|
||||
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: ['/assets/sounds/sounds.mp3', '/assets/sounds/sounds.ogg'],
|
||||
sprite: {
|
||||
laser: [3000, 700]
|
||||
}
|
||||
});
|
||||
},
|
||||
incrementUnread: function() {
|
||||
this.unreadMessages++;
|
||||
this.$unread.html(this.unreadMessages);
|
||||
this.$unread.show();
|
||||
},
|
||||
addMessage: function(message) {
|
||||
|
||||
if (!this.isOpen) 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.timestamp);
|
||||
|
||||
var date = (m.timestamp.getMonth() + 1) + '/' + m.timestamp.getDate();
|
||||
date += " " + addZero(m.timestamp.getHours()) + ":" + addZero(m.timestamp.getMinutes());
|
||||
m.timestamp = date;
|
||||
m.image = m.image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png';
|
||||
m.message = linker.link(m.message);
|
||||
var $html = $(this.messageTemplate(m));
|
||||
this.$messages.append($html);
|
||||
this.scrollMessages(200);
|
||||
|
||||
if (this.alertSound) this.sound.play('laser');
|
||||
},
|
||||
initialMessages: function() {
|
||||
var messages = this.messages.models;
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
Private.addMessage.call(this, messages[i]);
|
||||
}
|
||||
},
|
||||
handleInputMessage: function() {
|
||||
var message = {
|
||||
message: this.$messageInput.val(),
|
||||
timestamp: Date.now(),
|
||||
user: this.mapper.get('name'),
|
||||
image: this.mapper.get('image')
|
||||
};
|
||||
this.$messageInput.val('');
|
||||
$(document).trigger(chatView.events.message + '-' + this.room, [message]);
|
||||
},
|
||||
addParticipant: function(participant) {
|
||||
var p = _.clone(participant.attributes);
|
||||
var html = this.participantTemplate(p);
|
||||
this.$participants.append(html);
|
||||
},
|
||||
removeParticipant: function(participant) {
|
||||
this.$container.find('.participant-' + participant.get('username')).remove();
|
||||
}
|
||||
};
|
||||
|
||||
var Handlers = {
|
||||
buttonClick: function() {
|
||||
if (this.isOpen) this.close();
|
||||
else if (!this.isOpen) this.open();
|
||||
},
|
||||
videoToggleClick: function() {
|
||||
this.$videoToggle.toggleClass('active');
|
||||
},
|
||||
cursorToggleClick: function() {
|
||||
this.$cursorToggle.toggleClass('active');
|
||||
},
|
||||
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 = false; // whether to play sounds on arrival of new messages or not
|
||||
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);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
chatView.prototype.scrollMessages = function(duration) {
|
||||
duration = duration || 0;
|
||||
|
||||
var
|
||||
numMessages = this.$messages.find('.chat-message').length,
|
||||
messageHeight = 52;
|
||||
|
||||
this.$messages.animate({
|
||||
scrollTop: numMessages * messageHeight
|
||||
}, duration);
|
||||
}
|
||||
|
||||
chatView.prototype.close = function () {
|
||||
this.$container.css({
|
||||
right: '-300px'
|
||||
});
|
||||
this.$messageInput.blur();
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
chatView.prototype.remove = function () {
|
||||
this.$button.off();
|
||||
this.$container.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
chatView.events = {
|
||||
message: 'ChatView:message',
|
||||
inputFocus: 'ChatView:inputFocus',
|
||||
inputBlur: 'ChatView:inputBlur'
|
||||
};
|
||||
|
||||
return chatView;
|
||||
|
||||
})();
|
||||
|
111
app/assets/javascripts/src/views/room.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
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);
|
||||
}
|
||||
|
||||
room.prototype.leave = function() {
|
||||
for (var id in this.videos) {
|
||||
this.removeVideo(id);
|
||||
}
|
||||
this.isActiveRoom = false;
|
||||
this.webrtc.leaveRoom();
|
||||
this.chat.removeParticipants();
|
||||
}
|
||||
|
||||
room.prototype.setPeopleCount = function(count) {
|
||||
this.peopleCount = count;
|
||||
}
|
||||
|
||||
room.prototype.init = function () {
|
||||
var self = this;
|
||||
|
||||
/*this.roomRef.child('messages').on('child_added', function (snap) {
|
||||
self.messages.add(snap.val());
|
||||
});*/
|
||||
|
||||
$(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) {
|
||||
if (self.isActiveRoom) {
|
||||
self.addVideo(peer);
|
||||
}
|
||||
});
|
||||
|
||||
this.webrtc.on('peerStreamRemoved', function (peer) {
|
||||
if (self.isActiveRoom) {
|
||||
self.removeVideo(peer);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200 });
|
||||
|
||||
if (this._videoAdded) this._videoAdded(v);
|
||||
this.videos[peer.id] = v;
|
||||
}
|
||||
|
||||
room.prototype.removeVideo = function (peer) {
|
||||
console.log(peer);
|
||||
var id = typeof peer == 'string' ? peer : peer.id;
|
||||
this.videos[id].remove();
|
||||
delete this.videos[id];
|
||||
}
|
||||
|
||||
room.prototype.sendChatMessage = function (data) {
|
||||
//this.roomRef.child('messages').push(data);
|
||||
}
|
||||
|
||||
return room;
|
||||
})();
|
182
app/assets/javascripts/src/views/videoView.js
Normal file
|
@ -0,0 +1,182 @@
|
|||
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.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.$audioControl.addClass('active');
|
||||
} else {
|
||||
this.$audioControl.removeClass('active');
|
||||
}
|
||||
this.audioStatus = !this.audioStatus;
|
||||
$(document).trigger(videoView.events.audioControlClick, [this]);
|
||||
},
|
||||
videoControlClick: function() {
|
||||
if (this.videoStatus) {
|
||||
this.$videoControl.addClass('active');
|
||||
this.$avatar.show();
|
||||
} else {
|
||||
this.$videoControl.removeClass('active');
|
||||
this.$avatar.hide();
|
||||
}
|
||||
this.videoStatus = !this.videoStatus;
|
||||
$(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 = $('<img draggable="false" class="collaborator-video-avatar" src="/assets/default_profile.png" 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.remove = function () {
|
||||
this.$container.off();
|
||||
if (this.$parent) this.$parent.off('.video' + this.id);
|
||||
this.$container.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
*/
|
||||
videoView.events = {
|
||||
mousedown: "VideoView:mousedown",
|
||||
mouseup: "VideoView:mouseup",
|
||||
doubleClick: "VideoView:doubleClick",
|
||||
dragEnd: "VideoView:dragEnd",
|
||||
audioControlClick: "VideoView:audioControlClick",
|
||||
videoControlClick: "VideoView:videoControlClick",
|
||||
};
|
||||
|
||||
return videoView;
|
||||
})();
|
||||
|
||||
|
265
app/assets/stylesheets/junto.css
Normal file
|
@ -0,0 +1,265 @@
|
|||
.collaborator-video {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
cursor: default;
|
||||
color: #FFF;
|
||||
}
|
||||
.collaborator-video .video-cutoff {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
border-radius: 75px;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
-webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
-moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.collaborator-video .video-cutoff video {
|
||||
height: 150px;
|
||||
margin-left: -25px;
|
||||
}
|
||||
.collaborator-video .video-cutoff .collaborator-video-avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
display: none;
|
||||
}
|
||||
.collaborator-video .video-audio {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 85%;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
background: url(audio_sprite.png) no-repeat;
|
||||
}
|
||||
.collaborator-video .video-audio:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.collaborator-video .video-audio.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.collaborator-video .video-video {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 85%;
|
||||
left: 0px;
|
||||
cursor: pointer;
|
||||
background: url(camera_sprite.png) no-repeat;
|
||||
}
|
||||
.collaborator-video .video-video:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.collaborator-video .video-video.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.collaborator-video.my-video {
|
||||
left: 50px;
|
||||
top: 50px;
|
||||
}
|
||||
.chat-box {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: -300px;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: #424242;
|
||||
box-shadow: 0px 0px 16px 8px rgba(0, 0, 0, 0.23), -2px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.chat-box .chat-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -36px;
|
||||
width: 36px;
|
||||
height: 49px;
|
||||
background: url(junto.png) no-repeat 2px 9px, url(tray_tab.png) no-repeat;
|
||||
}
|
||||
.chat-box .chat-button .chat-unread {
|
||||
display: none;
|
||||
background: #DAB539;
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -11px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 11px;
|
||||
border: 2px solid #424242;
|
||||
color: #424242;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
.chat-box .junto-header {
|
||||
width: 100%;
|
||||
padding: 16px 8px 16px 16px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
background-color: #000000;
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.chat-box .junto-header .cursor-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 8px;
|
||||
margin-top: -8px;
|
||||
float: right;
|
||||
background: url(cursor_sprite.png) no-repeat;
|
||||
}
|
||||
.chat-box .junto-header .cursor-toggle:hover {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.chat-box .junto-header .cursor-toggle.active {
|
||||
background-position-y: -32px;
|
||||
}
|
||||
.chat-box .junto-header .video-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 32px;
|
||||
margin-top: -8px;
|
||||
float: right;
|
||||
background: url(video_sprite.png) no-repeat;
|
||||
}
|
||||
.chat-box .junto-header .video-toggle:hover {
|
||||
background-position-x: -32px;
|
||||
}
|
||||
.chat-box .junto-header .video-toggle.active {
|
||||
background-position-y: -32px;
|
||||
}
|
||||
.chat-box .participants {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
padding: 16px 0px 0px 0px;
|
||||
text-align: left;
|
||||
color: #f5f5f5;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.chat-box .participants .participant {
|
||||
width: 89%;
|
||||
padding: 8px 8px 2px 8px;
|
||||
color: #f5f5f5;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-image {
|
||||
width: 15%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #BBB;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-image img {
|
||||
border: 2px solid #424242;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.chat-box .participants .participant .chat-participant-name {
|
||||
width: 73%;
|
||||
float: left;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin-top: 12px;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
}
|
||||
.chat-box .chat-header {
|
||||
width: 100%;
|
||||
padding: 16px 8px 16px 16px;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
background-color: #000000;
|
||||
color: #f5f5f5;
|
||||
box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 32px;
|
||||
margin-top: -2px;
|
||||
float: right;
|
||||
background: url(sound_sprite.png) no-repeat;
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle:hover {
|
||||
background-position-x: -24px;
|
||||
}
|
||||
.chat-box .chat-header .sound-toggle.active {
|
||||
background-position-y: -24px;
|
||||
}
|
||||
.chat-box .chat-input {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 80px;
|
||||
width: 100%;
|
||||
padding: 8px 8px 8px 8px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.chat-box .chat-messages {
|
||||
width: 100%;
|
||||
height: 196px;
|
||||
padding: 16px 0px 0px 0px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message {
|
||||
width: 89%;
|
||||
padding: 8px 8px 2px 8px;
|
||||
color: #f5f5f5;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message a:link {
|
||||
color: #4fb5c0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message a:visited {
|
||||
color: #aea9fd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message a:hover {
|
||||
color: #dab539;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-user {
|
||||
width: 15%;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
color: #BBB;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-user img {
|
||||
border: 2px solid #424242;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-text {
|
||||
width: 73%;
|
||||
float: left;
|
||||
margin-top: 12px;
|
||||
padding: 2px 8px 0;
|
||||
text-align: left;
|
||||
}
|
||||
.chat-box .chat-messages .chat-message .chat-message-time {
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
color: #757575;
|
||||
}
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/assets/about_sprite-ec51351c3569a7f5ff1326ce9e81d4db.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
public/assets/addtopic_sprite-474fa6955c61eda7edb94a99b9152f6b.png
Executable file
After Width: | Height: | Size: 361 B |
BIN
public/assets/arrow_sprite-3bb38c6207a94c9448a74511b4fbb0da.png
Executable file
After Width: | Height: | Size: 566 B |
After Width: | Height: | Size: 715 B |
BIN
public/assets/arrowdown_sprite-5a48c18bd4a43024b2107430f04ca3b1.png
Executable file
After Width: | Height: | Size: 331 B |
After Width: | Height: | Size: 543 B |
After Width: | Height: | Size: 540 B |
BIN
public/assets/arrowright_sprite-27adb8ccab46306ee1b8c577355105b9.png
Executable file
After Width: | Height: | Size: 349 B |
BIN
public/assets/browser_icons-582d09c51a2c675b9716652435de546c.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
public/assets/compass_arrow-85c83dcd70a85ab0da93d7cd966459d0.png
Normal file
After Width: | Height: | Size: 145 B |
BIN
public/assets/context_sprite-ea6f317538820539fc90d69ad6e04c5f.png
Executable file
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2 KiB |
BIN
public/assets/edit-d6d3c4e443f674ccd3934ecc7ff5ca28.png
Executable file
After Width: | Height: | Size: 324 B |
BIN
public/assets/exploremaps_sprite-51c03ee16025abf84bb098f2c1dbacf1.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
public/assets/extents_sprite-0330c42177b14486b57af0bad7345e06.png
Executable file
After Width: | Height: | Size: 602 B |
After Width: | Height: | Size: 2.2 KiB |
BIN
public/assets/help_sprite-64737406a07e8575d5dad46c0a3aa707.png
Executable file
After Width: | Height: | Size: 853 B |
BIN
public/assets/home_dark-0210718ba049662df8bb312f2dd3c285.png
Executable file
After Width: | Height: | Size: 465 B |
BIN
public/assets/home_light-6a3a31a73a8611a764557126a6d634aa.png
Executable file
After Width: | Height: | Size: 452 B |
After Width: | Height: | Size: 110 KiB |
BIN
public/assets/icons/action-abd8f2e892ebe608b9112249167fb64e.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
public/assets/icons/bizarre-d6ef966323ca17b300d15bf928f9a9d7.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5 KiB |
After Width: | Height: | Size: 5 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 9.8 KiB |
BIN
public/assets/icons/closed-15267ab51fc3279b336662252acca9bf.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 9.6 KiB |
BIN
public/assets/icons/example-a5e82d0be449ac0c5356bd94434b6ad3.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 4 KiB |