This commit is contained in:
Connor Turland 2016-12-27 02:19:35 +00:00 committed by GitHub
commit 964dbeb2d1
17 changed files with 229 additions and 60 deletions

View file

@ -44,6 +44,7 @@ group :test do
end end
group :development, :test do group :development, :test do
gem 'puma'
gem 'better_errors' gem 'better_errors'
gem 'binding_of_caller' gem 'binding_of_caller'
gem 'pry-byebug' gem 'pry-byebug'

View file

@ -167,6 +167,7 @@ GEM
pry (~> 0.10) pry (~> 0.10)
pry-rails (0.3.4) pry-rails (0.3.4)
pry (>= 0.9.10) pry (>= 0.9.10)
puma (2.15.3)
pundit (1.1.0) pundit (1.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
pundit_extra (0.3.0) pundit_extra (0.3.0)
@ -298,6 +299,7 @@ DEPENDENCIES
pg pg
pry-byebug pry-byebug
pry-rails pry-rails
puma
pundit pundit
pundit_extra pundit_extra
rack-attack rack-attack

View file

@ -14,6 +14,7 @@
//= require jquery //= require jquery
//= require jquery-ui //= require jquery-ui
//= require jquery_ujs //= require jquery_ujs
//= require action_cable
//= require_directory ./lib //= require_directory ./lib
//= require ./webpacked/metamaps.bundle //= require ./webpacked/metamaps.bundle
//= require ./Metamaps.ServerData //= require ./Metamaps.ServerData

View file

@ -0,0 +1,4 @@
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end

View file

@ -0,0 +1,20 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.name
end
protected
def find_verified_user
verified_user = User.find_by(id: cookies.signed['user.id'])
if verified_user && cookies.signed['user.expires_at'] > Time.now
verified_user
else
reject_unauthorized_connection
end
end
end
end

View file

@ -0,0 +1,7 @@
class TopicChannel < ApplicationCable::Channel
# Called when the consumer has successfully
# become a subscriber of this channel.
def subscribed
stream_from "topic_#{params[:id]}"
end
end

View file

@ -22,6 +22,7 @@ class Synapse < ApplicationRecord
where(topic1_id: topic_id).or(where(topic2_id: topic_id)) where(topic1_id: topic_id).or(where(topic2_id: topic_id))
} }
after_create :after_created
after_update :after_updated after_update :after_updated
delegate :name, to: :user, prefix: true delegate :name, to: :user, prefix: true
@ -42,6 +43,25 @@ class Synapse < ApplicationRecord
super(methods: [:user_name, :user_image, :collaborator_ids]) super(methods: [:user_name, :user_image, :collaborator_ids])
end end
def filtered
{
id: id,
permission: permission,
user_id: user_id,
collaborator_ids: collaborator_ids
}
end
def after_created
data = {
synapse: filtered,
topic1: topic1.filtered,
topic2: topic2.filtered
}
ActionCable.server.broadcast 'topic_' + topic1_id.to_s, type: 'newSynapse', data: data
ActionCable.server.broadcast 'topic_' + topic2_id.to_s, type: 'newSynapse', data: data
end
def after_updated def after_updated
attrs = ['desc', 'category', 'permission', 'defer_to_map_id'] attrs = ['desc', 'category', 'permission', 'defer_to_map_id']
if attrs.any? {|k| changed_attributes.key?(k)} if attrs.any? {|k| changed_attributes.key?(k)}

View file

@ -90,6 +90,15 @@ class Topic < ApplicationRecord
end end
end end
def filtered
{
id: id,
permission: permission,
user_id: user_id,
collaborator_ids: collaborator_ids
}
end
# TODO: move to a decorator? # TODO: move to a decorator?
def synapses_csv(output_format = 'array') def synapses_csv(output_format = 'array')
output = [] output = []

View file

@ -0,0 +1,10 @@
Warden::Manager.after_set_user do |user,auth,opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = user.id
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
end
Warden::Manager.before_logout do |user, auth, opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = nil
auth.cookies.signed["#{scope}.expires_at"] = nil
end

