metamaps--metamaps/frontend/src/Metamaps/Util.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

236 lines
8.1 KiB
JavaScript

/* global $ */
import { Parser, HtmlRenderer, Node } from 'commonmark'
import { emojiIndex } from 'emoji-mart'
import { escapeRegExp } from 'lodash'
const emojiToShortcodes = {}
Object.keys(emojiIndex.emojis).forEach(key => {
const emoji = emojiIndex.emojis[key]
emojiToShortcodes[emoji.native] = emoji.colons
})
const Util = {
// helper function to determine how many lines are needed
// Line Splitter Function
// copyright Stephen Chapman, 19th April 2006
// you may copy this code but please keep the copyright notice as well
splitLine: function(st, n) {
var b = ''
var s = st || ''
while (s.length > n) {
var c = s.substring(0, n)
var d = c.lastIndexOf(' ')
var e = c.lastIndexOf('\n')
if (e !== -1) d = e
if (d === -1) d = n
b += c.substring(0, d) + '\n'
s = s.substring(d + 1)
}
return b + s
},
nowDateFormatted: function(date = new Date(Date.now())) {
const month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1)
const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
const year = date.getFullYear()
return month + '/' + day + '/' + year
},
decodeEntities: function(desc) {
let temp = document.createElement('p')
temp.innerHTML = desc // browser handles the topics
let str = temp.textContent || temp.innerText
temp = null // delete the element
return str
}, // decodeEntities
getDistance: function(p1, p2) {
return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2))
},
// Try using Visualize.mGraph
coordsToPixels: function(mGraph, coords) {
if (mGraph) {
const canvas = mGraph.canvas
const s = canvas.getSize()
const p = canvas.getPos()
const ox = canvas.translateOffsetX
const oy = canvas.translateOffsetY
const sx = canvas.scaleOffsetX
const sy = canvas.scaleOffsetY
return {
x: (coords.x / (1 / sx)) + p.x + s.width / 2 + ox,
y: (coords.y / (1 / sy)) + p.y + s.height / 2 + oy
}
} else {
return {
x: 0,
y: 0
}
}
},
// Try using Visualize.mGraph
pixelsToCoords: function(mGraph, pixels) {
if (mGraph) {
const canvas = mGraph.canvas
const s = canvas.getSize()
const p = canvas.getPos()
const ox = canvas.translateOffsetX
const oy = canvas.translateOffsetY
const sx = canvas.scaleOffsetX
const sy = canvas.scaleOffsetY
return {
x: (pixels.x - p.x - s.width / 2 - ox) * (1 / sx),
y: (pixels.y - p.y - s.height / 2 - oy) * (1 / sy)
}
} else {
return {
x: 0,
y: 0
}
}
},
getPastelColor: function(opts = {}) {
const rseed = opts.rseed === undefined ? Math.random() : opts.rseed
const gseed = opts.gseed === undefined ? Math.random() : opts.gseed
const bseed = opts.bseed === undefined ? Math.random() : opts.bseed
var r = (Math.round(rseed * 127) + 127).toString(16)
var g = (Math.round(gseed * 127) + 127).toString(16)
var b = (Math.round(bseed * 127) + 127).toString(16)
return Util.colorLuminance('#' + r + g + b, -0.4)
},
// darkens a hex value by 'lum' percentage
colorLuminance: function(hex, lum) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '')
if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
}
lum = lum || 0
// convert to decimal and change luminosity
var rgb = '#'
for (let i = 0; i < 3; i++) {
let c = parseInt(hex.substr(i * 2, 2), 16)
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16)
rgb += ('00' + c).substr(c.length)
}
return rgb
},
openLink: function(url) {
var win = (url !== '') ? window.open(url, '_blank') : 'empty'
if (win) {
// Browser has allowed it to be opened
return true
} else {
// Browser has blocked it
window.alert('Please allow popups in order to open the link')
return false
}
},
mdToHTML: text => {
const safeText = text || ''
const parsed = new Parser().parse(safeText)
// remove images to avoid http content in https context
const walker = parsed.walker()
for (let event = walker.next(); event = walker.next(); event) {
const node = event.node
if (node.type === 'image') {
const imageAlt = node.firstChild.literal
const imageSrc = node.destination
const textNode = new Node('text', node.sourcepos)
textNode.literal = `![${imageAlt}](${imageSrc})`
node.insertBefore(textNode)
node.unlink() // remove the image, replacing it with markdown
walker.resumeAt(textNode, false)
}
}
// use safe: true to filter xss
return new HtmlRenderer({ safe: true }).render(parsed)
},
logCanvasAttributes: function(canvas) {
const fakeMgraph = { canvas }
return {
scaleX: canvas.scaleOffsetX,
scaleY: canvas.scaleOffsetY,
centreCoords: Util.pixelsToCoords(fakeMgraph, { x: canvas.canvases[0].size.width / 2, y: canvas.canvases[0].size.height / 2 })
}
},
resizeCanvas: function(canvas) {
// Store the current canvas attributes, i.e. scale and map-coordinate at the centre of the user's screen
const oldAttr = Util.logCanvasAttributes(canvas)
// Resize the canvas to fill the new window size. Based on how JIT works, this also resets the map back to scale 1 and tranlations = 0
canvas.resize($(window).width(), $(window).height())
// Return the map to the original scale, and then put the previous central map-coordinate back to the centre of user's newly resized screen
canvas.scale(oldAttr.scaleX, oldAttr.scaleY)
const newAttr = Util.logCanvasAttributes(canvas)
canvas.translate(newAttr.centreCoords.x - oldAttr.centreCoords.x, newAttr.centreCoords.y - oldAttr.centreCoords.y)
},
removeEmoji: function(withEmoji) {
let text = withEmoji
Object.keys(emojiIndex.emojis).forEach(key => {
const emoji = emojiIndex.emojis[key]
text = text.replace(new RegExp(escapeRegExp(emoji.native), 'g'), emoji.colons)
})
return text
},
addEmoji: function(withoutEmoji, opts = { emoticons: true }) {
let text = withoutEmoji
Object.keys(emojiIndex.emojis).forEach(key => {
const emoji = emojiIndex.emojis[key]
text = text.replace(new RegExp(escapeRegExp(emoji.colons), 'g'), emoji.native)
})
if (opts.emoticons) {
Object.keys(emojiIndex.emoticons).forEach(emoticon => {
const key = emojiIndex.emoticons[emoticon]
const emoji = emojiIndex.emojis[key]
text = text.replace(new RegExp(escapeRegExp(emoticon), 'g'), emoji.native)
})
}
return text
},
isTester: function(currentUser) {
return ['connorturland@gmail.com', 'devin@callysto.com', 'chessscholar@gmail.com', 'solaureum@gmail.com', 'ishanshapiro@gmail.com'].indexOf(currentUser.get('email')) > -1
},
zoomOnPoint: function(graph, ans, zoomPoint) {
var s = graph.canvas.getSize(),
p = graph.canvas.getPos(),
ox = graph.canvas.translateOffsetX,
oy = graph.canvas.translateOffsetY,
sx = graph.canvas.scaleOffsetX,
sy = graph.canvas.scaleOffsetY;
var pointerCoordX = (zoomPoint.x - p.x - s.width / 2 - ox) * (1 / sx),
pointerCoordY = (zoomPoint.y - p.y - s.height / 2 - oy) * (1 / sy);
//This translates the canvas to be centred over the zoomPoint, then the canvas is zoomed as intended.
graph.canvas.translate(-pointerCoordX,-pointerCoordY);
graph.canvas.scale(ans, ans);
//Get the canvas attributes again now that is has changed
s = graph.canvas.getSize(),
p = graph.canvas.getPos(),
ox = graph.canvas.translateOffsetX,
oy = graph.canvas.translateOffsetY,
sx = graph.canvas.scaleOffsetX,
sy = graph.canvas.scaleOffsetY;
var newX = (zoomPoint.x - p.x - s.width / 2 - ox) * (1 / sx),
newY = (zoomPoint.y - p.y - s.height / 2 - oy) * (1 / sy);
//Translate the canvas to put the pointer back over top the same coordinate it was over before
graph.canvas.translate(newX-pointerCoordX,newY-pointerCoordY);
}
}
export default Util