metamaps--metamaps/frontend/src/Metamaps/Create.js
Connor Turland 7ee96bf6c6 Into master: two finger pan/zoom, map and topic follows (for internal testing) on the UI, map activity emails (#1084)
* fix topic spec

* fix synapse/mapping spec

* brakeman csrf warning suppressed :|

* follows for maps in the ui for internal testing only still (#1072)

* follows for maps in the ui for testers

* require user for these actions

* match how map follow works

* include ability to unfollow from email

* fixup templates

* add unfollow_from_email to the policies

* Update _cheatsheet.html.erb

Clean up text, clarify, and bring in line with current functionality

* topicsRegex and synapsesRegex should allow commas (#1073)

* even better import csv regexes

* prevent double prompt on file drop import

* topic card in react (#1031)

* its coming along

* links bar

* scssify a bunch

* metacode image working a bit better

* metacode selector in react topic card

* riek editing for name field on topic card

* riek submit on enter

* factor out Title and Links from Topic Card component, but not the listeners

* create working Desc editor

* styling is much better now

* textarea min height for desc

* disallow images in topic card markdown

* shift enter is linebreak, enter is save

* attachments split out, but it's pretty buggy

* move listeners into Links.js

* slightly wider metacodeTitle

* fix positioning on metacode selector

* fix metacode selection

* move metacode and permissions into subcomponents

* fixes

* prevent editing on desc/title if not authorized to edit

* fix topic card draggability

* fix embedly

* fix md test

* remove the removed link card manually with jquery

* fix test syntax

* eslint

* more eslin

* reuse authorizedToEdit

* convert metacode sets to a json object for react

* add the html in react whoop

* fix metacode styling

* sort wasn't working

* finishing metacode select

* readd the above link input border

* fix syntax

* multiline title editable textarea

* more portable metacode selector component

* factor out #metacodeOptions into one react component with a callback :D:D:D

* render metacodeOptions in right click menu with react

* render metacodeOptions in right click menu with react

* fix up right click menu's metacode editing

* fix topic card title character counter

* ignore metamaps secret bundle in ag

* simplify Attachments props

* factor out embedly card into its own component; it seems to help

* link resetter

* fix edit icon on title in topic card

* move mapCount and synapseCount hover/click logic to react

* fix up the showMore control

* metacode selection tweaks

* tweak links bar spacing in topic card

* rubocop

* remove TODOs

* more badass permissions selector

* close permission selector when you click outside

* fix overeager metacode selector

* more modular attachments component

* fix bug in Desc.js

* fix right click styling

* permission changes are different than edit rights

* bad module ref

* ensure maxLength on topic titles

* hellz yeah (#1074)

* fix drop from two touches to one

* don't commit activity service

* ability to select/unselect all metacodes in custom set with keyboard shortcut (fix #390) (#1078)

* ability to select/unselect all metacodes in custom set with keyboard shortcut

* select all button

* nicer all/none buttons

* set up react testing (#1080)

* install mocha-webpack. also switch hark to npm version instead of github version

* well, mocha-webpack runs

* add jsdom for tests

* upgrade to webpack 2

* fix npm run test errors

* ImportDialogBox component tests

* Fixes bug where pressing delete key while editing text will suggest... (#1083)

* Fixes bug where pressing delete key while editing text will suggest the deletion of selected map entities

* Changed the DEL key to remove entities instead of delete them

* temporarily disable code climate duplication engine

* add topic following for internal testing

* daily map activity emails (#1081)

* data prepared, task setup

* add the basics of the email template

* cover granular permissions

* unfollow this map

* break out permissions tests better

* rename so test runs
2017-03-06 22:49:46 -05:00

407 lines
13 KiB
JavaScript

/* global $, Hogan, Bloodhound */
import DataModel from './DataModel'
import Mouse from './Mouse'
import Selected from './Selected'
import Synapse from './Synapse'
import Topic from './Topic'
import Visualize from './Visualize'
import GlobalUI from './GlobalUI'
const Create = {
isSwitchingSet: false, // indicates whether the metacode set switch lightbox is open
selectedMetacodeSet: null,
selectedMetacodeSetIndex: null,
selectedMetacodeNames: [],
newSelectedMetacodeNames: [],
selectedMetacodes: [],
newSelectedMetacodes: [],
init: function() {
var self = Create
self.newTopic.init()
self.newSynapse.init()
// // SWITCHING METACODE SETS
$('#metacodeSwitchTabs').tabs({
active: self.selectedMetacodeSetIndex
}).addClass('ui-tabs-vertical ui-helper-clearfix')
$('#metacodeSwitchTabs .ui-tabs-nav li').removeClass('ui-corner-top').addClass('ui-corner-left')
$('.customMetacodeList li').click(self.toggleMetacodeSelected) // within the custom metacode set tab
$('.selectAll').click(self.metacodeSelectorSelectAll)
$('.selectNone').click(self.metacodeSelectorSelectNone)
},
toggleMetacodeSelected: function() {
var self = Create
if ($(this).attr('class') !== 'toggledOff') {
$(this).addClass('toggledOff')
var valueToRemove = $(this).attr('id')
var nameToRemove = $(this).attr('data-name')
self.newSelectedMetacodes.splice(self.newSelectedMetacodes.indexOf(valueToRemove), 1)
self.newSelectedMetacodeNames.splice(self.newSelectedMetacodeNames.indexOf(nameToRemove), 1)
} else if ($(this).attr('class') === 'toggledOff') {
$(this).removeClass('toggledOff')
self.newSelectedMetacodes.push($(this).attr('id'))
self.newSelectedMetacodeNames.push($(this).attr('data-name'))
}
self.updateSelectAllColors()
},
updateSelectAllColors: function() {
$('.selectAll, .selectNone').removeClass('selected')
if (Create.metacodeSelectorAreAllSelected()) {
$('.selectAll').addClass('selected')
} else if (Create.metacodeSelectorAreNoneSelected()) {
$('.selectNone').addClass('selected')
}
},
metacodeSelectorSelectAll: function() {
$('.customMetacodeList li.toggledOff').each(Create.toggleMetacodeSelected)
Create.updateSelectAllColors()
},
metacodeSelectorSelectNone: function() {
$('.customMetacodeList li').not('.toggledOff').each(Create.toggleMetacodeSelected)
Create.updateSelectAllColors()
},
metacodeSelectorAreAllSelected: function() {
return $('.customMetacodeList li').toArray()
.map(li => !$(li).is('.toggledOff')) // note the ! on this line
.reduce((curr, prev) => curr && prev)
},
metacodeSelectorAreNoneSelected: function() {
return $('.customMetacodeList li').toArray()
.map(li => $(li).is('.toggledOff'))
.reduce((curr, prev) => curr && prev)
},
metacodeSelectorToggleSelectAll: function() {
// should be called when Create.isSwitchingSet is true and .customMetacodeList is visible
if (!Create.isSwitchingSet) return
if (!$('.customMetacodeList').is(':visible')) return
// If all are selected, then select none. Otherwise, select all.
if (Create.metacodeSelectorAreAllSelected()) {
Create.metacodeSelectorSelectNone()
} else {
// if some, but not all, are selected, it still runs this function
Create.metacodeSelectorSelectAll()
}
},
updateMetacodeSet: function(set, index, custom) {
if (custom && Create.newSelectedMetacodes.length === 0) {
window.alert('Please select at least one metacode to use!')
return false
}
var codesToSwitchToIds
var metacodeModels = new DataModel.MetacodeCollection()
Create.selectedMetacodeSetIndex = index
Create.selectedMetacodeSet = 'metacodeset-' + set
if (!custom) {
codesToSwitchToIds = $('#metacodeSwitchTabs' + set).attr('data-metacodes').split(',')
$('.customMetacodeList li').addClass('toggledOff')
Create.selectedMetacodes = []
Create.selectedMetacodeNames = []
Create.newSelectedMetacodes = []
Create.newSelectedMetacodeNames = []
} else if (custom) {
// uses .slice to avoid setting the two arrays to the same actual array
Create.selectedMetacodes = Create.newSelectedMetacodes.slice(0)
Create.selectedMetacodeNames = Create.newSelectedMetacodeNames.slice(0)
codesToSwitchToIds = Create.selectedMetacodes.slice(0)
}
// sort by name
for (var i = 0; i < codesToSwitchToIds.length; i++) {
metacodeModels.add(DataModel.Metacodes.get(codesToSwitchToIds[i]))
}
metacodeModels.sort()
$('#metacodeImg, #metacodeImgTitle').empty()
$('#metacodeImg').removeData('cloudcarousel')
var newMetacodes = ''
metacodeModels.each(function(metacode) {
newMetacodes += '<img class="cloudcarousel" width="40" height="40" src="' + metacode.get('icon') + '" data-id="' + metacode.id + '" title="' + metacode.get('name') + '" alt="' + metacode.get('name') + '"/>'
})
$('#metacodeImg').empty().append(newMetacodes).CloudCarousel({
titleBox: $('#metacodeImgTitle'),
yRadius: 40,
xRadius: 190,
xPos: 170,
yPos: 40,
speed: 0.3,
mouseWheel: true,
bringToFront: true
})
GlobalUI.closeLightbox()
$('#topic_name').focus()
var mdata = {
'metacodes': {
'value': custom ? Create.selectedMetacodes.toString() : Create.selectedMetacodeSet
}
}
$.ajax({
type: 'POST',
dataType: 'json',
url: '/user/updatemetacodes',
data: mdata,
success: function(data) {
console.log('selected metacodes saved')
},
error: function() {
console.log('failed to save selected metacodes')
}
})
},
cancelMetacodeSetSwitch: function() {
var self = Create
self.isSwitchingSet = false
if (self.selectedMetacodeSet !== 'metacodeset-custom') {
$('.customMetacodeList li').addClass('toggledOff')
self.selectedMetacodes = []
self.selectedMetacodeNames = []
self.newSelectedMetacodes = []
self.newSelectedMetacodeNames = []
} else { // custom set is selected
// reset it to the current actual selection
$('.customMetacodeList li').addClass('toggledOff')
for (var i = 0; i < self.selectedMetacodes.length; i++) {
$('#' + self.selectedMetacodes[i]).removeClass('toggledOff')
}
// uses .slice to avoid setting the two arrays to the same actual array
self.newSelectedMetacodeNames = self.selectedMetacodeNames.slice(0)
self.newSelectedMetacodes = self.selectedMetacodes.slice(0)
}
$('#metacodeSwitchTabs').tabs('option', 'active', self.selectedMetacodeSetIndex)
$('#topic_name').focus()
},
newTopic: {
init: function() {
$('#topic_name').keyup(function(e) {
const ESC = 27
if (e.keyCode === ESC) {
Create.newTopic.hide()
} // if
Create.newTopic.name = $(this).val()
})
$('.pinCarousel').click(function() {
if (Create.newTopic.pinned) {
$('.pinCarousel').removeClass('isPinned')
Create.newTopic.pinned = false
} else {
$('.pinCarousel').addClass('isPinned')
Create.newTopic.pinned = true
}
})
var topicBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/topics/autocomplete_topic?term=%QUERY',
wildcard: '%QUERY'
}
})
// initialize the autocomplete results for the metacode spinner
$('#topic_name').typeahead(
{
highlight: true,
minLength: 2
},
[{
name: 'topic_autocomplete',
limit: 8,
display: function(s) { return s.label },
templates: {
suggestion: function(s) {
return Hogan.compile($('#topicAutocompleteTemplate').html()).render(s)
}
},
source: topicBloodhound
}]
)
// tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete
$('#topic_name').bind('typeahead:select', function(event, datum, dataset) {
Create.newTopic.beingCreated = false
if (datum.rtype === 'topic') {
Topic.getTopicFromAutocomplete(datum.id)
} else if (datum.rtype === 'map') {
Topic.getMapFromAutocomplete({
id: datum.id,
name: datum.label
})
}
})
// initialize metacode spinner and then hide it
$('#metacodeImg').CloudCarousel({
titleBox: $('#metacodeImgTitle'),
yRadius: 40,
xRadius: 190,
xPos: 170,
yPos: 40,
speed: 0.3,
mouseWheel: true,
bringToFront: true
})
$('.new_topic').hide()
$('#new_topic').attr('oncontextmenu', 'return false') // prevents the mouse up event from opening the default context menu on this element
},
name: null,
newId: 1,
beingCreated: false,
metacode: null,
x: null,
y: null,
addSynapse: false,
pinned: false,
open: function() {
$('#new_topic').fadeIn('fast', function() {
$('#topic_name').focus()
})
Create.newTopic.beingCreated = true
Create.newTopic.name = ''
GlobalUI.hideDiv('#instructions')
},
hide: function(force) {
if (force || !Create.newTopic.pinned) {
$('#new_topic').fadeOut('fast')
}
if (force) {
$('.pinCarousel').removeClass('isPinned')
Create.newTopic.pinned = false
}
if (DataModel.Topics.length === 0) {
GlobalUI.showDiv('#instructions')
}
Create.newTopic.beingCreated = false
},
reset: function() {
$('#topic_name').typeahead('val', '')
}
},
newSynapse: {
init: function() {
var synapseBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/search/synapses?term=%QUERY',
wildcard: '%QUERY'
}
})
var existingSynapseBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/search/synapses?topic1id=%TOPIC1&topic2id=%TOPIC2',
prepare: function(query, settings) {
var self = Create.newSynapse
if (Selected.Nodes.length < 2 && self.topic1id && self.topic2id) {
settings.url = settings.url.replace('%TOPIC1', self.topic1id).replace('%TOPIC2', self.topic2id)
return settings
} else {
return null
}
}
}
})
// initialize the autocomplete results for synapse creation
$('#synapse_desc').typeahead(
{
highlight: true,
minLength: 2
},
[{
name: 'synapse_autocomplete',
display: function(s) { return s.label },
templates: {
suggestion: function(s) {
return Hogan.compile("<div class='genericSynapseDesc'>{{label}}</div>").render(s)
}
},
source: synapseBloodhound
},
{
name: 'existing_synapses',
limit: 50,
display: function(s) { return s.label },
templates: {
suggestion: function(s) {
return Hogan.compile($('#synapseAutocompleteTemplate').html()).render(s)
},
header: '<h3>Existing synapses</h3>'
},
source: existingSynapseBloodhound
}]
)
$('#synapse_desc').keyup(function(e) {
const ESC = 27
if (e.keyCode === ESC) {
Create.newSynapse.hide()
} // if
Create.newSynapse.description = $(this).val()
})
$('#synapse_desc').focusout(function() {
if (Create.newSynapse.beingCreated) {
Synapse.createSynapseLocally()
}
})
$('#synapse_desc').keydown(function(e) {
const TAB = 9
if (Create.newSynapse.beingCreated && e.keyCode === TAB) {
e.preventDefault()
Synapse.createSynapseLocally()
}
})
$('#synapse_desc').bind('typeahead:select', function(event, datum, dataset) {
if (datum.id) { // if they clicked on an existing synapse get it
Synapse.getSynapseFromAutocomplete(datum.id)
} else {
Create.newSynapse.description = datum.value
Synapse.createSynapseLocally()
}
})
},
beingCreated: false,
description: null,
topic1id: null,
topic2id: null,
newSynapseId: null,
open: function() {
$('#new_synapse').fadeIn(100, function() {
$('#synapse_desc').focus()
})
Create.newSynapse.beingCreated = true
},
hide: function() {
$('#new_synapse').fadeOut('fast')
$('#synapse_desc').typeahead('val', '')
Create.newSynapse.beingCreated = false
Create.newTopic.addSynapse = false
Create.newSynapse.topic1id = 0
Create.newSynapse.topic2id = 0
Mouse.synapseStartCoordinates = []
if (Visualize.mGraph) Visualize.mGraph.plot()
}
}
}
export default Create