Merge pull request #420 from metamaps/feature/realtime.video
JUUUUUUUUNTOOOO
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/ellipsis.gif
Normal file
After Width: | Height: | Size: 220 B |
BIN
app/assets/images/invitepeer16.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
app/assets/images/junto.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/assets/images/junto_spinner_dark.gif
Normal file
After Width: | Height: | Size: 32 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,9 +20,12 @@
|
||||||
//= 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
|
||||||
//= require_directory ./shims
|
//= require_directory ./shims
|
||||||
//= require_directory ./require
|
//= require_directory ./require
|
||||||
//= require_directory ./famous
|
//= require_directory ./famous
|
||||||
|
|
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
9808
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();
|
||||||
|
};
|
|
@ -6,7 +6,7 @@ Metamaps.Backbone.Map = Backbone.Model.extend({
|
||||||
return _.omit(this.attributes, this.blacklist);
|
return _.omit(this.attributes, this.blacklist);
|
||||||
},
|
},
|
||||||
save: function (key, val, options) {
|
save: function (key, val, options) {
|
||||||
|
|
||||||
var attrs;
|
var attrs;
|
||||||
|
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
||||||
|
@ -206,6 +206,26 @@ Metamaps.Backbone.MapsCollection = Backbone.Collection.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Metamaps.Backbone.Message = Backbone.Model.extend({
|
||||||
|
urlRoot: '/messages',
|
||||||
|
blacklist: ['created_at', 'updated_at'],
|
||||||
|
toJSON: function (options) {
|
||||||
|
return _.omit(this.attributes, this.blacklist);
|
||||||
|
},
|
||||||
|
prepareLiForFilter: function () {
|
||||||
|
/*var li = '';
|
||||||
|
li += '<li data-id="' + this.id.toString() + '">';
|
||||||
|
li += '<img src="' + this.get("image") + '" data-id="' + this.id.toString() + '"';
|
||||||
|
li += ' alt="' + this.get('name') + '" />';
|
||||||
|
li += '<p>' + this.get('name') + '</p></li>';
|
||||||
|
return li;*/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Metamaps.Backbone.MessageCollection = Backbone.Collection.extend({
|
||||||
|
model: Metamaps.Backbone.Message,
|
||||||
|
url: '/messages'
|
||||||
|
});
|
||||||
|
|
||||||
Metamaps.Backbone.Mapper = Backbone.Model.extend({
|
Metamaps.Backbone.Mapper = Backbone.Model.extend({
|
||||||
urlRoot: '/users',
|
urlRoot: '/users',
|
||||||
blacklist: ['created_at', 'updated_at'],
|
blacklist: ['created_at', 'updated_at'],
|
||||||
|
@ -224,4 +244,4 @@ Metamaps.Backbone.Mapper = Backbone.Model.extend({
|
||||||
Metamaps.Backbone.MapperCollection = Backbone.Collection.extend({
|
Metamaps.Backbone.MapperCollection = Backbone.Collection.extend({
|
||||||
model: Metamaps.Backbone.Mapper,
|
model: Metamaps.Backbone.Mapper,
|
||||||
url: '/users'
|
url: '/users'
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var Metamaps = {}; // this variable declaration defines a Javascript object that will contain all the variables and functions used by us, broken down into 'sub-modules' that look something like this
|
var Metamaps = {}; // this variable declaration defines a Javascript object that will contain all the variables and functions used by us, broken down into 'sub-modules' that look something like this
|
||||||
/*
|
/*
|
||||||
|
|
||||||
* unless you are on a page with the Javascript InfoVis Toolkit (Topic or Map) the only section in the metamaps
|
* unless you are on a page with the Javascript InfoVis Toolkit (Topic or Map) the only section in the metamaps
|
||||||
* object will be these
|
* object will be these
|
||||||
GlobalUI
|
GlobalUI
|
||||||
Active
|
Active
|
||||||
|
@ -32,7 +32,7 @@ Map
|
||||||
Mapper
|
Mapper
|
||||||
Topic
|
Topic
|
||||||
Synapse
|
Synapse
|
||||||
JIT
|
JIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Metamaps.Active = {
|
Metamaps.Active = {
|
||||||
|
@ -84,9 +84,9 @@ Metamaps.GlobalUI = {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#lightbox_screen, #lightbox_close').click(self.closeLightbox);
|
$('#lightbox_screen, #lightbox_close').click(self.closeLightbox);
|
||||||
|
|
||||||
// initialize global backbone models and collections
|
// initialize global backbone models and collections
|
||||||
if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper);
|
if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper);
|
||||||
|
|
||||||
|
@ -107,26 +107,26 @@ Metamaps.GlobalUI = {
|
||||||
},
|
},
|
||||||
openLightbox: function (which) {
|
openLightbox: function (which) {
|
||||||
var self = Metamaps.GlobalUI;
|
var self = Metamaps.GlobalUI;
|
||||||
|
|
||||||
$('.lightboxContent').hide();
|
$('.lightboxContent').hide();
|
||||||
$('#' + which).show();
|
$('#' + which).show();
|
||||||
|
|
||||||
self.lightbox = which;
|
self.lightbox = which;
|
||||||
|
|
||||||
$('#lightbox_overlay').show();
|
$('#lightbox_overlay').show();
|
||||||
|
|
||||||
var heightOfContent = '-' + ($('#lightbox_main').height() / 2) + 'px';
|
var heightOfContent = '-' + ($('#lightbox_main').height() / 2) + 'px';
|
||||||
// animate the content in from the bottom
|
// animate the content in from the bottom
|
||||||
$('#lightbox_main').animate({
|
$('#lightbox_main').animate({
|
||||||
'top': '50%',
|
'top': '50%',
|
||||||
'margin-top': heightOfContent
|
'margin-top': heightOfContent
|
||||||
}, 200, 'easeOutCubic');
|
}, 200, 'easeOutCubic');
|
||||||
|
|
||||||
// fade the black overlay in
|
// fade the black overlay in
|
||||||
$('#lightbox_screen').animate({
|
$('#lightbox_screen').animate({
|
||||||
'opacity': '0.42'
|
'opacity': '0.42'
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
if (which == "switchMetacodes") {
|
if (which == "switchMetacodes") {
|
||||||
Metamaps.Create.isSwitchingSet = true;
|
Metamaps.Create.isSwitchingSet = true;
|
||||||
}
|
}
|
||||||
|
@ -134,22 +134,22 @@ Metamaps.GlobalUI = {
|
||||||
|
|
||||||
closeLightbox: function (event) {
|
closeLightbox: function (event) {
|
||||||
var self = Metamaps.GlobalUI;
|
var self = Metamaps.GlobalUI;
|
||||||
|
|
||||||
if (event) event.preventDefault();
|
if (event) event.preventDefault();
|
||||||
|
|
||||||
// animate the lightbox content offscreen
|
// animate the lightbox content offscreen
|
||||||
$('#lightbox_main').animate({
|
$('#lightbox_main').animate({
|
||||||
'top': '100%',
|
'top': '100%',
|
||||||
'margin-top': '0'
|
'margin-top': '0'
|
||||||
}, 200, 'easeInCubic');
|
}, 200, 'easeInCubic');
|
||||||
|
|
||||||
// fade the black overlay out
|
// fade the black overlay out
|
||||||
$('#lightbox_screen').animate({
|
$('#lightbox_screen').animate({
|
||||||
'opacity': '0.0'
|
'opacity': '0.0'
|
||||||
}, 200, function () {
|
}, 200, function () {
|
||||||
$('#lightbox_overlay').hide();
|
$('#lightbox_overlay').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (self.lightbox === 'forkmap') Metamaps.GlobalUI.CreateMap.reset('fork_map');
|
if (self.lightbox === 'forkmap') Metamaps.GlobalUI.CreateMap.reset('fork_map');
|
||||||
if (self.lightbox === 'newmap') Metamaps.GlobalUI.CreateMap.reset('new_map');
|
if (self.lightbox === 'newmap') Metamaps.GlobalUI.CreateMap.reset('new_map');
|
||||||
if (Metamaps.Create && Metamaps.Create.isSwitchingSet) {
|
if (Metamaps.Create && Metamaps.Create.isSwitchingSet) {
|
||||||
|
@ -160,14 +160,27 @@ Metamaps.GlobalUI = {
|
||||||
notifyUser: function (message, leaveOpen) {
|
notifyUser: function (message, leaveOpen) {
|
||||||
var self = Metamaps.GlobalUI;
|
var self = Metamaps.GlobalUI;
|
||||||
|
|
||||||
Metamaps.Famous.toast.surf.setContent(message);
|
function famousReady() {
|
||||||
Metamaps.Famous.toast.show();
|
Metamaps.Famous.toast.surf.setContent(message);
|
||||||
clearTimeout(self.notifyTimeOut);
|
Metamaps.Famous.toast.show();
|
||||||
if (!leaveOpen) {
|
clearTimeout(self.notifyTimeOut);
|
||||||
self.notifyTimeOut = setTimeout(function () {
|
if (!leaveOpen) {
|
||||||
Metamaps.Famous.toast.hide();
|
self.notifyTimeOut = setTimeout(function () {
|
||||||
}, 8000);
|
Metamaps.Famous.toast.hide();
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize the famous ui
|
||||||
|
var callFamous = function(){
|
||||||
|
if (Metamaps.Famous && Metamaps.Famous.toast) {
|
||||||
|
famousReady();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setTimeout(callFamous, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callFamous();
|
||||||
},
|
},
|
||||||
clearNotify: function() {
|
clearNotify: function() {
|
||||||
var self = Metamaps.GlobalUI;
|
var self = Metamaps.GlobalUI;
|
||||||
|
@ -198,13 +211,13 @@ Metamaps.GlobalUI.CreateMap = {
|
||||||
},
|
},
|
||||||
bindFormEvents: function () {
|
bindFormEvents: function () {
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
var self = Metamaps.GlobalUI.CreateMap;
|
||||||
|
|
||||||
$('.new_map button.cancel').unbind().bind('click', function (event) {
|
$('.new_map button.cancel').unbind().bind('click', function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Metamaps.GlobalUI.closeLightbox();
|
Metamaps.GlobalUI.closeLightbox();
|
||||||
});
|
});
|
||||||
$('.new_map button.submitMap').unbind().bind('click', self.submit);
|
$('.new_map button.submitMap').unbind().bind('click', self.submit);
|
||||||
|
|
||||||
// bind permission changer events on the createMap form
|
// bind permission changer events on the createMap form
|
||||||
$('.permIcon').unbind().bind('click', self.switchPermission);
|
$('.permIcon').unbind().bind('click', self.switchPermission);
|
||||||
},
|
},
|
||||||
|
@ -224,17 +237,17 @@ Metamaps.GlobalUI.CreateMap = {
|
||||||
},
|
},
|
||||||
switchPermission: function () {
|
switchPermission: function () {
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
var self = Metamaps.GlobalUI.CreateMap;
|
||||||
|
|
||||||
self.newMap.set('permission', $(this).attr('data-permission'));
|
self.newMap.set('permission', $(this).attr('data-permission'));
|
||||||
$(this).siblings('.permIcon').find('.mapPermIcon').removeClass('selected');
|
$(this).siblings('.permIcon').find('.mapPermIcon').removeClass('selected');
|
||||||
$(this).find('.mapPermIcon').addClass('selected');
|
$(this).find('.mapPermIcon').addClass('selected');
|
||||||
|
|
||||||
var permText = $(this).find('.tip').html();
|
var permText = $(this).find('.tip').html();
|
||||||
$(this).parents('.new_map').find('.permText').html(permText);
|
$(this).parents('.new_map').find('.permText').html(permText);
|
||||||
},
|
},
|
||||||
submit: function (event) {
|
submit: function (event) {
|
||||||
if (event) event.preventDefault();
|
if (event) event.preventDefault();
|
||||||
|
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
var self = Metamaps.GlobalUI.CreateMap;
|
||||||
|
|
||||||
if (Metamaps.GlobalUI.lightbox === 'forkmap') {
|
if (Metamaps.GlobalUI.lightbox === 'forkmap') {
|
||||||
|
@ -257,7 +270,7 @@ Metamaps.GlobalUI.CreateMap = {
|
||||||
success: self.success
|
success: self.success
|
||||||
// TODO add error message
|
// TODO add error message
|
||||||
});
|
});
|
||||||
|
|
||||||
Metamaps.GlobalUI.closeLightbox();
|
Metamaps.GlobalUI.closeLightbox();
|
||||||
Metamaps.GlobalUI.notifyUser('Working...');
|
Metamaps.GlobalUI.notifyUser('Working...');
|
||||||
},
|
},
|
||||||
|
@ -281,19 +294,19 @@ Metamaps.GlobalUI.CreateMap = {
|
||||||
|
|
||||||
//push the new map onto the collection of 'my maps'
|
//push the new map onto the collection of 'my maps'
|
||||||
Metamaps.Maps.Mine.add(model);
|
Metamaps.Maps.Mine.add(model);
|
||||||
|
|
||||||
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map';
|
||||||
var form = $(formId);
|
var form = $(formId);
|
||||||
|
|
||||||
Metamaps.GlobalUI.clearNotify();
|
Metamaps.GlobalUI.clearNotify();
|
||||||
$('#wrapper').append(self.generateSuccessMessage(model.id));
|
$('#wrapper').append(self.generateSuccessMessage(model.id));
|
||||||
|
|
||||||
},
|
},
|
||||||
reset: function (id) {
|
reset: function (id) {
|
||||||
var self = Metamaps.GlobalUI.CreateMap;
|
var self = Metamaps.GlobalUI.CreateMap;
|
||||||
|
|
||||||
var form = $('#' + id);
|
var form = $('#' + id);
|
||||||
|
|
||||||
if (id === "fork_map") {
|
if (id === "fork_map") {
|
||||||
self.topicsToMap = [];
|
self.topicsToMap = [];
|
||||||
self.synapsesToMap = [];
|
self.synapsesToMap = [];
|
||||||
|
@ -302,7 +315,7 @@ Metamaps.GlobalUI.CreateMap = {
|
||||||
else {
|
else {
|
||||||
form.html(self.emptyMapForm);
|
form.html(self.emptyMapForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bindFormEvents();
|
self.bindFormEvents();
|
||||||
self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' });
|
self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' });
|
||||||
|
|
||||||
|
@ -318,7 +331,7 @@ Metamaps.GlobalUI.Account = {
|
||||||
var self = Metamaps.GlobalUI.Account;
|
var self = Metamaps.GlobalUI.Account;
|
||||||
|
|
||||||
$('.sidebarAccountIcon').click(self.toggleBox);
|
$('.sidebarAccountIcon').click(self.toggleBox);
|
||||||
$('.sidebarAccountBox').click(function(event){
|
$('.sidebarAccountBox').click(function(event){
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
$('body').click(self.close);
|
$('body').click(self.close);
|
||||||
|
@ -334,7 +347,6 @@ Metamaps.GlobalUI.Account = {
|
||||||
open: function () {
|
open: function () {
|
||||||
var self = Metamaps.GlobalUI.Account;
|
var self = Metamaps.GlobalUI.Account;
|
||||||
|
|
||||||
Metamaps.Realtime.close();
|
|
||||||
Metamaps.Filter.close();
|
Metamaps.Filter.close();
|
||||||
$('.sidebarAccountIcon .tooltipsUnder').addClass('hide');
|
$('.sidebarAccountIcon .tooltipsUnder').addClass('hide');
|
||||||
|
|
||||||
|
@ -414,7 +426,7 @@ Metamaps.GlobalUI.Search = {
|
||||||
self.close(0, true);
|
self.close(0, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break; //console.log(e.which);
|
break; //console.log(e.which);
|
||||||
}
|
}
|
||||||
|
@ -480,7 +492,7 @@ Metamaps.GlobalUI.Search = {
|
||||||
var topics = {
|
var topics = {
|
||||||
name: 'topics',
|
name: 'topics',
|
||||||
limit: 9999,
|
limit: 9999,
|
||||||
|
|
||||||
display: function(s) { return s.label; },
|
display: function(s) { return s.label; },
|
||||||
templates: {
|
templates: {
|
||||||
notFound: function(s) {
|
notFound: function(s) {
|
||||||
|
@ -601,7 +613,7 @@ Metamaps.GlobalUI.Search = {
|
||||||
|
|
||||||
// tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on
|
// tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on
|
||||||
$('.sidebarSearchField').bind('typeahead:select', self.handleResultClick);
|
$('.sidebarSearchField').bind('typeahead:select', self.handleResultClick);
|
||||||
|
|
||||||
// don't do it, if they clicked on a 'addToMap' button
|
// don't do it, if they clicked on a 'addToMap' button
|
||||||
$('.sidebarSearch button.addToMap').click(function (event) {
|
$('.sidebarSearch button.addToMap').click(function (event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
|
@ -1047,7 +1047,6 @@ Metamaps.JIT = {
|
||||||
Metamaps.TopicCard.hideCard();
|
Metamaps.TopicCard.hideCard();
|
||||||
Metamaps.SynapseCard.hideCard();
|
Metamaps.SynapseCard.hideCard();
|
||||||
Metamaps.Create.newTopic.hide();
|
Metamaps.Create.newTopic.hide();
|
||||||
|
|
||||||
$('.rightclickmenu').remove();
|
$('.rightclickmenu').remove();
|
||||||
// reset the draw synapse positions to false
|
// reset the draw synapse positions to false
|
||||||
Metamaps.Mouse.synapseStartCoordinates = [];
|
Metamaps.Mouse.synapseStartCoordinates = [];
|
||||||
|
|
339
app/assets/javascripts/src/views/chatView.js.erb
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
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 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.$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/sounds.mp3' %>", "<%= asset_path 'sounds/sounds.ogg' %>"],
|
||||||
|
sprite: {
|
||||||
|
laser: [3000, 700]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
incrementUnread: function() {
|
||||||
|
this.unreadMessages++;
|
||||||
|
this.$unread.html(this.unreadMessages);
|
||||||
|
this.$unread.show();
|
||||||
|
},
|
||||||
|
addMessage: function(message, isInitial) {
|
||||||
|
|
||||||
|
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 (!isInitial && 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], 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 = false; // 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) {
|
||||||
|
this.messages.add(message);
|
||||||
|
Private.addMessage.call(this, message, isInitial);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
})();
|
194
app/assets/javascripts/src/views/room.js
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
$(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) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
messages.models.forEach(function (message) {
|
||||||
|
self.chat.addMessage(message, isInitial);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
room.events = {
|
||||||
|
newMessage: "Room:newMessage"
|
||||||
|
};
|
||||||
|
|
||||||
|
return room;
|
||||||
|
})();
|
207
app/assets/javascripts/src/views/videoView.js
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
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;
|
||||||
|
})();
|
|
@ -76,7 +76,7 @@ body,
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background: #d8d9da url(<%= asset_data_uri('shattered_@2X.png') %>);
|
background: #d8d9da url(<%= asset_data_uri('shattered_@2X.png') %>);
|
||||||
font-family: 'din-medium', helvetica, sans-serif;
|
font-family: 'din-medium', helvetica, sans-serif;
|
||||||
|
@ -132,6 +132,17 @@ a.button:active,
|
||||||
input[type="submit"]:active {
|
input[type="submit"]:active {
|
||||||
background: #429B46;
|
background: #429B46;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.button.btn-no {
|
||||||
|
background-color: #c04f4f;
|
||||||
|
}
|
||||||
|
button.button.btn-no:hover {
|
||||||
|
background-color: #A54242;
|
||||||
|
}
|
||||||
|
.toast .toast-button {
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Utility
|
* Utility
|
||||||
*/
|
*/
|
||||||
|
@ -628,8 +639,12 @@ label {
|
||||||
margin: 0 0 0 1.3em;
|
margin: 0 0 0 1.3em;
|
||||||
}
|
}
|
||||||
.main {
|
.main {
|
||||||
|
position: relative;
|
||||||
/*overflow:hidden; */
|
/*overflow:hidden; */
|
||||||
}
|
}
|
||||||
|
.main.compressed {
|
||||||
|
width: calc(100% - 300px);
|
||||||
|
}
|
||||||
#infovis-canvas {
|
#infovis-canvas {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
@ -713,7 +728,7 @@ label {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
.accountInnerArrow {
|
.accountInnerArrow {
|
||||||
|
@ -1022,7 +1037,7 @@ h3.filterBox {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.sidebarFilterBox li:hover {
|
.sidebarFilterBox li:hover {
|
||||||
|
|
||||||
}
|
}
|
||||||
#filter_by_mapper li img {
|
#filter_by_mapper li img {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
@ -1067,94 +1082,16 @@ h3.filterBox {
|
||||||
margin-top:8px;
|
margin-top:8px;
|
||||||
}
|
}
|
||||||
#filter_by_metacode {
|
#filter_by_metacode {
|
||||||
|
|
||||||
}
|
}
|
||||||
#filter_by_mapper {
|
#filter_by_mapper {
|
||||||
|
|
||||||
}
|
}
|
||||||
#filter_by_synapse {
|
#filter_by_synapse {
|
||||||
|
|
||||||
}
|
}
|
||||||
/* end filter by metacode */
|
/* end filter by metacode */
|
||||||
|
|
||||||
/* collaborate */
|
|
||||||
|
|
||||||
.sidebarCollaborate {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
.sidebarCollaborateBox {
|
|
||||||
display: none;
|
|
||||||
height: auto;
|
|
||||||
padding: 16px;
|
|
||||||
width: 238px;
|
|
||||||
}
|
|
||||||
h3.realtimeBoxTitle {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-align: left;
|
|
||||||
float: left;
|
|
||||||
font-size:18px;
|
|
||||||
line-height:18px;
|
|
||||||
}
|
|
||||||
.sidebarCollaborateBox .realtimeOnOff {
|
|
||||||
float: right;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
margin-left: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
font-size:12px;
|
|
||||||
}
|
|
||||||
.sidebarCollaborateBox .realtimeOnOff:hover, .sidebarCollaborateBox .realtimeOnOff.active {
|
|
||||||
color: #00bcd4;
|
|
||||||
}
|
|
||||||
.sidebarCollaborateBox .rtOff {
|
|
||||||
|
|
||||||
}
|
|
||||||
.sidebarCollaborateBox .rtOn {
|
|
||||||
|
|
||||||
}
|
|
||||||
.realtimeMapperList .rtMapper {
|
|
||||||
list-style-type: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
padding: 10px 34px;
|
|
||||||
display: block;
|
|
||||||
height: 14px;
|
|
||||||
font-family: 'din-regular', helvetica, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 14px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rtMapperSelf img {
|
|
||||||
border: 2px solid #424242;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rtUserImage {
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
left: 0;
|
|
||||||
border-radius: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.littleJuntoIcon {
|
|
||||||
width: 24px;
|
|
||||||
height:24px;
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
right: 0;
|
|
||||||
background-image: url(<%= asset_data_uri('junto24_sprite.png') %>);
|
|
||||||
}
|
|
||||||
.realtimeMapperList .littleRtOff .littleJuntoIcon {
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
.realtimeMapperList .littleRtOn .littleJuntoIcon {
|
|
||||||
background-position: -24px 0;
|
|
||||||
}
|
|
||||||
/* end collaborate */
|
|
||||||
|
|
||||||
.nodemargin {
|
.nodemargin {
|
||||||
padding-top: 120px;
|
padding-top: 120px;
|
||||||
}
|
}
|
||||||
|
@ -1189,7 +1126,7 @@ h3.realtimeBoxTitle {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.rightclickmenu > ul {
|
.rightclickmenu > ul {
|
||||||
|
|
||||||
}
|
}
|
||||||
.rightclickmenu li {
|
.rightclickmenu li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -1266,7 +1203,7 @@ h3.realtimeBoxTitle {
|
||||||
.rc-metacode li img {
|
.rc-metacode li img {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.rightclickmenu .rc-permission ul,
|
.rightclickmenu .rc-permission ul,
|
||||||
.rightclickmenu .rc-metacode ul,
|
.rightclickmenu .rc-metacode ul,
|
||||||
.rightclickmenu .rc-siblings ul {
|
.rightclickmenu .rc-siblings ul {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -1279,7 +1216,7 @@ h3.realtimeBoxTitle {
|
||||||
border-top-right-radius: 2px;
|
border-top-right-radius: 2px;
|
||||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.12), 0 3px 3px rgba(0,0,0,0.24);
|
box-shadow: 0px 3px 3px rgba(0,0,0,0.12), 0 3px 3px rgba(0,0,0,0.24);
|
||||||
}
|
}
|
||||||
.rightclickmenu .rc-permission:hover > ul,
|
.rightclickmenu .rc-permission:hover > ul,
|
||||||
.rightclickmenu .rc-metacode:hover > ul,
|
.rightclickmenu .rc-metacode:hover > ul,
|
||||||
.rightclickmenu .rc-siblings:hover > ul {
|
.rightclickmenu .rc-siblings:hover > ul {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -1288,7 +1225,7 @@ h3.realtimeBoxTitle {
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
}
|
}
|
||||||
.rightclickmenu li.changeP {
|
.rightclickmenu li.changeP {
|
||||||
|
|
||||||
}
|
}
|
||||||
.rightclickmenu li.changeP .rc-perm-icon {
|
.rightclickmenu li.changeP .rc-perm-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1350,13 +1287,13 @@ h3.realtimeBoxTitle {
|
||||||
top: -7px;
|
top: -7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.moveMenusUp .rc-metacode ul,
|
.moveMenusUp .rc-metacode ul,
|
||||||
.moveMenusUp .rc-permission ul,
|
.moveMenusUp .rc-permission ul,
|
||||||
.moveMenusUp .rc-siblings ul {
|
.moveMenusUp .rc-siblings ul {
|
||||||
top: auto;
|
top: auto;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
.moveMenusToLeft .rc-metacode ul,
|
.moveMenusToLeft .rc-metacode ul,
|
||||||
.moveMenusToLeft .rc-permission ul,
|
.moveMenusToLeft .rc-permission ul,
|
||||||
.moveMenusToLeft .rc-siblings ul {
|
.moveMenusToLeft .rc-siblings ul {
|
||||||
left: auto;
|
left: auto;
|
||||||
|
@ -1379,7 +1316,7 @@ h3.realtimeBoxTitle {
|
||||||
#new_synapse .tt-suggestion.tt-cursor {
|
#new_synapse .tt-suggestion.tt-cursor {
|
||||||
background: #E0E0E0;
|
background: #E0E0E0;
|
||||||
}
|
}
|
||||||
#new_topic .tt-suggestion,
|
#new_topic .tt-suggestion,
|
||||||
#new_synapse .tt-dataset h3,
|
#new_synapse .tt-dataset h3,
|
||||||
#new_synapse .tt-suggestion {
|
#new_synapse .tt-suggestion {
|
||||||
background: #F5F5F5;
|
background: #F5F5F5;
|
||||||
|
@ -1403,7 +1340,7 @@ h3.realtimeBoxTitle {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 9px 0 9px 4px;
|
padding: 9px 0 9px 4px;
|
||||||
}
|
}
|
||||||
#new_synapse .synapseDesc,
|
#new_synapse .synapseDesc,
|
||||||
#new_synapse .genericSynapseDesc {
|
#new_synapse .genericSynapseDesc {
|
||||||
width: 190px;
|
width: 190px;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
|
@ -2041,7 +1978,7 @@ and it won't be important on password protected instances */
|
||||||
}
|
}
|
||||||
|
|
||||||
#svi1 > p, #svi2 > p {
|
#svi1 > p, #svi2 > p {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
.lightboxContent {
|
.lightboxContent {
|
||||||
font-family: 'din-regular';
|
font-family: 'din-regular';
|
||||||
|
@ -2120,7 +2057,7 @@ and it won't be important on password protected instances */
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
.ui-tabs-vertical {
|
.ui-tabs-vertical {
|
||||||
|
|
||||||
}
|
}
|
||||||
.ui-tabs-vertical .ui-tabs-nav {
|
.ui-tabs-vertical .ui-tabs-nav {
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -2294,7 +2231,7 @@ and it won't be important on password protected instances */
|
||||||
|
|
||||||
#colophon a {
|
#colophon a {
|
||||||
color: #c04f4f;
|
color: #c04f4f;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#colophonCols {
|
#colophonCols {
|
||||||
|
@ -2365,15 +2302,15 @@ and it won't be important on password protected instances */
|
||||||
background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat -220px 0;
|
background: url(<%= asset_data_uri 'browser_icons.png' %>) no-repeat -220px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chromeIcon:hover{
|
#chromeIcon:hover{
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#fireFoxIcon:hover{
|
#fireFoxIcon:hover{
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#safariIcon:hover{
|
#safariIcon:hover{
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2593,7 +2530,7 @@ and it won't be important on password protected instances */
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
font-family: 'din-regular';
|
font-family: 'din-regular';
|
||||||
}
|
}
|
||||||
|
|
||||||
#cheatSheet .csItem a {
|
#cheatSheet .csItem a {
|
||||||
|
@ -2793,7 +2730,7 @@ and it won't be important on password protected instances */
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
.blackBox td.iconColor {
|
.blackBox td.iconColor {
|
||||||
|
|
||||||
}
|
}
|
||||||
.blackBox .field {
|
.blackBox .field {
|
||||||
margin: 15px 0 5px;
|
margin: 15px 0 5px;
|
||||||
|
@ -2919,7 +2856,7 @@ and it won't be important on password protected instances */
|
||||||
display: none;
|
display: none;
|
||||||
background-color: #4fb5c0;
|
background-color: #4fb5c0;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
padding: 2px 8px 2px 8px;
|
padding: 2px 8px 2px 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 7px;
|
top: 7px;
|
||||||
left:28px;
|
left:28px;
|
||||||
|
@ -2952,7 +2889,7 @@ and it won't be important on password protected instances */
|
||||||
background-position: 16px 8px;
|
background-position: 16px 8px;
|
||||||
-webkit-transform-origin: left center;
|
-webkit-transform-origin: left center;
|
||||||
transform-origin: left center;
|
transform-origin: left center;
|
||||||
}
|
}
|
||||||
.blockchain-btn {
|
.blockchain-btn {
|
||||||
display: inline;
|
display: inline;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#famousOverlay {
|
#famousOverlay {
|
||||||
position:fixed;
|
position:absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
|
|
||||||
/* upperLeftUI */
|
/* upperLeftUI */
|
||||||
.upperLeftUI {
|
.upperLeftUI {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 24px;
|
left: 24px;
|
||||||
z-index:3;
|
z-index:3;
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
/* upperRightUI */
|
/* upperRightUI */
|
||||||
|
|
||||||
.upperRightUI {
|
.upperRightUI {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
z-index:4;
|
z-index:4;
|
||||||
|
@ -166,9 +166,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.upperRightBox {
|
.upperRightBox {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top:52px;
|
top:42px;
|
||||||
right:24px;
|
right:0;
|
||||||
background-color: #E0E0E0;
|
background-color: #E0E0E0;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
box-shadow: 0 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||||
|
@ -187,17 +187,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.upperRightMapButtons {
|
.upperRightMapButtons {
|
||||||
position: relative;
|
|
||||||
top: -42px; /* puts it just offscreen */
|
top: -42px; /* puts it just offscreen */
|
||||||
}
|
}
|
||||||
.mapPage .upperRightMapButtons, .topicPage .upperRightMapButtons {
|
.mapPage .upperRightMapButtons, .topicPage .upperRightMapButtons {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topicPage .sidebarCollaborate {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upperRightIcon {
|
.upperRightIcon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
@ -205,20 +200,6 @@
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.sidebarCollaborateIcon {
|
|
||||||
background-position: 0 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.sidebarCollaborateIcon.blue {
|
|
||||||
background-position: -32px 0;
|
|
||||||
}
|
|
||||||
.sidebarCollaborateIcon.blue:hover {
|
|
||||||
background-position: -32px -32px;
|
|
||||||
}
|
|
||||||
/* only show the collaborate icon on commons */
|
|
||||||
.commonsMap .sidebarCollaborateIcon {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.sidebarFilterIcon {
|
.sidebarFilterIcon {
|
||||||
background-position: -64px 0;
|
background-position: -64px 0;
|
||||||
}
|
}
|
||||||
|
@ -384,7 +365,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoAndHelp {
|
.infoAndHelp {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
@ -424,14 +405,14 @@
|
||||||
/* mapControls */
|
/* mapControls */
|
||||||
|
|
||||||
.mapControls {
|
.mapControls {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
bottom: 24px;
|
bottom: 24px;
|
||||||
right:-32px; /* puts it just offscreen */
|
right:-32px; /* puts it just offscreen */
|
||||||
width:32px;
|
width:32px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
.mapPage .mapControls, .topicPage .mapControls {
|
.mapPage .mapControls, .topicPage .mapControls {
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topicPage .zoomExtents {
|
.topicPage .zoomExtents {
|
||||||
|
@ -474,14 +455,13 @@
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarCollaborateIcon:hover .tooltipsUnder,
|
.zoomExtents:hover .tooltips, .zoomIn:hover .tooltips, .zoomOut:hover .tooltips, .takeScreenshot:hover .tooltips, .sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
||||||
.sidebarFilterIcon:hover .tooltipsUnder, .sidebarForkIcon:hover .tooltipsUnder, .addMap:hover .tooltipsUnder, .authenticated .sidebarAccountIcon:hover .tooltipsUnder,
|
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove, .chat-button:hover .tooltips {
|
||||||
.mapInfoIcon:hover .tooltipsAbove, .openCheatsheet:hover .tooltipsAbove {
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltips {
|
.tooltips {
|
||||||
|
@ -532,10 +512,6 @@
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarCollaborateIcon .tooltipsUnder {
|
|
||||||
margin-left: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarFilterIcon .tooltipsUnder {
|
.sidebarFilterIcon .tooltipsUnder {
|
||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
}
|
}
|
||||||
|
@ -560,16 +536,20 @@
|
||||||
left: -11px;
|
left: -11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-button .tooltips {
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.openCheatsheet .tooltipsAbove {
|
.openCheatsheet .tooltipsAbove {
|
||||||
left: -4px;
|
left: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarAccountIcon .tooltipsUnder {
|
.sidebarAccountIcon .tooltipsUnder {
|
||||||
margin-left: -8px;
|
margin-left: -12px;
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomExtents div::after, .zoomIn div::after, .zoomOut div::after, .takeScreenshot div:after {
|
.zoomExtents div::after, .zoomIn div::after, .zoomOut div::after, .takeScreenshot div:after, .chat-button div.tooltips::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 57%;
|
top: 57%;
|
||||||
|
@ -582,21 +562,20 @@
|
||||||
border-bottom: 5px solid transparent;
|
border-bottom: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarCollaborateIcon div:after, .sidebarFilterIcon div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
.sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
||||||
left: 38%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarCollaborateIcon div:after, .sidebarFilterIcon div:after, .sidebarForkIcon div:after, .addMap div:after, .sidebarAccountIcon .tooltipsUnder:after {
|
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 129%;
|
right: 40%;
|
||||||
margin-top: -30px;
|
margin-top: -7px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-bottom: 4px solid #000000;
|
border-bottom: 4px solid #000000;
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
}
|
}
|
||||||
|
.sidebarFilterIcon div:after {
|
||||||
|
right: 37% !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mapInfoIcon div:after, .openCheatsheet div:after {
|
.mapInfoIcon div:after, .openCheatsheet div:after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -735,7 +714,7 @@
|
||||||
color: #F5F5F5;
|
color: #F5F5F5;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
z-index: 1 !important; /* important necessary for firefox */
|
z-index: 4 !important; /* important necessary for firefox */
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height:14px;
|
line-height:14px;
|
||||||
}
|
}
|
||||||
|
@ -764,3 +743,11 @@ box-shadow: 0px 1px 1.5px rgba(0,0,0,0.12), 0 1px 1px rgba(0,0,0,0.24);
|
||||||
body a#barometer_tab:hover {
|
body a#barometer_tab:hover {
|
||||||
background-position: 0 -110px;
|
background-position: 0 -110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideVideos .collaborator-video {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideCursors .collabCompass {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
348
app/assets/stylesheets/junto.css.erb
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
.collaborator-video {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
cursor: default;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.collaborator-video .video-receive {
|
||||||
|
position: absolute;
|
||||||
|
width: 160px;
|
||||||
|
padding: 20px 20px 20px 170px;
|
||||||
|
background: #424242;
|
||||||
|
height: 110px;
|
||||||
|
border-top-left-radius: 75px;
|
||||||
|
border-bottom-left-radius: 75px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
}
|
||||||
|
.collaborator-video .video-receive .video-statement {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.collaborator-video .video-receive .btn-group .btn-yes {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.collaborator-video .video-receive .btn-group .btn-no {
|
||||||
|
background-color: #c04f4f;
|
||||||
|
}
|
||||||
|
.collaborator-video .video-receive .btn-group .btn-no:hover {
|
||||||
|
background-color: #A54242;
|
||||||
|
}
|
||||||
|
.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(<%= asset_path '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(<%= asset_path '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: 30px;
|
||||||
|
top: 72px;
|
||||||
|
}
|
||||||
|
.chat-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 1;
|
||||||
|
width: 300px;
|
||||||
|
float: right;
|
||||||
|
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(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.chat-box .chat-button.active {
|
||||||
|
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
||||||
|
}
|
||||||
|
.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(<%= asset_path '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(<%= asset_path '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%;
|
||||||
|
min-height: 150px;
|
||||||
|
padding: 16px 0px 16px 0px;
|
||||||
|
text-align: left;
|
||||||
|
color: #f5f5f5;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.chat-box .participants .conversation-live {
|
||||||
|
display: none;
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
background: #c04f4f;
|
||||||
|
margin: 5px 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.chat-box .participants .conversation-live .call-action {
|
||||||
|
float: right;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #EBFF00;
|
||||||
|
}
|
||||||
|
.chat-box .participants .conversation-live .leave {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.chat-box .participants.is-participating .conversation-live .leave {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.chat-box .participants.is-participating .conversation-live .join {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.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 {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant .chat-participant-name {
|
||||||
|
width: 53%;
|
||||||
|
float: left;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 2px 8px 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant.is-self .chat-participant-invite-call,
|
||||||
|
.chat-box .participants .participant.is-self .chat-participant-invite-join {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.chat-box .participants.is-live .participant .chat-participant-invite-call {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant .chat-participant-invite-join {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.chat-box .participants.is-live.is-participating .participant:not(.active) .chat-participant-invite-join {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant .chat-participant-invite-call,
|
||||||
|
.chat-box .participants .participant .chat-participant-invite-join
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant.pending .chat-participant-invite-call,
|
||||||
|
.chat-box .participants .participant.pending .chat-participant-invite-join {
|
||||||
|
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant .chat-participant-participating {
|
||||||
|
float: right;
|
||||||
|
display: none;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
||||||
|
background: #4fc059;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.chat-box .participants .participant.active .chat-participant-participating {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.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 {
|
||||||
|
display: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 32px;
|
||||||
|
margin-top: -2px;
|
||||||
|
float: right;
|
||||||
|
background: url(<%= asset_path '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 {
|
||||||
|
min-height: 80px;
|
||||||
|
width: 94%;
|
||||||
|
padding: 8px 3% 8px 3%;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
.chat-box .chat-messages {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 0px 0px 0px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -13,11 +13,11 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
# this is for global login
|
# this is for global login
|
||||||
include ContentHelper
|
include ContentHelper
|
||||||
|
|
||||||
helper_method :user
|
helper_method :user
|
||||||
helper_method :authenticated?
|
helper_method :authenticated?
|
||||||
helper_method :admin?
|
helper_method :admin?
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
def after_sign_in_path_for(resource)
|
||||||
sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => 'https')
|
sign_in_url = url_for(:action => 'new', :controller => 'sessions', :only_path => false, :protocol => 'https')
|
||||||
|
|
||||||
|
@ -42,29 +42,29 @@ private
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_user
|
def require_user
|
||||||
unless authenticated?
|
unless authenticated?
|
||||||
redirect_to new_user_session_path, notice: "You must be logged in."
|
redirect_to new_user_session_path, notice: "You must be logged in."
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_admin
|
def require_admin
|
||||||
unless authenticated? && admin?
|
unless authenticated? && admin?
|
||||||
redirect_to root_url, notice: "You need to be an admin for that."
|
redirect_to root_url, notice: "You need to be an admin for that."
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user
|
def user
|
||||||
current_user
|
current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticated?
|
def authenticated?
|
||||||
current_user
|
current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin?
|
def admin?
|
||||||
authenticated? && current_user.admin
|
authenticated? && current_user.admin
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ class MapsController < ApplicationController
|
||||||
after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :usermaps]
|
after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :usermaps]
|
||||||
after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :usermaps]
|
after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :usermaps]
|
||||||
|
|
||||||
respond_to :html, :json
|
respond_to :html, :json, :csv
|
||||||
|
|
||||||
autocomplete :map, :name, :full => true, :extra_data => [:user_id]
|
autocomplete :map, :name, :full => true, :extra_data => [:user_id]
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class MapsController < ApplicationController
|
||||||
authorize @map
|
authorize @map
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {
|
format.html {
|
||||||
@allmappers = @map.contributors
|
@allmappers = @map.contributors
|
||||||
@alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
|
@alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
|
||||||
@allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) }
|
@allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) }
|
||||||
|
@ -80,10 +80,13 @@ class MapsController < ApplicationController
|
||||||
object = m.mappable
|
object = m.mappable
|
||||||
!object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id)))
|
!object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id)))
|
||||||
}
|
}
|
||||||
|
@allmessages = @map.messages.sort_by(&:created_at)
|
||||||
|
|
||||||
respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @map)
|
respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @allmessages, @map)
|
||||||
}
|
}
|
||||||
format.json { render json: @map }
|
format.json { render json: @map }
|
||||||
|
format.csv { send_data @map.to_csv }
|
||||||
|
format.xls
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -106,6 +109,7 @@ class MapsController < ApplicationController
|
||||||
@json['synapses'] = @allsynapses
|
@json['synapses'] = @allsynapses
|
||||||
@json['mappings'] = @allmappings
|
@json['mappings'] = @allmappings
|
||||||
@json['mappers'] = @allmappers
|
@json['mappers'] = @allmappers
|
||||||
|
@json['messages'] = @map.messages.sort_by(&:created_at)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json { render json: @json }
|
format.json { render json: @json }
|
||||||
|
@ -120,7 +124,7 @@ class MapsController < ApplicationController
|
||||||
@map.desc = params[:desc]
|
@map.desc = params[:desc]
|
||||||
@map.permission = params[:permission]
|
@map.permission = params[:permission]
|
||||||
@map.user = @user
|
@map.user = @user
|
||||||
@map.arranged = false
|
@map.arranged = false
|
||||||
|
|
||||||
if params[:topicsToMap]
|
if params[:topicsToMap]
|
||||||
@all = params[:topicsToMap]
|
@all = params[:topicsToMap]
|
||||||
|
|
67
app/controllers/messages_controller.rb
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
class MessagesController < ApplicationController
|
||||||
|
|
||||||
|
before_action :require_user, except: [:show]
|
||||||
|
after_action :verify_authorized
|
||||||
|
|
||||||
|
# GET /messages/1.json
|
||||||
|
def show
|
||||||
|
@message = Message.find(params[:id])
|
||||||
|
authorize @message
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { render json: @message }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /messages
|
||||||
|
# POST /messages.json
|
||||||
|
def create
|
||||||
|
@message = Message.new(message_params)
|
||||||
|
@message.user = current_user
|
||||||
|
authorize @message
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
if @message.save
|
||||||
|
format.json { render json: @message, status: :created, location: messages_url }
|
||||||
|
else
|
||||||
|
format.json { render json: @message.errors, status: :unprocessable_entity }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# PUT /messages/1
|
||||||
|
# PUT /messages/1.json
|
||||||
|
def update
|
||||||
|
@message = Message.find(params[:id])
|
||||||
|
authorize @message
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
if @message.update_attributes(message_params)
|
||||||
|
format.json { head :no_content }
|
||||||
|
else
|
||||||
|
format.json { render json: @message.errors, status: :unprocessable_entity }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /messages/1
|
||||||
|
# DELETE /messages/1.json
|
||||||
|
def destroy
|
||||||
|
@message = Message.find(params[:id])
|
||||||
|
authorize @message
|
||||||
|
|
||||||
|
@message.destroy
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { head :no_content }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Never trust parameters from the scary internet, only allow the white list through.
|
||||||
|
def message_params
|
||||||
|
#params.require(:message).permit(:id, :resource_id, :message)
|
||||||
|
params.permit(:id, :resource_id, :resource_type, :message)
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,7 @@ class Map < ActiveRecord::Base
|
||||||
has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy
|
has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy
|
||||||
has_many :topics, through: :topicmappings, source: :mappable, source_type: "Topic"
|
has_many :topics, through: :topicmappings, source: :mappable, source_type: "Topic"
|
||||||
has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse"
|
has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse"
|
||||||
|
has_many :messages, as: :resource, dependent: :destroy
|
||||||
|
|
||||||
has_many :webhooks, as: :hookable
|
has_many :webhooks, as: :hookable
|
||||||
has_many :events, -> { includes :user }, as: :eventable, dependent: :destroy
|
has_many :events, -> { includes :user }, as: :eventable, dependent: :destroy
|
||||||
|
@ -16,15 +17,16 @@ class Map < ActiveRecord::Base
|
||||||
#:full => ['940x630#', :png]
|
#:full => ['940x630#', :png]
|
||||||
},
|
},
|
||||||
:default_url => 'https://s3.amazonaws.com/metamaps-assets/site/missing-map.png'
|
:default_url => 'https://s3.amazonaws.com/metamaps-assets/site/missing-map.png'
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :arranged, inclusion: { in: [true, false] }
|
validates :arranged, inclusion: { in: [true, false] }
|
||||||
validates :permission, presence: true
|
validates :permission, presence: true
|
||||||
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
|
||||||
|
|
||||||
# Validate the attached image is image/jpg, image/png, etc
|
# Validate the attached image is image/jpg, image/png, etc
|
||||||
validates_attachment_content_type :screenshot, :content_type => /\Aimage\/.*\Z/
|
validates_attachment_content_type :screenshot, :content_type => /\Aimage\/.*\Z/
|
||||||
|
|
||||||
def mappings
|
def mappings
|
||||||
topicmappings + synapsemappings
|
topicmappings + synapsemappings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,11 +37,11 @@ class Map < ActiveRecord::Base
|
||||||
#return an array of the contributors to the map
|
#return an array of the contributors to the map
|
||||||
def contributors
|
def contributors
|
||||||
contributors = []
|
contributors = []
|
||||||
|
|
||||||
self.mappings.each do |m|
|
self.mappings.each do |m|
|
||||||
contributors.push(m.user) if !contributors.include?(m.user)
|
contributors.push(m.user) if !contributors.include?(m.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
return contributors
|
return contributors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -81,10 +83,28 @@ class Map < ActiveRecord::Base
|
||||||
json[:updated_at_clean] = updated_at_str
|
json[:updated_at_clean] = updated_at_str
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_csv(options = {})
|
||||||
|
CSV.generate(options) do |csv|
|
||||||
|
csv << ["id", "name", "metacode", "desc", "link", "user.name", "permission", "synapses"]
|
||||||
|
self.topics.each do |topic|
|
||||||
|
csv << [
|
||||||
|
topic.id,
|
||||||
|
topic.name,
|
||||||
|
topic.metacode.name,
|
||||||
|
topic.desc,
|
||||||
|
topic.link,
|
||||||
|
topic.user.name,
|
||||||
|
topic.permission,
|
||||||
|
topic.synapses_csv("text")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def decode_base64(imgBase64)
|
def decode_base64(imgBase64)
|
||||||
decoded_data = Base64.decode64(imgBase64)
|
decoded_data = Base64.decode64(imgBase64)
|
||||||
|
|
||||||
data = StringIO.new(decoded_data)
|
data = StringIO.new(decoded_data)
|
||||||
data.class_eval do
|
data.class_eval do
|
||||||
attr_accessor :content_type, :original_filename
|
attr_accessor :content_type, :original_filename
|
||||||
|
|
19
app/models/message.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
class Message < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
|
belongs_to :resource, polymorphic: true
|
||||||
|
|
||||||
|
def user_name
|
||||||
|
self.user.name
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_image
|
||||||
|
self.user.image.url
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(options={})
|
||||||
|
json = super(:methods =>[:user_name, :user_image])
|
||||||
|
json
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
class Metacode < ActiveRecord::Base
|
class Metacode < ActiveRecord::Base
|
||||||
has_many :in_metacode_sets
|
has_many :in_metacode_sets
|
||||||
has_many :metacode_sets, :through => :in_metacode_sets
|
has_many :metacode_sets, :through => :in_metacode_sets
|
||||||
has_many :topics
|
has_many :topics
|
||||||
|
|
||||||
# This method associates the attribute ":aws_icon" with a file attachment
|
# This method associates the attribute ":aws_icon" with a file attachment
|
||||||
|
@ -36,7 +36,7 @@ class Metacode < ActiveRecord::Base
|
||||||
return true if user.settings.metacodes.include? self.id.to_s
|
return true if user.settings.metacodes.include? self.id.to_s
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
def inMetacodeSet(metacode_set)
|
def inMetacodeSet(metacode_set)
|
||||||
return true if self.metacode_sets.include? metacode_set
|
return true if self.metacode_sets.include? metacode_set
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -11,9 +11,9 @@ class User < ActiveRecord::Base
|
||||||
after_create :generate_code
|
after_create :generate_code
|
||||||
|
|
||||||
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable
|
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable
|
||||||
|
|
||||||
serialize :settings, UserPreference
|
serialize :settings, UserPreference
|
||||||
|
|
||||||
validates :password, :presence => true,
|
validates :password, :presence => true,
|
||||||
:length => { :within => 8..40 },
|
:length => { :within => 8..40 },
|
||||||
:on => :create
|
:on => :create
|
||||||
|
@ -28,7 +28,7 @@ class User < ActiveRecord::Base
|
||||||
validates_uniqueness_of :email # done by devise
|
validates_uniqueness_of :email # done by devise
|
||||||
|
|
||||||
validates :joinedwithcode, :presence => true, :inclusion => { :in => $codes, :message => "%{value} is not valid" }, :on => :create
|
validates :joinedwithcode, :presence => true, :inclusion => { :in => $codes, :message => "%{value} is not valid" }, :on => :create
|
||||||
|
|
||||||
# This method associates the attribute ":image" with a file attachment
|
# This method associates the attribute ":image" with a file attachment
|
||||||
has_attached_file :image, :styles => {
|
has_attached_file :image, :styles => {
|
||||||
:thirtytwo => ['32x32#', :png],
|
:thirtytwo => ['32x32#', :png],
|
||||||
|
@ -37,7 +37,7 @@ class User < ActiveRecord::Base
|
||||||
:onetwentyeight => ['128x128#', :png]
|
:onetwentyeight => ['128x128#', :png]
|
||||||
},
|
},
|
||||||
:default_url => 'https://s3.amazonaws.com/metamaps-assets/site/user.png'
|
:default_url => 'https://s3.amazonaws.com/metamaps-assets/site/user.png'
|
||||||
|
|
||||||
# Validate the attached image is image/jpg, image/png, etc
|
# Validate the attached image is image/jpg, image/png, etc
|
||||||
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
|
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ class User < ActiveRecord::Base
|
||||||
json['rtype'] = "mapper"
|
json['rtype'] = "mapper"
|
||||||
json
|
json
|
||||||
end
|
end
|
||||||
|
|
||||||
#generate a random 8 letter/digit code that they can use to invite people
|
#generate a random 8 letter/digit code that they can use to invite people
|
||||||
def generate_code
|
def generate_code
|
||||||
self.code ||= rand(36**8).to_s(36)
|
self.code ||= rand(36**8).to_s(36)
|
||||||
|
@ -77,7 +77,7 @@ class User < ActiveRecord::Base
|
||||||
update(generation: User.find_by_code(joinedwithcode).generation + 1)
|
update(generation: User.find_by_code(joinedwithcode).generation + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def settings
|
def settings
|
||||||
# make sure we always return a UserPreference instance
|
# make sure we always return a UserPreference instance
|
||||||
if read_attribute(:settings).nil?
|
if read_attribute(:settings).nil?
|
||||||
|
@ -85,7 +85,7 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
read_attribute :settings
|
read_attribute :settings
|
||||||
end
|
end
|
||||||
|
|
||||||
def settings=(val)
|
def settings=(val)
|
||||||
write_attribute :settings, val
|
write_attribute :settings, val
|
||||||
end
|
end
|
||||||
|
|
36
app/policies/message_policy.rb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
class MessagePolicy < ApplicationPolicy
|
||||||
|
class Scope < Scope
|
||||||
|
def resolve
|
||||||
|
visible = ['public', 'commons']
|
||||||
|
permission = 'maps.permission IN (?)'
|
||||||
|
if user
|
||||||
|
scope.joins(:maps).where(permission + ' OR maps.user_id = ?', visible, user.id)
|
||||||
|
else
|
||||||
|
scope.where(permission, visible)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show?
|
||||||
|
resource_policy.show?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
record.resource.present? && resource_policy.update?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update?
|
||||||
|
record.user == user
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
record.user == user || admin_override
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
|
||||||
|
def resource_policy
|
||||||
|
@resource_policy ||= Pundit.policy(user, record.resource)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -20,28 +20,6 @@
|
||||||
<div class="upperRightUI">
|
<div class="upperRightUI">
|
||||||
<div class="supportUs upperRightEl openLightbox" data-open="donate">SUPPORT US!</div>
|
<div class="supportUs upperRightEl openLightbox" data-open="donate">SUPPORT US!</div>
|
||||||
<div class="mapElement upperRightEl upperRightMapButtons">
|
<div class="mapElement upperRightEl upperRightMapButtons">
|
||||||
<% if authenticated? %>
|
|
||||||
<!-- Realtime -->
|
|
||||||
<div class="sidebarCollaborate upperRightEl">
|
|
||||||
<div class="sidebarCollaborateIcon upperRightIcon blue"><div class="tooltipsUnder">Junto</div></div>
|
|
||||||
<div class="sidebarCollaborateBox upperRightBox">
|
|
||||||
<h3 class="realtimeBoxTitle">REALTIME</h3>
|
|
||||||
<span class="realtimeOnOff rtOff">OFF</span>
|
|
||||||
<span class="realtimeOnOff rtOn">ON</span>
|
|
||||||
<div class="clearfloat"></div>
|
|
||||||
<div class="realtimeMapperList">
|
|
||||||
<ul>
|
|
||||||
<li class="rtMapper littleRtOn rtMapperSelf">
|
|
||||||
<%= image_tag user.image.url(:thirtytwo), :size => "24x24", :class => "rtUserImage" %>
|
|
||||||
<%= user.name %> (me)
|
|
||||||
<div class="littleJuntoIcon"></div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> <!-- end sidebarCollaborate a.k.a realtime -->
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<!-- filtering -->
|
<!-- filtering -->
|
||||||
<div class="sidebarFilter upperRightEl">
|
<div class="sidebarFilter upperRightEl">
|
||||||
<div class="sidebarFilterIcon upperRightIcon"><div class="tooltipsUnder">Filter</div></div>
|
<div class="sidebarFilterIcon upperRightIcon"><div class="tooltipsUnder">Filter</div></div>
|
||||||
|
@ -58,7 +36,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
</div> <!-- end mapElement -->
|
</div> <!-- end mapElement -->
|
||||||
|
|
||||||
<% if authenticated? %>
|
<% if authenticated? %>
|
||||||
<!-- create new map -->
|
<!-- create new map -->
|
||||||
|
@ -83,4 +61,4 @@
|
||||||
</div><!-- end sidebarAccount -->
|
</div><!-- end sidebarAccount -->
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="clearfloat"></div>
|
<div class="clearfloat"></div>
|
||||||
</div><!-- end upperRightUI -->
|
</div><!-- end upperRightUI -->
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
Metamaps.Topics = <%= @alltopics.to_json.html_safe %>;
|
Metamaps.Topics = <%= @alltopics.to_json.html_safe %>;
|
||||||
Metamaps.Synapses = <%= @allsynapses.to_json.html_safe %>;
|
Metamaps.Synapses = <%= @allsynapses.to_json.html_safe %>;
|
||||||
Metamaps.Mappings = <%= @allmappings.to_json.html_safe %>;
|
Metamaps.Mappings = <%= @allmappings.to_json.html_safe %>;
|
||||||
|
Metamaps.Messages = <%= @allmessages.to_json.html_safe %>;
|
||||||
Metamaps.Visualize.type = "ForceDirected";
|
Metamaps.Visualize.type = "ForceDirected";
|
||||||
</script>
|
</script>
|
||||||
|
|
26
app/views/maps/show.xls.erb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Metacode</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Link</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Permission</th>
|
||||||
|
<th>Synapses</th>
|
||||||
|
</tr>
|
||||||
|
<% @map.topics.each do |topic| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= topic.id %></td>
|
||||||
|
<td><%= topic.name %></td>
|
||||||
|
<td><%= topic.metacode.name %></td>
|
||||||
|
<td><%= topic.desc %></td>
|
||||||
|
<td><%= topic.link %></td>
|
||||||
|
<td><%= topic.user.name %></td>
|
||||||
|
<td><%= topic.permission %></td>
|
||||||
|
<% topic.synapses_csv.each do |s_text| %>
|
||||||
|
<td><%= s_text %></td>
|
||||||
|
<% end %>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
|
@ -1,5 +1,6 @@
|
||||||
require File.expand_path('../boot', __FILE__)
|
require File.expand_path('../boot', __FILE__)
|
||||||
|
|
||||||
|
require 'csv'
|
||||||
require 'rails/all'
|
require 'rails/all'
|
||||||
require 'dotenv'
|
require 'dotenv'
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,5 @@
|
||||||
# Add new mime types for use in respond_to blocks:
|
# Add new mime types for use in respond_to blocks:
|
||||||
# Mime::Type.register "text/richtext", :rtf
|
# Mime::Type.register "text/richtext", :rtf
|
||||||
# Mime::Type.register_alias "text/html", :iphone
|
# Mime::Type.register_alias "text/html", :iphone
|
||||||
|
|
||||||
|
Mime::Type.register "application/xls", :xls
|
||||||
|
|
|
@ -19,6 +19,7 @@ Metamaps::Application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :messages, only: [:show, :create, :update, :destroy]
|
||||||
resources :mappings, except: [:index, :new, :edit]
|
resources :mappings, except: [:index, :new, :edit]
|
||||||
resources :metacode_sets, :except => [:show]
|
resources :metacode_sets, :except => [:show]
|
||||||
resources :metacodes, :except => [:show, :destroy]
|
resources :metacodes, :except => [:show, :destroy]
|
||||||
|
|
15
db/migrate/20151205205831_messages.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class Messages < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :messages do |t|
|
||||||
|
t.text :message
|
||||||
|
t.references :user
|
||||||
|
t.integer :resource_id
|
||||||
|
t.string :resource_type
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :messages, :user_id
|
||||||
|
add_index :messages, :resource_id
|
||||||
|
add_index :messages, :resource_type
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,7 +19,7 @@ Now you are ready to clone the Metamaps git repository:
|
||||||
|
|
||||||
The third `bundle install` command downloads and installs the rubygem
|
The third `bundle install` command downloads and installs the rubygem
|
||||||
dependencies of Metamaps.
|
dependencies of Metamaps.
|
||||||
|
|
||||||
At this point you should be in C:\git\metamaps_gen002, or whatever equivalent
|
At this point you should be in C:\git\metamaps_gen002, or whatever equivalent
|
||||||
directory you've chosen. The next step is to set up your database
|
directory you've chosen. The next step is to set up your database
|
||||||
configuration. From the metamaps_gen002 directory, run
|
configuration. From the metamaps_gen002 directory, run
|
||||||
|
@ -41,7 +41,7 @@ time with only one command; you don't need to repeat any of the previous steps
|
||||||
again. The command to run the server is:
|
again. The command to run the server is:
|
||||||
|
|
||||||
rails s
|
rails s
|
||||||
|
|
||||||
Navigate your browser to localhost:3000 once you have the server running
|
Navigate your browser to localhost:3000 once you have the server running
|
||||||
|
|
||||||
Sign in with the default account
|
Sign in with the default account
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io": "0.9.12"
|
"socket.io": "0.9.12",
|
||||||
|
"node-uuid": "1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
var io = require('socket.io').listen(5001);
|
var
|
||||||
|
io = require('socket.io').listen(5001),
|
||||||
|
signalServer = require('./signal'),
|
||||||
|
stunservers = [{"url": "stun:stun.l.google.com:19302"}];
|
||||||
|
|
||||||
|
io.set('log', false);
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
|
||||||
|
signalServer(io, stunservers);
|
||||||
|
|
||||||
io.on('connection', function (socket) {
|
io.on('connection', function (socket) {
|
||||||
|
|
||||||
// this will ping a new person with awareness of who's already on the map
|
// this will ping a new person with awareness of who's already on the map
|
||||||
|
@ -10,11 +17,43 @@ function start() {
|
||||||
userid: data.userid,
|
userid: data.userid,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
userrealtime: data.userrealtime,
|
userrealtime: data.userrealtime,
|
||||||
|
userinconversation: data.userinconversation,
|
||||||
userimage: data.userimage
|
userimage: data.userimage
|
||||||
};
|
};
|
||||||
socket.broadcast.emit(data.userToNotify + '-' + data.mapid + '-UpdateMapperList', existingUser);
|
socket.broadcast.emit(data.userToNotify + '-' + data.mapid + '-UpdateMapperList', existingUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// as a new mapper check whether there's a call in progress to join
|
||||||
|
socket.on('checkForCall', function (data) {
|
||||||
|
var socketsInRoom = io.sockets.clients(data.room);
|
||||||
|
if (socketsInRoom.length) socket.emit('maps-' + data.mapid + '-callInProgress');
|
||||||
|
});
|
||||||
|
// send the invitation to start a call
|
||||||
|
socket.on('inviteACall', function (data) {
|
||||||
|
socket.broadcast.emit(data.invited + '-' + data.mapid + '-invitedToCall', data.inviter);
|
||||||
|
});
|
||||||
|
// send an invitation to join a call in progress
|
||||||
|
socket.on('inviteToJoin', function (data) {
|
||||||
|
socket.broadcast.emit(data.invited + '-' + data.mapid + '-invitedToJoin', data.inviter);
|
||||||
|
});
|
||||||
|
// send response back to the inviter
|
||||||
|
socket.on('callAccepted', function (data) {
|
||||||
|
socket.broadcast.emit(data.inviter + '-' + data.mapid + '-callAccepted', data.invited);
|
||||||
|
socket.broadcast.emit('maps-' + data.mapid + '-callStarting');
|
||||||
|
});
|
||||||
|
socket.on('callDenied', function (data) {
|
||||||
|
socket.broadcast.emit(data.inviter + '-' + data.mapid + '-callDenied', data.invited);
|
||||||
|
});
|
||||||
|
socket.on('inviteDenied', function (data) {
|
||||||
|
socket.broadcast.emit(data.inviter + '-' + data.mapid + '-inviteDenied', data.invited);
|
||||||
|
});
|
||||||
|
socket.on('mapperJoinedCall', function (data) {
|
||||||
|
socket.broadcast.emit('maps-' + data.mapid + '-mapperJoinedCall', data.id);
|
||||||
|
});
|
||||||
|
socket.on('mapperLeftCall', function (data) {
|
||||||
|
socket.broadcast.emit('maps-' + data.mapid + '-mapperLeftCall', data.id);
|
||||||
|
});
|
||||||
|
|
||||||
// this will ping everyone on a map that there's a person just joined the map
|
// this will ping everyone on a map that there's a person just joined the map
|
||||||
socket.on('newMapperNotify', function (data) {
|
socket.on('newMapperNotify', function (data) {
|
||||||
socket.set('mapid', data.mapid);
|
socket.set('mapid', data.mapid);
|
||||||
|
@ -49,7 +88,7 @@ function start() {
|
||||||
// this will ping everyone on a map that there's a person just left the map
|
// this will ping everyone on a map that there's a person just left the map
|
||||||
socket.on('disconnect', end);
|
socket.on('disconnect', end);
|
||||||
socket.on('endMapperNotify', end);
|
socket.on('endMapperNotify', end);
|
||||||
|
|
||||||
// this will ping everyone on a map that someone just turned on realtime
|
// this will ping everyone on a map that someone just turned on realtime
|
||||||
socket.on('notifyStartRealtime', function (data) {
|
socket.on('notifyStartRealtime', function (data) {
|
||||||
var newUser = {
|
var newUser = {
|
||||||
|
@ -59,7 +98,7 @@ function start() {
|
||||||
|
|
||||||
socket.broadcast.emit('maps-' + data.mapid + '-newrealtime', newUser);
|
socket.broadcast.emit('maps-' + data.mapid + '-newrealtime', newUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
// this will ping everyone on a map that someone just turned on realtime
|
// this will ping everyone on a map that someone just turned on realtime
|
||||||
socket.on('notifyStopRealtime', function (data) {
|
socket.on('notifyStopRealtime', function (data) {
|
||||||
var newUser = {
|
var newUser = {
|
||||||
|
@ -86,6 +125,13 @@ function start() {
|
||||||
socket.broadcast.emit('maps-' + mapId + '-topicDrag', data);
|
socket.broadcast.emit('maps-' + mapId + '-topicDrag', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('newMessage', function (data) {
|
||||||
|
var mapId = data.mapid;
|
||||||
|
delete data.mapid;
|
||||||
|
|
||||||
|
socket.broadcast.emit('maps-' + mapId + '-newMessage', data);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('newTopic', function (data) {
|
socket.on('newTopic', function (data) {
|
||||||
var mapId = data.mapid;
|
var mapId = data.mapid;
|
||||||
delete data.mapid;
|
delete data.mapid;
|
||||||
|
@ -137,4 +183,4 @@ function start() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
111
realtime/signal.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
var uuid = require('node-uuid');
|
||||||
|
|
||||||
|
module.exports = function(io, stunservers) {
|
||||||
|
|
||||||
|
var
|
||||||
|
activePeople = 0;
|
||||||
|
|
||||||
|
function describeRoom(name) {
|
||||||
|
var clients = io.sockets.clients(name);
|
||||||
|
var result = {
|
||||||
|
clients: {}
|
||||||
|
};
|
||||||
|
clients.forEach(function (client) {
|
||||||
|
result.clients[client.id] = client.resources;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeCb(cb) {
|
||||||
|
if (typeof cb === 'function') {
|
||||||
|
return cb;
|
||||||
|
} else {
|
||||||
|
return function () {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.sockets.on('connection', function (client) {
|
||||||
|
activePeople += 1;
|
||||||
|
|
||||||
|
client.resources = {
|
||||||
|
screen: false,
|
||||||
|
video: true,
|
||||||
|
audio: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// pass a message to another id
|
||||||
|
client.on('message', function (details) {
|
||||||
|
if (!details) return;
|
||||||
|
|
||||||
|
var otherClient = io.sockets.sockets[details.to];
|
||||||
|
if (!otherClient) return;
|
||||||
|
|
||||||
|
details.from = client.id;
|
||||||
|
otherClient.emit('message', details);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('shareScreen', function () {
|
||||||
|
client.resources.screen = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('unshareScreen', function (type) {
|
||||||
|
client.resources.screen = false;
|
||||||
|
removeFeed('screen');
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('join', join);
|
||||||
|
|
||||||
|
function removeFeed(type) {
|
||||||
|
if (client.room) {
|
||||||
|
io.sockets.in(client.room).emit('remove', {
|
||||||
|
id: client.id,
|
||||||
|
type: type
|
||||||
|
});
|
||||||
|
if (!type) {
|
||||||
|
client.leave(client.room);
|
||||||
|
client.room = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function join(name, cb) {
|
||||||
|
// sanity check
|
||||||
|
if (typeof name !== 'string') return;
|
||||||
|
// leave any existing rooms
|
||||||
|
removeFeed();
|
||||||
|
safeCb(cb)(null, describeRoom(name));
|
||||||
|
client.join(name);
|
||||||
|
client.room = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want to pass "leave" directly because the
|
||||||
|
// event type string of "socket end" gets passed too.
|
||||||
|
client.on('disconnect', function () {
|
||||||
|
removeFeed();
|
||||||
|
activePeople -= 1;
|
||||||
|
});
|
||||||
|
client.on('leave', function () {
|
||||||
|
removeFeed();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('create', function (name, cb) {
|
||||||
|
if (arguments.length == 2) {
|
||||||
|
cb = (typeof cb == 'function') ? cb : function () {};
|
||||||
|
name = name || uuid();
|
||||||
|
} else {
|
||||||
|
cb = name;
|
||||||
|
name = uuid();
|
||||||
|
}
|
||||||
|
// check if exists
|
||||||
|
if (io.sockets.clients(name).length) {
|
||||||
|
safeCb(cb)('taken');
|
||||||
|
} else {
|
||||||
|
join(name);
|
||||||
|
safeCb(cb)(null, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// tell client about stun and turn servers and generate nonces
|
||||||
|
client.emit('stunservers', stunservers || []);
|
||||||
|
});
|
||||||
|
};
|