View file

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Metamaps::Application.routes.draw do Metamaps::Application.routes.draw do
use_doorkeeper use_doorkeeper
mount ActionCable.server => '/cable'
root to: 'main#home', via: :get root to: 'main#home', via: :get
get 'request', to: 'main#requestinvite', as: :request get 'request', to: 'main#requestinvite', as: :request

View file

@ -0,0 +1,54 @@
/* global $, ActionCable */
import Active from './Active'
import DataModel from './DataModel'
import Topic from './Topic'
const Cable = {
topicSubs: {},
init: () => {
let self = Cable
self.cable = ActionCable.createConsumer()
},
subAllTopics: () => {
let self = Cable
DataModel.Topics.models.forEach(topic => self.subTopic(topic.id))
},
subUnsubbedTopics: (topic1id, topic2id) => {
if (!Cable.topicSubs[topic1id]) Cable.subTopic(topic1id)
if (!Cable.topicSubs[topic2id]) Cable.subTopic(topic2id)
},
subTopic: id => {
let self = Cable
self.topicSubs[id] = self.cable.subscriptions.create({
channel: 'TopicChannel',
id: id
}, {
received: event => self[event.type](event.data)
})
},
unsubTopic: id => {
let self = Cable
self.topicSubs[id] && self.topicSubs[id].unsubscribe()
delete self.topicSubs[id]
},
unsubAllTopics: () => {
let self = Cable
Object.keys(self.topicSubs).forEach(id => {
self.topicSubs[id].unsubscribe()
})
self.topicSubs = {}
},
// begin event functions
newSynapse: data => {
const m = Active.Mapper
const s = new DataModel.Synapse(data.synapse)
const t1 = new DataModel.Topic(data.topic1)
const t2 = new DataModel.Topic(data.topic2)
if (t1.authorizeToShow(m) && t2.authorizeToShow(m) && s.authorizeToShow(m)) {
Topic.fetchForTopicView(data.synapse.id)
}
}
}
export default Cable

View file

