diff --git a/frontend/src/Metamaps/DataModel/Map.js b/frontend/src/Metamaps/DataModel/Map.js
new file mode 100644
index 00000000..3ae47d1a
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Map.js
@@ -0,0 +1,92 @@
+/* global $ */
+
+import _ from 'lodash'
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Active from '../Active'
+import { InfoBox } from '../Map'
+import Mapper from '../Mapper'
+import Realtime from '../Realtime'
+
+import MapperCollection from './MapperCollection'
+import TopicCollection from './TopicCollection'
+import SynapseCollection from './SynapseCollection'
+import MappingCollection from './MappingCollection'
+
+const Map = Backbone.Model.extend({
+ urlRoot: '/maps',
+ blacklist: ['created_at', 'updated_at', 'created_at_clean', 'updated_at_clean', 'user_name', 'contributor_count', 'topic_count', 'synapse_count', 'topics', 'synapses', 'mappings', 'mappers'],
+ toJSON: function (options) {
+ return _.omit(this.attributes, this.blacklist)
+ },
+ save: function (key, val, options) {
+ var attrs
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (key == null || typeof key === 'object') {
+ attrs = key
+ options = val
+ } else {
+ (attrs = {})[key] = val
+ }
+
+ var newOptions = options || {}
+ var s = newOptions.success
+
+ newOptions.success = function (model, response, opt) {
+ if (s) s(model, response, opt)
+ model.trigger('saved')
+ }
+ return Backbone.Model.prototype.save.call(this, attrs, newOptions)
+ },
+ initialize: function () {
+ this.on('changeByOther', this.updateView)
+ this.on('saved', this.savedEvent)
+ },
+ savedEvent: function () {
+ Realtime.updateMap(this)
+ },
+ authorizeToEdit: function (mapper) {
+ 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
+ }
+ },
+ authorizePermissionChange: function (mapper) {
+ if (mapper && this.get('user_id') === mapper.get('id')) {
+ return true
+ } else {
+ return false
+ }
+ },
+ getUser: function () {
+ return Mapper.get(this.get('user_id'))
+ },
+ updateView: function () {
+ var map = Active.Map
+ var isActiveMap = this.id === map.id
+ if (isActiveMap) {
+ InfoBox.updateNameDescPerm(this.get('name'), this.get('desc'), this.get('permission'))
+ this.updateMapWrapper()
+ // mobile menu
+ $('#header_content').html(this.get('name'))
+ document.title = this.get('name') + ' | Metamaps'
+ }
+ },
+ updateMapWrapper: function () {
+ var map = Active.Map
+ var isActiveMap = this.id === map.id
+ var authorized = map && map.authorizeToEdit(Active.Mapper) ? 'canEditMap' : ''
+ var commonsMap = map && map.get('permission') === 'commons' ? 'commonsMap' : ''
+ if (isActiveMap) {
+ $('.wrapper').removeClass('canEditMap commonsMap').addClass(authorized + ' ' + commonsMap)
+ }
+ }
+})
+
+export default Map
diff --git a/frontend/src/Metamaps/DataModel/MapCollection.js b/frontend/src/Metamaps/DataModel/MapCollection.js
new file mode 100644
index 00000000..f72e1e0e
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/MapCollection.js
@@ -0,0 +1,81 @@
+/* global Metamaps */
+
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Map from './Map'
+
+/*
+ * Dependencies:
+ * - Metamaps.Loading
+ */
+
+const MapCollection = Backbone.Collection.extend({
+ model: Map,
+ initialize: function (models, options) {
+ this.id = options.id
+ this.sortBy = options.sortBy
+
+ if (options.mapperId) {
+ this.mapperId = options.mapperId
+ }
+
+ // this.page represents the NEXT page to fetch
+ this.page = models.length > 0 ? (models.length < 20 ? 'loadedAll' : 2) : 1
+ },
+ url: function () {
+ if (!this.mapperId) {
+ return '/explore/' + this.id + '.json'
+ } else {
+ return '/explore/mapper/' + this.mapperId + '.json'
+ }
+ },
+ comparator: function (a, b) {
+ a = a.get(this.sortBy)
+ b = b.get(this.sortBy)
+ var temp
+ if (this.sortBy === 'name') {
+ a = a ? a.toLowerCase() : ''
+ b = b ? b.toLowerCase() : ''
+ } else {
+ // this is for updated_at and created_at
+ temp = a
+ a = b
+ b = temp
+ a = (new Date(a)).getTime()
+ b = (new Date(b)).getTime()
+ }
+ return a > b ? 1 : a < b ? -1 : 0
+ },
+ getMaps: function (cb) {
+ var self = this
+
+ Metamaps.Loading.show()
+
+ if (this.page !== 'loadedAll') {
+ var numBefore = this.length
+ this.fetch({
+ remove: false,
+ silent: true,
+ data: { page: this.page },
+ success: function (collection, response, options) {
+ // you can pass additional options to the event you trigger here as well
+ if (collection.length - numBefore < 20) {
+ self.page = 'loadedAll'
+ } else {
+ self.page += 1
+ }
+ self.trigger('successOnFetch', cb)
+ },
+ error: function (collection, response, options) {
+ // you can pass additional options to the event you trigger here as well
+ self.trigger('errorOnFetch')
+ }
+ })
+ } else {
+ self.trigger('successOnFetch', cb)
+ }
+ }
+})
+
+export default MapCollection
diff --git a/frontend/src/Metamaps/DataModel/Mapper.js b/frontend/src/Metamaps/DataModel/Mapper.js
new file mode 100644
index 00000000..3627fbd6
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Mapper.js
@@ -0,0 +1,21 @@
+import _ from 'lodash'
+import Backbone from 'backbone'
+Backbone.$ = window.$
+import outdent from 'outdent'
+
+const Mapper = Backbone.Model.extend({
+ urlRoot: '/users',
+ blacklist: ['created_at', 'updated_at'],
+ toJSON: function (options) {
+ return _.omit(this.attributes, this.blacklist)
+ },
+ prepareLiForFilter: function () {
+ return outdent`
+
+
+ ${this.get('name')}
+ `
+ }
+})
+
+export default Mapper
diff --git a/frontend/src/Metamaps/DataModel/MapperCollection.js b/frontend/src/Metamaps/DataModel/MapperCollection.js
new file mode 100644
index 00000000..e0ce2bed
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/MapperCollection.js
@@ -0,0 +1,11 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Mapper from './Mapper'
+
+const MapperCollection = Backbone.Collection.extend({
+ model: Mapper,
+ url: '/users'
+})
+
+export default MapperCollection
diff --git a/frontend/src/Metamaps/DataModel/Mapping.js b/frontend/src/Metamaps/DataModel/Mapping.js
new file mode 100644
index 00000000..8bf92ed2
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Mapping.js
@@ -0,0 +1,37 @@
+import _ from 'lodash'
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Active from '../Active'
+import Map from '../Map'
+import Synapse from '../Synapse'
+import Topic from '../Topic'
+
+const Mapping = Backbone.Model.extend({
+ urlRoot: '/mappings',
+ blacklist: ['created_at', 'updated_at'],
+ toJSON: function (options) {
+ return _.omit(this.attributes, this.blacklist)
+ },
+ initialize: function () {
+ if (this.isNew()) {
+ this.set({
+ 'user_id': Active.Mapper.id,
+ 'map_id': Active.Map ? Active.Map.id : null
+ })
+ }
+ },
+ getMap: function () {
+ return Map.get(this.get('map_id'))
+ },
+ getTopic: function () {
+ if (this.get('mappable_type') !== 'Topic') return false
+ return Topic.get(this.get('mappable_id'))
+ },
+ getSynapse: function () {
+ if (this.get('mappable_type') !== 'Synapse') return false
+ return Synapse.get(this.get('mappable_id'))
+ }
+})
+
+export default Mapping
diff --git a/frontend/src/Metamaps/DataModel/MappingCollection.js b/frontend/src/Metamaps/DataModel/MappingCollection.js
new file mode 100644
index 00000000..e475e098
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/MappingCollection.js
@@ -0,0 +1,11 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Mapping from './Mapping'
+
+const MappingCollection = Backbone.Collection.extend({
+ model: Mapping,
+ url: '/mappings'
+})
+
+export default MappingCollection
diff --git a/frontend/src/Metamaps/DataModel/Message.js b/frontend/src/Metamaps/DataModel/Message.js
new file mode 100644
index 00000000..f7dc9bee
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Message.js
@@ -0,0 +1,13 @@
+import _ from 'lodash'
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+const Message = Backbone.Model.extend({
+ urlRoot: '/messages',
+ blacklist: ['created_at', 'updated_at'],
+ toJSON: function (options) {
+ return _.omit(this.attributes, this.blacklist)
+ }
+})
+
+export default Message
diff --git a/frontend/src/Metamaps/DataModel/MessageCollection.js b/frontend/src/Metamaps/DataModel/MessageCollection.js
new file mode 100644
index 00000000..a572c212
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/MessageCollection.js
@@ -0,0 +1,11 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Message from './Message'
+
+const MessageCollection = Backbone.Collection.extend({
+ model: Message,
+ url: '/messages'
+})
+
+export default MessageCollection
diff --git a/frontend/src/Metamaps/DataModel/Metacode.js b/frontend/src/Metamaps/DataModel/Metacode.js
new file mode 100644
index 00000000..a3d523fd
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Metacode.js
@@ -0,0 +1,21 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+import outdent from 'outdent'
+
+const Metacode = Backbone.Model.extend({
+ initialize: function () {
+ var image = new Image()
+ image.crossOrigin = 'Anonymous'
+ image.src = this.get('icon')
+ this.set('image', image)
+ },
+ prepareLiForFilter: function () {
+ return outdent`
+
+
+ ${this.get('name').toLowerCase()}
+ `
+ }
+})
+
+export default Metacode
diff --git a/frontend/src/Metamaps/DataModel/MetacodeCollection.js b/frontend/src/Metamaps/DataModel/MetacodeCollection.js
new file mode 100644
index 00000000..ff4626d1
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/MetacodeCollection.js
@@ -0,0 +1,16 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Metacode from './Metacode'
+
+const MetacodeCollection = Backbone.Collection.extend({
+ model: Metacode,
+ url: '/metacodes',
+ comparator: function (a, b) {
+ a = a.get('name').toLowerCase()
+ b = b.get('name').toLowerCase()
+ return a > b ? 1 : a < b ? -1 : 0
+ }
+})
+
+export default MetacodeCollection
diff --git a/frontend/src/Metamaps/DataModel/Synapse.js b/frontend/src/Metamaps/DataModel/Synapse.js
new file mode 100644
index 00000000..2d8f4256
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Synapse.js
@@ -0,0 +1,185 @@
+/* global Metamaps, $ */
+
+import _ from 'lodash'
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Active from '../Active'
+import Filter from '../Filter'
+import JIT from '../JIT'
+import Realtime from '../Realtime'
+import SynapseCard from '../SynapseCard'
+import Visualize from '../Visualize'
+
+
+/*
+ * Dependencies:
+ * - Metamaps.Mappings
+ * - Metamaps.Topics
+ */
+
+const Synapse = Backbone.Model.extend({
+ urlRoot: '/synapses',
+ blacklist: ['edge', 'created_at', 'updated_at'],
+ toJSON: function (options) {
+ return _.omit(this.attributes, this.blacklist)
+ },
+ save: function (key, val, options) {
+ var attrs
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (key == null || typeof key === 'object') {
+ attrs = key
+ options = val
+ } else {
+ (attrs = {})[key] = val
+ }
+
+ var newOptions = options || {}
+ var s = newOptions.success
+
+ var permBefore = this.get('permission')
+
+ newOptions.success = function (model, response, opt) {
+ if (s) s(model, response, opt)
+ model.trigger('saved')
+
+ if (permBefore === 'private' && model.get('permission') !== 'private') {
+ model.trigger('noLongerPrivate')
+ }
+ else if (permBefore !== 'private' && model.get('permission') === 'private') {
+ model.trigger('nowPrivate')
+ }
+ }
+ return Backbone.Model.prototype.save.call(this, attrs, newOptions)
+ },
+ initialize: function () {
+ if (this.isNew()) {
+ this.set({
+ 'user_id': Active.Mapper.id,
+ 'permission': Active.Map ? Active.Map.get('permission') : 'commons',
+ 'category': 'from-to'
+ })
+ }
+
+ this.on('changeByOther', this.updateCardView)
+ this.on('change', this.updateEdgeView)
+ this.on('saved', this.savedEvent)
+ this.on('noLongerPrivate', function () {
+ var newSynapseData = {
+ mappingid: this.getMapping().id,
+ mappableid: this.id
+ }
+
+ $(document).trigger(JIT.events.newSynapse, [newSynapseData])
+ })
+ this.on('nowPrivate', function () {
+ $(document).trigger(JIT.events.removeSynapse, [{
+ mappableid: this.id
+ }])
+ })
+
+ this.on('change:desc', Filter.checkSynapses, this)
+ },
+ prepareLiForFilter: function () {
+ var li = ''
+ li += ''
+ li += ''
+ li += '' + this.get('desc') + '
'
+ return li
+ },
+ authorizeToEdit: function (mapper) {
+ if (mapper && (this.get('calculated_permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
+ else return false
+ },
+ authorizePermissionChange: function (mapper) {
+ if (mapper && this.get('user_id') === mapper.get('id')) return true
+ else return false
+ },
+ getTopic1: function () {
+ return Metamaps.Topics.get(this.get('topic1_id'))
+ },
+ getTopic2: function () {
+ return Metamaps.Topics.get(this.get('topic2_id'))
+ },
+ getDirection: function () {
+ var t1 = this.getTopic1(),
+ t2 = this.getTopic2()
+
+ return t1 && t2 ? [
+ t1.get('node').id,
+ t2.get('node').id
+ ] : false
+ },
+ getMapping: function () {
+ if (!Active.Map) return false
+
+ return Metamaps.Mappings.findWhere({
+ map_id: Active.Map.id,
+ mappable_type: 'Synapse',
+ mappable_id: this.isNew() ? this.cid : this.id
+ })
+ },
+ createEdge: function (providedMapping) {
+ var mapping, mappingID
+ var synapseID = this.isNew() ? this.cid : this.id
+
+ var edge = {
+ nodeFrom: this.get('topic1_id'),
+ nodeTo: this.get('topic2_id'),
+ data: {
+ $synapses: [],
+ $synapseIDs: [synapseID],
+ }
+ }
+
+ if (Active.Map) {
+ mapping = providedMapping || 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 (Active.Map) {
+ mapping = this.getMapping()
+ edge.getData('mappings').push(mapping)
+ }
+
+ return edge
+ },
+ savedEvent: function () {
+ Realtime.updateSynapse(this)
+ },
+ updateViews: function () {
+ this.updateCardView()
+ this.updateEdgeView()
+ },
+ updateCardView: function () {
+ var onPageWithSynapseCard = Active.Map || Active.Topic
+ var edge = this.get('edge')
+
+ // update synapse card, if this synapse is the one open there
+ if (onPageWithSynapseCard && edge == SynapseCard.openSynapseCard) {
+ SynapseCard.showCard(edge)
+ }
+ },
+ updateEdgeView: function () {
+ var onPageWithSynapseCard = Active.Map || Active.Topic
+ var edge = this.get('edge')
+
+ // update the edge on the map
+ if (onPageWithSynapseCard && edge) {
+ Visualize.mGraph.plot()
+ }
+ }
+})
+
+export default Synapse
diff --git a/frontend/src/Metamaps/DataModel/SynapseCollection.js b/frontend/src/Metamaps/DataModel/SynapseCollection.js
new file mode 100644
index 00000000..86bf8c47
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/SynapseCollection.js
@@ -0,0 +1,11 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Synapse from './Synapse'
+
+const SynapseCollection = Backbone.Collection.extend({
+ model: Synapse,
+ url: '/synapses'
+})
+
+export default SynapseCollection
diff --git a/frontend/src/Metamaps/DataModel/Topic.js b/frontend/src/Metamaps/DataModel/Topic.js
new file mode 100644
index 00000000..33a9bd3f
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/Topic.js
@@ -0,0 +1,181 @@
+/* global Metamaps, $ */
+
+import _ from 'lodash'
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Active from '../Active'
+import Filter from '../Filter'
+import JIT from '../JIT'
+import Realtime from '../Realtime'
+import TopicCard from '../TopicCard'
+import Visualize from '../Visualize'
+
+/*
+ * Dependencies:
+ * - Metamaps.Mappings
+ * - Metamaps.Metacodes
+ */
+
+const Topic = Backbone.Model.extend({
+ urlRoot: '/topics',
+ blacklist: ['node', 'created_at', 'updated_at', 'user_name', 'user_image', 'map_count', 'synapse_count'],
+ toJSON: function (options) {
+ return _.omit(this.attributes, this.blacklist)
+ },
+ save: function (key, val, options) {
+ var attrs
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (key == null || typeof key === 'object') {
+ attrs = key
+ options = val
+ } else {
+ (attrs = {})[key] = val
+ }
+
+ var newOptions = options || {}
+ var s = newOptions.success
+
+ var permBefore = this.get('permission')
+
+ newOptions.success = function (model, response, opt) {
+ if (s) s(model, response, opt)
+ model.trigger('saved')
+ model.set('calculated_permission', model.get('permission'))
+
+ if (permBefore === 'private' && model.get('permission') !== 'private') {
+ model.trigger('noLongerPrivate')
+ }
+ else if (permBefore !== 'private' && model.get('permission') === 'private') {
+ model.trigger('nowPrivate')
+ }
+ }
+ return Backbone.Model.prototype.save.call(this, attrs, newOptions)
+ },
+ initialize: function () {
+ if (this.isNew()) {
+ this.set({
+ 'user_id': Active.Mapper.id,
+ 'desc': this.get('desc') || '',
+ 'link': this.get('link') || '',
+ 'permission': Active.Map ? Active.Map.get('permission') : 'commons'
+ })
+ }
+
+ this.on('changeByOther', this.updateCardView)
+ this.on('change', this.updateNodeView)
+ this.on('saved', this.savedEvent)
+ this.on('nowPrivate', function () {
+ var removeTopicData = {
+ mappableid: this.id
+ }
+
+ $(document).trigger(JIT.events.removeTopic, [removeTopicData])
+ })
+ this.on('noLongerPrivate', function () {
+ var newTopicData = {
+ mappingid: this.getMapping().id,
+ mappableid: this.id
+ }
+
+ $(document).trigger(JIT.events.newTopic, [newTopicData])
+ })
+
+ this.on('change:metacode_id', Filter.checkMetacodes, this)
+ },
+ authorizeToEdit: function (mapper) {
+ if (mapper &&
+ (this.get('user_id') === mapper.get('id') ||
+ this.get('calculated_permission') === 'commons' ||
+ this.get('collaborator_ids').includes(mapper.get('id')))) {
+ return true
+ } else {
+ return false
+ }
+ },
+ authorizePermissionChange: function (mapper) {
+ if (mapper && this.get('user_id') === mapper.get('id')) return true
+ else return false
+ },
+ getDate: function () {},
+ getMetacode: function () {
+ return Metamaps.Metacodes.get(this.get('metacode_id'))
+ },
+ getMapping: function () {
+ if (!Active.Map) return false
+
+ return Metamaps.Mappings.findWhere({
+ map_id: Active.Map.id,
+ mappable_type: 'Topic',
+ mappable_id: this.isNew() ? this.cid : this.id
+ })
+ },
+ createNode: function () {
+ var mapping
+ var node = {
+ adjacencies: [],
+ id: this.isNew() ? this.cid : this.id,
+ name: this.get('name')
+ }
+
+ if (Active.Map) {
+ mapping = this.getMapping()
+ node.data = {
+ $mapping: null,
+ $mappingID: mapping.id
+ }
+ }
+
+ return node
+ },
+ updateNode: function () {
+ var mapping
+ var node = this.get('node')
+ node.setData('topic', this)
+
+ if (Active.Map) {
+ mapping = this.getMapping()
+ node.setData('mapping', mapping)
+ }
+
+ return node
+ },
+ savedEvent: function () {
+ Realtime.updateTopic(this)
+ },
+ updateViews: function () {
+ var onPageWithTopicCard = Active.Map || Active.Topic
+ var node = this.get('node')
+ // update topic card, if this topic is the one open there
+ if (onPageWithTopicCard && this == TopicCard.openTopicCard) {
+ TopicCard.showCard(node)
+ }
+
+ // update the node on the map
+ if (onPageWithTopicCard && node) {
+ node.name = this.get('name')
+ Visualize.mGraph.plot()
+ }
+ },
+ updateCardView: function () {
+ var onPageWithTopicCard = Active.Map || Active.Topic
+ var node = this.get('node')
+ // update topic card, if this topic is the one open there
+ if (onPageWithTopicCard && this == TopicCard.openTopicCard) {
+ TopicCard.showCard(node)
+ }
+ },
+ updateNodeView: function () {
+ var onPageWithTopicCard = Active.Map || Active.Topic
+ var node = this.get('node')
+
+ // update the node on the map
+ if (onPageWithTopicCard && node) {
+ node.name = this.get('name')
+ Visualize.mGraph.plot()
+ }
+ }
+})
+
+export default Topic
diff --git a/frontend/src/Metamaps/DataModel/TopicCollection.js b/frontend/src/Metamaps/DataModel/TopicCollection.js
new file mode 100644
index 00000000..4bcaf622
--- /dev/null
+++ b/frontend/src/Metamaps/DataModel/TopicCollection.js
@@ -0,0 +1,11 @@
+import Backbone from 'backbone'
+Backbone.$ = window.$
+
+import Topic from './Topic'
+
+const TopicCollection = Backbone.Collection.extend({
+ model: Topic,
+ url: '/topics'
+})
+
+export default TopicCollection
diff --git a/frontend/src/Metamaps/DataModel/index.js b/frontend/src/Metamaps/DataModel/index.js
index 1aaac52f..4ee6d10d 100644
--- a/frontend/src/Metamaps/DataModel/index.js
+++ b/frontend/src/Metamaps/DataModel/index.js
@@ -1,23 +1,26 @@
-/* global Metamaps, Backbone, $ */
-
-import _ from 'lodash'
-import Backbone from 'backbone'
-Backbone.$ = window.$
+/* global Metamaps */
import Active from '../Active'
import Filter from '../Filter'
-import JIT from '../JIT'
-import Map, { InfoBox } from '../Map'
-import Mapper from '../Mapper'
-import Realtime from '../Realtime'
-import Synapse from '../Synapse'
-import SynapseCard from '../SynapseCard'
-import Topic from '../Topic'
-import TopicCard from '../TopicCard'
-import Visualize from '../Visualize'
+import { InfoBox } from '../Map'
+
+import Map from './Map'
+import MapCollection from './MapCollection'
+import Message from './Message'
+import MessageCollection from './MessageCollection'
+import Mapper from './Mapper'
+import MapperCollection from './MapperCollection'
+import Metacode from './Metacode'
+import MetacodeCollection from './MetacodeCollection'
+import Topic from './Topic'
+import TopicCollection from './TopicCollection'
+import Synapse from './Synapse'
+import SynapseCollection from './SynapseCollection'
+import Mapping from './Mapping'
+import MappingCollection from './MappingCollection'
/*
- * Metamaps.DataModel.js
+ * DataModel.js
*
* Dependencies:
* - Metamaps.Collaborators
@@ -30,618 +33,36 @@ import Visualize from '../Visualize'
* - Metamaps.Topics
*/
-const DataModel = {}
+const DataModel = {
+ Map: Map,
+ MapCollection: MapCollection,
+ Message: Message,
+ MessageCollection: MessageCollection,
+ Mapper: Mapper,
+ MapperCollection: MapperCollection,
+ Metacode: Metacode,
+ MetacodeCollection: MetacodeCollection,
+ Topic: Topic,
+ TopicCollection: TopicCollection,
+ Synapse: Synapse,
+ SynapseCollection: SynapseCollection,
+ Mapping: Mapping,
+ MappingCollection: MappingCollection,
-DataModel.Map = Backbone.Model.extend({
- urlRoot: '/maps',
- blacklist: ['created_at', 'updated_at', 'created_at_clean', 'updated_at_clean', 'user_name', 'contributor_count', 'topic_count', 'synapse_count', 'topics', 'synapses', 'mappings', 'mappers'],
- toJSON: function (options) {
- return _.omit(this.attributes, this.blacklist)
- },
- save: function (key, val, options) {
- var attrs
+ init: function() {
+ var self = DataModel
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (key == null || typeof key === 'object') {
- attrs = key
- options = val
- } else {
- (attrs = {})[key] = val
- }
+ Metamaps.Metacodes = Metamaps.Metacodes ? new self.MetacodeCollection(Metamaps.Metacodes) : new self.MetacodeCollection()
- var newOptions = options || {}
- var s = newOptions.success
-
- newOptions.success = function (model, response, opt) {
- if (s) s(model, response, opt)
- model.trigger('saved')
- }
- return Backbone.Model.prototype.save.call(this, attrs, newOptions)
- },
- initialize: function () {
- this.on('changeByOther', this.updateView)
- this.on('saved', this.savedEvent)
- },
- savedEvent: function () {
- Realtime.updateMap(this)
- },
- authorizeToEdit: function (mapper) {
- 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
- }
- },
- authorizePermissionChange: function (mapper) {
- if (mapper && this.get('user_id') === mapper.get('id')) {
- return true
- } else {
- return false
- }
- },
- getUser: function () {
- return Mapper.get(this.get('user_id'))
- },
- updateView: function () {
- var map = Active.Map
- var isActiveMap = this.id === map.id
- if (isActiveMap) {
- InfoBox.updateNameDescPerm(this.get('name'), this.get('desc'), this.get('permission'))
- this.updateMapWrapper()
- // mobile menu
- $('#header_content').html(this.get('name'))
- document.title = this.get('name') + ' | Metamaps'
- }
- },
- updateMapWrapper: function () {
- var map = Active.Map
- var isActiveMap = this.id === map.id
- var authorized = map && map.authorizeToEdit(Active.Mapper) ? 'canEditMap' : ''
- var commonsMap = map && map.get('permission') === 'commons' ? 'commonsMap' : ''
- if (isActiveMap) {
- $('.wrapper').removeClass('canEditMap commonsMap').addClass(authorized + ' ' + commonsMap)
- }
- }
-})
-DataModel.MapsCollection = Backbone.Collection.extend({
- model: DataModel.Map,
- initialize: function (models, options) {
- this.id = options.id
- this.sortBy = options.sortBy
-
- if (options.mapperId) {
- this.mapperId = options.mapperId
- }
-
- // this.page represents the NEXT page to fetch
- this.page = models.length > 0 ? (models.length < 20 ? 'loadedAll' : 2) : 1
- },
- url: function () {
- if (!this.mapperId) {
- return '/explore/' + this.id + '.json'
- } else {
- return '/explore/mapper/' + this.mapperId + '.json'
- }
- },
- comparator: function (a, b) {
- a = a.get(this.sortBy)
- b = b.get(this.sortBy)
- var temp
- if (this.sortBy === 'name') {
- a = a ? a.toLowerCase() : ''
- b = b ? b.toLowerCase() : ''
- } else {
- // this is for updated_at and created_at
- temp = a
- a = b
- b = temp
- a = (new Date(a)).getTime()
- b = (new Date(b)).getTime()
- }
- return a > b ? 1 : a < b ? -1 : 0
- },
- getMaps: function (cb) {
- var self = this
-
- Metamaps.Loading.show()
-
- if (this.page !== 'loadedAll') {
- var numBefore = this.length
- this.fetch({
- remove: false,
- silent: true,
- data: { page: this.page },
- success: function (collection, response, options) {
- // you can pass additional options to the event you trigger here as well
- if (collection.length - numBefore < 20) {
- self.page = 'loadedAll'
- } else {
- self.page += 1
- }
- self.trigger('successOnFetch', cb)
- },
- error: function (collection, response, options) {
- // you can pass additional options to the event you trigger here as well
- self.trigger('errorOnFetch')
- }
- })
- } else {
- self.trigger('successOnFetch', cb)
- }
- }
-})
-
-DataModel.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
- */
- }
-})
-DataModel.MessageCollection = Backbone.Collection.extend({
- model: DataModel.Message,
- url: '/messages'
-})
-
-DataModel.Mapper = Backbone.Model.extend({
- urlRoot: '/users',
- 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
- }
-})
-
-DataModel.MapperCollection = Backbone.Collection.extend({
- model: DataModel.Mapper,
- url: '/users'
-})
-
-DataModel.init = function () {
- var self = DataModel
-
- self.Metacode = Backbone.Model.extend({
- initialize: function () {
- var image = new Image()
- image.crossOrigin = 'Anonymous'
- image.src = this.get('icon')
- this.set('image', image)
- },
- prepareLiForFilter: function () {
- var li = ''
- li += ''
- li += ''
- li += '' + this.get('name').toLowerCase() + '
'
- return li
- }
-
- })
- self.MetacodeCollection = Backbone.Collection.extend({
- model: this.Metacode,
- url: '/metacodes',
- comparator: function (a, b) {
- a = a.get('name').toLowerCase()
- b = b.get('name').toLowerCase()
- return a > b ? 1 : a < b ? -1 : 0
- }
- })
-
- self.Topic = Backbone.Model.extend({
- urlRoot: '/topics',
- blacklist: ['node', 'created_at', 'updated_at', 'user_name', 'user_image', 'map_count', 'synapse_count'],
- toJSON: function (options) {
- return _.omit(this.attributes, this.blacklist)
- },
- save: function (key, val, options) {
- var attrs
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (key == null || typeof key === 'object') {
- attrs = key
- options = val
- } else {
- (attrs = {})[key] = val
- }
-
- var newOptions = options || {}
- var s = newOptions.success
-
- var permBefore = this.get('permission')
-
- newOptions.success = function (model, response, opt) {
- if (s) s(model, response, opt)
- model.trigger('saved')
- model.set('calculated_permission', model.get('permission'))
-
- if (permBefore === 'private' && model.get('permission') !== 'private') {
- model.trigger('noLongerPrivate')
- }
- else if (permBefore !== 'private' && model.get('permission') === 'private') {
- model.trigger('nowPrivate')
- }
- }
- return Backbone.Model.prototype.save.call(this, attrs, newOptions)
- },
- initialize: function () {
- if (this.isNew()) {
- this.set({
- 'user_id': Active.Mapper.id,
- 'desc': this.get('desc') || '',
- 'link': this.get('link') || '',
- 'permission': Active.Map ? Active.Map.get('permission') : 'commons'
- })
- }
-
- this.on('changeByOther', this.updateCardView)
- this.on('change', this.updateNodeView)
- this.on('saved', this.savedEvent)
- this.on('nowPrivate', function () {
- var removeTopicData = {
- mappableid: this.id
- }
-
- $(document).trigger(JIT.events.removeTopic, [removeTopicData])
- })
- this.on('noLongerPrivate', function () {
- var newTopicData = {
- mappingid: this.getMapping().id,
- mappableid: this.id
- }
-
- $(document).trigger(JIT.events.newTopic, [newTopicData])
- })
-
- this.on('change:metacode_id', Filter.checkMetacodes, this)
- },
- authorizeToEdit: function (mapper) {
- if (mapper &&
- (this.get('user_id') === mapper.get('id') ||
- this.get('calculated_permission') === 'commons' ||
- this.get('collaborator_ids').includes(mapper.get('id')))) {
- return true
- } else {
- return false
- }
- },
- authorizePermissionChange: function (mapper) {
- if (mapper && this.get('user_id') === mapper.get('id')) return true
- else return false
- },
- getDate: function () {},
- getMetacode: function () {
- return Metamaps.Metacodes.get(this.get('metacode_id'))
- },
- getMapping: function () {
- if (!Active.Map) return false
-
- return Metamaps.Mappings.findWhere({
- map_id: Active.Map.id,
- mappable_type: 'Topic',
- mappable_id: this.isNew() ? this.cid : this.id
- })
- },
- createNode: function () {
- var mapping
- var node = {
- adjacencies: [],
- id: this.isNew() ? this.cid : this.id,
- name: this.get('name')
- }
-
- if (Active.Map) {
- mapping = this.getMapping()
- node.data = {
- $mapping: null,
- $mappingID: mapping.id
- }
- }
-
- return node
- },
- updateNode: function () {
- var mapping
- var node = this.get('node')
- node.setData('topic', this)
-
- if (Active.Map) {
- mapping = this.getMapping()
- node.setData('mapping', mapping)
- }
-
- return node
- },
- savedEvent: function () {
- Realtime.updateTopic(this)
- },
- updateViews: function () {
- var onPageWithTopicCard = Active.Map || Active.Topic
- var node = this.get('node')
- // update topic card, if this topic is the one open there
- if (onPageWithTopicCard && this == TopicCard.openTopicCard) {
- TopicCard.showCard(node)
- }
-
- // update the node on the map
- if (onPageWithTopicCard && node) {
- node.name = this.get('name')
- Visualize.mGraph.plot()
- }
- },
- updateCardView: function () {
- var onPageWithTopicCard = Active.Map || Active.Topic
- var node = this.get('node')
- // update topic card, if this topic is the one open there
- if (onPageWithTopicCard && this == TopicCard.openTopicCard) {
- TopicCard.showCard(node)
- }
- },
- updateNodeView: function () {
- var onPageWithTopicCard = Active.Map || Active.Topic
- var node = this.get('node')
-
- // update the node on the map
- if (onPageWithTopicCard && node) {
- node.name = this.get('name')
- Visualize.mGraph.plot()
- }
- }
- })
-
- self.TopicCollection = Backbone.Collection.extend({
- model: self.Topic,
- url: '/topics'
- })
-
- self.Synapse = Backbone.Model.extend({
- urlRoot: '/synapses',
- blacklist: ['edge', 'created_at', 'updated_at'],
- toJSON: function (options) {
- return _.omit(this.attributes, this.blacklist)
- },
- save: function (key, val, options) {
- var attrs
-
- // Handle both `"key", value` and `{key: value}` -style arguments.
- if (key == null || typeof key === 'object') {
- attrs = key
- options = val
- } else {
- (attrs = {})[key] = val
- }
-
- var newOptions = options || {}
- var s = newOptions.success
-
- var permBefore = this.get('permission')
-
- newOptions.success = function (model, response, opt) {
- if (s) s(model, response, opt)
- model.trigger('saved')
-
- if (permBefore === 'private' && model.get('permission') !== 'private') {
- model.trigger('noLongerPrivate')
- }
- else if (permBefore !== 'private' && model.get('permission') === 'private') {
- model.trigger('nowPrivate')
- }
- }
- return Backbone.Model.prototype.save.call(this, attrs, newOptions)
- },
- initialize: function () {
- if (this.isNew()) {
- this.set({
- 'user_id': Active.Mapper.id,
- 'permission': Active.Map ? Active.Map.get('permission') : 'commons',
- 'category': 'from-to'
- })
- }
-
- this.on('changeByOther', this.updateCardView)
- this.on('change', this.updateEdgeView)
- this.on('saved', this.savedEvent)
- this.on('noLongerPrivate', function () {
- var newSynapseData = {
- mappingid: this.getMapping().id,
- mappableid: this.id
- }
-
- $(document).trigger(JIT.events.newSynapse, [newSynapseData])
- })
- this.on('nowPrivate', function () {
- $(document).trigger(JIT.events.removeSynapse, [{
- mappableid: this.id
- }])
- })
-
- this.on('change:desc', Filter.checkSynapses, this)
- },
- prepareLiForFilter: function () {
- var li = ''
- li += ''
- li += ''
- li += '' + this.get('desc') + '
'
- return li
- },
- authorizeToEdit: function (mapper) {
- if (mapper && (this.get('calculated_permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
- else return false
- },
- authorizePermissionChange: function (mapper) {
- if (mapper && this.get('user_id') === mapper.get('id')) return true
- else return false
- },
- getTopic1: function () {
- return Metamaps.Topics.get(this.get('topic1_id'))
- },
- getTopic2: function () {
- return Metamaps.Topics.get(this.get('topic2_id'))
- },
- getDirection: function () {
- var t1 = this.getTopic1(),
- t2 = this.getTopic2()
-
- return t1 && t2 ? [
- t1.get('node').id,
- t2.get('node').id
- ] : false
- },
- getMapping: function () {
- if (!Active.Map) return false
-
- return Metamaps.Mappings.findWhere({
- map_id: Active.Map.id,
- mappable_type: 'Synapse',
- mappable_id: this.isNew() ? this.cid : this.id
- })
- },
- createEdge: function (providedMapping) {
- var mapping, mappingID
- var synapseID = this.isNew() ? this.cid : this.id
-
- var edge = {
- nodeFrom: this.get('topic1_id'),
- nodeTo: this.get('topic2_id'),
- data: {
- $synapses: [],
- $synapseIDs: [synapseID],
- }
- }
-
- if (Active.Map) {
- mapping = providedMapping || 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 (Active.Map) {
- mapping = this.getMapping()
- edge.getData('mappings').push(mapping)
- }
-
- return edge
- },
- savedEvent: function () {
- Realtime.updateSynapse(this)
- },
- updateViews: function () {
- this.updateCardView()
- this.updateEdgeView()
- },
- updateCardView: function () {
- var onPageWithSynapseCard = Active.Map || Active.Topic
- var edge = this.get('edge')
-
- // update synapse card, if this synapse is the one open there
- if (onPageWithSynapseCard && edge == SynapseCard.openSynapseCard) {
- SynapseCard.showCard(edge)
- }
- },
- updateEdgeView: function () {
- var onPageWithSynapseCard = Active.Map || Active.Topic
- var edge = this.get('edge')
-
- // update the edge on the map
- if (onPageWithSynapseCard && edge) {
- Visualize.mGraph.plot()
- }
- }
- })
-
- self.SynapseCollection = Backbone.Collection.extend({
- model: self.Synapse,
- url: '/synapses'
- })
-
- self.Mapping = Backbone.Model.extend({
- urlRoot: '/mappings',
- blacklist: ['created_at', 'updated_at'],
- toJSON: function (options) {
- return _.omit(this.attributes, this.blacklist)
- },
- initialize: function () {
- if (this.isNew()) {
- this.set({
- 'user_id': Active.Mapper.id,
- 'map_id': Active.Map ? Active.Map.id : null
- })
- }
- },
- getMap: function () {
- return Map.get(this.get('map_id'))
- },
- getTopic: function () {
- if (this.get('mappable_type') === 'Topic') return Topic.get(this.get('mappable_id'))
- else return false
- },
- getSynapse: function () {
- if (this.get('mappable_type') === 'Synapse') return Synapse.get(this.get('mappable_id'))
- else return false
- }
- })
-
- self.MappingCollection = Backbone.Collection.extend({
- model: self.Mapping,
- url: '/mappings'
- })
-
- Metamaps.Metacodes = Metamaps.Metacodes ? new self.MetacodeCollection(Metamaps.Metacodes) : new self.MetacodeCollection()
-
- Metamaps.Topics = Metamaps.Topics ? new self.TopicCollection(Metamaps.Topics) : new self.TopicCollection()
-
- Metamaps.Synapses = Metamaps.Synapses ? new self.SynapseCollection(Metamaps.Synapses) : new self.SynapseCollection()
-
- Metamaps.Mappers = Metamaps.Mappers ? new self.MapperCollection(Metamaps.Mappers) : new self.MapperCollection()
-
- Metamaps.Collaborators = Metamaps.Collaborators ? new self.MapperCollection(Metamaps.Collaborators) : new self.MapperCollection()
-
- // this is for topic view
- Metamaps.Creators = Metamaps.Creators ? new self.MapperCollection(Metamaps.Creators) : new self.MapperCollection()
-
- if (Active.Map) {
- Metamaps.Mappings = Metamaps.Mappings ? new self.MappingCollection(Metamaps.Mappings) : new self.MappingCollection()
-
- Active.Map = new self.Map(Active.Map)
- }
-
- if (Active.Topic) Active.Topic = new self.Topic(Active.Topic)
-
- // attach collection event listeners
- self.attachCollectionEvents = function () {
+ // attach collection event listeners
+ Metamaps.Topics = Metamaps.Topics ? new self.TopicCollection(Metamaps.Topics) : new self.TopicCollection()
Metamaps.Topics.on('add remove', function (topic) {
InfoBox.updateNumbers()
Filter.checkMetacodes()
Filter.checkMappers()
})
+ Metamaps.Synapses = Metamaps.Synapses ? new self.SynapseCollection(Metamaps.Synapses) : new self.SynapseCollection()
Metamaps.Synapses.on('add remove', function (synapse) {
InfoBox.updateNumbers()
Filter.checkSynapses()
@@ -649,6 +70,7 @@ DataModel.init = function () {
})
if (Active.Map) {
+ Metamaps.Mappings = Metamaps.Mappings ? new self.MappingCollection(Metamaps.Mappings) : new self.MappingCollection()
Metamaps.Mappings.on('add remove', function (mapping) {
InfoBox.updateNumbers()
Filter.checkSynapses()
@@ -656,8 +78,25 @@ DataModel.init = function () {
Filter.checkMappers()
})
}
+
+ Metamaps.Mappers = Metamaps.Mappers ? new self.MapperCollection(Metamaps.Mappers) : new self.MapperCollection()
+ Metamaps.Collaborators = Metamaps.Collaborators ? new self.MapperCollection(Metamaps.Collaborators) : new self.MapperCollection()
+ Metamaps.Creators = Metamaps.Creators ? new self.MapperCollection(Metamaps.Creators) : new self.MapperCollection()
+
+ if (Active.Map) {
+ Active.Map = new self.Map(Active.Map)
+ }
+
+ if (Active.Topic) {
+ Active.Topic = new self.Topic(Active.Topic)
+ }
}
- self.attachCollectionEvents()
-}; // end DataModel.init
+}
+
+export {
+ Map, MapCollection, Mapper, MapperCollection, Mapping, MappingCollection,
+ Message, MessageCollection, Metacode, MetacodeCollection,
+ Synapse, SynapseCollection, Topic, TopicCollection
+}
export default DataModel
diff --git a/frontend/src/Metamaps/GlobalUI/index.js b/frontend/src/Metamaps/GlobalUI/index.js
index 580aa831..e82516c2 100644
--- a/frontend/src/Metamaps/GlobalUI/index.js
+++ b/frontend/src/Metamaps/GlobalUI/index.js
@@ -51,13 +51,13 @@ const GlobalUI = {
}
var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : []
var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : []
- Metamaps.Maps.Mine = new Metamaps.DataModel.MapsCollection(myCollection, { id: 'mine', sortBy: 'updated_at' })
- Metamaps.Maps.Shared = new Metamaps.DataModel.MapsCollection(sharedCollection, { id: 'shared', sortBy: 'updated_at' })
- Metamaps.Maps.Starred = new Metamaps.DataModel.MapsCollection(starredCollection, { id: 'starred', sortBy: 'updated_at' })
+ Metamaps.Maps.Mine = new Metamaps.DataModel.MapCollection(myCollection, { id: 'mine', sortBy: 'updated_at' })
+ Metamaps.Maps.Shared = new Metamaps.DataModel.MapCollection(sharedCollection, { id: 'shared', sortBy: 'updated_at' })
+ Metamaps.Maps.Starred = new Metamaps.DataModel.MapCollection(starredCollection, { id: 'starred', sortBy: 'updated_at' })
// 'Mapper' refers to another mapper
- Metamaps.Maps.Mapper = new Metamaps.DataModel.MapsCollection(mapperCollection, mapperOptionsObj)
- Metamaps.Maps.Featured = new Metamaps.DataModel.MapsCollection(featuredCollection, { id: 'featured', sortBy: 'updated_at' })
- Metamaps.Maps.Active = new Metamaps.DataModel.MapsCollection(activeCollection, { id: 'active', sortBy: 'updated_at' })
+ Metamaps.Maps.Mapper = new Metamaps.DataModel.MapCollection(mapperCollection, mapperOptionsObj)
+ Metamaps.Maps.Featured = new Metamaps.DataModel.MapCollection(featuredCollection, { id: 'featured', sortBy: 'updated_at' })
+ Metamaps.Maps.Active = new Metamaps.DataModel.MapCollection(activeCollection, { id: 'active', sortBy: 'updated_at' })
},
showDiv: function (selector) {
$(selector).show()