diff --git a/Gemfile.lock b/Gemfile.lock index d44f02fa..4cfa192f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,14 +42,8 @@ GEM aws-sdk-v1 (1.66.0) json (~> 1.4) nokogiri (>= 1.4.4) - aws-sdk (2.1.19) - aws-sdk-resources (= 2.1.19) - aws-sdk-core (2.1.19) - jmespath (~> 1.0) - aws-sdk-resources (2.1.19) - aws-sdk-core (= 2.1.19) bcrypt (3.1.10) - best_in_place (3.0.3) + best_in_place (3.1.0) actionpack (>= 3.2) railties (>= 3.2) better_errors (2.1.1) @@ -59,12 +53,11 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) - byebug (4.0.5) - columnize (= 0.9.0) - cancancan (1.12.0) + byebug (8.2.1) + cancancan (1.13.1) climate_control (0.0.3) activesupport (>= 3.0) - cocaine (0.5.7) + cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.1.0) coffee-rails (4.1.0) @@ -73,8 +66,8 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.9.1.1) - columnize (0.9.0) + coffee-script-source (1.10.0) + concurrent-ruby (1.0.0) debug_inspector (0.0.2) devise (3.5.2) bcrypt (~> 3.0) @@ -83,7 +76,7 @@ GEM responders thread_safe (~> 0.1) warden (~> 1.2.3) - dotenv (2.0.0) + dotenv (2.0.2) erubis (2.7.0) execjs (2.6.0) ezcrypto (0.7.2) @@ -94,7 +87,7 @@ GEM globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) - jbuilder (2.3.1) + jbuilder (2.3.2) activesupport (>= 3.0.0, < 5) multi_json (~> 1.2) jquery-rails (4.0.5) @@ -112,28 +105,28 @@ GEM mail (2.6.3) mime-types (>= 1.16, < 3) method_source (0.8.2) - mime-types (2.6.1) + mime-types (2.99) mimemagic (0.3.0) - mini_portile (0.6.2) - minitest (5.8.0) + mini_portile2 (2.0.0) + minitest (5.8.3) multi_json (1.11.2) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) + nokogiri (1.6.7) + mini_portile2 (~> 2.0.0.rc2) oauth (0.4.7) orm_adapter (0.5.0) - paperclip (4.3.0) + paperclip (4.3.2) activemodel (>= 3.2.0) activesupport (>= 3.2.0) cocaine (~> 0.5.5) mime-types mimemagic (= 0.3.0) - pg (0.18.3) - pry (0.10.1) + pg (0.18.4) + pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - pry-byebug (3.1.0) - byebug (~> 4.0) + pry-byebug (3.3.0) + byebug (~> 8.0) pry (~> 0.10) pry-rails (0.3.4) pry (>= 0.9.10) @@ -174,10 +167,10 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (10.4.2) - redis (3.2.1) + redis (3.2.2) responders (2.1.0) railties (>= 4.2.0, < 5) - sass (3.4.18) + sass (3.4.19) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) sass (~> 3.1) @@ -185,8 +178,9 @@ GEM sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) slop (3.6.0) - sprockets (3.3.4) - rack (~> 1.0) + sprockets (3.5.0) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js.erb b/app/assets/javascripts/src/Metamaps.Backbone.js.erb index 352ca65f..55ba8107 100644 --- a/app/assets/javascripts/src/Metamaps.Backbone.js.erb +++ b/app/assets/javascripts/src/Metamaps.Backbone.js.erb @@ -206,6 +206,22 @@ 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 += '';       + li += '

    ' + this.get('name') + '

  • '; + return li;*/ + } +}); + Metamaps.Backbone.Mapper = Backbone.Model.extend({ urlRoot: '/users', blacklist: ['created_at', 'updated_at'], diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb index 2e17ffae..022fac17 100644 --- a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb +++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb @@ -333,7 +333,6 @@ Metamaps.GlobalUI.Account = { open: function () { var self = Metamaps.GlobalUI.Account; - Metamaps.Realtime.close(); Metamaps.Filter.close(); $('.sidebarAccountIcon .tooltipsUnder').addClass('hide'); diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb index 9ce6af0f..77d24474 100644 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -41,7 +41,7 @@ Metamaps.Settings = { }; Metamaps.Touch = { - touchPos: null, // this stores the x and y values of a current touch event + touchPos: null, // this stores the x and y values of a current touch event touchDragNode: null // this stores a reference to a JIT node that is being dragged }; @@ -113,7 +113,7 @@ Metamaps.Backbone.init = function () { return _.omit(this.attributes, this.blacklist); }, save: function (key, val, options) { - + var attrs; // Handle both `"key", value` and `{key: value}` -style arguments. @@ -151,7 +151,7 @@ Metamaps.Backbone.init = function () { "permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons' }); } - + this.on('changeByOther', this.updateCardView); this.on('change', this.updateNodeView); this.on('saved', this.savedEvent); @@ -189,9 +189,9 @@ Metamaps.Backbone.init = function () { return Metamaps.Metacodes.get(this.get('metacode_id')); }, getMapping: function () { - + if (!Metamaps.Active.Map) return false; - + return Metamaps.Mappings.findWhere({ map_id: Metamaps.Active.Map.id, mappable_type: "Topic", @@ -205,7 +205,7 @@ Metamaps.Backbone.init = function () { id: this.isNew() ? this.cid : this.id, name: this.get('name') }; - + if (Metamaps.Active.Map) { mapping = this.getMapping(); node.data = { @@ -213,19 +213,19 @@ Metamaps.Backbone.init = function () { $mappingID: mapping.id }; } - + return node; }, updateNode: function () { var mapping; var node = this.get('node'); node.setData('topic', this); - + if (Metamaps.Active.Map) { mapping = this.getMapping(); node.setData('mapping', mapping); } - + return node; }, savedEvent: function() { @@ -241,7 +241,7 @@ Metamaps.Backbone.init = function () { // update the node on the map if (onPageWithTopicCard && node) { - node.name = this.get('name'); + node.name = this.get('name'); Metamaps.Visualize.mGraph.plot(); } }, @@ -259,7 +259,7 @@ Metamaps.Backbone.init = function () { // update the node on the map if (onPageWithTopicCard && node) { - node.name = this.get('name'); + node.name = this.get('name'); Metamaps.Visualize.mGraph.plot(); } } @@ -277,7 +277,7 @@ Metamaps.Backbone.init = function () { return _.omit(this.attributes, this.blacklist); }, save: function (key, val, options) { - + var attrs; // Handle both `"key", value` and `{key: value}` -style arguments. @@ -366,9 +366,9 @@ Metamaps.Backbone.init = function () { ] : false; }, getMapping: function () { - + if (!Metamaps.Active.Map) return false; - + return Metamaps.Mappings.findWhere({ map_id: Metamaps.Active.Map.id, mappable_type: "Synapse", @@ -387,26 +387,26 @@ Metamaps.Backbone.init = function () { $synapseIDs: [synapseID], } }; - + if (Metamaps.Active.Map) { mapping = this.getMapping(); mappingID = mapping.isNew() ? mapping.cid : mapping.id; edge.data.$mappings = []; edge.data.$mappingIDs = [mappingID]; } - + return edge; }, updateEdge: function () { var mapping; var edge = this.get('edge'); edge.getData('synapses').push(this); - + if (Metamaps.Active.Map) { mapping = this.getMapping(); edge.getData('mappings').push(mapping); } - + return edge; }, savedEvent: function() { @@ -489,12 +489,12 @@ Metamaps.Backbone.init = function () { Metamaps.Active.Map = new self.Map(Metamaps.Active.Map); } - + if (Metamaps.Active.Topic) Metamaps.Active.Topic = new self.Topic(Metamaps.Active.Topic); //attach collection event listeners self.attachCollectionEvents = function () { - + Metamaps.Topics.on("add remove", function(topic){ Metamaps.Map.InfoBox.updateNumbers(); Metamaps.Filter.checkMetacodes(); @@ -506,7 +506,7 @@ Metamaps.Backbone.init = function () { Metamaps.Filter.checkSynapses(); Metamaps.Filter.checkMappers(); }); - + if (Metamaps.Active.Map) { Metamaps.Mappings.on("add remove", function(mapping){ Metamaps.Map.InfoBox.updateNumbers(); @@ -602,7 +602,7 @@ Metamaps.Create = { metacodeModels.each(function(metacode){ newMetacodes += '' + metacode.get('name') + ''; }); - + $('#metacodeImg').empty().append(newMetacodes).CloudCarousel({ titleBox: $('#metacodeImgTitle'), yRadius: 40, @@ -661,7 +661,7 @@ Metamaps.Create = { }, newTopic: { init: function () { - + $('#topic_name').keyup(function () { Metamaps.Create.newTopic.name = $(this).val(); }); @@ -680,7 +680,7 @@ Metamaps.Create = { { highlight: true, minLength: 2, - }, + }, [{ name: 'topic_autocomplete', limit: 8, @@ -770,12 +770,12 @@ Metamaps.Create = { { highlight: true, minLength: 2, - }, + }, [{ name: 'synapse_autocomplete', display: function(s) { return s.label; }, templates: { - suggestion: function(s) { + suggestion: function(s) { return Hogan.compile("
    {{label}}
    ").render(s); }, }, @@ -923,7 +923,7 @@ Metamaps.TopicCard = { $('#addLinkInput input').focus(); }; var inputEmbedFunc = function (event) { - + var element = this; setTimeout(function () { var text = $(element).val(); @@ -1019,9 +1019,9 @@ Metamaps.TopicCard = { if (!selectingMetacode) { selectingMetacode = true; - // this is to make sure the metacode + // this is to make sure the metacode // select is accessible onscreen, when opened - // while topic card is close to the right + // while topic card is close to the right // edge of the screen windowWidth = $(window).width(); showcardLeft = parseInt($('.showcard').css('left')); @@ -1030,7 +1030,7 @@ Metamaps.TopicCard = { $('.metacodeSelect').addClass('onRightEdge'); } - // this is to make sure the metacode + // this is to make sure the metacode // select is accessible onscreen, when opened // while topic card is close to the bottom // edge of the screen @@ -1205,13 +1205,13 @@ Metamaps.TopicCard = { var self=Metamaps.TopicCard; var nodeValues = {}; - + var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper); if (!authorized) { - + } else { - + } var desc_nil = "Click to add description..."; @@ -1290,7 +1290,7 @@ Metamaps.SynapseCard = { showCard: function (edge, e) { var self = Metamaps.SynapseCard; - //reset so we don't interfere with other edges, but first, save its x and y + //reset so we don't interfere with other edges, but first, save its x and y var myX = $('#edit_synapse').css('left'); var myY = $('#edit_synapse').css('top'); $('#edit_synapse').remove(); @@ -1423,7 +1423,7 @@ Metamaps.SynapseCard = { $('#editSynLowerBar').append(list); // attach click listeners to list items that - // will cause it to switch the displayed synapse + // will cause it to switch the displayed synapse // when you click it $('#switchSynapseList li').click(function(e){ e.stopPropagation(); @@ -1535,7 +1535,7 @@ Metamaps.SynapseCard = { if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) { $('#edit_synapse_left, #edit_synapse_right').click(function () { - + $(this).toggleClass('checked'); var leftChecked = $('#edit_synapse_left').is('.checked'); @@ -1628,7 +1628,7 @@ Metamaps.Visualize = { } } }); - + var pos = n.getPos(); pos.setc(-200, -200); }); @@ -1677,12 +1677,12 @@ Metamaps.Visualize = { $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); - + RGraphSettings.width = $(document).width(); RGraphSettings.height = $(document).height(); RGraphSettings.background = Metamaps.JIT.RGraph.background; RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance; - + self.mGraph = new $jit.RGraph(RGraphSettings); } else if (self.type == "ForceDirected" && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) { @@ -1691,7 +1691,7 @@ Metamaps.Visualize = { $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); - + FDSettings.width = $('body').width(); FDSettings.height = $('body').height(); @@ -1893,7 +1893,7 @@ Metamaps.Util = { }, checkURLisImage: function (url) { // when the page reloads the following regular expression will be screwed up - // please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/ + // please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/ return (url.match(/\.(jpeg|jpg|gif|png)$/) != null); }, checkURLisYoutubeVideo: function (url) { @@ -1907,17 +1907,16 @@ Metamaps.Util = { * */ Metamaps.Realtime = { - stringForLocalhost: 'http://localhost:5001', + stringForLocalhost: 'http://' + location.host.split(':')[0] + ':5001', stringForMetamaps: 'http://metamaps.cc:5001', stringForHeroku: 'http://gentle-savannah-1303.herokuapp.com', videoId: 'video-wrapper', socket: null, webrtc: null, readyToCall: false, - isOpen: false, - changing: false, mappersOnMap: {}, status: true, // stores whether realtime is True/On or False/Off, + broadcastingStatus: false, localVideo: null, init: function () { var self = Metamaps.Realtime; @@ -1928,14 +1927,12 @@ Metamaps.Realtime = { var turnOff = function () { self.turnOff(); }; + var toggleVideo = function () { + self.toggleVideo(); + }; $(".rtOn").click(reenableRealtime); $(".rtOff").click(turnOff); - - $('.sidebarCollaborateIcon').click(self.toggleBox); - $('.sidebarCollaborateBox').click(function(event){ - event.stopPropagation(); - }); - $('body').click(self.close); + $(".startVideo").click(toggleVideo); $(document).on(Metamaps.Views.chatView.events.openTray, function () { $('.main').addClass('compressed'); @@ -1955,7 +1952,7 @@ Metamaps.Realtime = { localVideoEl: self.videoId, remoteVideosEl: '', detectSpeakingEvents: true, - autoAdjustMic: true, + autoAdjustMic: false, //true, autoRequestMedia: false, localVideo: { autoplay: true, @@ -1968,66 +1965,45 @@ Metamaps.Realtime = { } }); self.webrtc.on('readyToCall', function () { + console.log('readyToCall'); + self.videoInitialized = true; self.readyToCall = true; if (self.localVideo && self.status) { $('#wrapper').append(self.localVideo.view.$container); } + self.socket.emit('videoAdded', { + avatar: Metamaps.Active.Mapper.get('image') + }); + self.webrtc.webrtc.peers.forEach(function (p) { + p.pc.addStream(self.webrtc.webrtc.localStream); + p.start(); + }); }); var $video = $('').attr('id', self.videoId); self.localVideo = { $video: $video, - view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, { DOUBLE_CLICK_TOLERANCE: 200 }), + view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, { + DOUBLE_CLICK_TOLERANCE: 200, + avatar: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '' + }), }; self.room = new Metamaps.Views.room({ webrtc: self.webrtc, socket: self.socket, - username: Metamaps.Active.Mapper.get('name'), - image: Metamaps.Active.Mapper.get('image'), + username: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('name') : '', + image: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '', room: 'global', $video: self.localVideo.$video, myVideoView: self.localVideo.view, config: { DOUBLE_CLICK_TOLERANCE: 200 } }); + if (Metamaps.Messages) self.room.messages.add(Metamaps.Messages); self.createChat(); - self.webrtc.startLocalVideo(); - }, - toggleBox: function (event) { - var self = Metamaps.Realtime; - - if (self.isOpen) self.close(); - else self.open(); - - event.stopPropagation(); - }, - open: function () { - var self = Metamaps.Realtime; - - Metamaps.GlobalUI.Account.close(); - Metamaps.Filter.close(); - $('.sidebarCollaborateIcon div').addClass('hide'); - - if (!self.isOpen && !self.changing) { - self.changing = true; - $('.sidebarCollaborateBox').fadeIn(200, function () { - self.changing = false; - self.isOpen = true; - }); - } - }, - close: function () { - var self = Metamaps.Realtime; - $(".sidebarCollaborateIcon div").removeClass('hide'); - if (!self.changing) { - self.changing = true; - $('.sidebarCollaborateBox').fadeOut(200, function () { - self.changing = false; - self.isOpen = false; - }); - } + //self.webrtc.startLocalVideo(); }, startActiveMap: function () { var self = Metamaps.Realtime; @@ -2043,6 +2019,7 @@ Metamaps.Realtime = { else if (publicMap) { self.attachMapListener(); } + self.room.messages.add(Metamaps.Messages); } }, endActiveMap: function () { @@ -2053,6 +2030,8 @@ Metamaps.Realtime = { self.socket.emit('endMapperNotify'); $(".collabCompass").remove(); self.status = false; + self.stopVideo(); + self.room.leave(); }, reenableRealtime: function() { var confirmString = "The layout of your map has fallen out of sync with the saved copy. "; @@ -2068,17 +2047,20 @@ Metamaps.Realtime = { var self = Metamaps.Realtime; if (notify) self.sendRealtimeOn(); - $(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn'); - $('.rtOn').addClass('active'); - $('.rtOff').removeClass('active'); + //$(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn'); + //$('.rtOn').addClass('active'); + //$('.rtOff').removeClass('active'); self.status = true; - $(".sidebarCollaborateIcon").addClass("blue"); + //$(".sidebarCollaborateIcon").addClass("blue"); $(".collabCompass").show(); - if (self.localVideo) $('#wrapper').append(self.localVideo.view.$container); + self.localVideo.view.$container.show(); self.room.room = 'map-' + Metamaps.Active.Map.id; self.room.join(function (err, roomDesc) { - console.log('joining'); - attachMediaStream(self.webrtc.webrtc.localStream, self.localVideo.$video[0]); + //attachMediaStream(self.webrtc.webrtc.localStream, self.localVideo.$video[0]); + + for (id in roomDesc.avatars) { + self.webrtc.webrtc.peers.find(function(p) { return p.id === id; }).avatar = roomDesc.avatars[id]; + } function addVideo(v) { // random position for now @@ -2094,22 +2076,52 @@ Metamaps.Realtime = { } self.room.videoAdded(addVideo); - + for (peer in self.room.videos) { addVideo(self.room.videos[peer]); } }); }, + startVideo: function() { + var + self = Metamaps.Realtime; + + if (!self.videoInitialized) { + self.webrtc.startLocalVideo(); + } + else { + self.webrtc.resume(); + } + self.broadcastingStatus = true; + self.localVideo.view.$container.show(); + $(".startVideo").html("stop broadcasting"); + }, + stopVideo: function () { + var + self = Metamaps.Realtime; + + self.webrtc.pause(); + self.broadcastingStatus = false; + self.localVideo.view.$container.hide(); + $(".startVideo").html("start my video"); + }, + toggleVideo: function () { + var + self = Metamaps.Realtime; + + if (!self.broadcastingStatus) self.startVideo(); + else self.stopVideo(); + }, turnOff: function (silent) { var self = Metamaps.Realtime; if (self.status) { if (!silent) self.sendRealtimeOff(); - $(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff'); - $('.rtOn').removeClass('active'); - $('.rtOff').addClass('active'); + //$(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff'); + //$('.rtOn').removeClass('active'); + //$('.rtOff').addClass('active'); self.status = false; - $(".sidebarCollaborateIcon").removeClass("blue"); + //$(".sidebarCollaborateIcon").removeClass("blue"); $(".collabCompass").hide(); $('#' + self.videoId).remove(); } @@ -2118,7 +2130,7 @@ Metamaps.Realtime = { var self = Metamaps.Realtime; var socket = Metamaps.Realtime.socket; var myId = Metamaps.Active.Mapper.id; - + socket.emit('newMapperNotify', { userid: myId, username: Metamaps.Active.Mapper.get("name"), @@ -2147,6 +2159,9 @@ Metamaps.Realtime = { // socket.on('maps-' + Metamaps.Active.Map.id + '-newTopic', self.newTopic); + // + socket.on('maps-' + Metamaps.Active.Map.id + '-newMessage', self.newMessage); + // socket.on('maps-' + Metamaps.Active.Map.id + '-removeTopic', self.removeTopic); @@ -2166,7 +2181,7 @@ Metamaps.Realtime = { socket.on('topicChangeFromServer', self.topicChange); socket.on('synapseChangeFromServer', self.synapseChange); self.attachMapListener(); - + // local event listeners that trigger events var sendCoords = function (event) { var pixels = { @@ -2228,6 +2243,11 @@ Metamaps.Realtime = { }; $(document).on(Metamaps.JIT.events.removeSynapse, sendRemoveSynapse); + var sendNewMessage = function (event, data) { + self.sendNewMessage(data); + }; + $(document).on(Metamaps.Views.room.events.newMessage, sendNewMessage); + }, attachMapListener: function(){ var self = Metamaps.Realtime; @@ -2281,7 +2301,7 @@ Metamaps.Realtime = { color: Metamaps.Util.getPastelColor(), realtime: data.userrealtime, coords: { - x: 0, + x: 0, y: 0 }, }; @@ -2322,7 +2342,7 @@ Metamaps.Realtime = { color: Metamaps.Util.getPastelColor(), realtime: true, coords: { - x: 0, + x: 0, y: 0 }, }; @@ -2340,7 +2360,7 @@ Metamaps.Realtime = { // create a div for the collaborators compass self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status); - + Metamaps.GlobalUI.notifyUser(data.username + ' just joined the map'); // send this new mapper back your details, and the awareness that you've loaded the map @@ -2445,7 +2465,7 @@ Metamaps.Realtime = { var yMax=$(document).height(); var compassDiameter=56; var compassArrowSize=24; - + var origPixels = Metamaps.Util.coordsToPixels(mapper.coords); var pixels = self.limitPixelsToScreen(origPixels); $('#compass' + id).css({ @@ -2459,12 +2479,12 @@ Metamaps.Realtime = { var dx = origPixels.x - pixels.x; // adjacent var ratio = dy / dx; var angle = Math.atan2(dy, dx); - + $('#compassArrow' + id).show().css({ transform: 'rotate(' + angle + 'rad)', "-webkit-transform": 'rotate(' + angle + 'rad)', }); - + if (dx > 0) { $('#compass' + id).addClass('labelLeft'); } @@ -2482,12 +2502,12 @@ Metamaps.Realtime = { var yMax=$(document).height(); var compassDiameter=56; var compassArrowSize=24; - + xLimit = Math.max(0 + compassArrowSize, pixels.x); xLimit = Math.min(xLimit, xMax - compassDiameter); yLimit = Math.max(0 + compassArrowSize, pixels.y); yLimit = Math.min(yLimit, yMax - compassDiameter); - + return {x:xLimit,y:yLimit}; }, sendCoords: function (coords) { @@ -2614,6 +2634,21 @@ Metamaps.Realtime = { }); } }, + // newMessage + sendNewMessage: function (data) { + var self = Metamaps.Realtime; + var socket = self.socket; + + var message = data.attributes; + message.mapid = Metamaps.Active.Map.id; + socket.emit('newMessage', message); + }, + newMessage: function (data) { + var self = Metamaps.Realtime; + var socket = self.socket; + + self.room.messages.add(data); + }, // newTopic sendNewTopic: function (data) { var self = Metamaps.Realtime; @@ -2803,7 +2838,7 @@ Metamaps.Realtime = { if (edge.getData("mappings").length - 1 === 0) { Metamaps.Control.hideEdge(edge); } - + var index = _.indexOf(edge.getData("synapses"), synapse); edge.getData("mappings").splice(index, 1); edge.getData("synapses").splice(index, 1); @@ -2853,7 +2888,7 @@ Metamaps.Control = { deleteSelected: function () { if (!Metamaps.Active.Map) return; - + var n = Metamaps.Selected.Nodes.length; var e = Metamaps.Selected.Edges.length; var ntext = n == 1 ? "1 topic" : n + " topics"; @@ -2891,7 +2926,7 @@ Metamaps.Control = { } }, deleteNode: function (nodeid) { // refers to deleting topics permanently - + if (!Metamaps.Active.Map) return; var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); @@ -2903,7 +2938,7 @@ Metamaps.Control = { var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); var topic = node.getData('topic'); - + var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin'); if (permToDelete) { var mappableid = topic.id; @@ -3020,7 +3055,7 @@ Metamaps.Control = { }, deselectEdge: function (edge) { edge.setData('showDesc', false, 'current'); - + edge.setDataset('current', { lineWidth: 2, color: Metamaps.Settings.colors.synapses.normal @@ -3072,13 +3107,13 @@ Metamaps.Control = { var synapse = edge.getData("synapses")[index]; var mapping = edge.getData("mappings")[index]; - + var permToDelete = Metamaps.Active.Mapper.id === synapse.get('user_id') || Metamaps.Active.Mapper.get('admin'); if (permToDelete) { if (edge.getData("synapses").length - 1 === 0) { Metamaps.Control.hideEdge(edge); } - + var mappableid = synapse.id; synapse.destroy(); @@ -3298,7 +3333,6 @@ Metamaps.Filter = { var self = Metamaps.Filter; Metamaps.GlobalUI.Account.close(); - Metamaps.Realtime.close(); $('.sidebarFilterIcon div').addClass('hide'); @@ -3336,13 +3370,13 @@ Metamaps.Filter = { self.visible.mappers = []; self.visible.synapses = []; - $('#filter_by_metacode ul').empty(); + $('#filter_by_metacode ul').empty(); $('#filter_by_mapper ul').empty(); $('#filter_by_synapse ul').empty(); $('.filterBox .showAll').addClass('active'); }, - /* + /* Most of this data essentially depends on the ruby function which are happening for filter inside view filterBox But what these function do is load this data into three accessible array within java : metacodes, mappers and synapses */ @@ -3355,7 +3389,7 @@ Metamaps.Filter = { metacode = $( this ).attr('data-id'); self.filters.metacodes.push(metacode); self.visible.metacodes.push(metacode); - }); + }); $('#filter_by_mapper li').each(function() { mapper = ($( this ).attr('data-id')); @@ -3364,7 +3398,7 @@ Metamaps.Filter = { }); $('#filter_by_synapse li').each(function() { - synapse = ($( this ).attr('data-id')); + synapse = ($( this ).attr('data-id')); self.filters.synapses.push(synapse); self.visible.synapses.push(synapse); }); @@ -3378,15 +3412,15 @@ Metamaps.Filter = { // an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce // code redundancy /* - @param + @param */ updateFilters: function (collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) { var self = Metamaps.Filter; - + var newList = []; var removed = []; var added = []; - + // the first option enables us to accept // ['Topics', 'Synapses'] as 'collection' if (typeof collection === "object") { @@ -3419,10 +3453,10 @@ Metamaps.Filter = { } }); } - + removed = _.difference(self.filters[filtersToUse], newList); added = _.difference(newList, self.filters[filtersToUse]); - + // remove the list items for things no longer present on the map _.each(removed, function(identifier) { $('#filter_by_' + listToModify + ' li[data-id="' + identifier + '"]').fadeOut('fast',function(){ @@ -3431,14 +3465,14 @@ Metamaps.Filter = { index = self.visible[filtersToUse].indexOf(identifier); self.visible[filtersToUse].splice(index, 1); }); - + var model, li, jQueryLi; function sortAlpha(a,b){ - return a.childNodes[1].innerHTML.toLowerCase() > b.childNodes[1].innerHTML.toLowerCase() ? 1 : -1; + return a.childNodes[1].innerHTML.toLowerCase() > b.childNodes[1].innerHTML.toLowerCase() ? 1 : -1; } // for each new filter to be added, create a list item for it and fade it in _.each(added, function (identifier) { - model = Metamaps[correlatedModel].get(identifier) || + model = Metamaps[correlatedModel].get(identifier) || Metamaps[correlatedModel].find(function (model) { return model.get(propertyToCheck) === identifier; }); @@ -3496,7 +3530,7 @@ Metamaps.Filter = { $('.showAllMappers').removeClass('active'); $('.hideAllMappers').addClass('active'); self.visible.mappers = []; - self.passFilters(); + self.passFilters(); }, filterNoMappers: function (e) { var self = Metamaps.Filter; @@ -3590,7 +3624,7 @@ Metamaps.Filter = { $('.hideAllSynapses').removeClass('active'); } }, - passFilters: function () { + passFilters: function () { var self = Metamaps.Filter; var visible = self.visible; @@ -3614,16 +3648,16 @@ Metamaps.Filter = { else passesMetacode = true; if (onMap) { - // when on a map, - // we filter by mapper according to the person who added the + // when on a map, + // we filter by mapper according to the person who added the // topic or synapse to the map var user_id = topic.getMapping().get("user_id").toString(); if (visible.mappers.indexOf(user_id) == -1) passesMapper = false; else passesMapper = true; } else { - // when on a topic view, - // we filter by mapper according to the person who created the + // when on a topic view, + // we filter by mapper according to the person who created the // topic or synapse var user_id = topic.get("user_id").toString(); if (visible.mappers.indexOf(user_id) == -1) passesMapper = false; @@ -3687,8 +3721,8 @@ Metamaps.Filter = { } if (onMap) { - // when on a map, - // we filter by mapper according to the person who added the + // when on a map, + // we filter by mapper according to the person who added the // topic or synapse to the map user_id = synapse.getMapping().get("user_id").toString(); } @@ -3709,12 +3743,12 @@ Metamaps.Filter = { } else if (!e) console.log(synapse); }); - + // run the animation - Metamaps.Visualize.mGraph.fx.animate({ - modes: ['node-property:alpha', - 'edge-property:alpha'], - duration: 200 + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['node-property:alpha', + 'edge-property:alpha'], + duration: 200 }); } }; // end Metamaps.Filter @@ -3752,7 +3786,7 @@ Metamaps.Listeners = { Metamaps.Visualize.mGraph.plot(); } - + break; case 69: //if e or E is pressed if (e.ctrlKey){ @@ -3973,7 +4007,7 @@ Metamaps.Topic = { Metamaps.JIT.prepareVizData(); // update filters - Metamaps.Filter.reset(); + Metamaps.Filter.reset(); // reset selected arrays Metamaps.Selected.reset(); @@ -4003,13 +4037,13 @@ Metamaps.Topic = { hideLabels: false, duration: 1000, onComplete: function () { - + } }); } }, fetchRelatives: function(node, metacode_id) { - + var topics = Metamaps.Topics.map(function(t){ return t.id }); var topics_string = topics.join(); @@ -4035,7 +4069,7 @@ Metamaps.Topic = { }); var i, l, t, s; - + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { t = Metamaps.Topics.get(n.id); t.set({ node: n }, { silent: true }); @@ -4064,7 +4098,7 @@ Metamaps.Topic = { url: "/topics/" + topic.id + "/relatives.json?" + paramsString, success: successCallback, error: function () { - + } }); }, @@ -4084,8 +4118,8 @@ Metamaps.Topic = { if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { Metamaps.Visualize.mGraph.graph.addNode(newnode); nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); - topic.set('node', nodeOnViz, {silent: true}); - topic.updateNode(); // links the topic and the mapping to the node + topic.set('node', nodeOnViz, {silent: true}); + topic.updateNode(); // links the topic and the mapping to the node nodeOnViz.setData("dim", 1, "start"); nodeOnViz.setData("dim", 25, "end"); @@ -4102,7 +4136,7 @@ Metamaps.Topic = { } if (Metamaps.Create.newTopic.addSynapse && permitCreateSynapseAfter) { Metamaps.Create.newSynapse.topic1id = tempNode.getData('topic').id; - + // position the form midpoint.x = tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - tempNode.pos.getc().x) / 2; midpoint.y = tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - tempNode.pos.getc().y) / 2; @@ -4134,7 +4168,7 @@ Metamaps.Topic = { Metamaps.Visualize.mGraph.loadJSON(newnode); nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); topic.set('node', nodeOnViz, {silent: true}); - topic.updateNode(); // links the topic and the mapping to the node + topic.updateNode(); // links the topic and the mapping to the node nodeOnViz.setData("dim", 1, "start"); nodeOnViz.setData("dim", 25, "end"); @@ -4158,7 +4192,7 @@ Metamaps.Topic = { }; $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); - }; + }; var topicSuccessCallback = function (topicModel, response) { if (Metamaps.Active.Map) { mapping.save({ mappable_id: topicModel.id }, { @@ -4469,6 +4503,7 @@ Metamaps.Map = { Metamaps.Topics = new bb.TopicCollection(data.topics); Metamaps.Synapses = new bb.SynapseCollection(data.synapses); Metamaps.Mappings = new bb.MappingCollection(data.mappings); + Metamaps.Messages = data.messages; Metamaps.Backbone.attachCollectionEvents(); var map = Metamaps.Active.Map; @@ -4493,7 +4528,7 @@ Metamaps.Map = { Metamaps.JIT.prepareVizData(); // update filters - Metamaps.Filter.reset(); + Metamaps.Filter.reset(); // reset selected arrays Metamaps.Selected.reset(); @@ -4560,7 +4595,7 @@ Metamaps.Map = { var desc = synapse.get("desc"); var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1; - // make sure that both topics are being added, otherwise, it + // make sure that both topics are being added, otherwise, it // doesn't make sense to add the synapse var topicsNotFiltered = nodes_array.indexOf(synapse.get('node1_id')) > -1; topicsNotFiltered = topicsNotFiltered && nodes_array.indexOf(synapse.get('node2_id')) > -1; @@ -4614,7 +4649,7 @@ Metamaps.Map = { self.timeToTurn += 1; // if true, it's time to turn if (self.timeToTurn === self.sideLength) { - + self.turnCount += 1; // if true, it's time to increase side length if (self.turnCount % 2 === 0) { @@ -4695,7 +4730,7 @@ Metamaps.Map = { sy = this.scaleOffsetY; this.translateOffsetX += x*sx; this.translateOffsetY += y*sy; - this.getCtx().translate(x, y); + this.getCtx().translate(x, y); }; canvas.getCtx = function() { return this.canvas.getContext("2d"); @@ -4743,7 +4778,7 @@ Metamaps.Map = { } node.visited = !T; }); - + var imageData = { encoded_image: canvas.canvas.toDataURL() }; @@ -4756,7 +4791,7 @@ Metamaps.Map = { var yyyy = today.getFullYear(); if(dd<10) { dd='0'+dd - } + } if(mm<10) { mm='0'+mm } @@ -4796,7 +4831,7 @@ Metamaps.Map.CheatSheet = { $('#cheatSheet').tabs(); $('#quickReference').tabs().addClass("ui-tabs-vertical ui-helper-clearfix"); $("#quickReference .ui-tabs-nav li").removeClass("ui-corner-top").addClass("ui-corner-left"); - + // id = the id of a vimeo video var switchVideo = function (element, id) { $('.tutorialItem').removeClass("active"); @@ -4833,7 +4868,7 @@ Metamaps.Map.InfoBox = { var self = Metamaps.Map.InfoBox; $('.mapInfoIcon').click(self.toggleBox); - $('.mapInfoBox').click(function(event){ + $('.mapInfoBox').click(function(event){ event.stopPropagation(); }); $('body').click(self.close); @@ -4976,7 +5011,7 @@ Metamaps.Map.InfoBox = { Metamaps.Mappers.each(function(m){ string += '
  • ' + '' + m.get("name") + '
  • '; }); - + string += ""; return string; }, @@ -5040,7 +5075,7 @@ Metamaps.Map.InfoBox = { Metamaps.Realtime.turnOn(); } else if (permBefore === 'commons' && permission === 'public') { - Metamaps.Realtime.turnOff(true); // true is to 'silence' + Metamaps.Realtime.turnOff(true); // true is to 'silence' // the notification that would otherwise be sent } shareable = permission === 'private' ? '' : 'shareable'; @@ -5228,14 +5263,14 @@ Metamaps.Admin = { $('#metacodes_value').val(self.selectMetacodes.toString()); }, selectAll: function () { - var self = Metamaps.Admin; + var self = Metamaps.Admin; $('.editMetacodes li').removeClass('toggledOff'); self.selectMetacodes = self.allMetacodes.slice(0); $('#metacodes_value').val(self.selectMetacodes.toString()); }, deselectAll: function () { - var self = Metamaps.Admin; + var self = Metamaps.Admin; $('.editMetacodes li').addClass('toggledOff'); self.selectMetacodes = []; @@ -5265,4 +5300,3 @@ Metamaps.Admin = { } } }; - diff --git a/app/assets/javascripts/src/views/chatView.js b/app/assets/javascripts/src/views/chatView.js index be4bd713..9af4a008 100644 --- a/app/assets/javascripts/src/views/chatView.js +++ b/app/assets/javascripts/src/views/chatView.js @@ -7,7 +7,7 @@ Metamaps.Views.chatView = (function () { var Private = { messageHTML: "
    " + - "
    " + + "
    " + "
    <%= message %>
    " + "
    <%= timestamp %>
    " + "
    " + @@ -46,9 +46,9 @@ Metamaps.Views.chatView = (function () { this.$container.append(this.$juntoHeader); this.$container.append(this.$participants); this.$container.append(this.$chatHeader); - this.$container.append(this.$messageInput); this.$container.append(this.$button); this.$container.append(this.$messages); + this.$container.append(this.$messageInput); }, addEventListeners: function() { var self = this; @@ -116,12 +116,12 @@ Metamaps.Views.chatView = (function () { var m = _.clone(message.attributes); var today = new Date(); - m.timestamp = new Date(m.timestamp); + 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.image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; + m.image = m.user_image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; m.message = linker.link(m.message); var $html = $(this.messageTemplate(m)); this.$messages.append($html); @@ -138,9 +138,6 @@ Metamaps.Views.chatView = (function () { handleInputMessage: function() { var message = { message: this.$messageInput.val(), - timestamp: Date.now(), - user: this.mapper.get('name'), - image: this.mapper.get('image') }; this.$messageInput.val(''); $(document).trigger(chatView.events.message + '-' + this.room, [message]); @@ -247,6 +244,12 @@ Metamaps.Views.chatView = (function () { }, duration); } + chatView.prototype.clearMessages = function () { + this.unreadMessages = 0; + this.$unread.hide(); + this.$messages.empty(); + } + chatView.prototype.close = function () { this.$container.css({ right: '-300px' diff --git a/app/assets/javascripts/src/views/room.js b/app/assets/javascripts/src/views/room.js index 2003fe83..de920fc3 100644 --- a/app/assets/javascripts/src/views/room.js +++ b/app/assets/javascripts/src/views/room.js @@ -40,6 +40,8 @@ Metamaps.Views.room = (function () { this.isActiveRoom = false; this.webrtc.leaveRoom(); this.chat.removeParticipants(); + this.chat.clearMessages(); + this.messages.reset(); } room.prototype.setPeopleCount = function(count) { @@ -76,6 +78,55 @@ Metamaps.Views.room = (function () { } }); + 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(); + }); + + this.socket.on('addVideo', function (data) { + var existingPeer = self.webrtc.webrtc.peers.find(function(p) { return p.id === data.id; }); + if (!existingPeer) { + var peer = self.webrtc.webrtc.createPeer({ + id: data.id, + type: 'video', + enableDataChannels: true, + receiveMedia: { + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + } + } + }); + peer.avatar = data.avatar; + self.webrtc.emit('createdPeer', peer); + peer.start(); + + // the rest will happen automatically through the 'peerStreamAdded' event and associated event handlers + } + }); + var sendChatMessage = function (event, data) { self.sendChatMessage(data); }; @@ -90,22 +141,46 @@ Metamaps.Views.room = (function () { var id = this.webrtc.getDomId(peer), video = attachMediaStream(peer.stream); - v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200 }); + v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar }); if (this._videoAdded) this._videoAdded(v); this.videos[peer.id] = v; } room.prototype.removeVideo = function (peer) { - console.log(peer); + console.log('removeVideo', peer); var id = typeof peer == 'string' ? peer : peer.id; this.videos[id].remove(); delete this.videos[id]; } room.prototype.sendChatMessage = function (data) { + var self = this; //this.roomRef.child('messages').push(data); + console.log(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.messages.add(model); + $(document).trigger(room.events.newMessage, [model]); + }, + error: function (model, response) { + console.log('error!', response); + } + }); } + /** + * @class + * @static + */ + room.events = { + newMessage: "Room:newMessage" + }; + return room; })(); \ No newline at end of file diff --git a/app/assets/javascripts/src/views/videoView.js b/app/assets/javascripts/src/views/videoView.js index 8d6bf23a..7f35dee8 100644 --- a/app/assets/javascripts/src/views/videoView.js +++ b/app/assets/javascripts/src/views/videoView.js @@ -127,7 +127,8 @@ Metamaps.Views.videoView = (function () { $vidContainer.addClass('video-cutoff'); $vidContainer.append(this.video); - this.$avatar = $(''); + this.avatar = config.avatar; + this.$avatar = $(''); $vidContainer.append(this.$avatar); this.$container.append($vidContainer); @@ -157,6 +158,11 @@ Metamaps.Views.videoView = (function () { }); } + 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); diff --git a/app/assets/stylesheets/junto.css b/app/assets/stylesheets/junto.css index 5aff8095..6807424e 100644 --- a/app/assets/stylesheets/junto.css +++ b/app/assets/stylesheets/junto.css @@ -69,6 +69,8 @@ } .chat-box { position: relative; + display: flex; + flex-direction: column; z-index: 1; width: 300px; float: right; @@ -141,7 +143,7 @@ } .chat-box .participants { width: 100%; - height: 150px; + min-height: 150px; padding: 16px 0px 0px 0px; text-align: left; color: #f5f5f5; @@ -203,10 +205,7 @@ background-position-y: -24px; } .chat-box .chat-input { - position: absolute; - bottom: 0; - left: 0; - height: 80px; + min-height: 80px; width: 100%; padding: 8px 8px 8px 8px; font-size: 13px; @@ -214,7 +213,6 @@ } .chat-box .chat-messages { width: 100%; - height: 196px; padding: 16px 0px 0px 0px; overflow-y: auto; } diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index add8a0c1..39a012d1 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -78,8 +78,9 @@ class MapsController < ApplicationController object = m.mappable !object || (object.permission == "private" && (!authenticated? || (authenticated? && @current.id != object.user_id))) } + @allmessages = @map.messages - respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @map) + respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @allmessages, @map) } format.json { render json: @map } end @@ -109,6 +110,7 @@ class MapsController < ApplicationController @json['synapses'] = @allsynapses @json['mappings'] = @allmappings @json['mappers'] = @allmappers + @json['messages'] = @map.messages respond_to do |format| format.json { render json: @json } diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb new file mode 100644 index 00000000..e47f9e38 --- /dev/null +++ b/app/controllers/messages_controller.rb @@ -0,0 +1,62 @@ +class MessagesController < ApplicationController + + before_filter :require_user, except: [:show] + + # GET /messages/1.json + def show + @message = Message.find(params[:id]) + + 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 + + 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]) + + 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]) + @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 diff --git a/app/models/map.rb b/app/models/map.rb index e6a10605..ca447894 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -6,6 +6,7 @@ class Map < ActiveRecord::Base has_many :synapsemappings, -> { Mapping.synapsemapping }, class_name: :Mapping, dependent: :destroy has_many :topics, through: :topicmappings, source: :mappable, source_type: "Topic" has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse" + has_many :messages, as: :resource, dependent: :destroy # This method associates the attribute ":image" with a file attachment has_attached_file :screenshot, :styles => { diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 00000000..ca9d8553 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,54 @@ +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 + + ##### PERMISSIONS ###### + + def authorize_to_delete(user) + if (self.user != user) + return false + end + return self + end + + # returns false if user not allowed to 'show' Topic, Synapse, or Map + def authorize_to_show(user) + if (self.resource && self.resource.permission == "private" && self.resource.user != user) + return false + end + return self + end + + # returns false if user not allowed to 'edit' Topic, Synapse, or Map + def authorize_to_edit(user) + if !user + return false + elsif (self.user != user) + return false + end + return self + end + + # returns Boolean if user allowed to view Topic, Synapse, or Map + def authorize_to_view(user) + if (self.resource && self.resource.permission == "private" && self.resource.user != user) + return false + end + return true + end + +end diff --git a/app/models/user.rb b/app/models/user.rb index 16b31872..0969a369 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,9 +10,9 @@ class User < ActiveRecord::Base before_create :generate_code devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable - + serialize :settings, UserPreference - + validates :password, :presence => true, :length => { :within => 8..40 }, :on => :create @@ -27,7 +27,7 @@ class User < ActiveRecord::Base validates_uniqueness_of :email # done by devise validates :joinedwithcode, :presence => true, :inclusion => { :in => $codes, :message => "%{value} is not valid" }, :on => :create - + # This method associates the attribute ":image" with a file attachment has_attached_file :image, :styles => { :thirtytwo => ['32x32#', :png], @@ -36,7 +36,7 @@ class User < ActiveRecord::Base :onetwentyeight => ['128x128#', :png] }, :default_url => 'https://s3.amazonaws.com/metamaps-assets/site/user.png' - + # Validate the attached image is image/jpg, image/png, etc validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/ @@ -61,7 +61,7 @@ class User < ActiveRecord::Base json['rtype'] = "mapper" json end - + #generate a random 8 letter/digit code that they can use to invite people def generate_code self.code = rand(36**8).to_s(36) @@ -78,10 +78,10 @@ class User < ActiveRecord::Base if code == joinedwithcode update(generation: 0) else - update(generation: User.find_by_code(joinedwithcode) + 1) + update(generation: User.find_by_code(joinedwithcode).generation + 1) end end - + def settings # make sure we always return a UserPreference instance if read_attribute(:settings).nil? @@ -89,7 +89,7 @@ class User < ActiveRecord::Base end read_attribute :settings end - + def settings=(val) write_attribute :settings, val end diff --git a/app/views/layouts/_upperelements.html.erb b/app/views/layouts/_upperelements.html.erb index 4d5bf6e5..55a619cc 100644 --- a/app/views/layouts/_upperelements.html.erb +++ b/app/views/layouts/_upperelements.html.erb @@ -20,6 +20,7 @@
    SUPPORT US!
    +
    start my video
    Filter
    diff --git a/app/views/maps/show.html.erb b/app/views/maps/show.html.erb index 0c5e4f97..5d6859b7 100644 --- a/app/views/maps/show.html.erb +++ b/app/views/maps/show.html.erb @@ -13,5 +13,6 @@ Metamaps.Topics = <%= @alltopics.to_json.html_safe %>; Metamaps.Synapses = <%= @allsynapses.to_json.html_safe %>; Metamaps.Mappings = <%= @allmappings.to_json.html_safe %>; + Metamaps.Messages = <%= @allmessages.to_json.html_safe %>; Metamaps.Visualize.type = "ForceDirected"; diff --git a/config/routes.rb b/config/routes.rb index a3ab6e3a..3646c1ab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,7 @@ Metamaps::Application.routes.draw do get 'search/mappers', to: 'main#searchmappers', as: :searchmappers get 'search/synapses', to: 'main#searchsynapses', as: :searchsynapses + resources :messages, only: [:show, :create, :update, :destroy] resources :mappings, except: [:index, :new, :edit] resources :metacode_sets, :except => [:show] resources :metacodes, :except => [:show, :destroy] diff --git a/db/migrate/20151205205831_messages.rb b/db/migrate/20151205205831_messages.rb new file mode 100644 index 00000000..e892ebcc --- /dev/null +++ b/db/migrate/20151205205831_messages.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 45fbcf67..ebc8860e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151025083043) do +ActiveRecord::Schema.define(version: 20151205205831) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -63,6 +63,19 @@ ActiveRecord::Schema.define(version: 20151025083043) do add_index "maps", ["user_id"], name: "index_maps_on_user_id", using: :btree + create_table "messages", force: :cascade do |t| + t.text "message" + t.integer "user_id" + t.integer "resource_id" + t.string "resource_type" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "messages", ["resource_id"], name: "index_messages_on_resource_id", using: :btree + add_index "messages", ["resource_type"], name: "index_messages_on_resource_type", using: :btree + add_index "messages", ["user_id"], name: "index_messages_on_user_id", using: :btree + create_table "metacode_sets", force: :cascade do |t| t.string "name" t.text "desc" diff --git a/realtime/realtime-server.js b/realtime/realtime-server.js index fb441646..f352480b 100644 --- a/realtime/realtime-server.js +++ b/realtime/realtime-server.js @@ -3,6 +3,8 @@ var signalServer = require('./signal'), stunservers = [{"url": "stun:stun.l.google.com:19302"}]; +io.set('log', false); + function start() { signalServer(io, stunservers); @@ -91,6 +93,13 @@ function start() { 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) { var mapId = data.mapid; delete data.mapid; diff --git a/realtime/signal.js b/realtime/signal.js index d77933ad..a08d5dfa 100644 --- a/realtime/signal.js +++ b/realtime/signal.js @@ -9,10 +9,12 @@ module.exports = function(io, stunservers) { function describeRoom(name) { var clients = io.sockets.clients(name); var result = { - clients: {} + clients: {}, + avatars: {} }; clients.forEach(function (client) { result.clients[client.id] = client.resources; + result.avatars[client.id] = client.avatar; }); return result; } @@ -38,7 +40,7 @@ module.exports = function(io, stunservers) { client.resources = { screen: false, - video: true, + video: false, audio: false }; @@ -63,6 +65,18 @@ module.exports = function(io, stunservers) { }); client.on('join', join); + client.on('videoAdded', videoAdded); + + function videoAdded(data) { + var socketsInRoom = io.sockets.clients(client.room); + client.resources.video = true; + client.avatar = data.avatar; + socketsInRoom.forEach(function(socket) { + if (socket.id !== client.id) { + socket.emit('addVideo', { id: client.id, avatar: data.avatar }); + } + }); + } client.on('requestRoomCount', function(name) { client.emit('room_count', {