@ -87,6 +87,10 @@ const Synapse = Backbone.Model.extend({
if (mapper && (this.get('permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true if (mapper && (this.get('permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
else return false else return false
}, },
authorizeToShow: function(mapper) {
if (this.get('permission') !== 'private' || (mapper && this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
else return false
},
authorizePermissionChange: function(mapper) { authorizePermissionChange: function(mapper) {
if (mapper && this.get('user_id') === mapper.get('id')) return true if (mapper && this.get('user_id') === mapper.get('id')) return true
else return false else return false

View file

@ -88,6 +88,10 @@ const Topic = Backbone.Model.extend({
return false return false
} }
}, },
authorizeToShow: function(mapper) {
if (this.get('permission') !== 'private' || (mapper && this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
else return false
},
authorizePermissionChange: function(mapper) { authorizePermissionChange: function(mapper) {
if (mapper && this.get('user_id') === mapper.get('id')) return true if (mapper && this.get('user_id') === mapper.get('id')) return true
else return false else return false

View file

@ -69,6 +69,28 @@ const JIT = {
self.topicLinkImage = new Image() self.topicLinkImage = new Image()
self.topicLinkImage.src = serverData['topic_link_signifier.png'] self.topicLinkImage.src = serverData['topic_link_signifier.png']
}, },
connectModelsToGraph: function () {
var i, l, t, s
Visualize.mGraph.graph.eachNode(function(n) {
t = DataModel.Topics.get(n.id)
t.set({ node: n }, { silent: true })
t.updateNode()
n.eachAdjacency(function(edge) {
if (!edge.getData('init')) {
edge.setData('init', true)
l = edge.getData('synapseIDs').length
for (i = 0; i < l; i++) {
s = DataModel.Synapses.get(edge.getData('synapseIDs')[i])
s.set({ edge: edge }, { silent: true })
s.updateEdge()
}
}
})
})
},
/** /**
* convert our topic JSON into something JIT can use * convert our topic JSON into something JIT can use
*/ */
@ -640,14 +662,14 @@ const JIT = {
} }
}, },
// this will just be used to patch the ForceDirected graphsettings with the few things which actually differ // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ
background: { /*background: {
levelDistance: 200, levelDistance: 200,
numberOfCircles: 4, numberOfCircles: 4,
CanvasStyles: { CanvasStyles: {
strokeStyle: '#333', strokeStyle: '#333',
lineWidth: 1.5 lineWidth: 1.5
} }
}, },*/
levelDistance: 200 levelDistance: 200
}, },
onMouseEnter: function(edge) { onMouseEnter: function(edge) {

View file

@ -4,6 +4,7 @@ import $jit from '../patched/JIT'
import Active from './Active' import Active from './Active'
import AutoLayout from './AutoLayout' import AutoLayout from './AutoLayout'
import Cable from './Cable'
import Create from './Create' import Create from './Create'
import DataModel from './DataModel' import DataModel from './DataModel'
import Filter from './Filter' import Filter from './Filter'
@ -43,6 +44,8 @@ const Topic = {
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses) DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
DataModel.attachCollectionEvents() DataModel.attachCollectionEvents()
Cable.subAllTopics()
document.title = Active.Topic.get('name') + ' | Metamaps' document.title = Active.Topic.get('name') + ' | Metamaps'
// set filter mapper H3 text // set filter mapper H3 text
@ -78,6 +81,7 @@ const Topic = {
TopicCard.hideCard() TopicCard.hideCard()
SynapseCard.hideCard() SynapseCard.hideCard()
Filter.close() Filter.close()
Cable.unsubAllTopics()
} }
}, },
centerOn: function(nodeid, callback) { centerOn: function(nodeid, callback) {
@ -94,6 +98,62 @@ const Topic = {
Active.Topic = DataModel.Topics.get(nodeid) Active.Topic = DataModel.Topics.get(nodeid)
} }
}, },
fetchForTopicView: function(synapseId) {
var self = this
var successCallback
successCallback = function(data) {
data = data.data
if (Visualize.mGraph.busy) {
// don't clash with centerOn
window.setTimeout(function() { successCallback(data) }, 100)
return
}
// todo: these won't work when the api works as expected
const topic1user = {
id: data.topic1.user_id,
image: data.topic1.user_image,
name: data.topic1.name
}
const topic2user = {
id: data.topic2.user_id,
image: data.topic2.user_image,
name: data.topic2.name
}
let creators = [data.user, topic1user, topic2user]
DataModel.Creators.add(creators)
DataModel.Topics.add(data.topic1)
DataModel.Topics.add(data.topic2)
Cable.subUnsubbedTopics(data.topic1.id, data.topic2.id)
var topicColl = new DataModel.TopicCollection([data.topic1, data.topic2])
data.topic1_id = data.topic1.id
data.topic2_id = data.topic2.id
data.user_id = data.user.id
delete data.topic1
delete data.topic2
delete data.user
DataModel.Synapses.add(data)
var synapseColl = new DataModel.SynapseCollection(data)
var graph = JIT.convertModelsToJIT(topicColl, synapseColl)[0]
console.log(graph)
Visualize.mGraph.op.sum(graph, {
type: 'fade',
duration: 500,
hideLabels: false
})
JIT.connectModelsToGraph()
}
$.ajax({
type: 'GET',
url: '/api/v2/synapses/' + synapseId + '?embed=topic1,topic2,user',
success: successCallback,
error: function() {}
})
},
fetchRelatives: function(nodes, metacodeId) { fetchRelatives: function(nodes, metacodeId) {
var self = this var self = this
@ -128,27 +188,8 @@ const Topic = {
duration: 500, duration: 500,
hideLabels: false hideLabels: false
}) })
JIT.connectModelsToGraph()
var i, l, t, s
Visualize.mGraph.graph.eachNode(function(n) {
t = DataModel.Topics.get(n.id)
t.set({ node: n }, { silent: true })
t.updateNode()
n.eachAdjacency(function(edge) {
if (!edge.getData('init')) {
edge.setData('init', true)
l = edge.getData('synapseIDs').length
for (i = 0; i < l; i++) {
s = DataModel.Synapses.get(edge.getData('synapseIDs')[i])
s.set({ edge: edge }, { silent: true })
s.updateEdge()
}
}
})
})
if ($.isArray(nodes) && nodes.length > 1) { if ($.isArray(nodes) && nodes.length > 1) {
self.fetchRelatives(nodes.slice(1), metacodeId) self.fetchRelatives(nodes.slice(1), metacodeId)
} }

View file

@ -48,28 +48,9 @@ const Visualize = {
computePositions: function() { computePositions: function() {
const self = Visualize const self = Visualize
JIT.connectModelsToGraph()
if (self.type === 'RGraph') { if (self.type === 'RGraph') {
let i
let l
self.mGraph.graph.eachNode(function(n) { self.mGraph.graph.eachNode(function(n) {
const topic = DataModel.Topics.get(n.id)
topic.set({ node: n }, { silent: true })
topic.updateNode()
n.eachAdjacency(function(edge) {
if (!edge.getData('init')) {
edge.setData('init', true)
l = edge.getData('synapseIDs').length
for (i = 0; i < l; i++) {
const synapse = DataModel.Synapses.get(edge.getData('synapseIDs')[i])
synapse.set({ edge: edge }, { silent: true })
synapse.updateEdge()
}
}
})
var pos = n.getPos() var pos = n.getPos()
pos.setc(-200, -200) pos.setc(-200, -200)
}) })
@ -77,23 +58,7 @@ const Visualize = {
} else if (self.type === 'ForceDirected') { } else if (self.type === 'ForceDirected') {
self.mGraph.graph.eachNode(function(n) { self.mGraph.graph.eachNode(function(n) {
const topic = DataModel.Topics.get(n.id) const topic = DataModel.Topics.get(n.id)
topic.set({ node: n }, { silent: true })
topic.updateNode()
const mapping = topic.getMapping() const mapping = topic.getMapping()
n.eachAdjacency(function(edge) {
if (!edge.getData('init')) {
edge.setData('init', true)
const l = edge.getData('synapseIDs').length
for (let i = 0; i < l; i++) {
const synapse = DataModel.Synapses.get(edge.getData('synapseIDs')[i])
synapse.set({ edge: edge }, { silent: true })
synapse.updateEdge()
}
}
})
const startPos = new $jit.Complex(0, 0) const startPos = new $jit.Complex(0, 0)
const endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')) const endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'))
n.setPos(startPos, 'start') n.setPos(startPos, 'start')

View file

@ -2,9 +2,10 @@ import Account from './Account'
import Active from './Active' import Active from './Active'
import Admin from './Admin' import Admin from './Admin'
import AutoLayout from './AutoLayout' import AutoLayout from './AutoLayout'
import DataModel from './DataModel' import Cable from './Cable'
import Control from './Control' import Control from './Control'
import Create from './Create' import Create from './Create'
import DataModel from './DataModel'
import Debug from './Debug' import Debug from './Debug'
import Filter from './Filter' import Filter from './Filter'
import GlobalUI, { import GlobalUI, {
@ -38,9 +39,10 @@ Metamaps.Account = Account
Metamaps.Active = Active Metamaps.Active = Active
Metamaps.Admin = Admin Metamaps.Admin = Admin
Metamaps.AutoLayout = AutoLayout Metamaps.AutoLayout = AutoLayout
Metamaps.DataModel = DataModel Metamaps.Cable = Cable
Metamaps.Control = Control Metamaps.Control = Control
Metamaps.Create = Create Metamaps.Create = Create
Metamaps.DataModel = DataModel
Metamaps.Debug = Debug Metamaps.Debug = Debug
Metamaps.Filter = Filter Metamaps.Filter = Filter
Metamaps.GlobalUI = GlobalUI Metamaps.GlobalUI = GlobalUI
@ -106,6 +108,8 @@ document.addEventListener('DOMContentLoaded', function() {
JIT.prepareVizData() JIT.prepareVizData()
GlobalUI.showDiv('#infovis') GlobalUI.showDiv('#infovis')
} }
if (Active.Topic) Cable.subAllTopics()
}) })
export default Metamaps export default Metamaps