This commit is contained in:
Connor Turland 2015-09-19 12:02:05 -04:00
parent fa67ec0444
commit 75554d58bf
222 changed files with 15185 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
app/assets/images/junto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -20,6 +20,9 @@
//= require ./src/Metamaps.Router //= require ./src/Metamaps.Router
//= require ./src/Metamaps.Backbone //= require ./src/Metamaps.Backbone
//= require ./src/Metamaps.Views //= require ./src/Metamaps.Views
//= require ./src/views/chatView
//= require ./src/views/videoView
//= require ./src/views/room
//= require ./src/JIT //= require ./src/JIT
//= require ./src/Metamaps //= require ./src/Metamaps
//= require ./src/Metamaps.JIT //= require ./src/Metamaps.JIT

File diff suppressed because it is too large Load diff

View 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;
};

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View 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();
};

View file

@ -1910,11 +1910,15 @@ Metamaps.Realtime = {
stringForLocalhost: 'http://localhost:5001', stringForLocalhost: 'http://localhost:5001',
stringForMetamaps: 'http://metamaps.cc:5001', stringForMetamaps: 'http://metamaps.cc:5001',
stringForHeroku: 'http://gentle-savannah-1303.herokuapp.com', stringForHeroku: 'http://gentle-savannah-1303.herokuapp.com',
videoId: 'video-wrapper',
socket: null, socket: null,
webrtc: null,
readyToCall: false,
isOpen: false, isOpen: false,
changing: false, changing: false,
mappersOnMap: {}, 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 () { init: function () {
var self = Metamaps.Realtime; var self = Metamaps.Realtime;
@ -1935,10 +1939,54 @@ Metamaps.Realtime = {
var railsEnv = $('body').data('env'); var railsEnv = $('body').data('env');
var whichToConnect = railsEnv === 'development' ? self.stringForLocalhost : self.stringForHeroku; 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.socket.on('connect', function () {
self.startActiveMap(); 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) { toggleBox: function (event) {
var self = Metamaps.Realtime; var self = Metamaps.Realtime;
@ -2019,6 +2067,31 @@ Metamaps.Realtime = {
self.status = true; self.status = true;
$(".sidebarCollaborateIcon").addClass("blue"); $(".sidebarCollaborateIcon").addClass("blue");
$(".collabCompass").show(); $(".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) { turnOff: function (silent) {
var self = Metamaps.Realtime; var self = Metamaps.Realtime;
@ -2031,6 +2104,7 @@ Metamaps.Realtime = {
self.status = false; self.status = false;
$(".sidebarCollaborateIcon").removeClass("blue"); $(".sidebarCollaborateIcon").removeClass("blue");
$(".collabCompass").hide(); $(".collabCompass").hide();
$('#' + self.videoId).remove();
} }
}, },
setupSocket: function () { setupSocket: function () {
@ -2154,6 +2228,12 @@ Metamaps.Realtime = {
socket.on('mapChangeFromServer', self.mapChange); socket.on('mapChangeFromServer', self.mapChange);
}, },
createChat: function() {
var self = Metamaps.Realtime;
$('#wrapper').append(self.room.chat.$container);
//self.room.chat.open();
},
sendRealtimeOn: function () { sendRealtimeOn: function () {
var self = Metamaps.Realtime; var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket; var socket = Metamaps.Realtime.socket;

View 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;
})();

View 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;
})();

View 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;
})();

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Some files were not shown because too many files have changed in this diff Show more