diff --git a/.travis.yml b/.travis.yml index 28559996..d607a7a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,4 +18,4 @@ before_script: - nvm use stable - npm install script: - - bundle exec rspec && npm test && bundle exec brakeman -q -z + - bundle exec rspec && bundle exec brakeman -q -z && npm test diff --git a/Gemfile.lock b/Gemfile.lock index c2fd0d28..79638ae7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -332,8 +332,5 @@ DEPENDENCIES uglifier uservoice-ruby -RUBY VERSION - ruby 2.3.0p0 - BUNDLED WITH 1.12.5 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 14f565fa..df086157 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,37 +13,7 @@ //= require jquery //= require jquery-ui //= require jquery_ujs -//= require ./webpacked/metamaps.bundle //= require_directory ./lib -//= require ./src/Metamaps.GlobalUI -//= require ./src/Metamaps.Router -//= require ./src/Metamaps.Backbone -//= require ./src/Metamaps.Views -//= require ./src/views/chatView -//= require ./src/views/videoView -//= require ./src/views/room -//= require ./src/JIT +//= require ./src/Metamaps.Erb +//= require ./webpacked/metamaps.bundle //= require ./src/check-canvas-support -//= require ./src/Metamaps -//= require ./src/Metamaps.Create -//= require ./src/Metamaps.TopicCard -//= require ./src/Metamaps.SynapseCard -//= require ./src/Metamaps.Visualize -//= require ./src/Metamaps.Util -//= require ./src/Metamaps.Realtime -//= require ./src/Metamaps.Control -//= require ./src/Metamaps.Filter -//= require ./src/Metamaps.Listeners -//= require ./src/Metamaps.Organize -//= require ./src/Metamaps.Topic -//= require ./src/Metamaps.Synapse -//= require ./src/Metamaps.Map -//= require ./src/Metamaps.Account -//= require ./src/Metamaps.Mapper -//= require ./src/Metamaps.Mobile -//= require ./src/Metamaps.Admin -//= require ./src/Metamaps.Import -//= require ./src/Metamaps.AutoLayout -//= require ./src/Metamaps.PasteInput -//= require ./src/Metamaps.JIT -//= require ./src/Metamaps.Debug diff --git a/app/assets/javascripts/lib/Autolinker.js b/app/assets/javascripts/lib/Autolinker.js deleted file mode 100644 index 6f363d4c..00000000 --- a/app/assets/javascripts/lib/Autolinker.js +++ /dev/null @@ -1,2756 +0,0 @@ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module unless amdModuleId is set - define([], function () { - return (root['Autolinker'] = factory()); - }); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like environments that support module.exports, - // like Node. - module.exports = factory(); - } else { - root['Autolinker'] = factory(); - } -}(this, function () { - -/*! - * Autolinker.js - * 0.17.1 - * - * Copyright(c) 2015 Gregory Jacobs - * MIT Licensed. http://www.opensource.org/licenses/mit-license.php - * - * https://github.com/gregjacobs/Autolinker.js - */ -/** - * @class Autolinker - * @extends Object - * - * Utility class used to process a given string of text, and wrap the matches in - * the appropriate anchor (<a>) tags to turn them into links. - * - * Any of the configuration options may be provided in an Object (map) provided - * to the Autolinker constructor, which will configure how the {@link #link link()} - * method will process the links. - * - * For example: - * - * var autolinker = new Autolinker( { - * newWindow : false, - * truncate : 30 - * } ); - * - * var html = autolinker.link( "Joe went to www.yahoo.com" ); - * // produces: 'Joe went to yahoo.com' - * - * - * The {@link #static-link static link()} method may also be used to inline options into a single call, which may - * be more convenient for one-off uses. For example: - * - * var html = Autolinker.link( "Joe went to www.yahoo.com", { - * newWindow : false, - * truncate : 30 - * } ); - * // produces: 'Joe went to yahoo.com' - * - * - * ## Custom Replacements of Links - * - * If the configuration options do not provide enough flexibility, a {@link #replaceFn} - * may be provided to fully customize the output of Autolinker. This function is - * called once for each URL/Email/Phone#/Twitter Handle/Hashtag match that is - * encountered. - * - * For example: - * - * var input = "..."; // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags - * - * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { - * console.log( "href = ", match.getAnchorHref() ); - * console.log( "text = ", match.getAnchorText() ); - * - * switch( match.getType() ) { - * case 'url' : - * console.log( "url: ", match.getUrl() ); - * - * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes - * tag.setAttr( 'rel', 'nofollow' ); - * tag.addClass( 'external-link' ); - * - * return tag; - * - * } else { - * return true; // let Autolinker perform its normal anchor tag replacement - * } - * - * case 'email' : - * var email = match.getEmail(); - * console.log( "email: ", email ); - * - * if( email === "my@own.address" ) { - * return false; // don't auto-link this particular email address; leave as-is - * } else { - * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) - * } - * - * case 'phone' : - * var phoneNumber = match.getPhoneNumber(); - * console.log( phoneNumber ); - * - * return '' + phoneNumber + ''; - * - * case 'twitter' : - * var twitterHandle = match.getTwitterHandle(); - * console.log( twitterHandle ); - * - * return '' + twitterHandle + ''; - * - * case 'hashtag' : - * var hashtag = match.getHashtag(); - * console.log( hashtag ); - * - * return '' + hashtag + ''; - * } - * } - * } ); - * - * - * The function may return the following values: - * - * - `true` (Boolean): Allow Autolinker to replace the match as it normally would. - * - `false` (Boolean): Do not replace the current match at all - leave as-is. - * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for - * the match. - * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text. - * - * @constructor - * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map). - */ -var Autolinker = function( cfg ) { - Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs. - - // Validate the value of the `hashtag` cfg. - var hashtag = this.hashtag; - if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' ) { - throw new Error( "invalid `hashtag` cfg - see docs" ); - } -}; - -Autolinker.prototype = { - constructor : Autolinker, // fix constructor property - - /** - * @cfg {Boolean} urls - * - * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. - */ - urls : true, - - /** - * @cfg {Boolean} email - * - * `true` if email addresses should be automatically linked, `false` if they should not be. - */ - email : true, - - /** - * @cfg {Boolean} twitter - * - * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. - */ - twitter : true, - - /** - * @cfg {Boolean} phone - * - * `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be. - */ - phone: true, - - /** - * @cfg {Boolean/String} hashtag - * - * A string for the service name to have hashtags (ex: "#myHashtag") - * auto-linked to. The currently-supported values are: - * - * - 'twitter' - * - 'facebook' - * - * Pass `false` to skip auto-linking of hashtags. - */ - hashtag : false, - - /** - * @cfg {Boolean} newWindow - * - * `true` if the links should open in a new window, `false` otherwise. - */ - newWindow : true, - - /** - * @cfg {Boolean} stripPrefix - * - * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped - * from the beginning of URL links' text, `false` otherwise. - */ - stripPrefix : true, - - /** - * @cfg {Number} truncate - * - * A number for how many characters long matched text should be truncated to inside the text of - * a link. If the matched text is over this number of characters, it will be truncated to this length by - * adding a two period ellipsis ('..') to the end of the string. - * - * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look - * something like this: 'yahoo.com/some/long/pat..' - */ - truncate : undefined, - - /** - * @cfg {String} className - * - * A CSS class name to add to the generated links. This class will be added to all links, as well as this class - * plus match suffixes for styling url/email/phone/twitter/hashtag links differently. - * - * For example, if this config is provided as "myLink", then: - * - * - URL links will have the CSS classes: "myLink myLink-url" - * - Email links will have the CSS classes: "myLink myLink-email", and - * - Twitter links will have the CSS classes: "myLink myLink-twitter" - * - Phone links will have the CSS classes: "myLink myLink-phone" - * - Hashtag links will have the CSS classes: "myLink myLink-hashtag" - */ - className : "", - - /** - * @cfg {Function} replaceFn - * - * A function to individually process each match found in the input string. - * - * See the class's description for usage. - * - * This function is called with the following parameters: - * - * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such - * as the instance's {@link #getTagBuilder tag builder}). - * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the - * match that the `replaceFn` is currently processing. See {@link Autolinker.match.Match} subclasses for details. - */ - - - /** - * @private - * @property {Autolinker.htmlParser.HtmlParser} htmlParser - * - * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated - * in the {@link #getHtmlParser} method. - */ - htmlParser : undefined, - - /** - * @private - * @property {Autolinker.matchParser.MatchParser} matchParser - * - * The MatchParser instance used to find matches in the text nodes of an input string passed to - * {@link #link}. This is lazily instantiated in the {@link #getMatchParser} method. - */ - matchParser : undefined, - - /** - * @private - * @property {Autolinker.AnchorTagBuilder} tagBuilder - * - * The AnchorTagBuilder instance used to build match replacement anchor tags. Note: this is lazily instantiated - * in the {@link #getTagBuilder} method. - */ - tagBuilder : undefined, - - /** - * Automatically links URLs, Email addresses, Phone numbers, Twitter - * handles, and Hashtags found in the given chunk of HTML. Does not link - * URLs found within HTML tags. - * - * For instance, if given the text: `You should go to http://www.yahoo.com`, - * then the result will be `You should go to - * <a href="http://www.yahoo.com">http://www.yahoo.com</a>` - * - * This method finds the text around any HTML elements in the input - * `textOrHtml`, which will be the text that is processed. Any original HTML - * elements will be left as-is, as well as the text that is already wrapped - * in anchor (<a>) tags. - * - * @param {String} textOrHtml The HTML or text to autolink matches within - * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, - * {@link #twitter}, and {@link #hashtag} options are enabled). - * @return {String} The HTML, with matches automatically linked. - */ - link : function( textOrHtml ) { - var htmlParser = this.getHtmlParser(), - htmlNodes = htmlParser.parse( textOrHtml ), - anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have - resultHtml = []; - - for( var i = 0, len = htmlNodes.length; i < len; i++ ) { - var node = htmlNodes[ i ], - nodeType = node.getType(), - nodeText = node.getText(); - - if( nodeType === 'element' ) { - // Process HTML nodes in the input `textOrHtml` - if( node.getTagName() === 'a' ) { - if( !node.isClosing() ) { // it's the start tag - anchorTagStackCount++; - } else { // it's the end tag - anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 - } - } - resultHtml.push( nodeText ); // now add the text of the tag itself verbatim - - } else if( nodeType === 'entity' || nodeType === 'comment' ) { - resultHtml.push( nodeText ); // append HTML entity nodes (such as ' ') or HTML comments (such as '') verbatim - - } else { - // Process text nodes in the input `textOrHtml` - if( anchorTagStackCount === 0 ) { - // If we're not within an tag, process the text node to linkify - var linkifiedStr = this.linkifyStr( nodeText ); - resultHtml.push( linkifiedStr ); - - } else { - // `text` is within an tag, simply append the text - we do not want to autolink anything - // already within an ... tag - resultHtml.push( nodeText ); - } - } - } - - return resultHtml.join( "" ); - }, - - /** - * Process the text that lies in between HTML tags, performing the anchor - * tag replacements for the matches, and returns the string with the - * replacements made. - * - * This method does the actual wrapping of matches with anchor tags. - * - * @private - * @param {String} str The string of text to auto-link. - * @return {String} The text with anchor tags auto-filled. - */ - linkifyStr : function( str ) { - return this.getMatchParser().replace( str, this.createMatchReturnVal, this ); - }, - - - /** - * Creates the return string value for a given match in the input string, - * for the {@link #linkifyStr} method. - * - * This method handles the {@link #replaceFn}, if one was provided. - * - * @private - * @param {Autolinker.match.Match} match The Match object that represents the match. - * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but - * may be the `matchStr` itself if the match is not to be replaced. - */ - createMatchReturnVal : function( match ) { - // Handle a custom `replaceFn` being provided - var replaceFnResult; - if( this.replaceFn ) { - replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg - } - - if( typeof replaceFnResult === 'string' ) { - return replaceFnResult; // `replaceFn` returned a string, use that - - } else if( replaceFnResult === false ) { - return match.getMatchedText(); // no replacement for the match - - } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { - return replaceFnResult.toAnchorString(); - - } else { // replaceFnResult === true, or no/unknown return value from function - // Perform Autolinker's default anchor tag generation - var tagBuilder = this.getTagBuilder(), - anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance - - return anchorTag.toAnchorString(); - } - }, - - - /** - * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. - * - * @protected - * @return {Autolinker.htmlParser.HtmlParser} - */ - getHtmlParser : function() { - var htmlParser = this.htmlParser; - - if( !htmlParser ) { - htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser(); - } - - return htmlParser; - }, - - - /** - * Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance. - * - * @protected - * @return {Autolinker.matchParser.MatchParser} - */ - getMatchParser : function() { - var matchParser = this.matchParser; - - if( !matchParser ) { - matchParser = this.matchParser = new Autolinker.matchParser.MatchParser( { - urls : this.urls, - email : this.email, - twitter : this.twitter, - phone : this.phone, - hashtag : this.hashtag, - stripPrefix : this.stripPrefix - } ); - } - - return matchParser; - }, - - - /** - * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it - * if it does not yet exist. - * - * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that - * Autolinker would normally generate, and then allow for modifications before returning it. For example: - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance - * tag.setAttr( 'rel', 'nofollow' ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test google.com - * - * @return {Autolinker.AnchorTagBuilder} - */ - getTagBuilder : function() { - var tagBuilder = this.tagBuilder; - - if( !tagBuilder ) { - tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { - newWindow : this.newWindow, - truncate : this.truncate, - className : this.className - } ); - } - - return tagBuilder; - } - -}; - - -/** - * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles, - * and Hashtags found in the given chunk of HTML. Does not link URLs found - * within HTML tags. - * - * For instance, if given the text: `You should go to http://www.yahoo.com`, - * then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` - * - * Example: - * - * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } ); - * // Produces: "Go to google.com" - * - * @static - * @param {String} textOrHtml The HTML or text to find matches within (depending - * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter}, - * and {@link #hashtag} options are enabled). - * @param {Object} [options] Any of the configuration options for the Autolinker - * class, specified in an Object (map). See the class description for an - * example call. - * @return {String} The HTML text, with matches automatically linked. - */ -Autolinker.link = function( textOrHtml, options ) { - var autolinker = new Autolinker( options ); - return autolinker.link( textOrHtml ); -}; - - -// Autolinker Namespaces -Autolinker.match = {}; -Autolinker.htmlParser = {}; -Autolinker.matchParser = {}; - -/*global Autolinker */ -/*jshint eqnull:true, boss:true */ -/** - * @class Autolinker.Util - * @singleton - * - * A few utility methods for Autolinker. - */ -Autolinker.Util = { - - /** - * @property {Function} abstractMethod - * - * A function object which represents an abstract method. - */ - abstractMethod : function() { throw "abstract"; }, - - - /** - * @private - * @property {RegExp} trimRegex - * - * The regular expression used to trim the leading and trailing whitespace - * from a string. - */ - trimRegex : /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - - /** - * Assigns (shallow copies) the properties of `src` onto `dest`. - * - * @param {Object} dest The destination object. - * @param {Object} src The source object. - * @return {Object} The destination object (`dest`) - */ - assign : function( dest, src ) { - for( var prop in src ) { - if( src.hasOwnProperty( prop ) ) { - dest[ prop ] = src[ prop ]; - } - } - - return dest; - }, - - - /** - * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype. - * - * @param {Function} superclass The constructor function for the superclass. - * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the - * special property `constructor`, which will be used as the new subclass's constructor function. - * @return {Function} The new subclass function. - */ - extend : function( superclass, protoProps ) { - var superclassProto = superclass.prototype; - - var F = function() {}; - F.prototype = superclassProto; - - var subclass; - if( protoProps.hasOwnProperty( 'constructor' ) ) { - subclass = protoProps.constructor; - } else { - subclass = function() { superclassProto.constructor.apply( this, arguments ); }; - } - - var subclassProto = subclass.prototype = new F(); // set up prototype chain - subclassProto.constructor = subclass; // fix constructor property - subclassProto.superclass = superclassProto; - - delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there - Autolinker.Util.assign( subclassProto, protoProps ); - - return subclass; - }, - - - /** - * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the - * end of the string (by default, two periods: '..'). If the `str` length does not exceed - * `len`, the string will be returned unchanged. - * - * @param {String} str The string to truncate and add an ellipsis to. - * @param {Number} truncateLen The length to truncate the string at. - * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str` - * when truncated. Defaults to '..' - */ - ellipsis : function( str, truncateLen, ellipsisChars ) { - if( str.length > truncateLen ) { - ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars; - str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars; - } - return str; - }, - - - /** - * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below). - * - * @param {Array} arr The array to find an element of. - * @param {*} element The element to find in the array, and return the index of. - * @return {Number} The index of the `element`, or -1 if it was not found. - */ - indexOf : function( arr, element ) { - if( Array.prototype.indexOf ) { - return arr.indexOf( element ); - - } else { - for( var i = 0, len = arr.length; i < len; i++ ) { - if( arr[ i ] === element ) return i; - } - return -1; - } - }, - - - - /** - * Performs the functionality of what modern browsers do when `String.prototype.split()` is called - * with a regular expression that contains capturing parenthesis. - * - * For example: - * - * // Modern browsers: - * "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ] - * - * // Old IE (including IE8): - * "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ] - * - * This method emulates the functionality of modern browsers for the old IE case. - * - * @param {String} str The string to split. - * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting - * character(s) will be spliced into the array, as in the "modern browsers" example in the - * description of this method. - * Note #1: the supplied regular expression **must** have the 'g' flag specified. - * Note #2: for simplicity's sake, the regular expression does not need - * to contain capturing parenthesis - it will be assumed that any match has them. - * @return {String[]} The split array of strings, with the splitting character(s) included. - */ - splitAndCapture : function( str, splitRegex ) { - if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" ); - - var result = [], - lastIdx = 0, - match; - - while( match = splitRegex.exec( str ) ) { - result.push( str.substring( lastIdx, match.index ) ); - result.push( match[ 0 ] ); // push the splitting char(s) - - lastIdx = match.index + match[ 0 ].length; - } - result.push( str.substring( lastIdx ) ); - - return result; - }, - - - /** - * Trims the leading and trailing whitespace from a string. - * - * @param {String} str The string to trim. - * @return {String} - */ - trim : function( str ) { - return str.replace( this.trimRegex, '' ); - } - -}; -/*global Autolinker */ -/*jshint boss:true */ -/** - * @class Autolinker.HtmlTag - * @extends Object - * - * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically. - * - * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use - * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}. - * - * ## Examples - * - * Example instantiation: - * - * var tag = new Autolinker.HtmlTag( { - * tagName : 'a', - * attrs : { 'href': 'http://google.com', 'class': 'external-link' }, - * innerHtml : 'Google' - * } ); - * - * tag.toAnchorString(); // Google - * - * // Individual accessor methods - * tag.getTagName(); // 'a' - * tag.getAttr( 'href' ); // 'http://google.com' - * tag.hasClass( 'external-link' ); // true - * - * - * Using mutator methods (which may be used in combination with instantiation config properties): - * - * var tag = new Autolinker.HtmlTag(); - * tag.setTagName( 'a' ); - * tag.setAttr( 'href', 'http://google.com' ); - * tag.addClass( 'external-link' ); - * tag.setInnerHtml( 'Google' ); - * - * tag.getTagName(); // 'a' - * tag.getAttr( 'href' ); // 'http://google.com' - * tag.hasClass( 'external-link' ); // true - * - * tag.toAnchorString(); // Google - * - * - * ## Example use within a {@link Autolinker#replaceFn replaceFn} - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text - * tag.setAttr( 'rel', 'nofollow' ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test google.com - * - * - * ## Example use with a new tag for the replacement - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = new Autolinker.HtmlTag( { - * tagName : 'button', - * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, - * innerHtml : 'Load URL: ' + match.getAnchorText() - * } ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test - */ -Autolinker.HtmlTag = Autolinker.Util.extend( Object, { - - /** - * @cfg {String} tagName - * - * The tag name. Ex: 'a', 'button', etc. - * - * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString} - * is executed. - */ - - /** - * @cfg {Object.} attrs - * - * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the - * values are the attribute values. - */ - - /** - * @cfg {String} innerHtml - * - * The inner HTML for the tag. - * - * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym - * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML} - * if you prefer, but this one is recommended. - */ - - /** - * @cfg {String} innerHTML - * - * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version - * for acronym names. - */ - - - /** - * @protected - * @property {RegExp} whitespaceRegex - * - * Regular expression used to match whitespace in a string of CSS classes. - */ - whitespaceRegex : /\s+/, - - - /** - * @constructor - * @param {Object} [cfg] The configuration properties for this class, in an Object (map) - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); - - this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym - }, - - - /** - * Sets the tag name that will be used to generate the tag with. - * - * @param {String} tagName - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setTagName : function( tagName ) { - this.tagName = tagName; - return this; - }, - - - /** - * Retrieves the tag name. - * - * @return {String} - */ - getTagName : function() { - return this.tagName || ""; - }, - - - /** - * Sets an attribute on the HtmlTag. - * - * @param {String} attrName The attribute name to set. - * @param {String} attrValue The attribute value to set. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setAttr : function( attrName, attrValue ) { - var tagAttrs = this.getAttrs(); - tagAttrs[ attrName ] = attrValue; - - return this; - }, - - - /** - * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`. - * - * @param {String} name The attribute name to retrieve. - * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag. - */ - getAttr : function( attrName ) { - return this.getAttrs()[ attrName ]; - }, - - - /** - * Sets one or more attributes on the HtmlTag. - * - * @param {Object.} attrs A key/value Object (map) of the attributes to set. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setAttrs : function( attrs ) { - var tagAttrs = this.getAttrs(); - Autolinker.Util.assign( tagAttrs, attrs ); - - return this; - }, - - - /** - * Retrieves the attributes Object (map) for the HtmlTag. - * - * @return {Object.} A key/value object of the attributes for the HtmlTag. - */ - getAttrs : function() { - return this.attrs || ( this.attrs = {} ); - }, - - - /** - * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag. - * - * @param {String} cssClass One or more space-separated CSS classes to set (overwrite). - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setClass : function( cssClass ) { - return this.setAttr( 'class', cssClass ); - }, - - - /** - * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes. - * - * @param {String} cssClass One or more space-separated CSS classes to add. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - addClass : function( cssClass ) { - var classAttr = this.getClass(), - whitespaceRegex = this.whitespaceRegex, - indexOf = Autolinker.Util.indexOf, // to support IE8 and below - classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), - newClasses = cssClass.split( whitespaceRegex ), - newClass; - - while( newClass = newClasses.shift() ) { - if( indexOf( classes, newClass ) === -1 ) { - classes.push( newClass ); - } - } - - this.getAttrs()[ 'class' ] = classes.join( " " ); - return this; - }, - - - /** - * Convenience method to remove one or more CSS classes from the HtmlTag. - * - * @param {String} cssClass One or more space-separated CSS classes to remove. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - removeClass : function( cssClass ) { - var classAttr = this.getClass(), - whitespaceRegex = this.whitespaceRegex, - indexOf = Autolinker.Util.indexOf, // to support IE8 and below - classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), - removeClasses = cssClass.split( whitespaceRegex ), - removeClass; - - while( classes.length && ( removeClass = removeClasses.shift() ) ) { - var idx = indexOf( classes, removeClass ); - if( idx !== -1 ) { - classes.splice( idx, 1 ); - } - } - - this.getAttrs()[ 'class' ] = classes.join( " " ); - return this; - }, - - - /** - * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when - * there are multiple. - * - * @return {String} - */ - getClass : function() { - return this.getAttrs()[ 'class' ] || ""; - }, - - - /** - * Convenience method to check if the tag has a CSS class or not. - * - * @param {String} cssClass The CSS class to check for. - * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise. - */ - hasClass : function( cssClass ) { - return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1; - }, - - - /** - * Sets the inner HTML for the tag. - * - * @param {String} html The inner HTML to set. - * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. - */ - setInnerHtml : function( html ) { - this.innerHtml = html; - - return this; - }, - - - /** - * Retrieves the inner HTML for the tag. - * - * @return {String} - */ - getInnerHtml : function() { - return this.innerHtml || ""; - }, - - - /** - * Override of superclass method used to generate the HTML string for the tag. - * - * @return {String} - */ - toAnchorString : function() { - var tagName = this.getTagName(), - attrsStr = this.buildAttrsStr(); - - attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes - - return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); - }, - - - /** - * Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate - * the stringified HtmlTag. - * - * @protected - * @return {String} Example return: `attr1="value1" attr2="value2"` - */ - buildAttrsStr : function() { - if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string - - var attrs = this.getAttrs(), - attrsArr = []; - - for( var prop in attrs ) { - if( attrs.hasOwnProperty( prop ) ) { - attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); - } - } - return attrsArr.join( " " ); - } - -} ); - -/*global Autolinker */ -/*jshint sub:true */ -/** - * @protected - * @class Autolinker.AnchorTagBuilder - * @extends Object - * - * Builds anchor (<a>) tags for the Autolinker utility when a match is found. - * - * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may - * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances - * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example: - * - * var html = Autolinker.link( "Test google.com", { - * replaceFn : function( autolinker, match ) { - * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance - * tag.setAttr( 'rel', 'nofollow' ); - * - * return tag; - * } - * } ); - * - * // generated html: - * // Test google.com - */ -Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { - - /** - * @cfg {Boolean} newWindow - * @inheritdoc Autolinker#newWindow - */ - - /** - * @cfg {Number} truncate - * @inheritdoc Autolinker#truncate - */ - - /** - * @cfg {String} className - * @inheritdoc Autolinker#className - */ - - - /** - * @constructor - * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); - }, - - - /** - * Generates the actual anchor (<a>) tag to use in place of the - * matched text, via its `match` object. - * - * @param {Autolinker.match.Match} match The Match instance to generate an - * anchor tag from. - * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag. - */ - build : function( match ) { - var tag = new Autolinker.HtmlTag( { - tagName : 'a', - attrs : this.createAttrs( match.getType(), match.getAnchorHref() ), - innerHtml : this.processAnchorText( match.getAnchorText() ) - } ); - - return tag; - }, - - - /** - * Creates the Object (map) of the HTML attributes for the anchor (<a>) - * tag being generated. - * - * @protected - * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of - * match that an anchor tag is being generated for. - * @param {String} href The href for the anchor tag. - * @return {Object} A key/value Object (map) of the anchor tag's attributes. - */ - createAttrs : function( matchType, anchorHref ) { - var attrs = { - 'href' : anchorHref // we'll always have the `href` attribute - }; - - var cssClass = this.createCssClass( matchType ); - if( cssClass ) { - attrs[ 'class' ] = cssClass; - } - if( this.newWindow ) { - attrs[ 'target' ] = "_blank"; - } - - return attrs; - }, - - - /** - * Creates the CSS class that will be used for a given anchor tag, based on - * the `matchType` and the {@link #className} config. - * - * @private - * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of - * match that an anchor tag is being generated for. - * @return {String} The CSS class string for the link. Example return: - * "myLink myLink-url". If no {@link #className} was configured, returns - * an empty string. - */ - createCssClass : function( matchType ) { - var className = this.className; - - if( !className ) - return ""; - else - return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", "myLink myLink-phone", "myLink myLink-twitter", or "myLink myLink-hashtag" - }, - - - /** - * Processes the `anchorText` by truncating the text according to the - * {@link #truncate} config. - * - * @private - * @param {String} anchorText The anchor tag's text (i.e. what will be - * displayed). - * @return {String} The processed `anchorText`. - */ - processAnchorText : function( anchorText ) { - anchorText = this.doTruncate( anchorText ); - - return anchorText; - }, - - - /** - * Performs the truncation of the `anchorText`, if the `anchorText` is - * longer than the {@link #truncate} option. Truncates the text to 2 - * characters fewer than the {@link #truncate} option, and adds ".." to the - * end. - * - * @private - * @param {String} text The anchor tag's text (i.e. what will be displayed). - * @return {String} The truncated anchor text. - */ - doTruncate : function( anchorText ) { - return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY ); - } - -} ); -/*global Autolinker */ -/** - * @private - * @class Autolinker.htmlParser.HtmlParser - * @extends Object - * - * An HTML parser implementation which simply walks an HTML string and returns an array of - * {@link Autolinker.htmlParser.HtmlNode HtmlNodes} that represent the basic HTML structure of the input string. - * - * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, effectively ignoring / "walking - * around" HTML tags. - */ -Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, { - - /** - * @private - * @property {RegExp} htmlRegex - * - * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and - * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html. - * - * Capturing groups: - * - * 1. The "!DOCTYPE" tag name, if a tag is a <!DOCTYPE> tag. - * 2. If it is an end tag, this group will have the '/'. - * 3. If it is a comment tag, this group will hold the comment text (i.e. - * the text inside the `<!--` and `-->`. - * 4. The tag name for all tags (other than the <!DOCTYPE> tag) - */ - htmlRegex : (function() { - var commentTagRegex = /!--([\s\S]+?)--/, - tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/, - attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char - attrValueRegex = /(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values - nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]' - - return new RegExp( [ - // for tag. Ex: ) - '(?:', - '<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag - - // Zero or more attributes following the tag name - '(?:', - '\\s+', // one or more whitespace chars before an attribute - - // Either: - // A. attr="value", or - // B. "value" alone (To cover example doctype tag: ) - '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', - ')*', - '>', - ')', - - '|', - - // All other HTML tags (i.e. tags that are not ) - '(?:', - '<(/)?', // Beginning of a tag or comment. Either '<' for a start tag, or '' - - ')', - ')', - '>', - ')' - ].join( "" ), 'gi' ); - } )(), - - /** - * @private - * @property {RegExp} htmlCharacterEntitiesRegex - * - * The regular expression that matches common HTML character entities. - * - * Ignoring & as it could be part of a query string -- handling it separately. - */ - htmlCharacterEntitiesRegex: /( | |<|<|>|>|"|"|')/gi, - - - /** - * Parses an HTML string and returns a simple array of {@link Autolinker.htmlParser.HtmlNode HtmlNodes} - * to represent the HTML structure of the input string. - * - * @param {String} html The HTML to parse. - * @return {Autolinker.htmlParser.HtmlNode[]} - */ - parse : function( html ) { - var htmlRegex = this.htmlRegex, - currentResult, - lastIndex = 0, - textAndEntityNodes, - nodes = []; // will be the result of the method - - while( ( currentResult = htmlRegex.exec( html ) ) !== null ) { - var tagText = currentResult[ 0 ], - commentText = currentResult[ 3 ], // if we've matched a comment - tagName = currentResult[ 1 ] || currentResult[ 4 ], // The tag (ex: "!DOCTYPE"), or another tag (ex: "a" or "img") - isClosingTag = !!currentResult[ 2 ], - inBetweenTagsText = html.substring( lastIndex, currentResult.index ); - - // Push TextNodes and EntityNodes for any text found between tags - if( inBetweenTagsText ) { - textAndEntityNodes = this.parseTextAndEntityNodes( inBetweenTagsText ); - nodes.push.apply( nodes, textAndEntityNodes ); - } - - // Push the CommentNode or ElementNode - if( commentText ) { - nodes.push( this.createCommentNode( tagText, commentText ) ); - } else { - nodes.push( this.createElementNode( tagText, tagName, isClosingTag ) ); - } - - lastIndex = currentResult.index + tagText.length; - } - - // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements. - if( lastIndex < html.length ) { - var text = html.substring( lastIndex ); - - // Push TextNodes and EntityNodes for any text found between tags - if( text ) { - textAndEntityNodes = this.parseTextAndEntityNodes( text ); - nodes.push.apply( nodes, textAndEntityNodes ); - } - } - - return nodes; - }, - - - /** - * Parses text and HTML entity nodes from a given string. The input string - * should not have any HTML tags (elements) within it. - * - * @private - * @param {String} text The text to parse. - * @return {Autolinker.htmlParser.HtmlNode[]} An array of HtmlNodes to - * represent the {@link Autolinker.htmlParser.TextNode TextNodes} and - * {@link Autolinker.htmlParser.EntityNode EntityNodes} found. - */ - parseTextAndEntityNodes : function( text ) { - var nodes = [], - textAndEntityTokens = Autolinker.Util.splitAndCapture( text, this.htmlCharacterEntitiesRegex ); // split at HTML entities, but include the HTML entities in the results array - - // Every even numbered token is a TextNode, and every odd numbered token is an EntityNode - // For example: an input `text` of "Test "this" today" would turn into the - // `textAndEntityTokens`: [ 'Test ', '"', 'this', '"', ' today' ] - for( var i = 0, len = textAndEntityTokens.length; i < len; i += 2 ) { - var textToken = textAndEntityTokens[ i ], - entityToken = textAndEntityTokens[ i + 1 ]; - - if( textToken ) nodes.push( this.createTextNode( textToken ) ); - if( entityToken ) nodes.push( this.createEntityNode( entityToken ) ); - } - return nodes; - }, - - - /** - * Factory method to create an {@link Autolinker.htmlParser.CommentNode CommentNode}. - * - * @private - * @param {String} tagText The full text of the tag (comment) that was - * matched, including its <!-- and -->. - * @param {String} comment The full text of the comment that was matched. - */ - createCommentNode : function( tagText, commentText ) { - return new Autolinker.htmlParser.CommentNode( { - text: tagText, - comment: Autolinker.Util.trim( commentText ) - } ); - }, - - - /** - * Factory method to create an {@link Autolinker.htmlParser.ElementNode ElementNode}. - * - * @private - * @param {String} tagText The full text of the tag (element) that was - * matched, including its attributes. - * @param {String} tagName The name of the tag. Ex: An <img> tag would - * be passed to this method as "img". - * @param {Boolean} isClosingTag `true` if it's a closing tag, false - * otherwise. - * @return {Autolinker.htmlParser.ElementNode} - */ - createElementNode : function( tagText, tagName, isClosingTag ) { - return new Autolinker.htmlParser.ElementNode( { - text : tagText, - tagName : tagName.toLowerCase(), - closing : isClosingTag - } ); - }, - - - /** - * Factory method to create a {@link Autolinker.htmlParser.EntityNode EntityNode}. - * - * @private - * @param {String} text The text that was matched for the HTML entity (such - * as '&nbsp;'). - * @return {Autolinker.htmlParser.EntityNode} - */ - createEntityNode : function( text ) { - return new Autolinker.htmlParser.EntityNode( { text: text } ); - }, - - - /** - * Factory method to create a {@link Autolinker.htmlParser.TextNode TextNode}. - * - * @private - * @param {String} text The text that was matched. - * @return {Autolinker.htmlParser.TextNode} - */ - createTextNode : function( text ) { - return new Autolinker.htmlParser.TextNode( { text: text } ); - } - -} ); -/*global Autolinker */ -/** - * @abstract - * @class Autolinker.htmlParser.HtmlNode - * - * Represents an HTML node found in an input string. An HTML node is one of the following: - * - * 1. An {@link Autolinker.htmlParser.ElementNode ElementNode}, which represents HTML tags. - * 2. A {@link Autolinker.htmlParser.TextNode TextNode}, which represents text outside or within HTML tags. - * 3. A {@link Autolinker.htmlParser.EntityNode EntityNode}, which represents one of the known HTML - * entities that Autolinker looks for. This includes common ones such as &quot; and &nbsp; - */ -Autolinker.htmlParser.HtmlNode = Autolinker.Util.extend( Object, { - - /** - * @cfg {String} text (required) - * - * The original text that was matched for the HtmlNode. - * - * - In the case of an {@link Autolinker.htmlParser.ElementNode ElementNode}, this will be the tag's - * text. - * - In the case of a {@link Autolinker.htmlParser.TextNode TextNode}, this will be the text itself. - * - In the case of a {@link Autolinker.htmlParser.EntityNode EntityNode}, this will be the text of - * the HTML entity. - */ - text : "", - - - /** - * @constructor - * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map). - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); - }, - - - /** - * Returns a string name for the type of node that this class represents. - * - * @abstract - * @return {String} - */ - getType : Autolinker.Util.abstractMethod, - - - /** - * Retrieves the {@link #text} for the HtmlNode. - * - * @return {String} - */ - getText : function() { - return this.text; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.htmlParser.CommentNode - * @extends Autolinker.htmlParser.HtmlNode - * - * Represents an HTML comment node that has been parsed by the - * {@link Autolinker.htmlParser.HtmlParser}. - * - * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more - * details. - */ -Autolinker.htmlParser.CommentNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, { - - /** - * @cfg {String} comment (required) - * - * The text inside the comment tag. This text is stripped of any leading or - * trailing whitespace. - */ - comment : '', - - - /** - * Returns a string name for the type of node that this class represents. - * - * @return {String} - */ - getType : function() { - return 'comment'; - }, - - - /** - * Returns the comment inside the comment tag. - * - * @return {String} - */ - getComment : function() { - return this.comment; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.htmlParser.ElementNode - * @extends Autolinker.htmlParser.HtmlNode - * - * Represents an HTML element node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}. - * - * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details. - */ -Autolinker.htmlParser.ElementNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, { - - /** - * @cfg {String} tagName (required) - * - * The name of the tag that was matched. - */ - tagName : '', - - /** - * @cfg {Boolean} closing (required) - * - * `true` if the element (tag) is a closing tag, `false` if its an opening tag. - */ - closing : false, - - - /** - * Returns a string name for the type of node that this class represents. - * - * @return {String} - */ - getType : function() { - return 'element'; - }, - - - /** - * Returns the HTML element's (tag's) name. Ex: for an <img> tag, returns "img". - * - * @return {String} - */ - getTagName : function() { - return this.tagName; - }, - - - /** - * Determines if the HTML element (tag) is a closing tag. Ex: <div> returns - * `false`, while </div> returns `true`. - * - * @return {Boolean} - */ - isClosing : function() { - return this.closing; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.htmlParser.EntityNode - * @extends Autolinker.htmlParser.HtmlNode - * - * Represents a known HTML entity node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}. - * Ex: '&nbsp;', or '&#160;' (which will be retrievable from the {@link #getText} method. - * - * Note that this class will only be returned from the HtmlParser for the set of checked HTML entity nodes - * defined by the {@link Autolinker.htmlParser.HtmlParser#htmlCharacterEntitiesRegex}. - * - * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details. - */ -Autolinker.htmlParser.EntityNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, { - - /** - * Returns a string name for the type of node that this class represents. - * - * @return {String} - */ - getType : function() { - return 'entity'; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.htmlParser.TextNode - * @extends Autolinker.htmlParser.HtmlNode - * - * Represents a text node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}. - * - * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details. - */ -Autolinker.htmlParser.TextNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, { - - /** - * Returns a string name for the type of node that this class represents. - * - * @return {String} - */ - getType : function() { - return 'text'; - } - -} ); -/*global Autolinker */ -/** - * @private - * @class Autolinker.matchParser.MatchParser - * @extends Object - * - * Used by Autolinker to parse potential matches, given an input string of text. - * - * The MatchParser is fed a non-HTML string in order to search for matches. - * Autolinker first uses the {@link Autolinker.htmlParser.HtmlParser} to "walk - * around" HTML tags, and then the text around the HTML tags is passed into the - * MatchParser in order to find the actual matches. - */ -Autolinker.matchParser.MatchParser = Autolinker.Util.extend( Object, { - - /** - * @cfg {Boolean} urls - * @inheritdoc Autolinker#urls - */ - urls : true, - - /** - * @cfg {Boolean} email - * @inheritdoc Autolinker#email - */ - email : true, - - /** - * @cfg {Boolean} twitter - * @inheritdoc Autolinker#twitter - */ - twitter : true, - - /** - * @cfg {Boolean} phone - * @inheritdoc Autolinker#phone - */ - phone: true, - - /** - * @cfg {Boolean/String} hashtag - * @inheritdoc Autolinker#hashtag - */ - hashtag : false, - - /** - * @cfg {Boolean} stripPrefix - * @inheritdoc Autolinker#stripPrefix - */ - stripPrefix : true, - - - /** - * @private - * @property {RegExp} matcherRegex - * - * The regular expression that matches URLs, email addresses, phone #s, - * Twitter handles, and Hashtags. - * - * This regular expression has the following capturing groups: - * - * 1. Group that is used to determine if there is a Twitter handle match - * (i.e. \@someTwitterUser). Simply check for its existence to determine - * if there is a Twitter handle match. The next couple of capturing - * groups give information about the Twitter handle match. - * 2. The whitespace character before the \@sign in a Twitter handle. This - * is needed because there are no lookbehinds in JS regular expressions, - * and can be used to reconstruct the original string in a replace(). - * 3. The Twitter handle itself in a Twitter match. If the match is - * '@someTwitterUser', the handle is 'someTwitterUser'. - * 4. Group that matches an email address. Used to determine if the match - * is an email address, as well as holding the full address. Ex: - * 'me@my.com' - * 5. Group that matches a URL in the input text. Ex: 'http://google.com', - * 'www.google.com', or just 'google.com'. This also includes a path, - * url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor - * 6. Group that matches a protocol URL (i.e. 'http://google.com'). This is - * used to match protocol URLs with just a single word, like 'http://localhost', - * where we won't double check that the domain name has at least one '.' - * in it. - * 7. A protocol-relative ('//') match for the case of a 'www.' prefixed - * URL. Will be an empty string if it is not a protocol-relative match. - * We need to know the character before the '//' in order to determine - * if it is a valid match or the // was in a string we don't want to - * auto-link. - * 8. A protocol-relative ('//') match for the case of a known TLD prefixed - * URL. Will be an empty string if it is not a protocol-relative match. - * See #6 for more info. - * 9. Group that is used to determine if there is a phone number match. The - * next 3 groups give segments of the phone number. - * 10. Group that is used to determine if there is a Hashtag match - * (i.e. \#someHashtag). Simply check for its existence to determine if - * there is a Hashtag match. The next couple of capturing groups give - * information about the Hashtag match. - * 11. The whitespace character before the #sign in a Hashtag handle. This - * is needed because there are no look-behinds in JS regular - * expressions, and can be used to reconstruct the original string in a - * replace(). - * 12. The Hashtag itself in a Hashtag match. If the match is - * '#someHashtag', the hashtag is 'someHashtag'. - */ - matcherRegex : (function() { - var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs - - hashtagRegex = /(^|[^\w])#(\w{1,15})/, // For matching a Hashtag. Ex: #games - - emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part) - phoneRegex = /(?:\+?\d{1,3}[-\s.])?\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}/, // ex: (123) 456-7890, 123 456 7890, 123-456-7890, etc. - protocolRegex = /(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\/\/)(?!\d+\/?)(?:\/\/)?)/, // match protocol, allow in format "http://" or "mailto:". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match "link:"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex) - wwwRegex = /(?:www\.)/, // starting with 'www.' - domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period - tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs) - - // Allow optional path, query string, and hash anchor, not ending in the following characters: "?!:,.;" - // http://blog.codinghorror.com/the-problem-with-urls/ - urlSuffixRegex = /[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]?!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]]/; - - return new RegExp( [ - '(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace() - // *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and - // *** Capturing group $3, which matches the actual twitter handle - twitterRegex.source, - ')', - - '|', - - '(', // *** Capturing group $4, which is used to determine an email match - emailRegex.source, - domainNameRegex.source, - tldRegex.source, - ')', - - '|', - - '(', // *** Capturing group $5, which is used to match a URL - '(?:', // parens to cover match for protocol (optional), and domain - '(', // *** Capturing group $6, for a protocol-prefixed url (ex: http://google.com) - protocolRegex.source, - domainNameRegex.source, - ')', - - '|', - - '(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com) - '(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character - wwwRegex.source, - domainNameRegex.source, - ')', - - '|', - - '(?:', // non-capturing paren for known a TLD url (ex: google.com) - '(.?//)?', // *** Capturing group $8 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character - domainNameRegex.source, - tldRegex.source, - ')', - ')', - - '(?:' + urlSuffixRegex.source + ')?', // match for path, query string, and/or hash anchor - optional - ')', - - '|', - - // this setup does not scale well for open extension :( Need to rethink design of autolinker... - // *** Capturing group $9, which matches a (USA for now) phone number - '(', - phoneRegex.source, - ')', - - '|', - - '(', // *** Capturing group $10, which can be used to check for a Hashtag match. Use group $12 for the actual Hashtag though. $11 may be used to reconstruct the original string in a replace() - // *** Capturing group $11, which matches the whitespace character before the '#' sign (needed because of no lookbehinds), and - // *** Capturing group $12, which matches the actual Hashtag - hashtagRegex.source, - ')' - ].join( "" ), 'gi' ); - } )(), - - /** - * @private - * @property {RegExp} charBeforeProtocolRelMatchRegex - * - * The regular expression used to retrieve the character before a - * protocol-relative URL match. - * - * This is used in conjunction with the {@link #matcherRegex}, which needs - * to grab the character before a protocol-relative '//' due to the lack of - * a negative look-behind in JavaScript regular expressions. The character - * before the match is stripped from the URL. - */ - charBeforeProtocolRelMatchRegex : /^(.)?\/\//, - - /** - * @private - * @property {Autolinker.MatchValidator} matchValidator - * - * The MatchValidator object, used to filter out any false positives from - * the {@link #matcherRegex}. See {@link Autolinker.MatchValidator} for details. - */ - - - /** - * @constructor - * @param {Object} [cfg] The configuration options for the AnchorTagBuilder - * instance, specified in an Object (map). - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); - - this.matchValidator = new Autolinker.MatchValidator(); - }, - - - /** - * Parses the input `text` to search for matches, and calls the `replaceFn` - * to allow replacements of the matches. Returns the `text` with matches - * replaced. - * - * @param {String} text The text to search and repace matches in. - * @param {Function} replaceFn The iterator function to handle the - * replacements. The function takes a single argument, a {@link Autolinker.match.Match} - * object, and should return the text that should make the replacement. - * @param {Object} [contextObj=window] The context object ("scope") to run - * the `replaceFn` in. - * @return {String} - */ - replace : function( text, replaceFn, contextObj ) { - var me = this; // for closure - - return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ) { - var matchDescObj = me.processCandidateMatch( matchStr, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ); // "match description" object - - // Return out with no changes for match types that are disabled (url, - // email, phone, etc.), or for matches that are invalid (false - // positives from the matcherRegex, which can't use look-behinds - // since they are unavailable in JS). - if( !matchDescObj ) { - return matchStr; - - } else { - // Generate replacement text for the match from the `replaceFn` - var replaceStr = replaceFn.call( contextObj, matchDescObj.match ); - return matchDescObj.prefixStr + replaceStr + matchDescObj.suffixStr; - } - } ); - }, - - - /** - * Processes a candidate match from the {@link #matcherRegex}. - * - * Not all matches found by the regex are actual URL/Email/Phone/Twitter/Hashtag - * matches, as determined by the {@link #matchValidator}. In this case, the - * method returns `null`. Otherwise, a valid Object with `prefixStr`, - * `match`, and `suffixStr` is returned. - * - * @private - * @param {String} matchStr The full match that was found by the - * {@link #matcherRegex}. - * @param {String} twitterMatch The matched text of a Twitter handle, if the - * match is a Twitter match. - * @param {String} twitterHandlePrefixWhitespaceChar The whitespace char - * before the @ sign in a Twitter handle match. This is needed because of - * no lookbehinds in JS regexes, and is need to re-include the character - * for the anchor tag replacement. - * @param {String} twitterHandle The actual Twitter user (i.e the word after - * the @ sign in a Twitter match). - * @param {String} emailAddressMatch The matched email address for an email - * address match. - * @param {String} urlMatch The matched URL string for a URL match. - * @param {String} protocolUrlMatch The match URL string for a protocol - * match. Ex: 'http://yahoo.com'. This is used to match something like - * 'http://localhost', where we won't double check that the domain name - * has at least one '.' in it. - * @param {String} wwwProtocolRelativeMatch The '//' for a protocol-relative - * match from a 'www' url, with the character that comes before the '//'. - * @param {String} tldProtocolRelativeMatch The '//' for a protocol-relative - * match from a TLD (top level domain) match, with the character that - * comes before the '//'. - * @param {String} phoneMatch The matched text of a phone number - * @param {String} hashtagMatch The matched text of a Twitter - * Hashtag, if the match is a Hashtag match. - * @param {String} hashtagPrefixWhitespaceChar The whitespace char - * before the # sign in a Hashtag match. This is needed because of no - * lookbehinds in JS regexes, and is need to re-include the character for - * the anchor tag replacement. - * @param {String} hashtag The actual Hashtag (i.e the word - * after the # sign in a Hashtag match). - * - * @return {Object} A "match description object". This will be `null` if the - * match was invalid, or if a match type is disabled. Otherwise, this will - * be an Object (map) with the following properties: - * @return {String} return.prefixStr The char(s) that should be prepended to - * the replacement string. These are char(s) that were needed to be - * included from the regex match that were ignored by processing code, and - * should be re-inserted into the replacement stream. - * @return {String} return.suffixStr The char(s) that should be appended to - * the replacement string. These are char(s) that were needed to be - * included from the regex match that were ignored by processing code, and - * should be re-inserted into the replacement stream. - * @return {Autolinker.match.Match} return.match The Match object that - * represents the match that was found. - */ - processCandidateMatch : function( - matchStr, twitterMatch, twitterHandlePrefixWhitespaceChar, twitterHandle, - emailAddressMatch, urlMatch, protocolUrlMatch, wwwProtocolRelativeMatch, - tldProtocolRelativeMatch, phoneMatch, hashtagMatch, - hashtagPrefixWhitespaceChar, hashtag - ) { - // Note: The `matchStr` variable wil be fixed up to remove characters that are no longer needed (which will - // be added to `prefixStr` and `suffixStr`). - - var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch, - match, // Will be an Autolinker.match.Match object - - prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter and Hashtag matches. - suffixStr = ""; // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked. - - // Return out with `null` for match types that are disabled (url, email, - // twitter, hashtag), or for matches that are invalid (false positives - // from the matcherRegex, which can't use look-behinds since they are - // unavailable in JS). - if( - ( urlMatch && !this.urls ) || - ( emailAddressMatch && !this.email ) || - ( phoneMatch && !this.phone ) || - ( twitterMatch && !this.twitter ) || - ( hashtagMatch && !this.hashtag ) || - !this.matchValidator.isValidMatch( urlMatch, protocolUrlMatch, protocolRelativeMatch ) - ) { - return null; - } - - // Handle a closing parenthesis at the end of the match, and exclude it - // if there is not a matching open parenthesis - // in the match itself. - if( this.matchHasUnbalancedClosingParen( matchStr ) ) { - matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")" - suffixStr = ")"; // this will be added after the generated tag - } - - if( emailAddressMatch ) { - match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } ); - - } else if( twitterMatch ) { - // fix up the `matchStr` if there was a preceding whitespace char, - // which was needed to determine the match itself (since there are - // no look-behinds in JS regexes) - if( twitterHandlePrefixWhitespaceChar ) { - prefixStr = twitterHandlePrefixWhitespaceChar; - matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match - } - match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } ); - - } else if( phoneMatch ) { - // remove non-numeric values from phone number string - var cleanNumber = matchStr.replace( /\D/g, '' ); - match = new Autolinker.match.Phone( { matchedText: matchStr, number: cleanNumber } ); - - } else if( hashtagMatch ) { - // fix up the `matchStr` if there was a preceding whitespace char, - // which was needed to determine the match itself (since there are - // no look-behinds in JS regexes) - if( hashtagPrefixWhitespaceChar ) { - prefixStr = hashtagPrefixWhitespaceChar; - matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match - } - match = new Autolinker.match.Hashtag( { matchedText: matchStr, serviceName: this.hashtag, hashtag: hashtag } ); - - } else { // url match - // If it's a protocol-relative '//' match, remove the character - // before the '//' (which the matcherRegex needed to match due to - // the lack of a negative look-behind in JavaScript regular - // expressions) - if( protocolRelativeMatch ) { - var charBeforeMatch = protocolRelativeMatch.match( this.charBeforeProtocolRelMatchRegex )[ 1 ] || ""; - - if( charBeforeMatch ) { // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes) - prefixStr = charBeforeMatch; - matchStr = matchStr.slice( 1 ); // remove the prefixed char from the match - } - } - - match = new Autolinker.match.Url( { - matchedText : matchStr, - url : matchStr, - protocolUrlMatch : !!protocolUrlMatch, - protocolRelativeMatch : !!protocolRelativeMatch, - stripPrefix : this.stripPrefix - } ); - } - - return { - prefixStr : prefixStr, - suffixStr : suffixStr, - match : match - }; - }, - - - /** - * Determines if a match found has an unmatched closing parenthesis. If so, - * this parenthesis will be removed from the match itself, and appended - * after the generated anchor tag in {@link #processCandidateMatch}. - * - * A match may have an extra closing parenthesis at the end of the match - * because the regular expression must include parenthesis for URLs such as - * "wikipedia.com/something_(disambiguation)", which should be auto-linked. - * - * However, an extra parenthesis *will* be included when the URL itself is - * wrapped in parenthesis, such as in the case of "(wikipedia.com/something_(disambiguation))". - * In this case, the last closing parenthesis should *not* be part of the - * URL itself, and this method will return `true`. - * - * @private - * @param {String} matchStr The full match string from the {@link #matcherRegex}. - * @return {Boolean} `true` if there is an unbalanced closing parenthesis at - * the end of the `matchStr`, `false` otherwise. - */ - matchHasUnbalancedClosingParen : function( matchStr ) { - var lastChar = matchStr.charAt( matchStr.length - 1 ); - - if( lastChar === ')' ) { - var openParensMatch = matchStr.match( /\(/g ), - closeParensMatch = matchStr.match( /\)/g ), - numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, - numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; - - if( numOpenParens < numCloseParens ) { - return true; - } - } - - return false; - } - -} ); -/*global Autolinker */ -/*jshint scripturl:true */ -/** - * @private - * @class Autolinker.MatchValidator - * @extends Object - * - * Used by Autolinker to filter out false positives from the - * {@link Autolinker.matchParser.MatchParser#matcherRegex}. - * - * Due to the limitations of regular expressions (including the missing feature - * of look-behinds in JS regular expressions), we cannot always determine the - * validity of a given match. This class applies a bit of additional logic to - * filter out any false positives that have been matched by the - * {@link Autolinker.matchParser.MatchParser#matcherRegex}. - */ -Autolinker.MatchValidator = Autolinker.Util.extend( Object, { - - /** - * @private - * @property {RegExp} invalidProtocolRelMatchRegex - * - * The regular expression used to check a potential protocol-relative URL - * match, coming from the {@link Autolinker.matchParser.MatchParser#matcherRegex}. - * A protocol-relative URL is, for example, "//yahoo.com" - * - * This regular expression checks to see if there is a word character before - * the '//' match in order to determine if we should actually autolink a - * protocol-relative URL. This is needed because there is no negative - * look-behind in JavaScript regular expressions. - * - * For instance, we want to autolink something like "Go to: //google.com", - * but we don't want to autolink something like "abc//google.com" - */ - invalidProtocolRelMatchRegex : /^[\w]\/\//, - - /** - * Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://' - * - * @private - * @property {RegExp} hasFullProtocolRegex - */ - hasFullProtocolRegex : /^[A-Za-z][-.+A-Za-z0-9]+:\/\//, - - /** - * Regex to find the URI scheme, such as 'mailto:'. - * - * This is used to filter out 'javascript:' and 'vbscript:' schemes. - * - * @private - * @property {RegExp} uriSchemeRegex - */ - uriSchemeRegex : /^[A-Za-z][-.+A-Za-z0-9]+:/, - - /** - * Regex to determine if at least one word char exists after the protocol (i.e. after the ':') - * - * @private - * @property {RegExp} hasWordCharAfterProtocolRegex - */ - hasWordCharAfterProtocolRegex : /:[^\s]*?[A-Za-z]/, - - - /** - * Determines if a given match found by the {@link Autolinker.matchParser.MatchParser} - * is valid. Will return `false` for: - * - * 1) URL matches which do not have at least have one period ('.') in the - * domain name (effectively skipping over matches like "abc:def"). - * However, URL matches with a protocol will be allowed (ex: 'http://localhost') - * 2) URL matches which do not have at least one word character in the - * domain name (effectively skipping over matches like "git:1.0"). - * 3) A protocol-relative url match (a URL beginning with '//') whose - * previous character is a word character (effectively skipping over - * strings like "abc//google.com") - * - * Otherwise, returns `true`. - * - * @param {String} urlMatch The matched URL, if there was one. Will be an - * empty string if the match is not a URL match. - * @param {String} protocolUrlMatch The match URL string for a protocol - * match. Ex: 'http://yahoo.com'. This is used to match something like - * 'http://localhost', where we won't double check that the domain name - * has at least one '.' in it. - * @param {String} protocolRelativeMatch The protocol-relative string for a - * URL match (i.e. '//'), possibly with a preceding character (ex, a - * space, such as: ' //', or a letter, such as: 'a//'). The match is - * invalid if there is a word character preceding the '//'. - * @return {Boolean} `true` if the match given is valid and should be - * processed, or `false` if the match is invalid and/or should just not be - * processed. - */ - isValidMatch : function( urlMatch, protocolUrlMatch, protocolRelativeMatch ) { - if( - ( protocolUrlMatch && !this.isValidUriScheme( protocolUrlMatch ) ) || - this.urlMatchDoesNotHaveProtocolOrDot( urlMatch, protocolUrlMatch ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost') - this.urlMatchDoesNotHaveAtLeastOneWordChar( urlMatch, protocolUrlMatch ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" - this.isInvalidProtocolRelativeMatch( protocolRelativeMatch ) // A protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com") - ) { - return false; - } - - return true; - }, - - - /** - * Determines if the URI scheme is a valid scheme to be autolinked. Returns - * `false` if the scheme is 'javascript:' or 'vbscript:' - * - * @private - * @param {String} uriSchemeMatch The match URL string for a full URI scheme - * match. Ex: 'http://yahoo.com' or 'mailto:a@a.com'. - * @return {Boolean} `true` if the scheme is a valid one, `false` otherwise. - */ - isValidUriScheme : function( uriSchemeMatch ) { - var uriScheme = uriSchemeMatch.match( this.uriSchemeRegex )[ 0 ].toLowerCase(); - - return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' ); - }, - - - /** - * Determines if a URL match does not have either: - * - * a) a full protocol (i.e. 'http://'), or - * b) at least one dot ('.') in the domain name (for a non-full-protocol - * match). - * - * Either situation is considered an invalid URL (ex: 'git:d' does not have - * either the '://' part, or at least one dot in the domain name. If the - * match was 'git:abc.com', we would consider this valid.) - * - * @private - * @param {String} urlMatch The matched URL, if there was one. Will be an - * empty string if the match is not a URL match. - * @param {String} protocolUrlMatch The match URL string for a protocol - * match. Ex: 'http://yahoo.com'. This is used to match something like - * 'http://localhost', where we won't double check that the domain name - * has at least one '.' in it. - * @return {Boolean} `true` if the URL match does not have a full protocol, - * or at least one dot ('.') in a non-full-protocol match. - */ - urlMatchDoesNotHaveProtocolOrDot : function( urlMatch, protocolUrlMatch ) { - return ( !!urlMatch && ( !protocolUrlMatch || !this.hasFullProtocolRegex.test( protocolUrlMatch ) ) && urlMatch.indexOf( '.' ) === -1 ); - }, - - - /** - * Determines if a URL match does not have at least one word character after - * the protocol (i.e. in the domain name). - * - * At least one letter character must exist in the domain name after a - * protocol match. Ex: skip over something like "git:1.0" - * - * @private - * @param {String} urlMatch The matched URL, if there was one. Will be an - * empty string if the match is not a URL match. - * @param {String} protocolUrlMatch The match URL string for a protocol - * match. Ex: 'http://yahoo.com'. This is used to know whether or not we - * have a protocol in the URL string, in order to check for a word - * character after the protocol separator (':'). - * @return {Boolean} `true` if the URL match does not have at least one word - * character in it after the protocol, `false` otherwise. - */ - urlMatchDoesNotHaveAtLeastOneWordChar : function( urlMatch, protocolUrlMatch ) { - if( urlMatch && protocolUrlMatch ) { - return !this.hasWordCharAfterProtocolRegex.test( urlMatch ); - } else { - return false; - } - }, - - - /** - * Determines if a protocol-relative match is an invalid one. This method - * returns `true` if there is a `protocolRelativeMatch`, and that match - * contains a word character before the '//' (i.e. it must contain - * whitespace or nothing before the '//' in order to be considered valid). - * - * @private - * @param {String} protocolRelativeMatch The protocol-relative string for a - * URL match (i.e. '//'), possibly with a preceding character (ex, a - * space, such as: ' //', or a letter, such as: 'a//'). The match is - * invalid if there is a word character preceding the '//'. - * @return {Boolean} `true` if it is an invalid protocol-relative match, - * `false` otherwise. - */ - isInvalidProtocolRelativeMatch : function( protocolRelativeMatch ) { - return ( !!protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) ); - } - -} ); -/*global Autolinker */ -/** - * @abstract - * @class Autolinker.match.Match - * - * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a - * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match. - * - * For example: - * - * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles - * - * var linkedText = Autolinker.link( input, { - * replaceFn : function( autolinker, match ) { - * console.log( "href = ", match.getAnchorHref() ); - * console.log( "text = ", match.getAnchorText() ); - * - * switch( match.getType() ) { - * case 'url' : - * console.log( "url: ", match.getUrl() ); - * - * case 'email' : - * console.log( "email: ", match.getEmail() ); - * - * case 'twitter' : - * console.log( "twitter: ", match.getTwitterHandle() ); - * } - * } - * } ); - * - * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}. - */ -Autolinker.match.Match = Autolinker.Util.extend( Object, { - - /** - * @cfg {String} matchedText (required) - * - * The original text that was matched. - */ - - - /** - * @constructor - * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map). - */ - constructor : function( cfg ) { - Autolinker.Util.assign( this, cfg ); - }, - - - /** - * Returns a string name for the type of match that this class represents. - * - * @abstract - * @return {String} - */ - getType : Autolinker.Util.abstractMethod, - - - /** - * Returns the original text that was matched. - * - * @return {String} - */ - getMatchedText : function() { - return this.matchedText; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @abstract - * @return {String} - */ - getAnchorHref : Autolinker.Util.abstractMethod, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @abstract - * @return {String} - */ - getAnchorText : Autolinker.Util.abstractMethod - -} ); -/*global Autolinker */ -/** - * @class Autolinker.match.Email - * @extends Autolinker.match.Match - * - * Represents a Email match found in an input string which should be Autolinked. - * - * See this class's superclass ({@link Autolinker.match.Match}) for more details. - */ -Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, { - - /** - * @cfg {String} email (required) - * - * The email address that was matched. - */ - - - /** - * Returns a string name for the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'email'; - }, - - - /** - * Returns the email address that was matched. - * - * @return {String} - */ - getEmail : function() { - return this.email; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - return 'mailto:' + this.email; - }, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return this.email; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.match.Hashtag - * @extends Autolinker.match.Match - * - * Represents a Hashtag match found in an input string which should be - * Autolinked. - * - * See this class's superclass ({@link Autolinker.match.Match}) for more - * details. - */ -Autolinker.match.Hashtag = Autolinker.Util.extend( Autolinker.match.Match, { - - /** - * @cfg {String} serviceName (required) - * - * The service to point hashtag matches to. See {@link Autolinker#hashtag} - * for available values. - */ - - /** - * @cfg {String} hashtag (required) - * - * The Hashtag that was matched, without the '#'. - */ - - - /** - * Returns the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'hashtag'; - }, - - - /** - * Returns the matched hashtag. - * - * @return {String} - */ - getHashtag : function() { - return this.hashtag; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - var serviceName = this.serviceName, - hashtag = this.hashtag; - - switch( serviceName ) { - case 'twitter' : - return 'https://twitter.com/hashtag/' + hashtag; - case 'facebook' : - return 'https://www.facebook.com/hashtag/' + hashtag; - - default : // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case. - throw new Error( 'Unknown service name to point hashtag to: ', serviceName ); - } - }, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return '#' + this.hashtag; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.match.Phone - * @extends Autolinker.match.Match - * - * Represents a Phone number match found in an input string which should be - * Autolinked. - * - * See this class's superclass ({@link Autolinker.match.Match}) for more - * details. - */ -Autolinker.match.Phone = Autolinker.Util.extend( Autolinker.match.Match, { - - /** - * @cfg {String} number (required) - * - * The phone number that was matched. - */ - - - /** - * Returns a string name for the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'phone'; - }, - - - /** - * Returns the phone number that was matched. - * - * @return {String} - */ - getNumber: function() { - return this.number; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - return 'tel:' + this.number; - }, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return this.matchedText; - } - -} ); - -/*global Autolinker */ -/** - * @class Autolinker.match.Twitter - * @extends Autolinker.match.Match - * - * Represents a Twitter match found in an input string which should be Autolinked. - * - * See this class's superclass ({@link Autolinker.match.Match}) for more details. - */ -Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { - - /** - * @cfg {String} twitterHandle (required) - * - * The Twitter handle that was matched. - */ - - - /** - * Returns the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'twitter'; - }, - - - /** - * Returns a string name for the type of match that this class represents. - * - * @return {String} - */ - getTwitterHandle : function() { - return this.twitterHandle; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - return 'https://twitter.com/' + this.twitterHandle; - }, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - return '@' + this.twitterHandle; - } - -} ); -/*global Autolinker */ -/** - * @class Autolinker.match.Url - * @extends Autolinker.match.Match - * - * Represents a Url match found in an input string which should be Autolinked. - * - * See this class's superclass ({@link Autolinker.match.Match}) for more details. - */ -Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, { - - /** - * @cfg {String} url (required) - * - * The url that was matched. - */ - - /** - * @cfg {Boolean} protocolUrlMatch (required) - * - * `true` if the URL is a match which already has a protocol (i.e. 'http://'), `false` if the match was from a 'www' or - * known TLD match. - */ - - /** - * @cfg {Boolean} protocolRelativeMatch (required) - * - * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//', - * and will be either http:// or https:// based on the protocol that the site is loaded under. - */ - - /** - * @cfg {Boolean} stripPrefix (required) - * @inheritdoc Autolinker#stripPrefix - */ - - - /** - * @private - * @property {RegExp} urlPrefixRegex - * - * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs. - */ - urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i, - - /** - * @private - * @property {RegExp} protocolRelativeRegex - * - * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes - * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com" - */ - protocolRelativeRegex : /^\/\//, - - /** - * @private - * @property {Boolean} protocolPrepended - * - * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the - * {@link #url} did not have a protocol) - */ - protocolPrepended : false, - - - /** - * Returns a string name for the type of match that this class represents. - * - * @return {String} - */ - getType : function() { - return 'url'; - }, - - - /** - * Returns the url that was matched, assuming the protocol to be 'http://' if the original - * match was missing a protocol. - * - * @return {String} - */ - getUrl : function() { - var url = this.url; - - // if the url string doesn't begin with a protocol, assume 'http://' - if( !this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended ) { - url = this.url = 'http://' + url; - - this.protocolPrepended = true; - } - - return url; - }, - - - /** - * Returns the anchor href that should be generated for the match. - * - * @return {String} - */ - getAnchorHref : function() { - var url = this.getUrl(); - - return url.replace( /&/g, '&' ); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html - }, - - - /** - * Returns the anchor text that should be generated for the match. - * - * @return {String} - */ - getAnchorText : function() { - var anchorText = this.getUrl(); - - if( this.protocolRelativeMatch ) { - // Strip off any protocol-relative '//' from the anchor text - anchorText = this.stripProtocolRelativePrefix( anchorText ); - } - if( this.stripPrefix ) { - anchorText = this.stripUrlPrefix( anchorText ); - } - anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one - - return anchorText; - }, - - - // --------------------------------------- - - // Utility Functionality - - /** - * Strips the URL prefix (such as "http://" or "https://") from the given text. - * - * @private - * @param {String} text The text of the anchor that is being generated, for which to strip off the - * url prefix (such as stripping off "http://") - * @return {String} The `anchorText`, with the prefix stripped. - */ - stripUrlPrefix : function( text ) { - return text.replace( this.urlPrefixRegex, '' ); - }, - - - /** - * Strips any protocol-relative '//' from the anchor text. - * - * @private - * @param {String} text The text of the anchor that is being generated, for which to strip off the - * protocol-relative prefix (such as stripping off "//") - * @return {String} The `anchorText`, with the protocol-relative prefix stripped. - */ - stripProtocolRelativePrefix : function( text ) { - return text.replace( this.protocolRelativeRegex, '' ); - }, - - - /** - * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed. - * - * @private - * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing - * slash ('/') that may exist. - * @return {String} The `anchorText`, with the trailing slash removed. - */ - removeTrailingSlash : function( anchorText ) { - if( anchorText.charAt( anchorText.length - 1 ) === '/' ) { - anchorText = anchorText.slice( 0, -1 ); - } - return anchorText; - } - -} ); -return Autolinker; - -})); diff --git a/app/assets/javascripts/src/Metamaps.Debug.js b/app/assets/javascripts/src/Metamaps.Debug.js deleted file mode 100644 index accd93a9..00000000 --- a/app/assets/javascripts/src/Metamaps.Debug.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Metamaps.Debug.js.erb - * - * Dependencies: none! - */ - -Metamaps.Debug = function () { - console.debug(Metamaps) - console.debug('Metamaps Version: ' + Metamaps.VERSION) -} -Metamaps.debug = function () { - Metamaps.Debug() -} diff --git a/app/assets/javascripts/src/Metamaps.Erb.js.erb b/app/assets/javascripts/src/Metamaps.Erb.js.erb new file mode 100644 index 00000000..60b64e46 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Erb.js.erb @@ -0,0 +1,20 @@ +/* global Metamaps */ + +/* + * Metamaps.Erb.js.erb + */ + +/* erb variables from rails */ +window.Metamaps = window.Metamaps || {} +Metamaps.Erb = {} +Metamaps.Erb['REALTIME_SERVER'] = '<%= ENV['REALTIME_SERVER'] %>' +Metamaps.Erb['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>' +Metamaps.Erb['user.png'] = '<%= asset_path('user.png') %>' +Metamaps.Erb['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>' +Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>' +Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>' +Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>' +Metamaps.Erb['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>' +Metamaps.Erb['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>' +Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %> +Metamaps.VERSION = '<%= METAMAPS_VERSION %>' diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js b/app/assets/javascripts/src/Metamaps.GlobalUI.js deleted file mode 100644 index a6466fdc..00000000 --- a/app/assets/javascripts/src/Metamaps.GlobalUI.js +++ /dev/null @@ -1,717 +0,0 @@ -var Metamaps = window.Metamaps || {}; // this variable declaration defines a Javascript object that will contain all the variables and functions used by us, broken down into 'sub-modules' that look something like this -/* - -* unless you are on a page with the Javascript InfoVis Toolkit (Topic or Map) the only section in the metamaps -* object will be these -GlobalUI -Active -Maps -Mappers -Backbone - -* all these get added when you are on a page with the Javascript Infovis Toolkit -Settings -Touch -Mouse -Selected -Metacodes -Topics -Synapses -Mappings -Create -TopicCard -SynapseCard -Visualize -Util -Realtime -Control -Filter -Listeners -Organize -Map -Mapper -Topic -Synapse -JIT -*/ - -Metamaps.Active = { - Map: null, - Topic: null, - Mapper: null -}; -Metamaps.Maps = {}; - -$(document).ready(function () { - // initialize all the modules - for (var prop in Metamaps) { - // this runs the init function within each sub-object on the Metamaps one - if (Metamaps.hasOwnProperty(prop) && - Metamaps[prop] != null && - Metamaps[prop].hasOwnProperty('init') && - typeof (Metamaps[prop].init) == 'function' - ) { - Metamaps[prop].init() - } - } - // load whichever page you are on - if (Metamaps.currentSection === "explore") { - var capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1) - - Metamaps.Views.exploreMaps.setCollection( Metamaps.Maps[capitalize] ) - if (Metamaps.currentPage === "mapper") { - Metamaps.Views.exploreMaps.fetchUserThenRender() - } - else { - Metamaps.Views.exploreMaps.render() - } - Metamaps.GlobalUI.showDiv('#explore') - } - else if (Metamaps.currentSection === "" && Metamaps.Active.Mapper) { - Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps.Active) - Metamaps.Views.exploreMaps.render() - Metamaps.GlobalUI.showDiv('#explore') - } - else if (Metamaps.Active.Map || Metamaps.Active.Topic) { - Metamaps.Loading.show() - Metamaps.JIT.prepareVizData() - Metamaps.GlobalUI.showDiv('#infovis') - } -}); - -Metamaps.GlobalUI = { - notifyTimeout: null, - lightbox: null, - init: function () { - var self = Metamaps.GlobalUI; - - self.Search.init(); - self.CreateMap.init(); - self.Account.init(); - - if ($('#toast').html().trim()) self.notifyUser($('#toast').html()) - - //bind lightbox clicks - $('.openLightbox').click(function (event) { - self.openLightbox($(this).attr('data-open')); - event.preventDefault(); - return false; - }); - - $('#lightbox_screen, #lightbox_close').click(self.closeLightbox); - - // initialize global backbone models and collections - if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper); - - var myCollection = Metamaps.Maps.Mine ? Metamaps.Maps.Mine : []; - var sharedCollection = Metamaps.Maps.Shared ? Metamaps.Maps.Shared : []; - var starredCollection = Metamaps.Maps.Starred ? Metamaps.Maps.Starred : []; - var mapperCollection = []; - var mapperOptionsObj = {id: 'mapper', sortBy: 'updated_at' }; - if (Metamaps.Maps.Mapper) { - mapperCollection = Metamaps.Maps.Mapper.models; - mapperOptionsObj.mapperId = Metamaps.Maps.Mapper.id; - } - var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : []; - var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : []; - Metamaps.Maps.Mine = new Metamaps.Backbone.MapsCollection(myCollection, {id: 'mine', sortBy: 'updated_at' }); - Metamaps.Maps.Shared = new Metamaps.Backbone.MapsCollection(sharedCollection, {id: 'shared', sortBy: 'updated_at' }); - Metamaps.Maps.Starred = new Metamaps.Backbone.MapsCollection(starredCollection, {id: 'starred', sortBy: 'updated_at' }); - // 'Mapper' refers to another mapper - Metamaps.Maps.Mapper = new Metamaps.Backbone.MapsCollection(mapperCollection, mapperOptionsObj); - Metamaps.Maps.Featured = new Metamaps.Backbone.MapsCollection(featuredCollection, {id: 'featured', sortBy: 'updated_at' }); - Metamaps.Maps.Active = new Metamaps.Backbone.MapsCollection(activeCollection, {id: 'active', sortBy: 'updated_at' }); - }, - showDiv: function (selector) { - $(selector).show() - $(selector).animate({ - opacity: 1 - }, 200, 'easeOutCubic') - }, - hideDiv: function (selector) { - $(selector).animate({ - opacity: 0 - }, 200, 'easeInCubic', function () { $(this).hide() }) - }, - openLightbox: function (which) { - var self = Metamaps.GlobalUI; - - $('.lightboxContent').hide(); - $('#' + which).show(); - - self.lightbox = which; - - $('#lightbox_overlay').show(); - - var heightOfContent = '-' + ($('#lightbox_main').height() / 2) + 'px'; - // animate the content in from the bottom - $('#lightbox_main').animate({ - 'top': '50%', - 'margin-top': heightOfContent - }, 200, 'easeOutCubic'); - - // fade the black overlay in - $('#lightbox_screen').animate({ - 'opacity': '0.42' - }, 200); - - if (which == "switchMetacodes") { - Metamaps.Create.isSwitchingSet = true; - } - }, - - closeLightbox: function (event) { - var self = Metamaps.GlobalUI; - - if (event) event.preventDefault(); - - // animate the lightbox content offscreen - $('#lightbox_main').animate({ - 'top': '100%', - 'margin-top': '0' - }, 200, 'easeInCubic'); - - // fade the black overlay out - $('#lightbox_screen').animate({ - 'opacity': '0.0' - }, 200, function () { - $('#lightbox_overlay').hide(); - }); - - if (self.lightbox === 'forkmap') Metamaps.GlobalUI.CreateMap.reset('fork_map'); - if (self.lightbox === 'newmap') Metamaps.GlobalUI.CreateMap.reset('new_map'); - if (Metamaps.Create && Metamaps.Create.isSwitchingSet) { - Metamaps.Create.cancelMetacodeSetSwitch(); - } - self.lightbox = null; - }, - notifyUser: function (message, leaveOpen) { - var self = Metamaps.GlobalUI; - - $('#toast').html(message) - self.showDiv('#toast') - clearTimeout(self.notifyTimeOut); - if (!leaveOpen) { - self.notifyTimeOut = setTimeout(function () { - self.hideDiv('#toast') - }, 8000); - } - }, - clearNotify: function() { - var self = Metamaps.GlobalUI; - - clearTimeout(self.notifyTimeOut); - self.hideDiv('#toast') - }, - shareInvite: function(inviteLink) { - window.prompt("To copy the invite link, press: Ctrl+C, Enter", inviteLink); - } -}; - -Metamaps.GlobalUI.CreateMap = { - newMap: null, - emptyMapForm: "", - emptyForkMapForm: "", - topicsToMap: [], - synapsesToMap: [], - init: function () { - var self = Metamaps.GlobalUI.CreateMap; - - self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' }); - - self.bindFormEvents(); - - self.emptyMapForm = $('#new_map').html(); - - }, - bindFormEvents: function () { - var self = Metamaps.GlobalUI.CreateMap; - - $('.new_map input, .new_map div').unbind('keypress').bind('keypress', function(event) { - if (event.keyCode === 13) self.submit() - }) - - $('.new_map button.cancel').unbind().bind('click', function (event) { - event.preventDefault(); - Metamaps.GlobalUI.closeLightbox(); - }); - $('.new_map button.submitMap').unbind().bind('click', self.submit); - - // bind permission changer events on the createMap form - $('.permIcon').unbind().bind('click', self.switchPermission); - }, - closeSuccess: function () { - $('#mapCreatedSuccess').fadeOut(300, function(){ - $(this).remove(); - }); - }, - generateSuccessMessage: function (id) { - var stringStart = ""; - return stringStart + page + stringEnd; - }, - switchPermission: function () { - var self = Metamaps.GlobalUI.CreateMap; - - self.newMap.set('permission', $(this).attr('data-permission')); - $(this).siblings('.permIcon').find('.mapPermIcon').removeClass('selected'); - $(this).find('.mapPermIcon').addClass('selected'); - - var permText = $(this).find('.tip').html(); - $(this).parents('.new_map').find('.permText').html(permText); - }, - submit: function (event) { - if (event) event.preventDefault(); - - var self = Metamaps.GlobalUI.CreateMap; - - if (Metamaps.GlobalUI.lightbox === 'forkmap') { - self.newMap.set('topicsToMap', self.topicsToMap); - self.newMap.set('synapsesToMap', self.synapsesToMap); - } - - var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; - var $form = $(formId); - - self.newMap.set('name', $form.find('#map_name').val()); - self.newMap.set('desc', $form.find('#map_desc').val()); - - if (self.newMap.get('name').length===0){ - self.throwMapNameError(); - return; - } - - self.newMap.save(null, { - success: self.success - // TODO add error message - }); - - Metamaps.GlobalUI.closeLightbox(); - Metamaps.GlobalUI.notifyUser('Working...'); - }, - throwMapNameError: function () { - var self = Metamaps.GlobalUI.CreateMap; - - var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; - var $form = $(formId); - - var message = $(""); - - $form.find('#map_name').after(message); - setTimeout(function(){ - message.fadeOut('fast', function(){ - message.remove(); - }); - }, 5000); - }, - success: function (model) { - var self = Metamaps.GlobalUI.CreateMap; - - //push the new map onto the collection of 'my maps' - Metamaps.Maps.Mine.add(model); - - var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; - var form = $(formId); - - Metamaps.GlobalUI.clearNotify(); - $('#wrapper').append(self.generateSuccessMessage(model.id)); - - }, - reset: function (id) { - var self = Metamaps.GlobalUI.CreateMap; - - var form = $('#' + id); - - if (id === "fork_map") { - self.topicsToMap = []; - self.synapsesToMap = []; - form.html(self.emptyForkMapForm); - } - else { - form.html(self.emptyMapForm); - } - - self.bindFormEvents(); - self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' }); - - return false; - }, -}; - - -Metamaps.GlobalUI.Account = { - isOpen: false, - changing: false, - init: function () { - var self = Metamaps.GlobalUI.Account; - - $('.sidebarAccountIcon').click(self.toggleBox); - $('.sidebarAccountBox').click(function(event){ - event.stopPropagation(); - }); - $('body').click(self.close); - }, - toggleBox: function (event) { - var self = Metamaps.GlobalUI.Account; - - if (self.isOpen) self.close(); - else self.open(); - - event.stopPropagation(); - }, - open: function () { - var self = Metamaps.GlobalUI.Account; - - Metamaps.Filter.close(); - $('.sidebarAccountIcon .tooltipsUnder').addClass('hide'); - - - if (!self.isOpen && !self.changing) { - self.changing = true; - $('.sidebarAccountBox').fadeIn(200, function () { - self.changing = false; - self.isOpen = true; - $('.sidebarAccountBox #user_email').focus(); - }); - } - }, - close: function () { - var self = Metamaps.GlobalUI.Account; - - $('.sidebarAccountIcon .tooltipsUnder').removeClass('hide'); - if (!self.changing) { - self.changing = true; - $('.sidebarAccountBox #user_email').blur(); - $('.sidebarAccountBox').fadeOut(200, function () { - self.changing = false; - self.isOpen = false; - }); - } - } -}; - - - -Metamaps.GlobalUI.Search = { - locked: false, - isOpen: false, - limitTopicsToMe: false, - limitMapsToMe: false, - timeOut: null, - changing: false, - optionsInitialized: false, - init: function () { - var self = Metamaps.GlobalUI.Search; - - var loader = new CanvasLoader('searchLoading'); - loader.setColor('#4fb5c0'); // default is '#000000' - loader.setDiameter(24); // default is 40 - loader.setDensity(41); // default is 40 - loader.setRange(0.9); // default is 1.3 - loader.show(); // Hidden by default - - // bind the hover events - $(".sidebarSearch").hover(function () { - self.open() - }, function () { - self.close(800, false) - }); - - $('.sidebarSearchIcon').click(function (e) { - $('.sidebarSearchField').focus(); - }); - $('.sidebarSearch').click(function (e) { - e.stopPropagation(); - }); - $('body').click(function (e) { - self.close(0, false); - }); - - // open if the search is closed and user hits ctrl+/ - // close if they hit ESC - $('body').bind('keyup', function (e) { - switch (e.which) { - case 191: - if ((e.ctrlKey && !self.isOpen) || (e.ctrlKey && self.locked)) { - self.open(true); // true for focus - } - break; - case 27: - if (self.isOpen) { - self.close(0, true); - } - break; - - default: - break; //console.log(e.which); - } - }); - - self.startTypeahead(); - }, - lock: function() { - var self = Metamaps.GlobalUI.Search; - self.locked = true; - }, - unlock: function() { - var self = Metamaps.GlobalUI.Search; - self.locked = false; - }, - open: function (focus) { - var self = Metamaps.GlobalUI.Search; - - clearTimeout(self.timeOut); - if (!self.isOpen && !self.changing && !self.locked) { - self.changing = true; - $('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({ - width: '400px' - }, 300, function () { - if (focus) $('.sidebarSearchField').focus(); - $('.sidebarSearchField, .sidebarSearch .tt-hint').css({ - padding: '7px 10px 3px 10px', - width: '380px' - }); - self.changing = false; - self.isOpen = true; - }); - } - }, - close: function (closeAfter, bypass) { - // for now - return - - var self = Metamaps.GlobalUI.Search; - - self.timeOut = setTimeout(function () { - if (!self.locked && !self.changing && self.isOpen && (bypass || $('.sidebarSearchField.tt-input').val() == '')) { - self.changing = true; - $('.sidebarSearchField, .sidebarSearch .tt-hint').css({ - padding: '7px 0 3px 0', - width: '400px' - }); - $('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({ - width: '0' - }, 300, function () { - $('.sidebarSearchField').typeahead('val', ''); - $('.sidebarSearchField').blur(); - self.changing = false; - self.isOpen = false; - }); - } - }, closeAfter); - }, - startTypeahead: function () { - var self = Metamaps.GlobalUI.Search; - - var mapheader = Metamaps.Active.Mapper ? '

Maps

' : '

Maps

'; - var topicheader = Metamaps.Active.Mapper ? '

Topics

' : '

Topics

'; - var mapperheader = '

Mappers

'; - - var topics = { - name: 'topics', - limit: 9999, - - display: function(s) { return s.label; }, - templates: { - notFound: function(s) { - return Hogan.compile(topicheader + $('#topicSearchTemplate').html()).render({ - value: "No results", - label: "No results", - typeImageURL: Metamaps.Erb['icons/wildcard.png'], - rtype: "noresult" - }); - }, - header: topicheader, - suggestion: function(s) { - return Hogan.compile($('#topicSearchTemplate').html()).render(s); - }, - }, - source: new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: '/search/topics', - prepare: function(query, settings) { - settings.url += '?term=' + query; - if (Metamaps.Active.Mapper && self.limitTopicsToMe) { - settings.url += "&user=" + Metamaps.Active.Mapper.id.toString(); - } - return settings; - }, - }, - }), - }; - - var maps = { - name: 'maps', - limit: 9999, - display: function(s) { return s.label; }, - templates: { - notFound: function(s) { - return Hogan.compile(mapheader + $('#mapSearchTemplate').html()).render({ - value: "No results", - label: "No results", - rtype: "noresult" - }); - }, - header: mapheader, - suggestion: function(s) { - return Hogan.compile($('#mapSearchTemplate').html()).render(s); - }, - }, - source: new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: '/search/maps', - prepare: function(query, settings) { - settings.url += '?term=' + query; - if (Metamaps.Active.Mapper && self.limitMapsToMe) { - settings.url += "&user=" + Metamaps.Active.Mapper.id.toString(); - } - return settings; - }, - }, - }), - }; - - var mappers = { - name: 'mappers', - limit: 9999, - display: function(s) { return s.label; }, - templates: { - notFound: function(s) { - return Hogan.compile(mapperheader + $('#mapperSearchTemplate').html()).render({ - value: "No results", - label: "No results", - rtype: "noresult", - profile: Metamaps.Erb['user.png'] - }); - }, - header: mapperheader, - suggestion: function(s) { - return Hogan.compile($('#mapperSearchTemplate').html()).render(s); - }, - }, - source: new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: '/search/mappers?term=%QUERY', - wildcard: '%QUERY', - }, - }), - }; - - // Take all that crazy setup data and put it together into one beautiful typeahead call! - $('.sidebarSearchField').typeahead( - { - highlight: true, - }, - [topics, maps, mappers] - ); - - //Set max height of the search results box to prevent it from covering bottom left footer - $('.sidebarSearchField').bind('typeahead:render', function (event) { - self.initSearchOptions(); - self.hideLoader(); - var h = $(window).height(); - $(".tt-dropdown-menu").css('max-height', h - 100); - if (self.limitTopicsToMe) { - $('#limitTopicsToMe').prop('checked', true); - } - if (self.limitMapsToMe) { - $('#limitMapsToMe').prop('checked', true); - } - }); - $(window).resize(function () { - var h = $(window).height(); - $(".tt-dropdown-menu").css('max-height', h - 100); - }); - - // tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on - $('.sidebarSearchField').bind('typeahead:select', self.handleResultClick); - - // don't do it, if they clicked on a 'addToMap' button - $('.sidebarSearch button.addToMap').click(function (event) { - event.stopPropagation(); - }); - - // make sure that when you click on 'limit to me' or 'toggle section' it works - $('.sidebarSearchField.tt-input').keyup(function(){ - if ($('.sidebarSearchField.tt-input').val() === '') { - self.hideLoader(); - } else { - self.showLoader(); - } - }); - - }, - handleResultClick: function (event, datum, dataset) { - var self = Metamaps.GlobalUI.Search; - - self.hideLoader(); - - if (["topic", "map", "mapper"].indexOf(datum.rtype) !== -1) { - self.close(0, true); - var win; - if (datum.rtype == "topic") { - Metamaps.Router.topics(datum.id); - } else if (datum.rtype == "map") { - Metamaps.Router.maps(datum.id); - } else if (datum.rtype == "mapper") { - Metamaps.Router.explore("mapper", datum.id); - } - } - }, - initSearchOptions: function () { - var self = Metamaps.GlobalUI.Search; - - function toggleResultSet(set) { - var s = $('.tt-dataset-' + set + ' .tt-suggestion, .tt-dataset-' + set + ' .resultnoresult'); - if (s.is(':visible')) { - s.hide(); - $(this).removeClass('minimizeResults').addClass('maximizeResults'); - } else { - s.show(); - $(this).removeClass('maximizeResults').addClass('minimizeResults'); - } - } - - $('.limitToMe').unbind().bind("change", function (e) { - if ($(this).attr('id') == 'limitTopicsToMe') { - self.limitTopicsToMe = !self.limitTopicsToMe; - } - if ($(this).attr('id') == 'limitMapsToMe') { - self.limitMapsToMe = !self.limitMapsToMe; - } - - // set the value of the search equal to itself to retrigger the - // autocomplete event - var searchQuery = $('.sidebarSearchField.tt-input').val(); - $(".sidebarSearchField").typeahead('val', '') - .typeahead('val', searchQuery); - }); - - // when the user clicks minimize section, hide the results for that section - $('.minimizeMapperResults').unbind().click(function (e) { - toggleResultSet.call(this, 'mappers'); - }); - $('.minimizeTopicResults').unbind().click(function (e) { - toggleResultSet.call(this, 'topics'); - }); - $('.minimizeMapResults').unbind().click(function (e) { - toggleResultSet.call(this, 'maps'); - }); - }, - hideLoader: function () { - $('#searchLoading').hide(); - }, - showLoader: function () { - $('#searchLoading').show(); - } -}; diff --git a/app/assets/javascripts/src/Metamaps.Listeners.js b/app/assets/javascripts/src/Metamaps.Listeners.js deleted file mode 100644 index 948893cb..00000000 --- a/app/assets/javascripts/src/Metamaps.Listeners.js +++ /dev/null @@ -1,122 +0,0 @@ -/* global Metamaps, $ */ - -/* - * Metamaps.Listeners.js.erb - * - * Dependencies: - * - Metamaps.Active - * - Metamaps.Control - * - Metamaps.JIT - * - Metamaps.Visualize - */ -Metamaps.Listeners = { - init: function () { - var self = this - $(document).on('keydown', function (e) { - if (!(Metamaps.Active.Map || Metamaps.Active.Topic)) return - - switch (e.which) { - case 13: // if enter key is pressed - Metamaps.JIT.enterKeyHandler() - e.preventDefault() - break - case 27: // if esc key is pressed - Metamaps.JIT.escKeyHandler() - break - case 65: // if a or A is pressed - if (e.ctrlKey) { - Metamaps.Control.deselectAllNodes() - Metamaps.Control.deselectAllEdges() - - e.preventDefault() - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - Metamaps.Control.selectNode(n, e) - }) - - Metamaps.Visualize.mGraph.plot() - } - - break - case 68: // if d or D is pressed - if (e.ctrlKey) { - e.preventDefault() - Metamaps.Control.deleteSelected() - } - break - case 69: // if e or E is pressed - if (e.ctrlKey && Metamaps.Active.Map) { - e.preventDefault() - Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas) - break - } - if (e.altKey && Metamaps.Active.Topic) { - e.preventDefault() - - if (Metamaps.Active.Topic) { - self.centerAndReveal(Metamaps.Selected.Nodes, { - center: true, - reveal: false - }) - } - break - } - break - case 72: // if h or H is pressed - if (e.ctrlKey) { - e.preventDefault() - Metamaps.Control.hideSelectedNodes() - Metamaps.Control.hideSelectedEdges() - } - break - case 77: // if m or M is pressed - if (e.ctrlKey) { - e.preventDefault() - Metamaps.Control.removeSelectedNodes() - Metamaps.Control.removeSelectedEdges() - } - break - case 82: // if r or R is pressed - if (e.altKey && Metamaps.Active.Topic) { - e.preventDefault() - self.centerAndReveal(Metamaps.Selected.Nodes, { - center: false, - reveal: true - }) - } - break - case 84: // if t or T is pressed - if (e.altKey && Metamaps.Active.Topic) { - e.preventDefault() - self.centerAndReveal(Metamaps.Selected.Nodes, { - center: true, - reveal: true - }) - } - break - default: - // console.log(e.which) - break - } - }) - - $(window).resize(function () { - if (Metamaps.Visualize && Metamaps.Visualize.mGraph) Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height()) - if ((Metamaps.Active.Map || Metamaps.Active.Topic) && Metamaps.Famous && Metamaps.Famous.maps.surf) Metamaps.Famous.maps.reposition() - if (Metamaps.Active.Map && Metamaps.Realtime.inConversation) Metamaps.Realtime.positionVideos() - Metamaps.Mobile.resizeTitle() - }) - }, - centerAndReveal: function(nodes, opts) { - if (nodes.length < 1) return - var node = nodes[nodes.length - 1] - if (opts.center && opts.reveal) { - Metamaps.Topic.centerOn(node.id, function() { - Metamaps.Topic.fetchRelatives(nodes) - }) - } else if (opts.center) { - Metamaps.Topic.centerOn(node.id) - } else if (opts.reveal) { - Metamaps.Topic.fetchRelatives(nodes) - } - } -}; // end Metamaps.Listeners diff --git a/app/assets/javascripts/src/Metamaps.Map.js b/app/assets/javascripts/src/Metamaps.Map.js deleted file mode 100644 index 264e3c48..00000000 --- a/app/assets/javascripts/src/Metamaps.Map.js +++ /dev/null @@ -1,754 +0,0 @@ -/* global Metamaps, $ */ - -/* - * Metamaps.Map.js.erb - * - * Dependencies: - * - Metamaps.AutoLayout - * - Metamaps.Create - * - Metamaps.Erb - * - Metamaps.Filter - * - Metamaps.JIT - * - Metamaps.Loading - * - Metamaps.Maps - * - Metamaps.Realtime - * - Metamaps.Router - * - Metamaps.Selected - * - Metamaps.SynapseCard - * - Metamaps.TopicCard - * - Metamaps.Visualize - * - Metamaps.Active - * - Metamaps.Backbone - * - Metamaps.GlobalUI - * - Metamaps.Mappers - * - Metamaps.Mappings - * - Metamaps.Messages - * - Metamaps.Synapses - * - Metamaps.Topics - * - * Major sub-modules: - * - Metamaps.Map.CheatSheet - * - Metamaps.Map.InfoBox - */ - -Metamaps.Map = { - events: { - editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper' - }, - init: function () { - var self = Metamaps.Map - - // prevent right clicks on the main canvas, so as to not get in the way of our right clicks - $('#center-container').bind('contextmenu', function (e) { - return false - }) - - $('.starMap').click(function () { - if ($(this).is('.starred')) self.unstar() - else self.star() - }) - - $('.sidebarFork').click(function () { - self.fork() - }) - - Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html() - - self.updateStar() - self.InfoBox.init() - self.CheatSheet.init() - - $(document).on(Metamaps.Map.events.editedByActiveMapper, self.editedByActiveMapper) - }, - launch: function (id) { - var bb = Metamaps.Backbone - var start = function (data) { - Metamaps.Active.Map = new bb.Map(data.map) - Metamaps.Mappers = new bb.MapperCollection(data.mappers) - Metamaps.Collaborators = new bb.MapperCollection(data.collaborators) - 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.Stars = data.stars - Metamaps.Backbone.attachCollectionEvents() - - var map = Metamaps.Active.Map - var mapper = Metamaps.Active.Mapper - - // add class to .wrapper for specifying whether you can edit the map - if (map.authorizeToEdit(mapper)) { - $('.wrapper').addClass('canEditMap') - } - - // add class to .wrapper for specifying if the map can - // be collaborated on - if (map.get('permission') === 'commons') { - $('.wrapper').addClass('commonsMap') - } - - Metamaps.Map.updateStar() - - // set filter mapper H3 text - $('#filter_by_mapper h3').html('MAPPERS') - - // build and render the visualization - Metamaps.Visualize.type = 'ForceDirected' - Metamaps.JIT.prepareVizData() - - // update filters - Metamaps.Filter.reset() - - // reset selected arrays - Metamaps.Selected.reset() - - // set the proper mapinfobox content - Metamaps.Map.InfoBox.load() - - // these three update the actual filter box with the right list items - Metamaps.Filter.checkMetacodes() - Metamaps.Filter.checkSynapses() - Metamaps.Filter.checkMappers() - - Metamaps.Realtime.startActiveMap() - Metamaps.Loading.hide() - - // for mobile - $('#header_content').html(map.get('name')) - } - - $.ajax({ - url: '/maps/' + id + '/contains.json', - success: start - }) - }, - end: function () { - if (Metamaps.Active.Map) { - $('.wrapper').removeClass('canEditMap commonsMap') - Metamaps.AutoLayout.resetSpiral() - - $('.rightclickmenu').remove() - Metamaps.TopicCard.hideCard() - Metamaps.SynapseCard.hideCard() - Metamaps.Create.newTopic.hide(true) // true means force (and override pinned) - Metamaps.Create.newSynapse.hide() - Metamaps.Filter.close() - Metamaps.Map.InfoBox.close() - Metamaps.Realtime.endActiveMap() - } - }, - updateStar: function () { - if (!Metamaps.Active.Mapper || !Metamaps.Stars) return - // update the star/unstar icon - if (Metamaps.Stars.find(function (s) { return s.user_id === Metamaps.Active.Mapper.id })) { - $('.starMap').addClass('starred') - $('.starMap .tooltipsAbove').html('Unstar') - } else { - $('.starMap').removeClass('starred') - $('.starMap .tooltipsAbove').html('Star') - } - }, - star: function () { - var self = Metamaps.Map - - if (!Metamaps.Active.Map) return - $.post('/maps/' + Metamaps.Active.Map.id + '/star') - Metamaps.Stars.push({ user_id: Metamaps.Active.Mapper.id, map_id: Metamaps.Active.Map.id }) - Metamaps.Maps.Starred.add(Metamaps.Active.Map) - Metamaps.GlobalUI.notifyUser('Map is now starred') - self.updateStar() - }, - unstar: function () { - var self = Metamaps.Map - - if (!Metamaps.Active.Map) return - $.post('/maps/' + Metamaps.Active.Map.id + '/unstar') - Metamaps.Stars = Metamaps.Stars.filter(function (s) { return s.user_id != Metamaps.Active.Mapper.id }) - Metamaps.Maps.Starred.remove(Metamaps.Active.Map) - self.updateStar() - }, - fork: function () { - Metamaps.GlobalUI.openLightbox('forkmap') - - var nodes_data = '', - synapses_data = '' - var nodes_array = [] - var synapses_array = [] - // collect the unfiltered topics - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { - // if the opacity is less than 1 then it's filtered - if (n.getData('alpha') === 1) { - var id = n.getData('topic').id - nodes_array.push(id) - var x, y - if (n.pos.x && n.pos.y) { - x = n.pos.x - y = n.pos.y - } else { - var x = Math.cos(n.pos.theta) * n.pos.rho - var y = Math.sin(n.pos.theta) * n.pos.rho - } - nodes_data += id + '/' + x + '/' + y + ',' - } - }) - // collect the unfiltered synapses - Metamaps.Synapses.each(function (synapse) { - var desc = synapse.get('desc') - - var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1 - // 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 - if (descNotFiltered && topicsNotFiltered) { - synapses_array.push(synapse.id) - } - }) - - synapses_data = synapses_array.join() - nodes_data = nodes_data.slice(0, -1) - - Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data - Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data - }, - leavePrivateMap: function () { - var map = Metamaps.Active.Map - Metamaps.Maps.Active.remove(map) - Metamaps.Maps.Featured.remove(map) - Metamaps.Router.home() - Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.') - }, - cantEditNow: function () { - Metamaps.Realtime.turnOff(true); // true is for 'silence' - Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.') - Metamaps.Active.Map.trigger('changeByOther') - }, - canEditNow: function () { - var confirmString = "You've been granted permission to edit this map. " - confirmString += 'Do you want to reload and enable realtime collaboration?' - var c = confirm(confirmString) - if (c) { - Metamaps.Router.maps(Metamaps.Active.Map.id) - } - }, - editedByActiveMapper: function () { - if (Metamaps.Active.Mapper) { - Metamaps.Mappers.add(Metamaps.Active.Mapper) - } - }, - exportImage: function () { - var canvas = {} - - canvas.canvas = document.createElement('canvas') - canvas.canvas.width = 1880 // 960 - canvas.canvas.height = 1260 // 630 - - canvas.scaleOffsetX = 1 - canvas.scaleOffsetY = 1 - canvas.translateOffsetY = 0 - canvas.translateOffsetX = 0 - canvas.denySelected = true - - canvas.getSize = function () { - if (this.size) return this.size - var canvas = this.canvas - return this.size = { - width: canvas.width, - height: canvas.height - } - } - canvas.scale = function (x, y) { - var px = this.scaleOffsetX * x, - py = this.scaleOffsetY * y - var dx = this.translateOffsetX * (x - 1) / px, - dy = this.translateOffsetY * (y - 1) / py - this.scaleOffsetX = px - this.scaleOffsetY = py - this.getCtx().scale(x, y) - this.translate(dx, dy) - } - canvas.translate = function (x, y) { - var sx = this.scaleOffsetX, - sy = this.scaleOffsetY - this.translateOffsetX += x * sx - this.translateOffsetY += y * sy - this.getCtx().translate(x, y) - } - canvas.getCtx = function () { - return this.canvas.getContext('2d') - } - // center it - canvas.getCtx().translate(1880 / 2, 1260 / 2) - - var mGraph = Metamaps.Visualize.mGraph - - var id = mGraph.root - var root = mGraph.graph.getNode(id) - var T = !!root.visited - - // pass true to avoid basing it on a selection - Metamaps.JIT.zoomExtents(null, canvas, true) - - var c = canvas.canvas, - ctx = canvas.getCtx(), - scale = canvas.scaleOffsetX - - // draw a grey background - ctx.fillStyle = '#d8d9da' - var xPoint = (-(c.width / scale) / 2) - (canvas.translateOffsetX / scale), - yPoint = (-(c.height / scale) / 2) - (canvas.translateOffsetY / scale) - ctx.fillRect(xPoint, yPoint, c.width / scale, c.height / scale) - - // draw the graph - mGraph.graph.eachNode(function (node) { - var nodeAlpha = node.getData('alpha') - node.eachAdjacency(function (adj) { - var nodeTo = adj.nodeTo - if (!!nodeTo.visited === T && node.drawn && nodeTo.drawn) { - mGraph.fx.plotLine(adj, canvas) - } - }) - if (node.drawn) { - mGraph.fx.plotNode(node, canvas) - } - if (!mGraph.labelsHidden) { - if (node.drawn && nodeAlpha >= 0.95) { - mGraph.labels.plotLabel(canvas, node) - } else { - mGraph.labels.hideLabel(node, false) - } - } - node.visited = !T - }) - - var imageData = { - encoded_image: canvas.canvas.toDataURL() - } - - var map = Metamaps.Active.Map - - var today = new Date() - var dd = today.getDate() - var mm = today.getMonth() + 1; // January is 0! - var yyyy = today.getFullYear() - if (dd < 10) { - dd = '0' + dd - } - if (mm < 10) { - mm = '0' + mm - } - today = mm + '/' + dd + '/' + yyyy - - var mapName = map.get('name').split(' ').join([separator = '-']) - var downloadMessage = '' - downloadMessage += 'Captured map screenshot! ' - downloadMessage += "DOWNLOAD" - Metamaps.GlobalUI.notifyUser(downloadMessage) - - $.ajax({ - type: 'POST', - dataType: 'json', - url: '/maps/' + Metamaps.Active.Map.id + '/upload_screenshot', - data: imageData, - success: function (data) { - console.log('successfully uploaded map screenshot') - }, - error: function () { - console.log('failed to save map screenshot') - } - }) - } -} - -/* - * - * CHEATSHEET - * - */ -Metamaps.Map.CheatSheet = { - init: function () { - // tab the 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') - $(element).addClass('active') - $('#tutorialVideo').attr('src', '//player.vimeo.com/video/' + id) - } - - $('#gettingStarted').click(function () { - // switchVideo(this,'88334167') - }) - $('#upYourSkillz').click(function () { - // switchVideo(this,'100118167') - }) - $('#advancedMapping').click(function () { - // switchVideo(this,'88334167') - }) - } -}; // end Metamaps.Map.CheatSheet - -/* - * - * INFOBOX - * - */ -Metamaps.Map.InfoBox = { - isOpen: false, - changing: false, - selectingPermission: false, - changePermissionText: "
As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
", - nameHTML: '{{name}}', - descHTML: '{{desc}}', - init: function () { - var self = Metamaps.Map.InfoBox - - $('.mapInfoIcon').click(self.toggleBox) - $('.mapInfoBox').click(function (event) { - event.stopPropagation() - }) - $('body').click(self.close) - - self.attachEventListeners() - - self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html()) - - var querystring = window.location.search.replace(/^\?/, '') - if (querystring == 'new') { - self.open() - $('.mapInfoBox').addClass('mapRequestTitle') - } - }, - toggleBox: function (event) { - var self = Metamaps.Map.InfoBox - - if (self.isOpen) self.close() - else self.open() - - event.stopPropagation() - }, - open: function () { - var self = Metamaps.Map.InfoBox - $('.mapInfoIcon div').addClass('hide') - if (!self.isOpen && !self.changing) { - self.changing = true - $('.mapInfoBox').fadeIn(200, function () { - self.changing = false - self.isOpen = true - }) - } - }, - close: function () { - var self = Metamaps.Map.InfoBox - - $('.mapInfoIcon div').removeClass('hide') - if (!self.changing) { - self.changing = true - $('.mapInfoBox').fadeOut(200, function () { - self.changing = false - self.isOpen = false - self.hidePermissionSelect() - $('.mapContributors .tip').hide() - }) - } - }, - load: function () { - var self = Metamaps.Map.InfoBox - - var map = Metamaps.Active.Map - - var obj = map.pick('permission', 'topic_count', 'synapse_count') - - var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper) - var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper) - var relevantPeople = map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators - var shareable = map.get('permission') !== 'private' - - obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name') - obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc') - obj['map_creator_tip'] = isCreator ? self.changePermissionText : '' - - obj['contributor_count'] = relevantPeople.length - obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : '' - obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : '' - obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : Metamaps.Erb['user.png'] - obj['contributor_list'] = self.createContributorList() - - obj['user_name'] = isCreator ? 'You' : map.get('user_name') - obj['created_at'] = map.get('created_at_clean') - obj['updated_at'] = map.get('updated_at_clean') - - var classes = isCreator ? 'yourMap' : '' - classes += canEdit ? ' canEdit' : '' - classes += shareable ? ' shareable' : '' - $('.mapInfoBox').removeClass('shareable yourMap canEdit') - .addClass(classes) - .html(self.generateBoxHTML.render(obj)) - - self.attachEventListeners() - }, - attachEventListeners: function () { - var self = Metamaps.Map.InfoBox - - $('.mapInfoBox.canEdit .best_in_place').best_in_place() - - // because anyone who can edit the map can change the map title - var bipName = $('.mapInfoBox .best_in_place_name') - bipName.unbind('best_in_place:activate').bind('best_in_place:activate', function () { - var $el = bipName.find('textarea') - var el = $el[0] - - $el.attr('maxlength', '140') - - $('.mapInfoName').append('
') - - var callback = function (data) { - $('.nameCounter.forMap').html(data.all + '/140') - } - Countable.live(el, callback) - }) - bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function () { - $('.nameCounter.forMap').remove() - }) - - $('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function () { - var name = $(this).html() - Metamaps.Active.Map.set('name', name) - Metamaps.Active.Map.trigger('saved') - // mobile menu - $('#header_content').html(name) - $('.mapInfoBox').removeClass('mapRequestTitle') - document.title = name + ' | Metamaps' - }) - - $('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function () { - var desc = $(this).html() - Metamaps.Active.Map.set('desc', desc) - Metamaps.Active.Map.trigger('saved') - }) - - $('.yourMap .mapPermission').unbind().click(self.onPermissionClick) - // .yourMap in the unbind/bind is just a namespace for the events - // not a reference to the class .yourMap on the .mapInfoBox - $('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect) - - $('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap) - - $('.mapContributors span, #mapContribs').unbind().click(function (event) { - $('.mapContributors .tip').toggle() - event.stopPropagation() - }) - $('.mapContributors .tip').unbind().click(function (event) { - event.stopPropagation() - }) - $('.mapContributors .tip li a').click(Metamaps.Router.intercept) - - $('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () { - $('.mapContributors .tip').hide() - }) - - self.addTypeahead() - }, - addTypeahead: function () { - var self = Metamaps.Map.InfoBox - - if (!Metamaps.Active.Map) return - - // for autocomplete - var collaborators = { - name: 'collaborators', - limit: 9999, - display: function(s) { return s.label; }, - templates: { - notFound: function(s) { - return Hogan.compile($('#collaboratorSearchTemplate').html()).render({ - value: "No results", - label: "No results", - rtype: "noresult", - profile: Metamaps.Erb['user.png'], - }); - }, - suggestion: function(s) { - return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s); - }, - }, - source: new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: '/search/mappers?term=%QUERY', - wildcard: '%QUERY', - }, - }) - } - - // for adding map collaborators, who will have edit rights - if (Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id')) { - $('.collaboratorSearchField').typeahead( - { - highlight: false, - }, - [collaborators] - ) - $('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick) - $('.mapContributors .removeCollaborator').click(function () { - self.removeCollaborator(parseInt($(this).data('id'))) - }) - } - }, - removeCollaborator: function (collaboratorId) { - var self = Metamaps.Map.InfoBox - Metamaps.Collaborators.remove(Metamaps.Collaborators.get(collaboratorId)) - var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id }) - $.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds }) - self.updateNumbers() - }, - addCollaborator: function (newCollaboratorId) { - var self = Metamaps.Map.InfoBox - - if (Metamaps.Collaborators.get(newCollaboratorId)) { - Metamaps.GlobalUI.notifyUser('That user already has access') - return - } - - function callback(mapper) { - Metamaps.Collaborators.add(mapper) - var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id }) - $.post('/maps/' + Metamaps.Active.Map.id + '/access', { access: mapperIds }) - var name = Metamaps.Collaborators.get(newCollaboratorId).get('name') - Metamaps.GlobalUI.notifyUser(name + ' will be notified by email') - self.updateNumbers() - } - - $.getJSON('/users/' + newCollaboratorId + '.json', callback) - }, - handleResultClick: function (event, item) { - var self = Metamaps.Map.InfoBox - - self.addCollaborator(item.id) - $('.collaboratorSearchField').typeahead('val', '') - }, - updateNameDescPerm: function (name, desc, perm) { - $('.mapInfoBox').removeClass('mapRequestTitle') - $('.mapInfoName .best_in_place_name').html(name) - $('.mapInfoDesc .best_in_place_desc').html(desc) - $('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm) - }, - createContributorList: function () { - var self = Metamaps.Map.InfoBox - var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators - var activeMapperIsCreator = Metamaps.Active.Mapper && Metamaps.Active.Mapper.id === Metamaps.Active.Map.get('user_id') - var string = '' - string += '' - - if (activeMapperIsCreator) { - string += '
' - } - return string - }, - updateNumbers: function () { - if (!Metamaps.Active.Map) return - - var self = Metamaps.Map.InfoBox - var mapper = Metamaps.Active.Mapper - var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators - - var contributors_class = '' - if (relevantPeople.length === 2) contributors_class = 'multiple mTwo' - else if (relevantPeople.length > 2) contributors_class = 'multiple' - - var contributors_image = Metamaps.Erb['user.png'] - if (relevantPeople.length > 0) { - // get the first contributor and use their image - contributors_image = relevantPeople.models[0].get('image') - } - $('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class) - $('.mapContributors span').text(relevantPeople.length) - $('.mapContributors .tip').html(self.createContributorList()) - self.addTypeahead() - $('.mapContributors .tip').unbind().click(function (event) { - event.stopPropagation() - }) - $('.mapTopics').text(Metamaps.Topics.length) - $('.mapSynapses').text(Metamaps.Synapses.length) - - $('.mapEditedAt').html('Last edited: ' + Metamaps.Util.nowDateFormatted()) - }, - onPermissionClick: function (event) { - var self = Metamaps.Map.InfoBox - - if (!self.selectingPermission) { - self.selectingPermission = true - $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow - if ($(this).hasClass('commons')) { - $(this).append('
') - } else if ($(this).hasClass('public')) { - $(this).append('
') - } else if ($(this).hasClass('private')) { - $(this).append('
') - } - $('.mapPermission .permissionSelect li').click(self.selectPermission) - event.stopPropagation() - } - }, - hidePermissionSelect: function () { - var self = Metamaps.Map.InfoBox - - self.selectingPermission = false - $('.mapPermission').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow - $('.mapPermission .permissionSelect').remove() - }, - selectPermission: function (event) { - var self = Metamaps.Map.InfoBox - - self.selectingPermission = false - var permission = $(this).attr('class') - Metamaps.Active.Map.save({ - permission: permission - }) - Metamaps.Active.Map.updateMapWrapper() - shareable = permission === 'private' ? '' : 'shareable' - $('.mapPermission').removeClass('commons public private minimize').addClass(permission) - $('.mapPermission .permissionSelect').remove() - $('.mapInfoBox').removeClass('shareable').addClass(shareable) - event.stopPropagation() - }, - deleteActiveMap: function () { - var confirmString = 'Are you sure you want to delete this map? ' - confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.' - - var doIt = confirm(confirmString) - var map = Metamaps.Active.Map - var mapper = Metamaps.Active.Mapper - var authorized = map.authorizePermissionChange(mapper) - - if (doIt && authorized) { - Metamaps.Map.InfoBox.close() - Metamaps.Maps.Active.remove(map) - Metamaps.Maps.Featured.remove(map) - Metamaps.Maps.Mine.remove(map) - Metamaps.Maps.Shared.remove(map) - map.destroy() - Metamaps.Router.home() - Metamaps.GlobalUI.notifyUser('Map eliminated!') - } - else if (!authorized) { - alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?") - } - } -}; // end Metamaps.Map.InfoBox diff --git a/app/assets/javascripts/src/Metamaps.Mapper.js b/app/assets/javascripts/src/Metamaps.Mapper.js deleted file mode 100644 index 7d565479..00000000 --- a/app/assets/javascripts/src/Metamaps.Mapper.js +++ /dev/null @@ -1,20 +0,0 @@ -/* global Metamaps, $ */ - -/* - * Metamaps.Mapper.js.erb - * - * Dependencies: none! - */ - -Metamaps.Mapper = { - // this function is to retrieve a mapper JSON object from the database - // @param id = the id of the mapper to retrieve - get: function (id, callback) { - return $.ajax({ - url: '/users/' + id + '.json', - success: function (data) { - callback(new Metamaps.Backbone.Mapper(data)) - } - }) - } -}; // end Metamaps.Mapper diff --git a/app/assets/javascripts/src/Metamaps.Router.js b/app/assets/javascripts/src/Metamaps.Router.js deleted file mode 100644 index 3ef30986..00000000 --- a/app/assets/javascripts/src/Metamaps.Router.js +++ /dev/null @@ -1,245 +0,0 @@ -/* global Metamaps, Backbone, $ */ - -/* - * Metamaps.Router.js.erb - * - * Dependencies: - * - Metamaps.Active - * - Metamaps.GlobalUI - * - Metamaps.JIT - * - Metamaps.Loading - * - Metamaps.Map - * - Metamaps.Maps - * - Metamaps.Topic - * - Metamaps.Views - * - Metamaps.Visualize - */ - -;(function () { - var Router = Backbone.Router.extend({ - routes: { - '': 'home', // #home - 'explore/:section': 'explore', // #explore/active - 'explore/:section/:id': 'explore', // #explore/mapper/1234 - 'maps/:id': 'maps' // #maps/7 - }, - home: function () { - clearTimeout(Metamaps.Router.timeoutId) - - if (Metamaps.Active.Mapper) document.title = 'Explore Active Maps | Metamaps' - else document.title = 'Home | Metamaps' - - Metamaps.Router.currentSection = '' - Metamaps.Router.currentPage = '' - $('.wrapper').removeClass('mapPage topicPage') - - var classes = Metamaps.Active.Mapper ? 'homePage explorePage' : 'homePage' - $('.wrapper').addClass(classes) - - var navigate = function () { - Metamaps.Router.timeoutId = setTimeout(function () { - Metamaps.Router.navigate('') - }, 300) - } - - // all this only for the logged in home page - if (Metamaps.Active.Mapper) { - $('.homeButton a').attr('href', '/') - Metamaps.GlobalUI.hideDiv('#yield') - - Metamaps.GlobalUI.showDiv('#explore') - - Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps.Active) - if (Metamaps.Maps.Active.length === 0) { - Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render - } else { - Metamaps.Views.exploreMaps.render(navigate) - } - } else { - // logged out home page - Metamaps.GlobalUI.hideDiv('#explore') - Metamaps.GlobalUI.showDiv('#yield') - Metamaps.Router.timeoutId = setTimeout(navigate, 500) - } - - Metamaps.GlobalUI.hideDiv('#infovis') - Metamaps.GlobalUI.hideDiv('#instructions') - Metamaps.Map.end() - Metamaps.Topic.end() - Metamaps.Active.Map = null - Metamaps.Active.Topic = null - }, - explore: function (section, id) { - clearTimeout(Metamaps.Router.timeoutId) - - // just capitalize the variable section - // either 'featured', 'mapper', or 'active' - var capitalize = section.charAt(0).toUpperCase() + section.slice(1) - - if (section === 'shared' || section === 'featured' || section === 'active' || section === 'starred') { - document.title = 'Explore ' + capitalize + ' Maps | Metamaps' - } else if (section === 'mapper') { - $.ajax({ - url: '/users/' + id + '.json', - success: function (response) { - document.title = response.name + ' | Metamaps' - }, - error: function () {} - }) - } else if (section === 'mine') { - document.title = 'Explore My Maps | Metamaps' - } - - if (Metamaps.Active.Mapper && section != 'mapper') $('.homeButton a').attr('href', '/explore/' + section) - $('.wrapper').removeClass('homePage mapPage topicPage') - $('.wrapper').addClass('explorePage') - - Metamaps.Router.currentSection = 'explore' - Metamaps.Router.currentPage = section - - // this will mean it's a mapper page being loaded - if (id) { - if (Metamaps.Maps.Mapper.mapperId !== id) { - // empty the collection if we are trying to load the maps - // collection of a different mapper than we had previously - Metamaps.Maps.Mapper.reset() - Metamaps.Maps.Mapper.page = 1 - } - Metamaps.Maps.Mapper.mapperId = id - } - - Metamaps.Views.exploreMaps.setCollection(Metamaps.Maps[capitalize]) - - var navigate = function () { - var path = '/explore/' + Metamaps.Router.currentPage - - // alter url if for mapper profile page - if (Metamaps.Router.currentPage === 'mapper') { - path += '/' + Metamaps.Maps.Mapper.mapperId - } - - Metamaps.Router.navigate(path) - } - var navigateTimeout = function () { - Metamaps.Router.timeoutId = setTimeout(navigate, 300) - } - if (Metamaps.Maps[capitalize].length === 0) { - Metamaps.Loading.show() - setTimeout(function () { - Metamaps.Maps[capitalize].getMaps(navigate) // this will trigger an explore maps render - }, 300) // wait 300 milliseconds till the other animations are done to do the fetch - } else { - if (id) { - Metamaps.Views.exploreMaps.fetchUserThenRender(navigateTimeout) - } else { - Metamaps.Views.exploreMaps.render(navigateTimeout) - } - } - - Metamaps.GlobalUI.showDiv('#explore') - Metamaps.GlobalUI.hideDiv('#yield') - Metamaps.GlobalUI.hideDiv('#infovis') - Metamaps.GlobalUI.hideDiv('#instructions') - Metamaps.Map.end() - Metamaps.Topic.end() - Metamaps.Active.Map = null - Metamaps.Active.Topic = null - }, - maps: function (id) { - clearTimeout(Metamaps.Router.timeoutId) - - document.title = 'Map ' + id + ' | Metamaps' - - Metamaps.Router.currentSection = 'map' - Metamaps.Router.currentPage = id - - $('.wrapper').removeClass('homePage explorePage topicPage') - $('.wrapper').addClass('mapPage') - // another class will be added to wrapper if you - // can edit this map '.canEditMap' - - Metamaps.GlobalUI.hideDiv('#yield') - Metamaps.GlobalUI.hideDiv('#explore') - - // clear the visualization, if there was one, before showing its div again - if (Metamaps.Visualize.mGraph) { - Metamaps.Visualize.mGraph.graph.empty() - Metamaps.Visualize.mGraph.plot() - Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas) - } - Metamaps.GlobalUI.showDiv('#infovis') - Metamaps.Topic.end() - Metamaps.Active.Topic = null - - Metamaps.Loading.show() - Metamaps.Map.end() - Metamaps.Map.launch(id) - }, - topics: function (id) { - clearTimeout(Metamaps.Router.timeoutId) - - document.title = 'Topic ' + id + ' | Metamaps' - - Metamaps.Router.currentSection = 'topic' - Metamaps.Router.currentPage = id - - $('.wrapper').removeClass('homePage explorePage mapPage') - $('.wrapper').addClass('topicPage') - - Metamaps.GlobalUI.hideDiv('#yield') - Metamaps.GlobalUI.hideDiv('#explore') - - // clear the visualization, if there was one, before showing its div again - if (Metamaps.Visualize.mGraph) { - Metamaps.Visualize.mGraph.graph.empty() - Metamaps.Visualize.mGraph.plot() - Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas) - } - Metamaps.GlobalUI.showDiv('#infovis') - Metamaps.Map.end() - Metamaps.Active.Map = null - - Metamaps.Topic.end() - Metamaps.Topic.launch(id) - } - }) - - Metamaps.Router = new Router() - Metamaps.Router.currentPage = '' - Metamaps.Router.currentSection = undefined - Metamaps.Router.timeoutId = undefined - - Metamaps.Router.intercept = function (evt) { - var segments - - var href = { - prop: $(this).prop('href'), - attr: $(this).attr('href') - } - var root = window.location.protocol + '//' + window.location.host + Backbone.history.options.root - - if (href.prop && href.prop === root) href.attr = '' - - if (href.prop && href.prop.slice(0, root.length) === root) { - evt.preventDefault() - - segments = href.attr.split('/') - segments.splice(0, 1) // pop off the element created by the first / - - if (href.attr === '') { - Metamaps.Router.home() - } else { - Metamaps.Router[segments[0]](segments[1], segments[2]) - } - } - } - - Metamaps.Router.init = function () { - Backbone.history.start({ - silent: true, - pushState: true, - root: '/' - }) - $(document).on('click', 'a[data-router="true"]', Metamaps.Router.intercept) - } -})() diff --git a/app/assets/javascripts/src/Metamaps.Views.js b/app/assets/javascripts/src/Metamaps.Views.js deleted file mode 100644 index d027d22c..00000000 --- a/app/assets/javascripts/src/Metamaps.Views.js +++ /dev/null @@ -1,87 +0,0 @@ -/* global Metamaps, $ */ - -/* - * Metamaps.Views.js.erb - * - * Dependencies: - * - Metamaps.Loading - * - Metamaps.Active - * - Metamaps.ReactComponents - */ - -Metamaps.Views = { - exploreMaps: { - setCollection: function (collection) { - var self = Metamaps.Views.exploreMaps - - if (self.collection) { - self.collection.off('add', self.render) - self.collection.off('successOnFetch', self.handleSuccess) - self.collection.off('errorOnFetch', self.handleError) - } - self.collection = collection - self.collection.on('add', self.render) - self.collection.on('successOnFetch', self.handleSuccess) - self.collection.on('errorOnFetch', self.handleError) - }, - render: function (mapperObj, cb) { - var self = Metamaps.Views.exploreMaps - - if (typeof mapperObj === 'function') { - cb = mapperObj - mapperObj = null - } - - var exploreObj = { - currentUser: Metamaps.Active.Mapper, - section: self.collection.id, - displayStyle: 'grid', - maps: self.collection, - moreToLoad: self.collection.page != 'loadedAll', - user: mapperObj, - loadMore: self.loadMore - } - ReactDOM.render( - React.createElement(Metamaps.ReactComponents.Maps, exploreObj), - document.getElementById('explore') - ) - - if (cb) cb() - Metamaps.Loading.hide() - }, - loadMore: function () { - var self = Metamaps.Views.exploreMaps - - if (self.collection.page != "loadedAll") { - self.collection.getMaps() - } - else self.render() - }, - handleSuccess: function (cb) { - var self = Metamaps.Views.exploreMaps - - if (self.collection && self.collection.id === 'mapper') { - self.fetchUserThenRender(cb) - } else { - self.render(cb) - } - }, - handleError: function () { - console.log('error loading maps!') // TODO - }, - fetchUserThenRender: function (cb) { - var self = Metamaps.Views.exploreMaps - - // first load the mapper object and then call the render function - $.ajax({ - url: '/users/' + self.collection.mapperId + '/details.json', - success: function (response) { - self.render(response, cb) - }, - error: function () { - self.render(cb) - } - }) - } - } -} diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb deleted file mode 100644 index 839d701e..00000000 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ /dev/null @@ -1,74 +0,0 @@ -/* global Metamaps */ - -/* - * Metamaps.js.erb - */ - -// TODO eliminate these 5 top-level variables -Metamaps.panningInt = null -Metamaps.tempNode = null -Metamaps.tempInit = false -Metamaps.tempNode2 = null -Metamaps.VERSION = '<%= METAMAPS_VERSION %>' - -/* erb variables from rails */ -Metamaps.Erb = {} -Metamaps.Erb['REALTIME_SERVER'] = '<%= ENV['REALTIME_SERVER'] %>' -Metamaps.Erb['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>' -Metamaps.Erb['user.png'] = '<%= asset_path('user.png') %>' -Metamaps.Erb['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>' -Metamaps.Erb['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>' -Metamaps.Erb['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>' -Metamaps.Erb['synapse16.png'] = '<%= asset_path('synapse16.png') %>' -Metamaps.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %> - -Metamaps.Settings = { - embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages - sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database - colors: { - background: '#344A58', - synapses: { - normal: '#888888', - hover: '#888888', - selected: '#FFFFFF' - }, - topics: { - selected: '#FFFFFF' - }, - labels: { - background: '#18202E', - text: '#DDD' - } - }, -} - -Metamaps.Touch = { - 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 -} - -Metamaps.Mouse = { - didPan: false, - didBoxZoom: false, - changeInX: 0, - changeInY: 0, - edgeHoveringOver: false, - boxStartCoordinates: false, - boxEndCoordinates: false, - synapseStartCoordinates: [], - synapseEndCoordinates: null, - lastNodeClick: 0, - lastCanvasClick: 0, - DOUBLE_CLICK_TOLERANCE: 300 -} - -Metamaps.Selected = { - reset: function () { - var self = Metamaps.Selected - - self.Nodes = [] - self.Edges = [] - }, - Nodes: [], - Edges: [] -} diff --git a/app/assets/javascripts/src/views/chatView.js.erb b/app/assets/javascripts/src/views/chatView.js.erb deleted file mode 100644 index 7a1e7f8e..00000000 --- a/app/assets/javascripts/src/views/chatView.js.erb +++ /dev/null @@ -1,343 +0,0 @@ -Metamaps.Views = Metamaps.Views || {}; - -Metamaps.Views.chatView = (function () { - var - chatView, - linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false }); - - var Private = { - messageHTML: "
" + - "
" + - "
{{ message }}
" + - "
{{ timestamp }}
" + - "
" + - "
", - participantHTML: "
" + - "
" + - "
{{ username }} {{ selfName }}
" + - "" + - "" + - "
" + - "
" + - "
", - templates: function() { - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; - this.messageTemplate = _.template(Private.messageHTML); - - this.participantTemplate = _.template(Private.participantHTML); - }, - createElements: function() { - this.$unread = $('
'); - this.$button = $('
Chat
'); - this.$messageInput = $(''); - this.$juntoHeader = $('
PARTICIPANTS
'); - this.$videoToggle = $('
'); - this.$cursorToggle = $('
'); - this.$participants = $('
'); - this.$conversationInProgress = $('
LIVE LEAVEJOIN
'); - this.$chatHeader = $('
CHAT
'); - this.$soundToggle = $('
'); - this.$messages = $('
'); - this.$container = $('
'); - }, - attachElements: function() { - this.$button.append(this.$unread); - - this.$juntoHeader.append(this.$videoToggle); - this.$juntoHeader.append(this.$cursorToggle); - - this.$chatHeader.append(this.$soundToggle); - - this.$participants.append(this.$conversationInProgress); - - this.$container.append(this.$juntoHeader); - this.$container.append(this.$participants); - this.$container.append(this.$chatHeader); - this.$container.append(this.$button); - this.$container.append(this.$messages); - this.$container.append(this.$messageInput); - }, - addEventListeners: function() { - var self = this; - - this.participants.on('add', function (participant) { - Private.addParticipant.call(self, participant); - }); - - this.participants.on('remove', function (participant) { - Private.removeParticipant.call(self, participant); - }); - - this.$button.on('click', function () { - Handlers.buttonClick.call(self); - }); - this.$videoToggle.on('click', function () { - Handlers.videoToggleClick.call(self); - }); - this.$cursorToggle.on('click', function () { - Handlers.cursorToggleClick.call(self); - }); - this.$soundToggle.on('click', function () { - Handlers.soundToggleClick.call(self); - }); - this.$messageInput.on('keyup', function (event) { - Handlers.keyUp.call(self, event); - }); - this.$messageInput.on('focus', function () { - Handlers.inputFocus.call(self); - }); - this.$messageInput.on('blur', function () { - Handlers.inputBlur.call(self); - }); - }, - initializeSounds: function() { - this.sound = new Howl({ - urls: ["<%= asset_path 'sounds/MM_sounds.mp3' %>", "<%= asset_path 'sounds/MM_sounds.ogg' %>"], - sprite: { - joinmap: [0, 561], - leavemap: [1000, 592], - receivechat: [2000, 318], - sendchat: [3000, 296], - sessioninvite: [4000, 5393, true] - } - }); - }, - incrementUnread: function() { - this.unreadMessages++; - this.$unread.html(this.unreadMessages); - this.$unread.show(); - }, - addMessage: function(message, isInitial, wasMe) { - - if (!this.isOpen && !isInitial) Private.incrementUnread.call(this); - - function addZero(i) { - if (i < 10) { - i = "0" + i; - } - return i; - } - var m = _.clone(message.attributes); - - var today = new Date(); - 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.user_image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; // TODO: remove - m.message = linker.link(m.message); - var $html = $(this.messageTemplate(m)); - this.$messages.append($html); - if (!isInitial) this.scrollMessages(200); - - if (!wasMe && !isInitial && this.alertSound) this.sound.play('receivechat'); - }, - initialMessages: function() { - var messages = this.messages.models; - for (var i = 0; i < messages.length; i++) { - Private.addMessage.call(this, messages[i], true); - } - }, - handleInputMessage: function() { - var message = { - message: this.$messageInput.val(), - }; - this.$messageInput.val(''); - $(document).trigger(chatView.events.message + '-' + this.room, [message]); - }, - addParticipant: function(participant) { - var p = _.clone(participant.attributes); - if (p.self) { - p.selfClass = 'is-self'; - p.selfName = '(me)'; - } else { - p.selfClass = ''; - p.selfName = ''; - } - var html = this.participantTemplate(p); - this.$participants.append(html); - }, - removeParticipant: function(participant) { - this.$container.find('.participant-' + participant.get('id')).remove(); - } - }; - - var Handlers = { - buttonClick: function() { - if (this.isOpen) this.close(); - else if (!this.isOpen) this.open(); - }, - videoToggleClick: function() { - this.$videoToggle.toggleClass('active'); - this.videosShowing = !this.videosShowing; - $(document).trigger(this.videosShowing ? chatView.events.videosOn : chatView.events.videosOff); - }, - cursorToggleClick: function() { - this.$cursorToggle.toggleClass('active'); - this.cursorsShowing = !this.cursorsShowing; - $(document).trigger(this.cursorsShowing ? chatView.events.cursorsOn : chatView.events.cursorsOff); - }, - soundToggleClick: function() { - this.alertSound = !this.alertSound; - this.$soundToggle.toggleClass('active'); - }, - keyUp: function(event) { - switch(event.which) { - case 13: // enter - Private.handleInputMessage.call(this); - break; - } - }, - inputFocus: function() { - $(document).trigger(chatView.events.inputFocus); - }, - inputBlur: function() { - $(document).trigger(chatView.events.inputBlur); - } - }; - - chatView = function(messages, mapper, room) { - var self = this; - - this.room = room; - this.mapper = mapper; - this.messages = messages; // backbone collection - - this.isOpen = false; - this.alertSound = true; // whether to play sounds on arrival of new messages or not - this.cursorsShowing = true; - this.videosShowing = true; - this.unreadMessages = 0; - this.participants = new Backbone.Collection(); - - Private.templates.call(this); - Private.createElements.call(this); - Private.attachElements.call(this); - Private.addEventListeners.call(this); - Private.initialMessages.call(this); - Private.initializeSounds.call(this); - this.$container.css({ - right: '-300px' - }); - }; - - chatView.prototype.conversationInProgress = function (participating) { - this.$conversationInProgress.show(); - this.$participants.addClass('is-live'); - if (participating) this.$participants.addClass('is-participating'); - this.$button.addClass('active'); - - // hide invite to call buttons - } - - chatView.prototype.conversationEnded = function () { - this.$conversationInProgress.hide(); - this.$participants.removeClass('is-live'); - this.$participants.removeClass('is-participating'); - this.$button.removeClass('active'); - this.$participants.find('.participant').removeClass('active'); - this.$participants.find('.participant').removeClass('pending'); - } - - chatView.prototype.leaveConversation = function () { - this.$participants.removeClass('is-participating'); - } - - chatView.prototype.mapperJoinedCall = function (id) { - this.$participants.find('.participant-' + id).addClass('active'); - } - - chatView.prototype.mapperLeftCall = function (id) { - this.$participants.find('.participant-' + id).removeClass('active'); - } - - chatView.prototype.invitationPending = function (id) { - this.$participants.find('.participant-' + id).addClass('pending'); - } - - chatView.prototype.invitationAnswered = function (id) { - this.$participants.find('.participant-' + id).removeClass('pending'); - } - - chatView.prototype.addParticipant = function (participant) { - this.participants.add(participant); - } - - chatView.prototype.removeParticipant = function (username) { - var p = this.participants.find(function (p) { return p.get('username') === username; }); - if (p) { - this.participants.remove(p); - } - } - - chatView.prototype.removeParticipants = function () { - this.participants.remove(this.participants.models); - } - - chatView.prototype.open = function () { - this.$container.css({ - right: '0' - }); - this.$messageInput.focus(); - this.isOpen = true; - this.unreadMessages = 0; - this.$unread.hide(); - this.scrollMessages(0); - $(document).trigger(chatView.events.openTray); - } - - chatView.prototype.addMessage = function(message, isInitial, wasMe) { - this.messages.add(message); - Private.addMessage.call(this, message, isInitial, wasMe); - } - - chatView.prototype.scrollMessages = function(duration) { - duration = duration || 0; - - this.$messages.animate({ - scrollTop: this.$messages[0].scrollHeight - }, duration); - } - - chatView.prototype.clearMessages = function () { - this.unreadMessages = 0; - this.$unread.hide(); - this.$messages.empty(); - } - - chatView.prototype.close = function () { - this.$container.css({ - right: '-300px' - }); - this.$messageInput.blur(); - this.isOpen = false; - $(document).trigger(chatView.events.closeTray); - } - - chatView.prototype.remove = function () { - this.$button.off(); - this.$container.remove(); - } - - /** - * @class - * @static - */ - chatView.events = { - message: 'ChatView:message', - openTray: 'ChatView:openTray', - closeTray: 'ChatView:closeTray', - inputFocus: 'ChatView:inputFocus', - inputBlur: 'ChatView:inputBlur', - cursorsOff: 'ChatView:cursorsOff', - cursorsOn: 'ChatView:cursorsOn', - videosOff: 'ChatView:videosOff', - videosOn: 'ChatView:videosOn' - }; - - return chatView; - -})(); diff --git a/app/assets/javascripts/src/views/room.js b/app/assets/javascripts/src/views/room.js deleted file mode 100644 index 4595c3cb..00000000 --- a/app/assets/javascripts/src/views/room.js +++ /dev/null @@ -1,195 +0,0 @@ -Metamaps.Views = Metamaps.Views || {}; - -Metamaps.Views.room = (function () { - - var ChatView = Metamaps.Views.chatView; - var VideoView = Metamaps.Views.videoView; - - var room = function(opts) { - var self = this; - - this.isActiveRoom = false; - this.socket = opts.socket; - this.webrtc = opts.webrtc; - //this.roomRef = opts.firebase; - this.room = opts.room; - this.config = opts.config; - this.peopleCount = 0; - - this.$myVideo = opts.$video; - this.myVideo = opts.myVideoView; - - this.messages = new Backbone.Collection(); - this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image }); - this.chat = new ChatView(this.messages, this.currentMapper, this.room); - - this.videos = {}; - - this.init(); - }; - - room.prototype.join = function(cb) { - this.isActiveRoom = true; - this.webrtc.joinRoom(this.room, cb); - this.chat.conversationInProgress(true); // true indicates participation - } - - room.prototype.conversationInProgress = function() { - this.chat.conversationInProgress(false); // false indicates not participating - } - - room.prototype.conversationEnding = function() { - this.chat.conversationEnded(); - } - - room.prototype.leaveVideoOnly = function() { - this.chat.leaveConversation(); // the conversation will carry on without you - for (var id in this.videos) { - this.removeVideo(id); - } - this.isActiveRoom = false; - this.webrtc.leaveRoom(); - } - - room.prototype.leave = function() { - for (var id in this.videos) { - this.removeVideo(id); - } - this.isActiveRoom = false; - this.webrtc.leaveRoom(); - this.chat.conversationEnded(); - this.chat.removeParticipants(); - this.chat.clearMessages(); - this.messages.reset(); - } - - room.prototype.setPeopleCount = function(count) { - this.peopleCount = count; - } - - room.prototype.init = function () { - var self = this; - - $(document).on(VideoView.events.audioControlClick, function (event, videoView) { - if (!videoView.audioStatus) self.webrtc.mute(); - else if (videoView.audioStatus) self.webrtc.unmute(); - }); - $(document).on(VideoView.events.videoControlClick, function (event, videoView) { - if (!videoView.videoStatus) self.webrtc.pauseVideo(); - else if (videoView.videoStatus) self.webrtc.resumeVideo(); - }); - - this.webrtc.webrtc.off('peerStreamAdded'); - this.webrtc.webrtc.off('peerStreamRemoved'); - this.webrtc.on('peerStreamAdded', function (peer) { - var mapper = Metamaps.Realtime.mappersOnMap[peer.nick]; - peer.avatar = mapper.image; - peer.username = mapper.name; - if (self.isActiveRoom) { - self.addVideo(peer); - } - }); - - this.webrtc.on('peerStreamRemoved', function (peer) { - if (self.isActiveRoom) { - self.removeVideo(peer); - } - }); - - 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(); - }); - - var sendChatMessage = function (event, data) { - self.sendChatMessage(data); - }; - $(document).on(ChatView.events.message + '-' + this.room, sendChatMessage); - } - - room.prototype.videoAdded = function (callback) { - this._videoAdded = callback; - } - - room.prototype.addVideo = function (peer) { - var - id = this.webrtc.getDomId(peer), - video = attachMediaStream(peer.stream); - - var - v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar, username: peer.username }); - - this.videos[peer.id] = v; - if (this._videoAdded) this._videoAdded(v, peer.nick); - } - - room.prototype.removeVideo = function (peer) { - var id = typeof peer == 'string' ? peer : peer.id; - if (this.videos[id]) { - this.videos[id].remove(); - delete this.videos[id]; - } - } - - room.prototype.sendChatMessage = function (data) { - var self = this; - //this.roomRef.child('messages').push(data); - if (self.chat.alertSound) self.chat.sound.play('sendchat'); - 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.addMessages(new Metamaps.Backbone.MessageCollection(model), false, true); - $(document).trigger(room.events.newMessage, [model]); - }, - error: function (model, response) { - console.log('error!', response); - } - }); - } - - // they should be instantiated as backbone models before they get - // passed to this function - room.prototype.addMessages = function (messages, isInitial, wasMe) { - var self = this; - - messages.models.forEach(function (message) { - self.chat.addMessage(message, isInitial, wasMe); - }); - } - - /** - * @class - * @static - */ - room.events = { - newMessage: "Room:newMessage" - }; - - return room; -})(); diff --git a/app/assets/javascripts/src/views/videoView.js b/app/assets/javascripts/src/views/videoView.js deleted file mode 100644 index b9d39c06..00000000 --- a/app/assets/javascripts/src/views/videoView.js +++ /dev/null @@ -1,207 +0,0 @@ -Metamaps.Views = Metamaps.Views || {}; - -Metamaps.Views.videoView = (function () { - - var videoView; - - var Private = { - addControls: function() { - var self = this; - - this.$audioControl = $('
'); - this.$videoControl = $('
'); - - this.$audioControl.on('click', function () { - Handlers.audioControlClick.call(self); - }); - - this.$videoControl.on('click', function () { - Handlers.videoControlClick.call(self); - }); - - this.$container.append(this.$audioControl); - this.$container.append(this.$videoControl); - }, - cancelClick: function() { - this.mouseIsDown = false; - - if (this.hasMoved) { - - } - - $(document).trigger(videoView.events.dragEnd); - } - }; - - var Handlers = { - mousedown: function(event) { - this.mouseIsDown = true; - this.hasMoved = false; - this.mouseMoveStart = { - x: event.pageX, - y: event.pageY - }; - this.posStart = { - x: parseInt(this.$container.css('left'), '10'), - y: parseInt(this.$container.css('top'), '10') - } - - $(document).trigger(videoView.events.mousedown); - }, - mouseup: function(event) { - $(document).trigger(videoView.events.mouseup, [this]); - - var storedTime = this.lastClick; - var now = Date.now(); - this.lastClick = now; - - if (now - storedTime < this.config.DOUBLE_CLICK_TOLERANCE) { - $(document).trigger(videoView.events.doubleClick, [this]); - } - }, - mousemove: function(event) { - var - diffX, - diffY, - newX, - newY; - - if (this.$parent && this.mouseIsDown) { - this.manuallyPositioned = true; - this.hasMoved = true; - diffX = event.pageX - this.mouseMoveStart.x; - diffY = this.mouseMoveStart.y - event.pageY; - newX = this.posStart.x + diffX; - newY = this.posStart.y - diffY; - this.$container.css({ - top: newY, - left: newX - }); - } - }, - audioControlClick: function() { - if (this.audioStatus) { - this.audioOff(); - } else { - this.audioOn(); - } - $(document).trigger(videoView.events.audioControlClick, [this]); - }, - videoControlClick: function() { - if (this.videoStatus) { - this.videoOff(); - } else { - this.videoOn(); - } - $(document).trigger(videoView.events.videoControlClick, [this]); - }, - }; - - var videoView = function(video, $parent, id, isMyself, config) { - var self = this; - - this.$parent = $parent; // mapView - - this.video = video; - this.id = id; - - this.config = config; - - this.mouseIsDown = false; - this.mouseDownOffset = { x: 0, y: 0 }; - this.lastClick = null; - this.hasMoved = false; - - this.audioStatus = true; - this.videoStatus = true; - - this.$container = $('
'); - this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : '')); - this.$container.attr('id', 'container_' + id); - - - var $vidContainer = $('
'); - $vidContainer.addClass('video-cutoff'); - $vidContainer.append(this.video); - - this.avatar = config.avatar; - this.$avatar = $(''); - $vidContainer.append(this.$avatar); - - this.$container.append($vidContainer); - - this.$container.on('mousedown', function (event) { - Handlers.mousedown.call(self, event); - }); - - if (isMyself) { - Private.addControls.call(this); - } - - // suppress contextmenu - this.video.oncontextmenu = function () { return false; }; - - if (this.$parent) this.setParent(this.$parent); - }; - - videoView.prototype.setParent = function($parent) { - var self = this; - this.$parent = $parent; - this.$parent.off('.video' + this.id); - this.$parent.on('mouseup.video' + this.id, function (event) { - Handlers.mouseup.call(self, event); - Private.cancelClick.call(self); - }); - this.$parent.on('mousemove.video' + this.id, function (event) { - Handlers.mousemove.call(self, event); - }); - } - - 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); - this.$container.remove(); - } - - videoView.prototype.videoOff = function () { - this.$videoControl.addClass('active'); - this.$avatar.show(); - this.videoStatus = false; - } - - videoView.prototype.videoOn = function () { - this.$videoControl.removeClass('active'); - this.$avatar.hide(); - this.videoStatus = true; - } - - videoView.prototype.audioOff = function () { - this.$audioControl.addClass('active'); - this.audioStatus = false; - } - - videoView.prototype.audioOn = function () { - this.$audioControl.removeClass('active'); - this.audioStatus = true; - } - - /** - * @class - * @static - */ - videoView.events = { - mousedown: "VideoView:mousedown", - mouseup: "VideoView:mouseup", - doubleClick: "VideoView:doubleClick", - dragEnd: "VideoView:dragEnd", - audioControlClick: "VideoView:audioControlClick", - videoControlClick: "VideoView:videoControlClick", - }; - - return videoView; -})(); diff --git a/doc/production/first-deploy.md b/doc/production/first-deploy.md index cf98eda4..cc3a1f4a 100644 --- a/doc/production/first-deploy.md +++ b/doc/production/first-deploy.md @@ -87,8 +87,6 @@ server to see what problems show up: sudo npm install -g forever (crontab -u metamaps -l 2>/dev/null; echo "@reboot $(which forever) --append -l /home/metamaps/logs/forever.realtime.log start /home/metamaps/metamaps/realtime/realtime-server.js") | crontab -u metamaps - - cd /home/metamaps/metamaps/realtime - npm install mkdir -p /home/metamaps/logs forever --append -l /home/metamaps/logs/forever.realtime.log \ start /home/metamaps/metamaps/realtime/realtime-server.js diff --git a/doc/production/pull-changes.md b/doc/production/pull-changes.md index 0fb5d568..30f41cf5 100644 --- a/doc/production/pull-changes.md +++ b/doc/production/pull-changes.md @@ -29,9 +29,7 @@ Now that you have the code, run these commands: rake perms:fix passenger-config restart-app . - cd realtime - npm install - forever list #find the uid, e.g. xQKv + forever list #find the uid of the realtime server, e.g. xQKv forever restart xQKv sudo service metamaps_delayed_job restart diff --git a/app/assets/javascripts/src/Metamaps.Account.js b/frontend/src/Metamaps/Account.js similarity index 85% rename from app/assets/javascripts/src/Metamaps.Account.js rename to frontend/src/Metamaps/Account.js index a2286ad8..10311cbd 100644 --- a/app/assets/javascripts/src/Metamaps.Account.js +++ b/frontend/src/Metamaps/Account.js @@ -1,42 +1,34 @@ -/* global Metamaps, $ */ - -/* - * Metamaps.Account.js.erb - * - * Dependencies: - * - Metamaps.Erb +/* + * Metamaps.Erb */ -Metamaps.Account = { +const Account = { listenersInitialized: false, - init: function () { - var self = Metamaps.Account - }, initListeners: function () { - var self = Metamaps.Account + var self = Account $('#user_image').change(self.showImagePreview) self.listenersInitialized = true }, toggleChangePicture: function () { - var self = Metamaps.Account + var self = Account $('.userImageMenu').toggle() if (!self.listenersInitialized) self.initListeners() }, openChangePicture: function () { - var self = Metamaps.Account + var self = Account $('.userImageMenu').show() if (!self.listenersInitialized) self.initListeners() }, closeChangePicture: function () { - var self = Metamaps.Account + var self = Account $('.userImageMenu').hide() }, showLoading: function () { - var self = Metamaps.Account + var self = Account var loader = new CanvasLoader('accountPageLoading') loader.setColor('#4FC059'); // default is '#000000' @@ -47,7 +39,7 @@ Metamaps.Account = { $('#accountPageLoading').show() }, showImagePreview: function () { - var self = Metamaps.Account + var self = Account var file = $('#user_image')[0].files[0] @@ -93,10 +85,10 @@ Metamaps.Account = { } }, removePicture: function () { - var self = Metamaps.Account + var self = Account $('.userImageDiv canvas').remove() - $('.userImageDiv img').attr('src', Metamaps.Erb['user.png']).show() + $('.userImageDiv img').attr('src', window.Metamaps.Erb['user.png']).show() $('.userImageMenu').hide() var input = $('#user_image') @@ -120,3 +112,5 @@ Metamaps.Account = { $('#user_password_confirmation').val('') } } + +export default Account diff --git a/frontend/src/Metamaps/Active.js b/frontend/src/Metamaps/Active.js new file mode 100644 index 00000000..c61a8bb9 --- /dev/null +++ b/frontend/src/Metamaps/Active.js @@ -0,0 +1,7 @@ +const Active = { + Map: null, + Topic: null, + Mapper: null +}; + +export default Active diff --git a/app/assets/javascripts/src/Metamaps.Admin.js b/frontend/src/Metamaps/Admin.js similarity index 82% rename from app/assets/javascripts/src/Metamaps.Admin.js rename to frontend/src/Metamaps/Admin.js index a0192012..5d080c2e 100644 --- a/app/assets/javascripts/src/Metamaps.Admin.js +++ b/frontend/src/Metamaps/Admin.js @@ -1,35 +1,29 @@ -/* global Metamaps, $ */ +/* global $ */ -/* - * Metamaps.Admin.js.erb - * - * Dependencies: none! - */ - -Metamaps.Admin = { +const Admin = { selectMetacodes: [], allMetacodes: [], init: function () { - var self = Metamaps.Admin + var self = Admin $('#metacodes_value').val(self.selectMetacodes.toString()) }, selectAll: function () { - var self = Metamaps.Admin + var self = 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 = Admin $('.editMetacodes li').addClass('toggledOff') self.selectMetacodes = [] $('#metacodes_value').val(0) }, liClickHandler: function () { - var self = Metamaps.Admin + var self = Admin if ($(this).attr('class') != 'toggledOff') { $(this).addClass('toggledOff') @@ -44,7 +38,7 @@ Metamaps.Admin = { } }, validate: function () { - var self = Metamaps.Admin + var self = Admin if (self.selectMetacodes.length == 0) { alert('Would you pretty please select at least one metacode for the set?') @@ -52,3 +46,5 @@ Metamaps.Admin = { } } } + +export default Admin diff --git a/app/assets/javascripts/src/Metamaps.AutoLayout.js b/frontend/src/Metamaps/AutoLayout.js similarity index 89% rename from app/assets/javascripts/src/Metamaps.AutoLayout.js rename to frontend/src/Metamaps/AutoLayout.js index 51e105c2..ee9dc33c 100644 --- a/app/assets/javascripts/src/Metamaps.AutoLayout.js +++ b/frontend/src/Metamaps/AutoLayout.js @@ -1,12 +1,4 @@ -/* global Metamaps */ - -/* - * Metmaaps.AutoLayout.js - * - * Dependencies: none! - */ - -Metamaps.AutoLayout = { +const AutoLayout = { nextX: 0, nextY: 0, sideLength: 1, @@ -16,7 +8,7 @@ Metamaps.AutoLayout = { timeToTurn: 0, getNextCoord: function () { - var self = Metamaps.AutoLayout + var self = AutoLayout var nextX = self.nextX var nextY = self.nextY @@ -63,7 +55,7 @@ Metamaps.AutoLayout = { } }, resetSpiral: function () { - var self = Metamaps.AutoLayout + var self = AutoLayout self.nextX = 0 self.nextY = 0 self.nextXshift = 1 @@ -73,3 +65,5 @@ Metamaps.AutoLayout = { self.turnCount = 0 } } + +export default AutoLayout diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js b/frontend/src/Metamaps/Backbone/index.js similarity index 79% rename from app/assets/javascripts/src/Metamaps.Backbone.js rename to frontend/src/Metamaps/Backbone/index.js index 2c1f58af..2c7ae530 100644 --- a/app/assets/javascripts/src/Metamaps.Backbone.js +++ b/frontend/src/Metamaps/Backbone/index.js @@ -1,33 +1,38 @@ -/* global Metamaps, Backbone, _, $ */ +/* global Metamaps, Backbone, $ */ + +import _ from 'lodash' +import Backbone from 'backbone' +Backbone.$ = window.$ + +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' /* * Metamaps.Backbone.js.erb * * Dependencies: - * - Metamaps.Active * - Metamaps.Collaborators * - Metamaps.Creators - * - Metamaps.Filter - * - Metamaps.JIT * - Metamaps.Loading - * - Metamaps.Map - * - Metamaps.Mapper * - Metamaps.Mappers * - Metamaps.Mappings * - Metamaps.Metacodes - * - Metamaps.Realtime - * - Metamaps.Synapse - * - Metamaps.SynapseCard * - Metamaps.Synapses - * - Metamaps.Topic - * - Metamaps.TopicCard * - Metamaps.Topics - * - Metamaps.Visualize */ -Metamaps.Backbone = {} +const _Backbone = {} -Metamaps.Backbone.Map = Backbone.Model.extend({ +_Backbone.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) { @@ -58,7 +63,7 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ this.on('saved', this.savedEvent) }, savedEvent: function () { - Metamaps.Realtime.sendMapChange(this) + Realtime.sendMapChange(this) }, authorizeToEdit: function (mapper) { if (mapper && ( @@ -78,10 +83,10 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ } }, getUser: function () { - return Metamaps.Mapper.get(this.get('user_id')) + return Mapper.get(this.get('user_id')) }, fetchContained: function () { - var bb = Metamaps.Backbone + var bb = _Backbone var that = this var start = function (data) { that.set('mappers', new bb.MapperCollection(data.mappers)) @@ -122,10 +127,10 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ return this.get('mappers') }, updateView: function () { - var map = Metamaps.Active.Map + var map = Active.Map var isActiveMap = this.id === map.id if (isActiveMap) { - Metamaps.Map.InfoBox.updateNameDescPerm(this.get('name'), this.get('desc'), this.get('permission')) + InfoBox.updateNameDescPerm(this.get('name'), this.get('desc'), this.get('permission')) this.updateMapWrapper() // mobile menu $('#header_content').html(this.get('name')) @@ -133,17 +138,17 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ } }, updateMapWrapper: function () { - var map = Metamaps.Active.Map + var map = Active.Map var isActiveMap = this.id === map.id - var authorized = map && map.authorizeToEdit(Metamaps.Active.Mapper) ? 'canEditMap' : '' + 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) } } }) -Metamaps.Backbone.MapsCollection = Backbone.Collection.extend({ - model: Metamaps.Backbone.Map, +_Backbone.MapsCollection = Backbone.Collection.extend({ + model: _Backbone.Map, initialize: function (models, options) { this.id = options.id this.sortBy = options.sortBy @@ -210,7 +215,7 @@ Metamaps.Backbone.MapsCollection = Backbone.Collection.extend({ } }) -Metamaps.Backbone.Message = Backbone.Model.extend({ +_Backbone.Message = Backbone.Model.extend({ urlRoot: '/messages', blacklist: ['created_at', 'updated_at'], toJSON: function (options) { @@ -226,12 +231,12 @@ Metamaps.Backbone.Message = Backbone.Model.extend({ */ } }) -Metamaps.Backbone.MessageCollection = Backbone.Collection.extend({ - model: Metamaps.Backbone.Message, +_Backbone.MessageCollection = Backbone.Collection.extend({ + model: _Backbone.Message, url: '/messages' }) -Metamaps.Backbone.Mapper = Backbone.Model.extend({ +_Backbone.Mapper = Backbone.Model.extend({ urlRoot: '/users', blacklist: ['created_at', 'updated_at'], toJSON: function (options) { @@ -247,13 +252,13 @@ Metamaps.Backbone.Mapper = Backbone.Model.extend({ } }) -Metamaps.Backbone.MapperCollection = Backbone.Collection.extend({ - model: Metamaps.Backbone.Mapper, +_Backbone.MapperCollection = Backbone.Collection.extend({ + model: _Backbone.Mapper, url: '/users' }) -Metamaps.Backbone.init = function () { - var self = Metamaps.Backbone +_Backbone.init = function () { + var self = _Backbone self.Metacode = Backbone.Model.extend({ initialize: function () { @@ -320,10 +325,10 @@ Metamaps.Backbone.init = function () { initialize: function () { if (this.isNew()) { this.set({ - 'user_id': Metamaps.Active.Mapper.id, + 'user_id': Active.Mapper.id, 'desc': this.get('desc') || '', 'link': this.get('link') || '', - 'permission': Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons' + 'permission': Active.Map ? Active.Map.get('permission') : 'commons' }) } @@ -335,7 +340,7 @@ Metamaps.Backbone.init = function () { mappableid: this.id } - $(document).trigger(Metamaps.JIT.events.removeTopic, [removeTopicData]) + $(document).trigger(JIT.events.removeTopic, [removeTopicData]) }) this.on('noLongerPrivate', function () { var newTopicData = { @@ -343,10 +348,10 @@ Metamaps.Backbone.init = function () { mappableid: this.id } - $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]) + $(document).trigger(JIT.events.newTopic, [newTopicData]) }) - this.on('change:metacode_id', Metamaps.Filter.checkMetacodes, this) + this.on('change:metacode_id', Filter.checkMetacodes, this) }, authorizeToEdit: function (mapper) { if (mapper && @@ -367,10 +372,10 @@ Metamaps.Backbone.init = function () { return Metamaps.Metacodes.get(this.get('metacode_id')) }, getMapping: function () { - if (!Metamaps.Active.Map) return false + if (!Active.Map) return false return Metamaps.Mappings.findWhere({ - map_id: Metamaps.Active.Map.id, + map_id: Active.Map.id, mappable_type: 'Topic', mappable_id: this.isNew() ? this.cid : this.id }) @@ -383,7 +388,7 @@ Metamaps.Backbone.init = function () { name: this.get('name') } - if (Metamaps.Active.Map) { + if (Active.Map) { mapping = this.getMapping() node.data = { $mapping: null, @@ -398,7 +403,7 @@ Metamaps.Backbone.init = function () { var node = this.get('node') node.setData('topic', this) - if (Metamaps.Active.Map) { + if (Active.Map) { mapping = this.getMapping() node.setData('mapping', mapping) } @@ -406,38 +411,38 @@ Metamaps.Backbone.init = function () { return node }, savedEvent: function () { - Metamaps.Realtime.sendTopicChange(this) + Realtime.sendTopicChange(this) }, updateViews: function () { - var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic + 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 == Metamaps.TopicCard.openTopicCard) { - Metamaps.TopicCard.showCard(node) + if (onPageWithTopicCard && this == TopicCard.openTopicCard) { + TopicCard.showCard(node) } // update the node on the map if (onPageWithTopicCard && node) { node.name = this.get('name') - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } }, updateCardView: function () { - var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic + 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 == Metamaps.TopicCard.openTopicCard) { - Metamaps.TopicCard.showCard(node) + if (onPageWithTopicCard && this == TopicCard.openTopicCard) { + TopicCard.showCard(node) } }, updateNodeView: function () { - var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic + 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') - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } } }) @@ -485,8 +490,8 @@ Metamaps.Backbone.init = function () { initialize: function () { if (this.isNew()) { this.set({ - 'user_id': Metamaps.Active.Mapper.id, - 'permission': Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons', + 'user_id': Active.Mapper.id, + 'permission': Active.Map ? Active.Map.get('permission') : 'commons', 'category': 'from-to' }) } @@ -500,15 +505,15 @@ Metamaps.Backbone.init = function () { mappableid: this.id } - $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]) + $(document).trigger(JIT.events.newSynapse, [newSynapseData]) }) this.on('nowPrivate', function () { - $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ + $(document).trigger(JIT.events.removeSynapse, [{ mappableid: this.id }]) }) - this.on('change:desc', Metamaps.Filter.checkSynapses, this) + this.on('change:desc', Filter.checkSynapses, this) }, prepareLiForFilter: function () { var li = '' @@ -542,10 +547,10 @@ Metamaps.Backbone.init = function () { ] : false }, getMapping: function () { - if (!Metamaps.Active.Map) return false + if (!Active.Map) return false return Metamaps.Mappings.findWhere({ - map_id: Metamaps.Active.Map.id, + map_id: Active.Map.id, mappable_type: 'Synapse', mappable_id: this.isNew() ? this.cid : this.id }) @@ -563,7 +568,7 @@ Metamaps.Backbone.init = function () { } } - if (Metamaps.Active.Map) { + if (Active.Map) { mapping = providedMapping || this.getMapping() mappingID = mapping.isNew() ? mapping.cid : mapping.id edge.data.$mappings = [] @@ -577,7 +582,7 @@ Metamaps.Backbone.init = function () { var edge = this.get('edge') edge.getData('synapses').push(this) - if (Metamaps.Active.Map) { + if (Active.Map) { mapping = this.getMapping() edge.getData('mappings').push(mapping) } @@ -585,28 +590,28 @@ Metamaps.Backbone.init = function () { return edge }, savedEvent: function () { - Metamaps.Realtime.sendSynapseChange(this) + Realtime.sendSynapseChange(this) }, updateViews: function () { this.updateCardView() this.updateEdgeView() }, updateCardView: function () { - var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic + 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 == Metamaps.SynapseCard.openSynapseCard) { - Metamaps.SynapseCard.showCard(edge) + if (onPageWithSynapseCard && edge == SynapseCard.openSynapseCard) { + SynapseCard.showCard(edge) } }, updateEdgeView: function () { - var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic + var onPageWithSynapseCard = Active.Map || Active.Topic var edge = this.get('edge') // update the edge on the map if (onPageWithSynapseCard && edge) { - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } } }) @@ -625,20 +630,20 @@ Metamaps.Backbone.init = function () { initialize: function () { if (this.isNew()) { this.set({ - 'user_id': Metamaps.Active.Mapper.id, - 'map_id': Metamaps.Active.Map ? Metamaps.Active.Map.id : null + 'user_id': Active.Mapper.id, + 'map_id': Active.Map ? Active.Map.id : null }) } }, getMap: function () { - return Metamaps.Map.get(this.get('map_id')) + return Map.get(this.get('map_id')) }, getTopic: function () { - if (this.get('mappable_type') === 'Topic') return Metamaps.Topic.get(this.get('mappable_id')) + 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 Metamaps.Synapse.get(this.get('mappable_id')) + if (this.get('mappable_type') === 'Synapse') return Synapse.get(this.get('mappable_id')) else return false } }) @@ -661,36 +666,38 @@ Metamaps.Backbone.init = function () { // this is for topic view Metamaps.Creators = Metamaps.Creators ? new self.MapperCollection(Metamaps.Creators) : new self.MapperCollection() - if (Metamaps.Active.Map) { + if (Active.Map) { Metamaps.Mappings = Metamaps.Mappings ? new self.MappingCollection(Metamaps.Mappings) : new self.MappingCollection() - Metamaps.Active.Map = new self.Map(Metamaps.Active.Map) + Active.Map = new self.Map(Active.Map) } - if (Metamaps.Active.Topic) Metamaps.Active.Topic = new self.Topic(Metamaps.Active.Topic) + if (Active.Topic) Active.Topic = new self.Topic(Active.Topic) // attach collection event listeners self.attachCollectionEvents = function () { Metamaps.Topics.on('add remove', function (topic) { - Metamaps.Map.InfoBox.updateNumbers() - Metamaps.Filter.checkMetacodes() - Metamaps.Filter.checkMappers() + InfoBox.updateNumbers() + Filter.checkMetacodes() + Filter.checkMappers() }) Metamaps.Synapses.on('add remove', function (synapse) { - Metamaps.Map.InfoBox.updateNumbers() - Metamaps.Filter.checkSynapses() - Metamaps.Filter.checkMappers() + InfoBox.updateNumbers() + Filter.checkSynapses() + Filter.checkMappers() }) - if (Metamaps.Active.Map) { + if (Active.Map) { Metamaps.Mappings.on('add remove', function (mapping) { - Metamaps.Map.InfoBox.updateNumbers() - Metamaps.Filter.checkSynapses() - Metamaps.Filter.checkMetacodes() - Metamaps.Filter.checkMappers() + InfoBox.updateNumbers() + Filter.checkSynapses() + Filter.checkMetacodes() + Filter.checkMappers() }) } } self.attachCollectionEvents() -}; // end Metamaps.Backbone.init +}; // end _Backbone.init + +export default _Backbone diff --git a/app/assets/javascripts/src/Metamaps.Control.js b/frontend/src/Metamaps/Control.js similarity index 53% rename from app/assets/javascripts/src/Metamaps.Control.js rename to frontend/src/Metamaps/Control.js index da6854c2..2c14cfca 100644 --- a/app/assets/javascripts/src/Metamaps.Control.js +++ b/frontend/src/Metamaps/Control.js @@ -1,154 +1,156 @@ /* global Metamaps, $ */ +import _ from 'lodash' + +import Active from './Active' +import Filter from './Filter' +import GlobalUI from './GlobalUI' +import JIT from './JIT' +import Mouse from './Mouse' +import Selected from './Selected' +import Settings from './Settings' +import Visualize from './Visualize' + /* - * Metamaps.Control.js.erb + * Metamaps.Control.js * * Dependencies: - * - Metamaps.Active - * - Metamaps.Control - * - Metamaps.Filter - * - Metamaps.GlobalUI - * - Metamaps.JIT * - Metamaps.Mappings * - Metamaps.Metacodes - * - Metamaps.Mouse - * - Metamaps.Selected - * - Metamaps.Settings * - Metamaps.Synapses * - Metamaps.Topics - * - Metamaps.Visualize */ -Metamaps.Control = { +const Control = { init: function () {}, selectNode: function (node, e) { var filtered = node.getData('alpha') === 0 - if (filtered || Metamaps.Selected.Nodes.indexOf(node) != -1) return + if (filtered || Selected.Nodes.indexOf(node) != -1) return node.selected = true node.setData('dim', 30, 'current') - Metamaps.Selected.Nodes.push(node) + Selected.Nodes.push(node) }, deselectAllNodes: function () { - var l = Metamaps.Selected.Nodes.length + var l = Selected.Nodes.length for (var i = l - 1; i >= 0; i -= 1) { - var node = Metamaps.Selected.Nodes[i] - Metamaps.Control.deselectNode(node) + var node = Selected.Nodes[i] + Control.deselectNode(node) } - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() }, deselectNode: function (node) { delete node.selected node.setData('dim', 25, 'current') // remove the node - Metamaps.Selected.Nodes.splice( - Metamaps.Selected.Nodes.indexOf(node), 1) + Selected.Nodes.splice( + Selected.Nodes.indexOf(node), 1) }, deleteSelected: function () { - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var n = Metamaps.Selected.Nodes.length - var e = Metamaps.Selected.Edges.length + var n = Selected.Nodes.length + var e = Selected.Edges.length var ntext = n == 1 ? '1 topic' : n + ' topics' var etext = e == 1 ? '1 synapse' : e + ' synapses' var text = 'You have ' + ntext + ' and ' + etext + ' selected. ' - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } var r = confirm(text + 'Are you sure you want to permanently delete them all? This will remove them from all maps they appear on.') if (r == true) { - Metamaps.Control.deleteSelectedEdges() - Metamaps.Control.deleteSelectedNodes() + Control.deleteSelectedEdges() + Control.deleteSelectedNodes() } }, deleteSelectedNodes: function () { // refers to deleting topics permanently - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } - var l = Metamaps.Selected.Nodes.length + var l = Selected.Nodes.length for (var i = l - 1; i >= 0; i -= 1) { - var node = Metamaps.Selected.Nodes[i] - Metamaps.Control.deleteNode(node.id) + var node = Selected.Nodes[i] + Control.deleteNode(node.id) } }, deleteNode: function (nodeid) { // refers to deleting topics permanently - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } - var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid) + var node = 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') + var permToDelete = Active.Mapper.id === topic.get('user_id') || Active.Mapper.get('admin') if (permToDelete) { var mappableid = topic.id var mapping = node.getData('mapping') topic.destroy() Metamaps.Mappings.remove(mapping) - $(document).trigger(Metamaps.JIT.events.deleteTopic, [{ + $(document).trigger(JIT.events.deleteTopic, [{ mappableid: mappableid }]) - Metamaps.Control.hideNode(nodeid) + Control.hideNode(nodeid) } else { - Metamaps.GlobalUI.notifyUser('Only topics you created can be deleted') + GlobalUI.notifyUser('Only topics you created can be deleted') } }, removeSelectedNodes: function () { // refers to removing topics permanently from a map - if (Metamaps.Active.Topic) { + if (Active.Topic) { // hideNode will handle synapses as well - var nodeids = _.map(Metamaps.Selected.Nodes, function(node) { + var nodeids = _.map(Selected.Nodes, function(node) { return node.id }) _.each(nodeids, function(nodeid) { - if (Metamaps.Active.Topic.id !== nodeid) { + if (Active.Topic.id !== nodeid) { Metamaps.Topics.remove(nodeid) - Metamaps.Control.hideNode(nodeid) + Control.hideNode(nodeid) } }) return } - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var l = Metamaps.Selected.Nodes.length, + var l = Selected.Nodes.length, i, node, - authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } for (i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i] - Metamaps.Control.removeNode(node.id) + node = Selected.Nodes[i] + Control.removeNode(node.id) } }, removeNode: function (nodeid) { // refers to removing topics permanently from a map - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) - var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) + var node = Visualize.mGraph.graph.getNode(nodeid) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } @@ -157,116 +159,116 @@ Metamaps.Control = { var mapping = node.getData('mapping') mapping.destroy() Metamaps.Topics.remove(topic) - $(document).trigger(Metamaps.JIT.events.removeTopic, [{ + $(document).trigger(JIT.events.removeTopic, [{ mappableid: mappableid }]) - Metamaps.Control.hideNode(nodeid) + Control.hideNode(nodeid) }, hideSelectedNodes: function () { - var l = Metamaps.Selected.Nodes.length, + var l = Selected.Nodes.length, i, node for (i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i] - Metamaps.Control.hideNode(node.id) + node = Selected.Nodes[i] + Control.hideNode(node.id) } }, hideNode: function (nodeid) { - var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid) - var graph = Metamaps.Visualize.mGraph + var node = Visualize.mGraph.graph.getNode(nodeid) + var graph = Visualize.mGraph - Metamaps.Control.deselectNode(node) + Control.deselectNode(node) node.setData('alpha', 0, 'end') node.eachAdjacency(function (adj) { adj.setData('alpha', 0, 'end') }) - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.animate({ modes: ['node-property:alpha', 'edge-property:alpha' ], duration: 500 }) setTimeout(function () { - if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph" + if (nodeid == Visualize.mGraph.root) { // && Visualize.type === "RGraph" var newroot = _.find(graph.graph.nodes, function (n) { return n.id !== nodeid; }) graph.root = newroot ? newroot.id : null } - Metamaps.Visualize.mGraph.graph.removeNode(nodeid) + Visualize.mGraph.graph.removeNode(nodeid) }, 500) - Metamaps.Filter.checkMetacodes() - Metamaps.Filter.checkMappers() + Filter.checkMetacodes() + Filter.checkMappers() }, selectEdge: function (edge) { var filtered = edge.getData('alpha') === 0; // don't select if the edge is filtered - if (filtered || Metamaps.Selected.Edges.indexOf(edge) != -1) return + if (filtered || Selected.Edges.indexOf(edge) != -1) return - var width = Metamaps.Mouse.edgeHoveringOver === edge ? 4 : 2 + var width = Mouse.edgeHoveringOver === edge ? 4 : 2 edge.setDataset('current', { showDesc: true, lineWidth: width, - color: Metamaps.Settings.colors.synapses.selected + color: Settings.colors.synapses.selected }) - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() - Metamaps.Selected.Edges.push(edge) + Selected.Edges.push(edge) }, deselectAllEdges: function () { - var l = Metamaps.Selected.Edges.length + var l = Selected.Edges.length for (var i = l - 1; i >= 0; i -= 1) { - var edge = Metamaps.Selected.Edges[i] - Metamaps.Control.deselectEdge(edge) + var edge = Selected.Edges[i] + Control.deselectEdge(edge) } - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() }, deselectEdge: function (edge) { edge.setData('showDesc', false, 'current') edge.setDataset('current', { lineWidth: 2, - color: Metamaps.Settings.colors.synapses.normal + color: Settings.colors.synapses.normal }) - if (Metamaps.Mouse.edgeHoveringOver == edge) { + if (Mouse.edgeHoveringOver == edge) { edge.setDataset('current', { showDesc: true, lineWidth: 4 }) } - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() // remove the edge - Metamaps.Selected.Edges.splice( - Metamaps.Selected.Edges.indexOf(edge), 1) + Selected.Edges.splice( + Selected.Edges.indexOf(edge), 1) }, deleteSelectedEdges: function () { // refers to deleting topics permanently var edge, - l = Metamaps.Selected.Edges.length + l = Selected.Edges.length - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } for (var i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i] - Metamaps.Control.deleteEdge(edge) + edge = Selected.Edges[i] + Control.deleteEdge(edge) } }, deleteEdge: function (edge) { - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } @@ -275,10 +277,10 @@ 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') + var permToDelete = Active.Mapper.id === synapse.get('user_id') || Active.Mapper.get('admin') if (permToDelete) { if (edge.getData('synapses').length - 1 === 0) { - Metamaps.Control.hideEdge(edge) + Control.hideEdge(edge) } var mappableid = synapse.id synapse.destroy() @@ -290,46 +292,46 @@ Metamaps.Control = { if (edge.getData('displayIndex')) { delete edge.data.$displayIndex } - $(document).trigger(Metamaps.JIT.events.deleteSynapse, [{ + $(document).trigger(JIT.events.deleteSynapse, [{ mappableid: mappableid }]) } else { - Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted') + GlobalUI.notifyUser('Only synapses you created can be deleted') } }, removeSelectedEdges: function () { // Topic view is handled by removeSelectedNodes - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var l = Metamaps.Selected.Edges.length, + var l = Selected.Edges.length, i, edge - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } for (i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i] - Metamaps.Control.removeEdge(edge) + edge = Selected.Edges[i] + Control.removeEdge(edge) } - Metamaps.Selected.Edges = [ ] + Selected.Edges = [ ] }, removeEdge: function (edge) { - if (!Metamaps.Active.Map) return + if (!Active.Map) return - var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map.authorizeToEdit(Active.Mapper) if (!authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') return } if (edge.getData('mappings').length - 1 === 0) { - Metamaps.Control.hideEdge(edge) + Control.hideEdge(edge) } var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0 @@ -346,51 +348,51 @@ Metamaps.Control = { if (edge.getData('displayIndex')) { delete edge.data.$displayIndex } - $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ + $(document).trigger(JIT.events.removeSynapse, [{ mappableid: mappableid }]) }, hideSelectedEdges: function () { var edge, - l = Metamaps.Selected.Edges.length, + l = Selected.Edges.length, i for (i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i] - Metamaps.Control.hideEdge(edge) + edge = Selected.Edges[i] + Control.hideEdge(edge) } - Metamaps.Selected.Edges = [ ] + Selected.Edges = [ ] }, hideEdge: function (edge) { var from = edge.nodeFrom.id var to = edge.nodeTo.id edge.setData('alpha', 0, 'end') - Metamaps.Control.deselectEdge(edge) - Metamaps.Visualize.mGraph.fx.animate({ + Control.deselectEdge(edge) + Visualize.mGraph.fx.animate({ modes: ['edge-property:alpha'], duration: 500 }) setTimeout(function () { - Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to) + Visualize.mGraph.graph.removeAdjacence(from, to) }, 500) - Metamaps.Filter.checkSynapses() - Metamaps.Filter.checkMappers() + Filter.checkSynapses() + Filter.checkMappers() }, updateSelectedPermissions: function (permission) { var edge, synapse, node, topic - Metamaps.GlobalUI.notifyUser('Working...') + GlobalUI.notifyUser('Working...') // variables to keep track of how many nodes and synapses you had the ability to change the permission of var nCount = 0, sCount = 0 // change the permission of the selected synapses, if logged in user is the original creator - var l = Metamaps.Selected.Edges.length + var l = Selected.Edges.length for (var i = l - 1; i >= 0; i -= 1) { - edge = Metamaps.Selected.Edges[i] + edge = Selected.Edges[i] synapse = edge.getData('synapses')[0] - if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + if (synapse.authorizePermissionChange(Active.Mapper)) { synapse.save({ permission: permission }) @@ -399,12 +401,12 @@ Metamaps.Control = { } // change the permission of the selected topics, if logged in user is the original creator - var l = Metamaps.Selected.Nodes.length + var l = Selected.Nodes.length for (var i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i] + node = Selected.Nodes[i] topic = node.getData('topic') - if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + if (topic.authorizePermissionChange(Active.Mapper)) { topic.save({ permission: permission }) @@ -416,12 +418,12 @@ Metamaps.Control = { var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses') var message = nString + sString + ' you created updated to ' + permission - Metamaps.GlobalUI.notifyUser(message) + GlobalUI.notifyUser(message) }, updateSelectedMetacodes: function (metacode_id) { var node, topic - Metamaps.GlobalUI.notifyUser('Working...') + GlobalUI.notifyUser('Working...') var metacode = Metamaps.Metacodes.get(metacode_id) @@ -429,12 +431,12 @@ Metamaps.Control = { var nCount = 0 // change the permission of the selected topics, if logged in user is the original creator - var l = Metamaps.Selected.Nodes.length + var l = Selected.Nodes.length for (var i = l - 1; i >= 0; i -= 1) { - node = Metamaps.Selected.Nodes[i] + node = Selected.Nodes[i] topic = node.getData('topic') - if (topic.authorizeToEdit(Metamaps.Active.Mapper)) { + if (topic.authorizeToEdit(Active.Mapper)) { topic.save({ 'metacode_id': metacode_id }) @@ -445,7 +447,9 @@ Metamaps.Control = { var nString = nCount == 1 ? (nCount.toString() + ' topic') : (nCount.toString() + ' topics') var message = nString + ' you can edit updated to ' + metacode.get('name') - Metamaps.GlobalUI.notifyUser(message) - Metamaps.Visualize.mGraph.plot() + GlobalUI.notifyUser(message) + Visualize.mGraph.plot() }, -}; // end Metamaps.Control +} + +export default Control diff --git a/app/assets/javascripts/src/Metamaps.Create.js b/frontend/src/Metamaps/Create.js similarity index 78% rename from app/assets/javascripts/src/Metamaps.Create.js rename to frontend/src/Metamaps/Create.js index 6f3bbb62..c9252aba 100644 --- a/app/assets/javascripts/src/Metamaps.Create.js +++ b/frontend/src/Metamaps/Create.js @@ -1,20 +1,21 @@ /* global Metamaps, $ */ +import Mouse from './Mouse' +import Selected from './Selected' +import Synapse from './Synapse' +import Topic from './Topic' +import Visualize from './Visualize' +import GlobalUI from './GlobalUI' + /* * Metamaps.Create.js * * Dependencies: * - Metamaps.Backbone - * - Metamaps.GlobalUI * - Metamaps.Metacodes - * - Metamaps.Mouse - * - Metamaps.Selected - * - Metamaps.Synapse - * - Metamaps.Topic - * - Metamaps.Visualize */ -Metamaps.Create = { +const Create = { isSwitchingSet: false, // indicates whether the metacode set switch lightbox is open selectedMetacodeSet: null, selectedMetacodeSetIndex: null, @@ -23,7 +24,7 @@ Metamaps.Create = { selectedMetacodes: [], newSelectedMetacodes: [], init: function () { - var self = Metamaps.Create + var self = Create self.newTopic.init() self.newSynapse.init() @@ -36,7 +37,7 @@ Metamaps.Create = { $('.customMetacodeList li').click(self.toggleMetacodeSelected) // within the custom metacode set tab }, toggleMetacodeSelected: function () { - var self = Metamaps.Create + var self = Create if ($(this).attr('class') != 'toggledOff') { $(this).addClass('toggledOff') @@ -51,29 +52,29 @@ Metamaps.Create = { } }, updateMetacodeSet: function (set, index, custom) { - if (custom && Metamaps.Create.newSelectedMetacodes.length == 0) { + if (custom && Create.newSelectedMetacodes.length == 0) { alert('Please select at least one metacode to use!') return false } var codesToSwitchToIds var metacodeModels = new Metamaps.Backbone.MetacodeCollection() - Metamaps.Create.selectedMetacodeSetIndex = index - Metamaps.Create.selectedMetacodeSet = 'metacodeset-' + set + Create.selectedMetacodeSetIndex = index + Create.selectedMetacodeSet = 'metacodeset-' + set if (!custom) { codesToSwitchToIds = $('#metacodeSwitchTabs' + set).attr('data-metacodes').split(',') $('.customMetacodeList li').addClass('toggledOff') - Metamaps.Create.selectedMetacodes = [] - Metamaps.Create.selectedMetacodeNames = [] - Metamaps.Create.newSelectedMetacodes = [] - Metamaps.Create.newSelectedMetacodeNames = [] + Create.selectedMetacodes = [] + Create.selectedMetacodeNames = [] + Create.newSelectedMetacodes = [] + Create.newSelectedMetacodeNames = [] } else if (custom) { // uses .slice to avoid setting the two arrays to the same actual array - Metamaps.Create.selectedMetacodes = Metamaps.Create.newSelectedMetacodes.slice(0) - Metamaps.Create.selectedMetacodeNames = Metamaps.Create.newSelectedMetacodeNames.slice(0) - codesToSwitchToIds = Metamaps.Create.selectedMetacodes.slice(0) + Create.selectedMetacodes = Create.newSelectedMetacodes.slice(0) + Create.selectedMetacodeNames = Create.newSelectedMetacodeNames.slice(0) + codesToSwitchToIds = Create.selectedMetacodes.slice(0) } // sort by name @@ -100,12 +101,12 @@ Metamaps.Create = { bringToFront: true }) - Metamaps.GlobalUI.closeLightbox() + GlobalUI.closeLightbox() $('#topic_name').focus() var mdata = { 'metacodes': { - 'value': custom ? Metamaps.Create.selectedMetacodes.toString() : Metamaps.Create.selectedMetacodeSet + 'value': custom ? Create.selectedMetacodes.toString() : Create.selectedMetacodeSet } } $.ajax({ @@ -123,7 +124,7 @@ Metamaps.Create = { }, cancelMetacodeSetSwitch: function () { - var self = Metamaps.Create + var self = Create self.isSwitchingSet = false if (self.selectedMetacodeSet != 'metacodeset-custom') { @@ -148,17 +149,17 @@ Metamaps.Create = { newTopic: { init: function () { $('#topic_name').keyup(function () { - Metamaps.Create.newTopic.name = $(this).val() + Create.newTopic.name = $(this).val() }) $('.pinCarousel').click(function() { - if (Metamaps.Create.newTopic.pinned) { + if (Create.newTopic.pinned) { $('.pinCarousel').removeClass('isPinned') - Metamaps.Create.newTopic.pinned = false + Create.newTopic.pinned = false } else { $('.pinCarousel').addClass('isPinned') - Metamaps.Create.newTopic.pinned = true + Create.newTopic.pinned = true } }) @@ -192,7 +193,7 @@ Metamaps.Create = { // 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) { - Metamaps.Topic.getTopicFromAutocomplete(datum.id) + Topic.getTopicFromAutocomplete(datum.id) }) // initialize metacode spinner and then hide it @@ -220,24 +221,24 @@ Metamaps.Create = { $('#new_topic').fadeIn('fast', function () { $('#topic_name').focus() }) - Metamaps.Create.newTopic.beingCreated = true - Metamaps.Create.newTopic.name = '' + Create.newTopic.beingCreated = true + Create.newTopic.name = '' }, hide: function (force) { - if (force || !Metamaps.Create.newTopic.pinned) { + if (force || !Create.newTopic.pinned) { $('#new_topic').fadeOut('fast') - Metamaps.Create.newTopic.beingCreated = false + Create.newTopic.beingCreated = false } if (force) { $('.pinCarousel').removeClass('isPinned') - Metamaps.Create.newTopic.pinned = false + Create.newTopic.pinned = false } $('#topic_name').typeahead('val', '') } }, newSynapse: { init: function () { - var self = Metamaps.Create.newSynapse + var self = Create.newSynapse var synapseBloodhound = new Bloodhound({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), @@ -253,8 +254,8 @@ Metamaps.Create = { remote: { url: '/search/synapses?topic1id=%TOPIC1&topic2id=%TOPIC2', prepare: function (query, settings) { - var self = Metamaps.Create.newSynapse - if (Metamaps.Selected.Nodes.length < 2) { + var self = Create.newSynapse + if (Selected.Nodes.length < 2) { settings.url = settings.url.replace('%TOPIC1', self.topic1id).replace('%TOPIC2', self.topic2id) return settings } else { @@ -299,23 +300,23 @@ Metamaps.Create = { if (e.keyCode === BACKSPACE && $(this).val() === '' || e.keyCode === DELETE && $(this).val() === '' || e.keyCode === ESC) { - Metamaps.Create.newSynapse.hide() + Create.newSynapse.hide() } // if - Metamaps.Create.newSynapse.description = $(this).val() + Create.newSynapse.description = $(this).val() }) $('#synapse_desc').focusout(function () { - if (Metamaps.Create.newSynapse.beingCreated) { - Metamaps.Synapse.createSynapseLocally() + if (Create.newSynapse.beingCreated) { + Synapse.createSynapseLocally() } }) $('#synapse_desc').bind('typeahead:select', function (event, datum, dataset) { if (datum.id) { // if they clicked on an existing synapse get it - Metamaps.Synapse.getSynapseFromAutocomplete(datum.id) + Synapse.getSynapseFromAutocomplete(datum.id) } else { - Metamaps.Create.newSynapse.description = datum.value - Metamaps.Synapse.createSynapseLocally() + Create.newSynapse.description = datum.value + Synapse.createSynapseLocally() } }) }, @@ -328,17 +329,19 @@ Metamaps.Create = { $('#new_synapse').fadeIn(100, function () { $('#synapse_desc').focus() }) - Metamaps.Create.newSynapse.beingCreated = true + Create.newSynapse.beingCreated = true }, hide: function () { $('#new_synapse').fadeOut('fast') $('#synapse_desc').typeahead('val', '') - Metamaps.Create.newSynapse.beingCreated = false - Metamaps.Create.newTopic.addSynapse = false - Metamaps.Create.newSynapse.topic1id = 0 - Metamaps.Create.newSynapse.topic2id = 0 - Metamaps.Mouse.synapseStartCoordinates = [] - Metamaps.Visualize.mGraph.plot() + Create.newSynapse.beingCreated = false + Create.newTopic.addSynapse = false + Create.newSynapse.topic1id = 0 + Create.newSynapse.topic2id = 0 + Mouse.synapseStartCoordinates = [] + Visualize.mGraph.plot() }, } -}; // end Metamaps.Create +} + +export default Create diff --git a/frontend/src/Metamaps/Debug.js b/frontend/src/Metamaps/Debug.js new file mode 100644 index 00000000..0fe5f769 --- /dev/null +++ b/frontend/src/Metamaps/Debug.js @@ -0,0 +1,6 @@ +const Debug = (arg = window.Metamaps) => { + console.debug(arg) + console.debug(`Metamaps Version: ${arg.VERSION}`) +} + +export default Debug diff --git a/app/assets/javascripts/src/Metamaps.Filter.js b/frontend/src/Metamaps/Filter.js similarity index 91% rename from app/assets/javascripts/src/Metamaps.Filter.js rename to frontend/src/Metamaps/Filter.js index 1dba099c..f67c6ec8 100644 --- a/app/assets/javascripts/src/Metamaps.Filter.js +++ b/frontend/src/Metamaps/Filter.js @@ -1,21 +1,24 @@ /* global Metamaps, $ */ +import _ from 'lodash' + +import Active from './Active' +import Control from './Control' +import GlobalUI from './GlobalUI' +import Settings from './Settings' +import Visualize from './Visualize' + /* * Metamaps.Filter.js.erb * * Dependencies: - * - Metamaps.Active - * - Metamaps.Control * - Metamaps.Creators - * - Metamaps.GlobalUI * - Metamaps.Mappers * - Metamaps.Metacodes - * - Metamaps.Settings * - Metamaps.Synapses * - Metamaps.Topics - * - Metamaps.Visualize */ -Metamaps.Filter = { +const Filter = { filters: { name: '', metacodes: [], @@ -30,7 +33,7 @@ Metamaps.Filter = { isOpen: false, changing: false, init: function () { - var self = Metamaps.Filter + var self = Filter $('.sidebarFilterIcon').click(self.toggleBox) @@ -45,7 +48,7 @@ Metamaps.Filter = { self.getFilterData() }, toggleBox: function (event) { - var self = Metamaps.Filter + var self = Filter if (self.isOpen) self.close() else self.open() @@ -53,9 +56,9 @@ Metamaps.Filter = { event.stopPropagation() }, open: function () { - var self = Metamaps.Filter + var self = Filter - Metamaps.GlobalUI.Account.close() + GlobalUI.Account.close() $('.sidebarFilterIcon div').addClass('hide') if (!self.isOpen && !self.changing) { @@ -69,7 +72,7 @@ Metamaps.Filter = { } }, close: function () { - var self = Metamaps.Filter + var self = Filter $('.sidebarFilterIcon div').removeClass('hide') if (!self.changing) { @@ -82,7 +85,7 @@ Metamaps.Filter = { } }, reset: function () { - var self = Metamaps.Filter + var self = Filter self.filters.metacodes = [] self.filters.mappers = [] @@ -102,7 +105,7 @@ Metamaps.Filter = { But what these function do is load this data into three accessible array within java : metacodes, mappers and synapses */ getFilterData: function () { - var self = Metamaps.Filter + var self = Filter var metacode, mapper, synapse @@ -125,7 +128,7 @@ Metamaps.Filter = { }) }, bindLiClicks: function () { - var self = Metamaps.Filter + var self = Filter $('#filter_by_metacode ul li').unbind().click(self.toggleMetacode) $('#filter_by_mapper ul li').unbind().click(self.toggleMapper) $('#filter_by_synapse ul li').unbind().click(self.toggleSynapse) @@ -136,7 +139,7 @@ Metamaps.Filter = { @param */ updateFilters: function (collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) { - var self = Metamaps.Filter + var self = Filter var newList = [] var removed = [] @@ -211,12 +214,12 @@ Metamaps.Filter = { self.bindLiClicks() }, checkMetacodes: function () { - var self = Metamaps.Filter + var self = Filter self.updateFilters('Topics', 'metacode_id', 'Metacodes', 'metacodes', 'metacode') }, checkMappers: function () { - var self = Metamaps.Filter - var onMap = Metamaps.Active.Map ? true : false + var self = Filter + var onMap = Active.Map ? true : false if (onMap) { self.updateFilters('Mappings', 'user_id', 'Mappers', 'mappers', 'mapper') } else { @@ -225,11 +228,11 @@ Metamaps.Filter = { } }, checkSynapses: function () { - var self = Metamaps.Filter + var self = Filter self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse') }, filterAllMetacodes: function (e) { - var self = Metamaps.Filter + var self = Filter $('#filter_by_metacode ul li').addClass('toggledOff') $('.showAllMetacodes').removeClass('active') $('.hideAllMetacodes').addClass('active') @@ -237,7 +240,7 @@ Metamaps.Filter = { self.passFilters() }, filterNoMetacodes: function (e) { - var self = Metamaps.Filter + var self = Filter $('#filter_by_metacode ul li').removeClass('toggledOff') $('.showAllMetacodes').addClass('active') $('.hideAllMetacodes').removeClass('active') @@ -245,7 +248,7 @@ Metamaps.Filter = { self.passFilters() }, filterAllMappers: function (e) { - var self = Metamaps.Filter + var self = Filter $('#filter_by_mapper ul li').addClass('toggledOff') $('.showAllMappers').removeClass('active') $('.hideAllMappers').addClass('active') @@ -253,7 +256,7 @@ Metamaps.Filter = { self.passFilters() }, filterNoMappers: function (e) { - var self = Metamaps.Filter + var self = Filter $('#filter_by_mapper ul li').removeClass('toggledOff') $('.showAllMappers').addClass('active') $('.hideAllMappers').removeClass('active') @@ -261,7 +264,7 @@ Metamaps.Filter = { self.passFilters() }, filterAllSynapses: function (e) { - var self = Metamaps.Filter + var self = Filter $('#filter_by_synapse ul li').addClass('toggledOff') $('.showAllSynapses').removeClass('active') $('.hideAllSynapses').addClass('active') @@ -269,7 +272,7 @@ Metamaps.Filter = { self.passFilters() }, filterNoSynapses: function (e) { - var self = Metamaps.Filter + var self = Filter $('#filter_by_synapse ul li').removeClass('toggledOff') $('.showAllSynapses').addClass('active') $('.hideAllSynapses').removeClass('active') @@ -280,7 +283,7 @@ Metamaps.Filter = { // to reduce code redundancy // gets called in the context of a list item in a filter box toggleLi: function (whichToFilter) { - var self = Metamaps.Filter, index + var self = Filter, index var id = $(this).attr('data-id') if (self.visible[whichToFilter].indexOf(id) == -1) { self.visible[whichToFilter].push(id) @@ -293,7 +296,7 @@ Metamaps.Filter = { self.passFilters() }, toggleMetacode: function () { - var self = Metamaps.Filter + var self = Filter self.toggleLi.call(this, 'metacodes') if (self.visible.metacodes.length === self.filters.metacodes.length) { @@ -309,7 +312,7 @@ Metamaps.Filter = { } }, toggleMapper: function () { - var self = Metamaps.Filter + var self = Filter self.toggleLi.call(this, 'mappers') if (self.visible.mappers.length === self.filters.mappers.length) { @@ -325,7 +328,7 @@ Metamaps.Filter = { } }, toggleSynapse: function () { - var self = Metamaps.Filter + var self = Filter self.toggleLi.call(this, 'synapses') if (self.visible.synapses.length === self.filters.synapses.length) { @@ -341,16 +344,16 @@ Metamaps.Filter = { } }, passFilters: function () { - var self = Metamaps.Filter + var self = Filter var visible = self.visible var passesMetacode, passesMapper, passesSynapse var onMap - if (Metamaps.Active.Map) { + if (Active.Map) { onMap = true } - else if (Metamaps.Active.Topic) { + else if (Active.Topic) { onMap = false } @@ -386,10 +389,10 @@ Metamaps.Filter = { else console.log(topic) } else { if (n) { - Metamaps.Control.deselectNode(n, true) + Control.deselectNode(n, true) n.setData('alpha', opacityForFilter, 'end') n.eachAdjacency(function (e) { - Metamaps.Control.deselectEdge(e, true) + Control.deselectEdge(e, true) }) } else console.log(topic) @@ -442,12 +445,12 @@ Metamaps.Filter = { if (visible.mappers.indexOf(user_id) == -1) passesMapper = false else passesMapper = true - var color = Metamaps.Settings.colors.synapses.normal + var color = Settings.colors.synapses.normal if (passesSynapse && passesMapper) { e.setData('alpha', 1, 'end') e.setData('color', color, 'end') } else { - Metamaps.Control.deselectEdge(e, true) + Control.deselectEdge(e, true) e.setData('alpha', opacityForFilter, 'end') } @@ -457,10 +460,12 @@ Metamaps.Filter = { }) // run the animation - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.animate({ modes: ['node-property:alpha', 'edge-property:alpha'], duration: 200 }) } -}; // end Metamaps.Filter +} + +export default Filter diff --git a/frontend/src/Metamaps/GlobalUI.js b/frontend/src/Metamaps/GlobalUI.js new file mode 100644 index 00000000..b24b31c7 --- /dev/null +++ b/frontend/src/Metamaps/GlobalUI.js @@ -0,0 +1,646 @@ +import Active from './Active' +import Create from './Create' +import Filter from './Filter' +import Router from './Router' + +/* + * Metamaps.Backbone + * Metamaps.Erb + * Metamaps.Maps + */ + +const GlobalUI = { + notifyTimeout: null, + lightbox: null, + init: function () { + var self = GlobalUI; + + self.Search.init(); + self.CreateMap.init(); + self.Account.init(); + + if ($('#toast').html().trim()) self.notifyUser($('#toast').html()) + + //bind lightbox clicks + $('.openLightbox').click(function (event) { + self.openLightbox($(this).attr('data-open')); + event.preventDefault(); + return false; + }); + + $('#lightbox_screen, #lightbox_close').click(self.closeLightbox); + + // initialize global backbone models and collections + if (Active.Mapper) Active.Mapper = new Metamaps.Backbone.Mapper(Active.Mapper); + + var myCollection = Metamaps.Maps.Mine ? Metamaps.Maps.Mine : []; + var sharedCollection = Metamaps.Maps.Shared ? Metamaps.Maps.Shared : []; + var starredCollection = Metamaps.Maps.Starred ? Metamaps.Maps.Starred : []; + var mapperCollection = []; + var mapperOptionsObj = {id: 'mapper', sortBy: 'updated_at' }; + if (Metamaps.Maps.Mapper) { + mapperCollection = Metamaps.Maps.Mapper.models; + mapperOptionsObj.mapperId = Metamaps.Maps.Mapper.id; + } + var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : []; + var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : []; + Metamaps.Maps.Mine = new Metamaps.Backbone.MapsCollection(myCollection, {id: 'mine', sortBy: 'updated_at' }); + Metamaps.Maps.Shared = new Metamaps.Backbone.MapsCollection(sharedCollection, {id: 'shared', sortBy: 'updated_at' }); + Metamaps.Maps.Starred = new Metamaps.Backbone.MapsCollection(starredCollection, {id: 'starred', sortBy: 'updated_at' }); + // 'Mapper' refers to another mapper + Metamaps.Maps.Mapper = new Metamaps.Backbone.MapsCollection(mapperCollection, mapperOptionsObj); + Metamaps.Maps.Featured = new Metamaps.Backbone.MapsCollection(featuredCollection, {id: 'featured', sortBy: 'updated_at' }); + Metamaps.Maps.Active = new Metamaps.Backbone.MapsCollection(activeCollection, {id: 'active', sortBy: 'updated_at' }); + }, + showDiv: function (selector) { + $(selector).show() + $(selector).animate({ + opacity: 1 + }, 200, 'easeOutCubic') + }, + hideDiv: function (selector) { + $(selector).animate({ + opacity: 0 + }, 200, 'easeInCubic', function () { $(this).hide() }) + }, + openLightbox: function (which) { + var self = GlobalUI; + + $('.lightboxContent').hide(); + $('#' + which).show(); + + self.lightbox = which; + + $('#lightbox_overlay').show(); + + var heightOfContent = '-' + ($('#lightbox_main').height() / 2) + 'px'; + // animate the content in from the bottom + $('#lightbox_main').animate({ + 'top': '50%', + 'margin-top': heightOfContent + }, 200, 'easeOutCubic'); + + // fade the black overlay in + $('#lightbox_screen').animate({ + 'opacity': '0.42' + }, 200); + + if (which == "switchMetacodes") { + Create.isSwitchingSet = true; + } + }, + + closeLightbox: function (event) { + var self = GlobalUI; + + if (event) event.preventDefault(); + + // animate the lightbox content offscreen + $('#lightbox_main').animate({ + 'top': '100%', + 'margin-top': '0' + }, 200, 'easeInCubic'); + + // fade the black overlay out + $('#lightbox_screen').animate({ + 'opacity': '0.0' + }, 200, function () { + $('#lightbox_overlay').hide(); + }); + + if (self.lightbox === 'forkmap') GlobalUI.CreateMap.reset('fork_map'); + if (self.lightbox === 'newmap') GlobalUI.CreateMap.reset('new_map'); + if (Create && Create.isSwitchingSet) { + Create.cancelMetacodeSetSwitch(); + } + self.lightbox = null; + }, + notifyUser: function (message, leaveOpen) { + var self = GlobalUI; + + $('#toast').html(message) + self.showDiv('#toast') + clearTimeout(self.notifyTimeOut); + if (!leaveOpen) { + self.notifyTimeOut = setTimeout(function () { + self.hideDiv('#toast') + }, 8000); + } + }, + clearNotify: function() { + var self = GlobalUI; + + clearTimeout(self.notifyTimeOut); + self.hideDiv('#toast') + }, + shareInvite: function(inviteLink) { + window.prompt("To copy the invite link, press: Ctrl+C, Enter", inviteLink); + } +} + +GlobalUI.CreateMap = { + newMap: null, + emptyMapForm: "", + emptyForkMapForm: "", + topicsToMap: [], + synapsesToMap: [], + init: function () { + var self = GlobalUI.CreateMap; + + self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' }); + + self.bindFormEvents(); + + self.emptyMapForm = $('#new_map').html(); + + }, + bindFormEvents: function () { + var self = GlobalUI.CreateMap; + + $('.new_map input, .new_map div').unbind('keypress').bind('keypress', function(event) { + if (event.keyCode === 13) self.submit() + }) + + $('.new_map button.cancel').unbind().bind('click', function (event) { + event.preventDefault(); + GlobalUI.closeLightbox(); + }); + $('.new_map button.submitMap').unbind().bind('click', self.submit); + + // bind permission changer events on the createMap form + $('.permIcon').unbind().bind('click', self.switchPermission); + }, + closeSuccess: function () { + $('#mapCreatedSuccess').fadeOut(300, function(){ + $(this).remove(); + }); + }, + generateSuccessMessage: function (id) { + var stringStart = "
SUCCESS!
Your map has been created. Do you want to: Go to your new map"; + stringStart += "ORStay on this "; + var page = Active.Map ? 'map' : 'page'; + var stringEnd = "
"; + return stringStart + page + stringEnd; + }, + switchPermission: function () { + var self = GlobalUI.CreateMap; + + self.newMap.set('permission', $(this).attr('data-permission')); + $(this).siblings('.permIcon').find('.mapPermIcon').removeClass('selected'); + $(this).find('.mapPermIcon').addClass('selected'); + + var permText = $(this).find('.tip').html(); + $(this).parents('.new_map').find('.permText').html(permText); + }, + submit: function (event) { + if (event) event.preventDefault(); + + var self = GlobalUI.CreateMap; + + if (GlobalUI.lightbox === 'forkmap') { + self.newMap.set('topicsToMap', self.topicsToMap); + self.newMap.set('synapsesToMap', self.synapsesToMap); + } + + var formId = GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; + var $form = $(formId); + + self.newMap.set('name', $form.find('#map_name').val()); + self.newMap.set('desc', $form.find('#map_desc').val()); + + if (self.newMap.get('name').length===0){ + self.throwMapNameError(); + return; + } + + self.newMap.save(null, { + success: self.success + // TODO add error message + }); + + GlobalUI.closeLightbox(); + GlobalUI.notifyUser('Working...'); + }, + throwMapNameError: function () { + var self = GlobalUI.CreateMap; + + var formId = GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; + var $form = $(formId); + + var message = $(""); + + $form.find('#map_name').after(message); + setTimeout(function(){ + message.fadeOut('fast', function(){ + message.remove(); + }); + }, 5000); + }, + success: function (model) { + var self = GlobalUI.CreateMap; + + //push the new map onto the collection of 'my maps' + Metamaps.Maps.Mine.add(model); + + var formId = GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; + var form = $(formId); + + GlobalUI.clearNotify(); + $('#wrapper').append(self.generateSuccessMessage(model.id)); + + }, + reset: function (id) { + var self = GlobalUI.CreateMap; + + var form = $('#' + id); + + if (id === "fork_map") { + self.topicsToMap = []; + self.synapsesToMap = []; + form.html(self.emptyForkMapForm); + } + else { + form.html(self.emptyMapForm); + } + + self.bindFormEvents(); + self.newMap = new Metamaps.Backbone.Map({ permission: 'commons' }); + + return false; + }, +} + +GlobalUI.Account = { + isOpen: false, + changing: false, + init: function () { + var self = GlobalUI.Account; + + $('.sidebarAccountIcon').click(self.toggleBox); + $('.sidebarAccountBox').click(function(event){ + event.stopPropagation(); + }); + $('body').click(self.close); + }, + toggleBox: function (event) { + var self = GlobalUI.Account; + + if (self.isOpen) self.close(); + else self.open(); + + event.stopPropagation(); + }, + open: function () { + var self = GlobalUI.Account; + + Filter.close(); + $('.sidebarAccountIcon .tooltipsUnder').addClass('hide'); + + + if (!self.isOpen && !self.changing) { + self.changing = true; + $('.sidebarAccountBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + $('.sidebarAccountBox #user_email').focus(); + }); + } + }, + close: function () { + var self = GlobalUI.Account; + + $('.sidebarAccountIcon .tooltipsUnder').removeClass('hide'); + if (!self.changing) { + self.changing = true; + $('.sidebarAccountBox #user_email').blur(); + $('.sidebarAccountBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + } +} + +GlobalUI.Search = { + locked: false, + isOpen: false, + limitTopicsToMe: false, + limitMapsToMe: false, + timeOut: null, + changing: false, + optionsInitialized: false, + init: function () { + var self = GlobalUI.Search; + + var loader = new CanvasLoader('searchLoading'); + loader.setColor('#4fb5c0'); // default is '#000000' + loader.setDiameter(24); // default is 40 + loader.setDensity(41); // default is 40 + loader.setRange(0.9); // default is 1.3 + loader.show(); // Hidden by default + + // bind the hover events + $(".sidebarSearch").hover(function () { + self.open() + }, function () { + self.close(800, false) + }); + + $('.sidebarSearchIcon').click(function (e) { + $('.sidebarSearchField').focus(); + }); + $('.sidebarSearch').click(function (e) { + e.stopPropagation(); + }); + $('body').click(function (e) { + self.close(0, false); + }); + + // open if the search is closed and user hits ctrl+/ + // close if they hit ESC + $('body').bind('keyup', function (e) { + switch (e.which) { + case 191: + if ((e.ctrlKey && !self.isOpen) || (e.ctrlKey && self.locked)) { + self.open(true); // true for focus + } + break; + case 27: + if (self.isOpen) { + self.close(0, true); + } + break; + + default: + break; //console.log(e.which); + } + }); + + self.startTypeahead(); + }, + lock: function() { + var self = GlobalUI.Search; + self.locked = true; + }, + unlock: function() { + var self = GlobalUI.Search; + self.locked = false; + }, + open: function (focus) { + var self = GlobalUI.Search; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing && !self.locked) { + self.changing = true; + $('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({ + width: '400px' + }, 300, function () { + if (focus) $('.sidebarSearchField').focus(); + $('.sidebarSearchField, .sidebarSearch .tt-hint').css({ + padding: '7px 10px 3px 10px', + width: '380px' + }); + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function (closeAfter, bypass) { + // for now + return + + var self = GlobalUI.Search; + + self.timeOut = setTimeout(function () { + if (!self.locked && !self.changing && self.isOpen && (bypass || $('.sidebarSearchField.tt-input').val() == '')) { + self.changing = true; + $('.sidebarSearchField, .sidebarSearch .tt-hint').css({ + padding: '7px 0 3px 0', + width: '400px' + }); + $('.sidebarSearch .twitter-typeahead, .sidebarSearch .tt-hint, .sidebarSearchField').animate({ + width: '0' + }, 300, function () { + $('.sidebarSearchField').typeahead('val', ''); + $('.sidebarSearchField').blur(); + self.changing = false; + self.isOpen = false; + }); + } + }, closeAfter); + }, + startTypeahead: function () { + var self = GlobalUI.Search; + + var mapheader = Active.Mapper ? '

Maps

' : '

Maps

'; + var topicheader = Active.Mapper ? '

Topics

' : '

Topics

'; + var mapperheader = '

Mappers

'; + + var topics = { + name: 'topics', + limit: 9999, + + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile(topicheader + $('#topicSearchTemplate').html()).render({ + value: "No results", + label: "No results", + typeImageURL: Metamaps.Erb['icons/wildcard.png'], + rtype: "noresult" + }); + }, + header: topicheader, + suggestion: function(s) { + return Hogan.compile($('#topicSearchTemplate').html()).render(s); + }, + }, + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/topics', + prepare: function(query, settings) { + settings.url += '?term=' + query; + if (Active.Mapper && self.limitTopicsToMe) { + settings.url += "&user=" + Active.Mapper.id.toString(); + } + return settings; + }, + }, + }), + }; + + var maps = { + name: 'maps', + limit: 9999, + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile(mapheader + $('#mapSearchTemplate').html()).render({ + value: "No results", + label: "No results", + rtype: "noresult" + }); + }, + header: mapheader, + suggestion: function(s) { + return Hogan.compile($('#mapSearchTemplate').html()).render(s); + }, + }, + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/maps', + prepare: function(query, settings) { + settings.url += '?term=' + query; + if (Active.Mapper && self.limitMapsToMe) { + settings.url += "&user=" + Active.Mapper.id.toString(); + } + return settings; + }, + }, + }), + }; + + var mappers = { + name: 'mappers', + limit: 9999, + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile(mapperheader + $('#mapperSearchTemplate').html()).render({ + value: "No results", + label: "No results", + rtype: "noresult", + profile: Metamaps.Erb['user.png'] + }); + }, + header: mapperheader, + suggestion: function(s) { + return Hogan.compile($('#mapperSearchTemplate').html()).render(s); + }, + }, + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/mappers?term=%QUERY', + wildcard: '%QUERY', + }, + }), + }; + + // Take all that crazy setup data and put it together into one beautiful typeahead call! + $('.sidebarSearchField').typeahead( + { + highlight: true, + }, + [topics, maps, mappers] + ); + + //Set max height of the search results box to prevent it from covering bottom left footer + $('.sidebarSearchField').bind('typeahead:render', function (event) { + self.initSearchOptions(); + self.hideLoader(); + var h = $(window).height(); + $(".tt-dropdown-menu").css('max-height', h - 100); + if (self.limitTopicsToMe) { + $('#limitTopicsToMe').prop('checked', true); + } + if (self.limitMapsToMe) { + $('#limitMapsToMe').prop('checked', true); + } + }); + $(window).resize(function () { + var h = $(window).height(); + $(".tt-dropdown-menu").css('max-height', h - 100); + }); + + // tell the autocomplete to launch a new tab with the topic, map, or mapper you clicked on + $('.sidebarSearchField').bind('typeahead:select', self.handleResultClick); + + // don't do it, if they clicked on a 'addToMap' button + $('.sidebarSearch button.addToMap').click(function (event) { + event.stopPropagation(); + }); + + // make sure that when you click on 'limit to me' or 'toggle section' it works + $('.sidebarSearchField.tt-input').keyup(function(){ + if ($('.sidebarSearchField.tt-input').val() === '') { + self.hideLoader(); + } else { + self.showLoader(); + } + }); + + }, + handleResultClick: function (event, datum, dataset) { + var self = GlobalUI.Search; + + self.hideLoader(); + + if (["topic", "map", "mapper"].indexOf(datum.rtype) !== -1) { + self.close(0, true); + var win; + if (datum.rtype == "topic") { + Router.topics(datum.id); + } else if (datum.rtype == "map") { + Router.maps(datum.id); + } else if (datum.rtype == "mapper") { + Router.explore("mapper", datum.id); + } + } + }, + initSearchOptions: function () { + var self = GlobalUI.Search; + + function toggleResultSet(set) { + var s = $('.tt-dataset-' + set + ' .tt-suggestion, .tt-dataset-' + set + ' .resultnoresult'); + if (s.is(':visible')) { + s.hide(); + $(this).removeClass('minimizeResults').addClass('maximizeResults'); + } else { + s.show(); + $(this).removeClass('maximizeResults').addClass('minimizeResults'); + } + } + + $('.limitToMe').unbind().bind("change", function (e) { + if ($(this).attr('id') == 'limitTopicsToMe') { + self.limitTopicsToMe = !self.limitTopicsToMe; + } + if ($(this).attr('id') == 'limitMapsToMe') { + self.limitMapsToMe = !self.limitMapsToMe; + } + + // set the value of the search equal to itself to retrigger the + // autocomplete event + var searchQuery = $('.sidebarSearchField.tt-input').val(); + $(".sidebarSearchField").typeahead('val', '') + .typeahead('val', searchQuery); + }); + + // when the user clicks minimize section, hide the results for that section + $('.minimizeMapperResults').unbind().click(function (e) { + toggleResultSet.call(this, 'mappers'); + }); + $('.minimizeTopicResults').unbind().click(function (e) { + toggleResultSet.call(this, 'topics'); + }); + $('.minimizeMapResults').unbind().click(function (e) { + toggleResultSet.call(this, 'maps'); + }); + }, + hideLoader: function () { + $('#searchLoading').hide(); + }, + showLoader: function () { + $('#searchLoading').show(); + } +} + +export default GlobalUI diff --git a/app/assets/javascripts/src/Metamaps.Import.js b/frontend/src/Metamaps/Import.js similarity index 92% rename from app/assets/javascripts/src/Metamaps.Import.js rename to frontend/src/Metamaps/Import.js index 2dee51d0..d5a4b4e1 100644 --- a/app/assets/javascripts/src/Metamaps.Import.js +++ b/frontend/src/Metamaps/Import.js @@ -1,19 +1,23 @@ /* global Metamaps, $ */ +import Active from './Active' +import GlobalUI from './GlobalUI' +import Map from './Map' +import Synapse from './Synapse' +import Topic from './Topic' + /* * Metamaps.Import.js.erb * * Dependencies: - * - Metamaps.Active * - Metamaps.Backbone - * - Metamaps.Map * - Metamaps.Mappings * - Metamaps.Metacodes * - Metamaps.Synapses * - Metamaps.Topics */ -Metamaps.Import = { +const Import = { // note that user is not imported topicWhitelist: [ 'id', 'name', 'metacode', 'x', 'y', 'description', 'link', 'permission' @@ -24,19 +28,19 @@ Metamaps.Import = { cidMappings: {}, // to be filled by import_id => cid mappings handleTSV: function (text) { - var self = Metamaps.Import + var self = Import results = self.parseTabbedString(text) self.handle(results) }, handleJSON: function (text) { - var self = Metamaps.Import + var self = Import results = JSON.parse(text) self.handle(results) }, handle: function(results) { - var self = Metamaps.Import + var self = Import var topics = results.topics var synapses = results.synapses @@ -60,7 +64,7 @@ Metamaps.Import = { }, parseTabbedString: function (text) { - var self = Metamaps.Import + var self = Import // determine line ending and split lines var delim = '\n' @@ -186,7 +190,7 @@ Metamaps.Import = { }, importTopics: function (parsedTopics) { - var self = Metamaps.Import + var self = Import // up to 25 topics: scale 100 // up to 81 topics: scale 200 @@ -219,7 +223,7 @@ Metamaps.Import = { }, importSynapses: function (parsedSynapses) { - var self = Metamaps.Import + var self = Import parsedSynapses.forEach(function (synapse) { // only createSynapseWithParameters once both topics are persisted @@ -255,16 +259,16 @@ Metamaps.Import = { createTopicWithParameters: function (name, metacode_name, permission, desc, link, xloc, yloc, import_id, opts) { - var self = Metamaps.Import - $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + var self = Import + $(document).trigger(Map.events.editedByActiveMapper) var metacode = Metamaps.Metacodes.where({name: metacode_name})[0] || null if (metacode === null) { metacode = Metamaps.Metacodes.where({ name: 'Wildcard' })[0] console.warn("Couldn't find metacode " + metacode_name + ' so used Wildcard instead.') } - var topic_permission = permission || Metamaps.Active.Map.get('permission') - var defer_to_map_id = permission === topic_permission ? Metamaps.Active.Map.get('id') : null + var topic_permission = permission || Active.Map.get('permission') + var defer_to_map_id = permission === topic_permission ? Active.Map.get('id') : null var topic = new Metamaps.Backbone.Topic({ name: name, metacode_id: metacode.id, @@ -272,7 +276,7 @@ Metamaps.Import = { defer_to_map_id: defer_to_map_id, desc: desc || "", link: link || "", - calculated_permission: Metamaps.Active.Map.get('permission') + calculated_permission: Active.Map.get('permission') }) Metamaps.Topics.add(topic) @@ -289,11 +293,11 @@ Metamaps.Import = { Metamaps.Mappings.add(mapping) // this function also includes the creation of the topic in the database - Metamaps.Topic.renderTopic(mapping, topic, true, true, { + Topic.renderTopic(mapping, topic, true, true, { success: opts.success }) - Metamaps.GlobalUI.hideDiv('#instructions') + GlobalUI.hideDiv('#instructions') }, createSynapseWithParameters: function (desc, category, permission, @@ -321,6 +325,8 @@ Metamaps.Import = { }) Metamaps.Mappings.add(mapping) - Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, true) + Synapse.renderSynapse(mapping, synapse, node1, node2, true) } } + +export default Import diff --git a/app/assets/javascripts/src/Metamaps.JIT.js b/frontend/src/Metamaps/JIT.js similarity index 74% rename from app/assets/javascripts/src/Metamaps.JIT.js rename to frontend/src/Metamaps/JIT.js index d5e82081..5eccbc6c 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js +++ b/frontend/src/Metamaps/JIT.js @@ -1,4 +1,42 @@ -Metamaps.JIT = { +/* global Metamaps, $jit */ + +import _ from 'lodash' + +import $jit from '../patched/JIT' + +import Active from './Active' +import Control from './Control' +import Create from './Create' +import Filter from './Filter' +import GlobalUI from './GlobalUI' +import Map from './Map' +import Mouse from './Mouse' +import Realtime from './Realtime' +import Selected from './Selected' +import Settings from './Settings' +import Synapse from './Synapse' +import SynapseCard from './SynapseCard' +import Topic from './Topic' +import TopicCard from './TopicCard' +import Util from './Util' +import Visualize from './Visualize' + + +/* + * Metamaps.Erb + * Metamaps.Mappings + * Metamaps.Metacodes + * Metamaps.Synapses + * Metamaps.Topics + */ + +let panningInt + +const JIT = { + tempInit: false, + tempNode: null, + tempNode2: null, + events: { topicDrag: 'Metamaps:JIT:events:topicDrag', newTopic: 'Metamaps:JIT:events:newTopic', @@ -16,17 +54,17 @@ Metamaps.JIT = { * This method will bind the event handlers it is interested and initialize the class. */ init: function () { - var self = Metamaps.JIT + var self = JIT $('.zoomIn').click(self.zoomIn) $('.zoomOut').click(self.zoomOut) var zoomExtents = function (event) { - self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas) + self.zoomExtents(event, Visualize.mGraph.canvas) } $('.zoomExtents').click(zoomExtents) - $('.takeScreenshot').click(Metamaps.Map.exportImage) + $('.takeScreenshot').click(Map.exportImage) self.topicDescImage = new Image() self.topicDescImage.src = Metamaps.Erb['topic_description_signifier.png'] @@ -61,18 +99,18 @@ Metamaps.JIT = { synapsesToRemove.push(s) } else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { - existingEdge = _.findWhere(edges, { + existingEdge = _.find(edges, { nodeFrom: edge.nodeFrom, nodeTo: edge.nodeTo }) || - _.findWhere(edges, { + _.find(edges, { nodeFrom: edge.nodeTo, nodeTo: edge.nodeFrom }) if (existingEdge) { // for when you're dealing with multiple relationships between the same two topics - if (Metamaps.Active.Map) { + if (Active.Map) { mapping = s.getMapping() existingEdge.data['$mappingIDs'].push(mapping.id) } @@ -92,12 +130,12 @@ Metamaps.JIT = { return [jitReady, synapsesToRemove] }, prepareVizData: function () { - var self = Metamaps.JIT + var self = JIT var mapping // reset/empty vizData self.vizData = [] - Metamaps.Visualize.loadLater = false + Visualize.loadLater = false var results = self.convertModelsToJIT(Metamaps.Topics, Metamaps.Synapses) @@ -113,12 +151,12 @@ Metamaps.JIT = { if (self.vizData.length == 0) { $('#instructions div').hide() $('#instructions div.addTopic').show() - Metamaps.GlobalUI.showDiv('#instructions') - Metamaps.Visualize.loadLater = true + GlobalUI.showDiv('#instructions') + Visualize.loadLater = true } - else Metamaps.GlobalUI.hideDiv('#instructions') + else GlobalUI.hideDiv('#instructions') - Metamaps.Visualize.render() + Visualize.render() }, // prepareVizData edgeRender: function (adj, canvas) { // get nodes cartesian coordinates @@ -143,10 +181,10 @@ Metamaps.JIT = { // label placement on edges if (canvas.denySelected) { - var color = Metamaps.Settings.colors.synapses.normal + var color = Settings.colors.synapses.normal canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color } - Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas) + JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas) // check for edge label in data var desc = synapse.get('desc') @@ -183,7 +221,7 @@ Metamaps.JIT = { if (!canvas.denySelected && desc != '' && showDesc) { // '&' to '&' - desc = Metamaps.Util.decodeEntities(desc) + desc = Util.decodeEntities(desc) // now adjust the label placement var ctx = canvas.getCtx() @@ -191,7 +229,7 @@ Metamaps.JIT = { ctx.fillStyle = '#FFF' ctx.textBaseline = 'alphabetic' - var arrayOfLabelLines = Metamaps.Util.splitLine(desc, 30).split('\n') + var arrayOfLabelLines = Util.splitLine(desc, 30).split('\n') var index, lineWidths = [] for (index = 0; index < arrayOfLabelLines.length; ++index) { lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) @@ -247,19 +285,21 @@ Metamaps.JIT = { ForceDirected: { animateSavedLayout: { modes: ['linear'], - transition: $jit.Trans.Quad.easeInOut, + // TODO fix tests so we don't need _.get + transition: _.get($jit, 'Trans.Quad.easeInOut'), duration: 800, onComplete: function () { - Metamaps.Visualize.mGraph.busy = false - $(document).trigger(Metamaps.JIT.events.animationDone) + Visualize.mGraph.busy = false + $(document).trigger(JIT.events.animationDone) } }, animateFDLayout: { modes: ['linear'], - transition: $jit.Trans.Elastic.easeOut, + // TODO fix tests so we don't need _.get + transition: _.get($jit, 'Trans.Elastic.easeOut'), duration: 800, onComplete: function () { - Metamaps.Visualize.mGraph.busy = false + Visualize.mGraph.busy = false } }, graphSettings: { @@ -298,7 +338,7 @@ Metamaps.JIT = { }, Edge: { overridable: true, - color: Metamaps.Settings.colors.synapses.normal, + color: Settings.colors.synapses.normal, type: 'customEdge', lineWidth: 2, alpha: 1 @@ -309,7 +349,7 @@ Metamaps.JIT = { size: 20, family: 'arial', textBaseline: 'alphabetic', - color: Metamaps.Settings.colors.labels.text + color: Settings.colors.labels.text }, // Add Tips Tips: { @@ -321,26 +361,26 @@ Metamaps.JIT = { enable: true, enableForEdges: true, onMouseMove: function (node, eventInfo, e) { - Metamaps.JIT.onMouseMoveHandler(node, eventInfo, e) + JIT.onMouseMoveHandler(node, eventInfo, e) // console.log('called mouse move handler') }, // Update node positions when dragged onDragMove: function (node, eventInfo, e) { - Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e) + JIT.onDragMoveTopicHandler(node, eventInfo, e) // console.log('called drag move handler') }, onDragEnd: function (node, eventInfo, e) { - Metamaps.JIT.onDragEndTopicHandler(node, eventInfo, e, false) + JIT.onDragEndTopicHandler(node, eventInfo, e, false) // console.log('called drag end handler') }, onDragCancel: function (node, eventInfo, e) { - Metamaps.JIT.onDragCancelHandler(node, eventInfo, e, false) + JIT.onDragCancelHandler(node, eventInfo, e, false) }, // Implement the same handler for touchscreens onTouchStart: function (node, eventInfo, e) {}, // Implement the same handler for touchscreens onTouchMove: function (node, eventInfo, e) { - Metamaps.JIT.onDragMoveTopicHandler(node, eventInfo, e) + JIT.onDragMoveTopicHandler(node, eventInfo, e) }, // Implement the same handler for touchscreens onTouchEnd: function (node, eventInfo, e) {}, @@ -351,27 +391,27 @@ Metamaps.JIT = { // remove the rightclickmenu $('.rightclickmenu').remove() - if (Metamaps.Mouse.boxStartCoordinates) { + if (Mouse.boxStartCoordinates) { if (e.ctrlKey) { - Metamaps.Visualize.mGraph.busy = false - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() + Visualize.mGraph.busy = false + Mouse.boxEndCoordinates = eventInfo.getPos() - var bS = Metamaps.Mouse.boxStartCoordinates - var bE = Metamaps.Mouse.boxEndCoordinates + var bS = Mouse.boxStartCoordinates + var bE = Mouse.boxEndCoordinates if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { - Metamaps.JIT.zoomToBox(e) + JIT.zoomToBox(e) return } else { - Metamaps.Mouse.boxStartCoordinates = null - Metamaps.Mouse.boxEndCoordinates = null + Mouse.boxStartCoordinates = null + Mouse.boxEndCoordinates = null } // console.log('called zoom to box') } if (e.shiftKey) { - Metamaps.Visualize.mGraph.busy = false - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() - Metamaps.JIT.selectWithBox(e) + Visualize.mGraph.busy = false + Mouse.boxEndCoordinates = eventInfo.getPos() + JIT.selectWithBox(e) // console.log('called select with box') return } @@ -381,13 +421,13 @@ Metamaps.JIT = { // clicking on a edge, node, or clicking on blank part of canvas? if (node.nodeFrom) { - Metamaps.JIT.selectEdgeOnClickHandler(node, e) + JIT.selectEdgeOnClickHandler(node, e) // console.log('called selectEdgeOnClickHandler') } else if (node && !node.nodeFrom) { - Metamaps.JIT.selectNodeOnClickHandler(node, e) + JIT.selectNodeOnClickHandler(node, e) // console.log('called selectNodeOnClickHandler') } else { - Metamaps.JIT.canvasClickHandler(eventInfo.getPos(), e) + JIT.canvasClickHandler(eventInfo.getPos(), e) // console.log('called canvasClickHandler') } // if }, @@ -396,10 +436,10 @@ Metamaps.JIT = { // remove the rightclickmenu $('.rightclickmenu').remove() - if (Metamaps.Mouse.boxStartCoordinates) { - Metamaps.Visualize.mGraph.busy = false - Metamaps.Mouse.boxEndCoordinates = eventInfo.getPos() - Metamaps.JIT.selectWithBox(e) + if (Mouse.boxStartCoordinates) { + Visualize.mGraph.busy = false + Mouse.boxEndCoordinates = eventInfo.getPos() + JIT.selectWithBox(e) return } @@ -407,9 +447,9 @@ Metamaps.JIT = { // clicking on a edge, node, or clicking on blank part of canvas? if (node.nodeFrom) { - Metamaps.JIT.selectEdgeOnRightClickHandler(node, e) + JIT.selectEdgeOnRightClickHandler(node, e) } else if (node && !node.nodeFrom) { - Metamaps.JIT.selectNodeOnRightClickHandler(node, e) + JIT.selectNodeOnRightClickHandler(node, e) } else { // console.log('right clicked on open space') } @@ -433,7 +473,7 @@ Metamaps.JIT = { if (!canvas.denySelected && node.selected) { ctx.beginPath() ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false) - ctx.strokeStyle = Metamaps.Settings.colors.topics.selected + ctx.strokeStyle = Settings.colors.topics.selected ctx.lineWidth = 2 ctx.stroke() } @@ -453,7 +493,7 @@ Metamaps.JIT = { // if the topic has a link, draw a small image to indicate that var hasLink = topic && topic.get('link') !== '' && topic.get('link') !== null - var linkImage = Metamaps.JIT.topicLinkImage + var linkImage = JIT.topicLinkImage var linkImageLoaded = linkImage.complete || (typeof linkImage.naturalWidth !== 'undefined' && linkImage.naturalWidth !== 0) @@ -463,7 +503,7 @@ Metamaps.JIT = { // if the topic has a desc, draw a small image to indicate that var hasDesc = topic && topic.get('desc') !== '' && topic.get('desc') !== null - var descImage = Metamaps.JIT.topicDescImage + var descImage = JIT.topicDescImage var descImageLoaded = descImage.complete || (typeof descImage.naturalWidth !== 'undefined' && descImage.naturalWidth !== 0) @@ -474,8 +514,8 @@ Metamaps.JIT = { 'contains': function (node, pos) { var npos = node.pos.getc(true), dim = node.getData('dim'), - arrayOfLabelLines = Metamaps.Util.splitLine(node.name, 30).split('\n'), - ctx = Metamaps.Visualize.mGraph.canvas.getCtx() + arrayOfLabelLines = Util.splitLine(node.name, 30).split('\n'), + ctx = Visualize.mGraph.canvas.getCtx() var height = 25 * arrayOfLabelLines.length @@ -498,7 +538,7 @@ Metamaps.JIT = { edgeSettings: { 'customEdge': { 'render': function (adj, canvas) { - Metamaps.JIT.edgeRender(adj, canvas) + JIT.edgeRender(adj, canvas) }, 'contains': function (adj, pos) { var from = adj.nodeFrom.pos.getc(), @@ -517,10 +557,11 @@ Metamaps.JIT = { ForceDirected3D: { animate: { modes: ['linear'], - transition: $jit.Trans.Elastic.easeOut, + // TODO fix tests so we don't need _.get + transition: _.get($jit, 'Trans.Elastic.easeOut'), duration: 2500, onComplete: function () { - Metamaps.Visualize.mGraph.busy = false + Visualize.mGraph.busy = false } }, graphSettings: { @@ -581,13 +622,13 @@ Metamaps.JIT = { onMouseMove: function (node, eventInfo, e) { // if(this.i++ % 3) return var pos = eventInfo.getPos() - Metamaps.Visualize.cameraPosition.x += (pos.x - Metamaps.Visualize.cameraPosition.x) * 0.5 - Metamaps.Visualize.cameraPosition.y += (-pos.y - Metamaps.Visualize.cameraPosition.y) * 0.5 - Metamaps.Visualize.mGraph.plot() + Visualize.cameraPosition.x += (pos.x - Visualize.cameraPosition.x) * 0.5 + Visualize.cameraPosition.y += (-pos.y - Visualize.cameraPosition.y) * 0.5 + Visualize.mGraph.plot() }, onMouseWheel: function (delta) { - Metamaps.Visualize.cameraPosition.z += -delta * 20 - Metamaps.Visualize.mGraph.plot() + Visualize.cameraPosition.z += -delta * 20 + Visualize.mGraph.plot() }, onClick: function () {} }, @@ -608,7 +649,7 @@ Metamaps.JIT = { modes: ['polar'], duration: 800, onComplete: function () { - Metamaps.Visualize.mGraph.busy = false + Visualize.mGraph.busy = false } }, // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ @@ -628,10 +669,10 @@ Metamaps.JIT = { // don't do anything if the edge is filtered // or if the canvas is animating - if (filtered || Metamaps.Visualize.mGraph.busy) return + if (filtered || Visualize.mGraph.busy) return $('canvas').css('cursor', 'pointer') - var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge) + var edgeIsSelected = Selected.Edges.indexOf(edge) // following if statement only executes if the edge being hovered over is not selected if (edgeIsSelected == -1) { edge.setData('showDesc', true, 'current') @@ -640,16 +681,16 @@ Metamaps.JIT = { edge.setDataset('end', { lineWidth: 4 }) - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth'], duration: 100 }) - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() }, // onMouseEnter onMouseLeave: function (edge) { if (edge.getData('alpha') === 0) return; // don't do anything if the edge is filtered $('canvas').css('cursor', 'default') - var edgeIsSelected = Metamaps.Selected.Edges.indexOf(edge) + var edgeIsSelected = Selected.Edges.indexOf(edge) // following if statement only executes if the edge being hovered over is not selected if (edgeIsSelected == -1) { edge.setData('showDesc', false, 'current') @@ -658,75 +699,75 @@ Metamaps.JIT = { edge.setDataset('end', { lineWidth: 2 }) - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth'], duration: 100 }) - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() }, // onMouseLeave onMouseMoveHandler: function (node, eventInfo, e) { - var self = Metamaps.JIT + var self = JIT - if (Metamaps.Visualize.mGraph.busy) return + if (Visualize.mGraph.busy) return var node = eventInfo.getNode() var edge = eventInfo.getEdge() // if we're on top of a node object, act like there aren't edges under it if (node != false) { - if (Metamaps.Mouse.edgeHoveringOver) { - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + if (Mouse.edgeHoveringOver) { + self.onMouseLeave(Mouse.edgeHoveringOver) } $('canvas').css('cursor', 'pointer') return } - if (edge == false && Metamaps.Mouse.edgeHoveringOver != false) { + if (edge == false && Mouse.edgeHoveringOver != false) { // mouse not on an edge, but we were on an edge previously - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) - } else if (edge != false && Metamaps.Mouse.edgeHoveringOver == false) { + self.onMouseLeave(Mouse.edgeHoveringOver) + } else if (edge != false && Mouse.edgeHoveringOver == false) { // mouse is on an edge, but there isn't a stored edge self.onMouseEnter(edge) - } else if (edge != false && Metamaps.Mouse.edgeHoveringOver != edge) { + } else if (edge != false && Mouse.edgeHoveringOver != edge) { // mouse is on an edge, but a different edge is stored - self.onMouseLeave(Metamaps.Mouse.edgeHoveringOver) + self.onMouseLeave(Mouse.edgeHoveringOver) self.onMouseEnter(edge) } // could be false - Metamaps.Mouse.edgeHoveringOver = edge + Mouse.edgeHoveringOver = edge if (!node && !edge) { $('canvas').css('cursor', 'default') } }, // onMouseMoveHandler enterKeyHandler: function () { - var creatingMap = Metamaps.GlobalUI.lightbox + var creatingMap = GlobalUI.lightbox if (creatingMap === 'newmap' || creatingMap === 'forkmap') { - Metamaps.GlobalUI.CreateMap.submit() + GlobalUI.CreateMap.submit() } // this is to submit new topic creation - else if (Metamaps.Create.newTopic.beingCreated) { - Metamaps.Topic.createTopicLocally() + else if (Create.newTopic.beingCreated) { + Topic.createTopicLocally() } // to submit new synapse creation - else if (Metamaps.Create.newSynapse.beingCreated) { - Metamaps.Synapse.createSynapseLocally() + else if (Create.newSynapse.beingCreated) { + Synapse.createSynapseLocally() } }, // enterKeyHandler escKeyHandler: function () { - Metamaps.Control.deselectAllEdges() - Metamaps.Control.deselectAllNodes() + Control.deselectAllEdges() + Control.deselectAllNodes() }, // escKeyHandler onDragMoveTopicHandler: function (node, eventInfo, e) { - var self = Metamaps.JIT + var self = JIT // this is used to send nodes that are moving to // other realtime collaborators on the same map var positionsToSend = {} var topic - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper) if (node && !node.nodeFrom) { var pos = eventInfo.getPos() @@ -742,34 +783,34 @@ Metamaps.JIT = { } else if (whatToDo == 'only-drag-this-one') { node.pos.setc(pos.x, pos.y) - if (Metamaps.Active.Map) { + if (Active.Map) { topic = node.getData('topic') // we use the topic ID not the node id // because we can't depend on the node id // to be the same as on other collaborators // maps positionsToSend[topic.id] = pos - $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]) + $(document).trigger(JIT.events.topicDrag, [positionsToSend]) } } else { - var len = Metamaps.Selected.Nodes.length + var len = Selected.Nodes.length // first define offset for each node var xOffset = [] var yOffset = [] for (var i = 0; i < len; i += 1) { - var n = Metamaps.Selected.Nodes[i] + var n = Selected.Nodes[i] xOffset[i] = n.pos.x - node.pos.x yOffset[i] = n.pos.y - node.pos.y } // for for (var i = 0; i < len; i += 1) { - var n = Metamaps.Selected.Nodes[i] + var n = Selected.Nodes[i] var x = pos.x + xOffset[i] var y = pos.y + yOffset[i] n.pos.setc(x, y) - if (Metamaps.Active.Map) { + if (Active.Map) { topic = n.getData('topic') // we use the topic ID not the node id // because we can't depend on the node id @@ -779,64 +820,64 @@ Metamaps.JIT = { } } // for - if (Metamaps.Active.Map) { - $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]) + if (Active.Map) { + $(document).trigger(JIT.events.topicDrag, [positionsToSend]) } } // if if (whatToDo == 'deselect') { - Metamaps.Control.deselectNode(node) + Control.deselectNode(node) } - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } // if it's a right click or holding down alt, start synapse creation ->third option is for firefox else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) { - if (Metamaps.tempInit == false) { - Metamaps.tempNode = node - Metamaps.tempInit = true + if (JIT.tempInit == false) { + JIT.tempNode = node + JIT.tempInit = true - Metamaps.Create.newTopic.hide() - Metamaps.Create.newSynapse.hide() + Create.newTopic.hide() + Create.newSynapse.hide() // set the draw synapse start positions - var l = Metamaps.Selected.Nodes.length + var l = Selected.Nodes.length if (l > 0) { for (var i = l - 1; i >= 0; i -= 1) { - var n = Metamaps.Selected.Nodes[i] - Metamaps.Mouse.synapseStartCoordinates.push({ + var n = Selected.Nodes[i] + Mouse.synapseStartCoordinates.push({ x: n.pos.getc().x, y: n.pos.getc().y }) } } else { - Metamaps.Mouse.synapseStartCoordinates = [{ - x: Metamaps.tempNode.pos.getc().x, - y: Metamaps.tempNode.pos.getc().y + Mouse.synapseStartCoordinates = [{ + x: JIT.tempNode.pos.getc().x, + y: JIT.tempNode.pos.getc().y }] } - Metamaps.Mouse.synapseEndCoordinates = { + Mouse.synapseEndCoordinates = { x: pos.x, y: pos.y } } // - temp = eventInfo.getNode() - if (temp != false && temp.id != node.id && Metamaps.Selected.Nodes.indexOf(temp) == -1) { // this means a Node has been returned - Metamaps.tempNode2 = temp + let temp = eventInfo.getNode() + if (temp != false && temp.id != node.id && Selected.Nodes.indexOf(temp) == -1) { // this means a Node has been returned + JIT.tempNode2 = temp - Metamaps.Mouse.synapseEndCoordinates = { - x: Metamaps.tempNode2.pos.getc().x, - y: Metamaps.tempNode2.pos.getc().y + Mouse.synapseEndCoordinates = { + x: JIT.tempNode2.pos.getc().x, + y: JIT.tempNode2.pos.getc().y } // before making the highlighted one bigger, make sure all the others are regular size - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + Visualize.mGraph.graph.eachNode(function (n) { n.setData('dim', 25, 'current') }) temp.setData('dim', 35, 'current') - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } else if (!temp) { - Metamaps.tempNode2 = null - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + JIT.tempNode2 = null + Visualize.mGraph.graph.eachNode(function (n) { n.setData('dim', 25, 'current') }) // pop up node creation :) @@ -844,73 +885,73 @@ Metamaps.JIT = { var myY = e.clientY - 30 $('#new_topic').css('left', myX + 'px') $('#new_topic').css('top', myY + 'px') - Metamaps.Create.newTopic.x = eventInfo.getPos().x - Metamaps.Create.newTopic.y = eventInfo.getPos().y - Metamaps.Visualize.mGraph.plot() + Create.newTopic.x = eventInfo.getPos().x + Create.newTopic.y = eventInfo.getPos().y + Visualize.mGraph.plot() - Metamaps.Mouse.synapseEndCoordinates = { + Mouse.synapseEndCoordinates = { x: pos.x, y: pos.y } } } - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && Metamaps.Active.Topic) { - Metamaps.GlobalUI.notifyUser('Cannot create in Topic view.') + else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && Active.Topic) { + GlobalUI.notifyUser('Cannot create in Topic view.') } else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && !authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + GlobalUI.notifyUser('Cannot edit Public map.') } } }, // onDragMoveTopicHandler onDragCancelHandler: function (node, eventInfo, e) { - Metamaps.tempNode = null - if (Metamaps.tempNode2) Metamaps.tempNode2.setData('dim', 25, 'current') - Metamaps.tempNode2 = null - Metamaps.tempInit = false + JIT.tempNode = null + if (JIT.tempNode2) JIT.tempNode2.setData('dim', 25, 'current') + JIT.tempNode2 = null + JIT.tempInit = false // reset the draw synapse positions to false - Metamaps.Mouse.synapseStartCoordinates = [] - Metamaps.Mouse.synapseEndCoordinates = null - Metamaps.Visualize.mGraph.plot() + Mouse.synapseStartCoordinates = [] + Mouse.synapseEndCoordinates = null + Visualize.mGraph.plot() }, // onDragCancelHandler onDragEndTopicHandler: function (node, eventInfo, e) { var midpoint = {}, pixelPos, mapping - if (Metamaps.tempInit && Metamaps.tempNode2 == null) { + if (JIT.tempInit && JIT.tempNode2 == null) { // this means you want to add a new topic, and then a synapse - Metamaps.Create.newTopic.addSynapse = true - Metamaps.Create.newTopic.open() - } else if (Metamaps.tempInit && Metamaps.tempNode2 != null) { + Create.newTopic.addSynapse = true + Create.newTopic.open() + } else if (JIT.tempInit && JIT.tempNode2 != null) { // this means you want to create a synapse between two existing topics - Metamaps.Create.newTopic.addSynapse = false - Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id - Metamaps.Create.newSynapse.topic2id = Metamaps.tempNode2.getData('topic').id - Metamaps.tempNode2.setData('dim', 25, 'current') - Metamaps.Visualize.mGraph.plot() - midpoint.x = Metamaps.tempNode.pos.getc().x + (Metamaps.tempNode2.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2 - midpoint.y = Metamaps.tempNode.pos.getc().y + (Metamaps.tempNode2.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2 - pixelPos = Metamaps.Util.coordsToPixels(midpoint) + Create.newTopic.addSynapse = false + Create.newSynapse.topic1id = JIT.tempNode.getData('topic').id + Create.newSynapse.topic2id = JIT.tempNode2.getData('topic').id + JIT.tempNode2.setData('dim', 25, 'current') + Visualize.mGraph.plot() + midpoint.x = JIT.tempNode.pos.getc().x + (JIT.tempNode2.pos.getc().x - JIT.tempNode.pos.getc().x) / 2 + midpoint.y = JIT.tempNode.pos.getc().y + (JIT.tempNode2.pos.getc().y - JIT.tempNode.pos.getc().y) / 2 + pixelPos = Util.coordsToPixels(midpoint) $('#new_synapse').css('left', pixelPos.x + 'px') $('#new_synapse').css('top', pixelPos.y + 'px') - Metamaps.Create.newSynapse.open() - Metamaps.tempNode = null - Metamaps.tempNode2 = null - Metamaps.tempInit = false - } else if (!Metamaps.tempInit && node && !node.nodeFrom) { + Create.newSynapse.open() + JIT.tempNode = null + JIT.tempNode2 = null + JIT.tempInit = false + } else if (!JIT.tempInit && node && !node.nodeFrom) { // this means you dragged an existing node, autosave that to the database // check whether to save mappings var checkWhetherToSave = function () { - var map = Metamaps.Active.Map + var map = Active.Map if (!map) return false - var mapper = Metamaps.Active.Mapper + var mapper = Active.Mapper // this case // covers when it is a public map owned by you // and also when it's a private map var activeMappersMap = map.authorizePermissionChange(mapper) var commonsMap = map.get('permission') === 'commons' - var realtimeOn = Metamaps.Realtime.status + var realtimeOn = Realtime.status // don't save if commons map, and you have realtime off, // even if you're map creator @@ -924,9 +965,9 @@ Metamaps.JIT = { yloc: node.getPos().y }) // also save any other selected nodes that also got dragged along - var l = Metamaps.Selected.Nodes.length + var l = Selected.Nodes.length for (var i = l - 1; i >= 0; i -= 1) { - var n = Metamaps.Selected.Nodes[i] + var n = Selected.Nodes[i] if (n !== node) { mapping = n.getData('mapping') mapping.save({ @@ -940,61 +981,61 @@ Metamaps.JIT = { }, // onDragEndTopicHandler canvasClickHandler: function (canvasLoc, e) { // grab the location and timestamp of the click - var storedTime = Metamaps.Mouse.lastCanvasClick + var storedTime = Mouse.lastCanvasClick var now = Date.now() // not compatible with IE8 FYI - Metamaps.Mouse.lastCanvasClick = now + Mouse.lastCanvasClick = now - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper) - if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE && !Metamaps.Mouse.didPan) { - if (Metamaps.Active.Map && !authorized) { - Metamaps.GlobalUI.notifyUser('Cannot edit Public map.') + if (now - storedTime < Mouse.DOUBLE_CLICK_TOLERANCE && !Mouse.didPan) { + if (Active.Map && !authorized) { + GlobalUI.notifyUser('Cannot edit Public map.') return } - else if (Metamaps.Active.Topic) { - Metamaps.GlobalUI.notifyUser('Cannot create in Topic view.') + else if (Active.Topic) { + GlobalUI.notifyUser('Cannot create in Topic view.') return } // DOUBLE CLICK // pop up node creation :) - Metamaps.Create.newTopic.addSynapse = false - Metamaps.Create.newTopic.x = canvasLoc.x - Metamaps.Create.newTopic.y = canvasLoc.y + Create.newTopic.addSynapse = false + Create.newTopic.x = canvasLoc.x + Create.newTopic.y = canvasLoc.y $('#new_topic').css('left', e.clientX + 'px') $('#new_topic').css('top', e.clientY + 'px') - Metamaps.Create.newTopic.open() - } else if (!Metamaps.Mouse.didPan) { + Create.newTopic.open() + } else if (!Mouse.didPan) { // SINGLE CLICK, no pan - Metamaps.Filter.close() - Metamaps.TopicCard.hideCard() - Metamaps.SynapseCard.hideCard() - Metamaps.Create.newTopic.hide() + Filter.close() + TopicCard.hideCard() + SynapseCard.hideCard() + Create.newTopic.hide() $('.rightclickmenu').remove() // reset the draw synapse positions to false - Metamaps.Mouse.synapseStartCoordinates = [] - Metamaps.Mouse.synapseEndCoordinates = null - Metamaps.tempInit = false - Metamaps.tempNode = null - Metamaps.tempNode2 = null + Mouse.synapseStartCoordinates = [] + Mouse.synapseEndCoordinates = null + JIT.tempInit = false + JIT.tempNode = null + JIT.tempNode2 = null if (!e.ctrlKey && !e.shiftKey) { - Metamaps.Control.deselectAllEdges() - Metamaps.Control.deselectAllNodes() + Control.deselectAllEdges() + Control.deselectAllNodes() } } }, // canvasClickHandler nodeDoubleClickHandler: function (node, e) { - Metamaps.TopicCard.showCard(node) + TopicCard.showCard(node) }, // nodeDoubleClickHandler edgeDoubleClickHandler: function (adj, e) { - Metamaps.SynapseCard.showCard(adj, e) + SynapseCard.showCard(adj, e) }, // nodeDoubleClickHandler nodeWasDoubleClicked: function () { // grab the timestamp of the click - var storedTime = Metamaps.Mouse.lastNodeClick + var storedTime = Mouse.lastNodeClick var now = Date.now() // not compatible with IE8 FYI - Metamaps.Mouse.lastNodeClick = now + Mouse.lastNodeClick = now - if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE) { + if (now - storedTime < Mouse.DOUBLE_CLICK_TOLERANCE) { return true } else { return false @@ -1007,12 +1048,12 @@ Metamaps.JIT = { // 3 others are selected only, no shift: drag only this one // 4 this node and others were selected, so drag them (just return false) // return value: deselect node again after? - if (Metamaps.Selected.Nodes.length == 0) { + if (Selected.Nodes.length == 0) { return 'only-drag-this-one' } - if (Metamaps.Selected.Nodes.indexOf(node) == -1) { + if (Selected.Nodes.indexOf(node) == -1) { if (e.shiftKey) { - Metamaps.Control.selectNode(node, e) + Control.selectNode(node, e) return 'nothing' } else { return 'only-drag-this-one' @@ -1032,18 +1073,18 @@ Metamaps.JIT = { }, selectWithBox: function (e) { var self = this - var sX = Metamaps.Mouse.boxStartCoordinates.x, - sY = Metamaps.Mouse.boxStartCoordinates.y, - eX = Metamaps.Mouse.boxEndCoordinates.x, - eY = Metamaps.Mouse.boxEndCoordinates.y + var sX = Mouse.boxStartCoordinates.x, + sY = Mouse.boxStartCoordinates.y, + eX = Mouse.boxEndCoordinates.x, + eY = Mouse.boxEndCoordinates.y if (!e.shiftKey) { - Metamaps.Control.deselectAllNodes() - Metamaps.Control.deselectAllEdges() + Control.deselectAllNodes() + Control.deselectAllEdges() } // select all nodes that are within the box - Metamaps.Visualize.mGraph.graph.eachNode(function(n) { + Visualize.mGraph.graph.eachNode(function(n) { var pos = self.getNodeXY(n) var x = pos.x, y = pos.y @@ -1056,12 +1097,12 @@ Metamaps.JIT = { (sX < x && x < eX && sY > y && y > eY)) { if (e.shiftKey) { if (n.selected) { - Metamaps.Control.deselectNode(n) + Control.deselectNode(n) } else { - Metamaps.Control.selectNode(n, e) + Control.selectNode(n, e) } } else { - Metamaps.Control.selectNode(n, e) + Control.selectNode(n, e) } } }) @@ -1162,30 +1203,30 @@ Metamaps.JIT = { if (selectTest) { // shiftKey = toggleSelect, otherwise if (e.shiftKey) { - if (Metamaps.Selected.Edges.indexOf(edge) != -1) { - Metamaps.Control.deselectEdge(edge) + if (Selected.Edges.indexOf(edge) != -1) { + Control.deselectEdge(edge) } else { - Metamaps.Control.selectEdge(edge) + Control.selectEdge(edge) } } else { - Metamaps.Control.selectEdge(edge) + Control.selectEdge(edge) } } }) - Metamaps.Mouse.boxStartCoordinates = false - Metamaps.Mouse.boxEndCoordinates = false - Metamaps.Visualize.mGraph.plot() + Mouse.boxStartCoordinates = false + Mouse.boxEndCoordinates = false + Visualize.mGraph.plot() }, // selectWithBox drawSelectBox: function (eventInfo, e) { - var ctx = Metamaps.Visualize.mGraph.canvas.getCtx() + var ctx = Visualize.mGraph.canvas.getCtx() - var startX = Metamaps.Mouse.boxStartCoordinates.x, - startY = Metamaps.Mouse.boxStartCoordinates.y, + var startX = Mouse.boxStartCoordinates.x, + startY = Mouse.boxStartCoordinates.y, currX = eventInfo.getPos().x, currY = eventInfo.getPos().y - Metamaps.Visualize.mGraph.canvas.clear() - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.canvas.clear() + Visualize.mGraph.plot() ctx.beginPath() ctx.moveTo(startX, startY) @@ -1197,9 +1238,9 @@ Metamaps.JIT = { ctx.stroke() }, // drawSelectBox selectNodeOnClickHandler: function (node, e) { - if (Metamaps.Visualize.mGraph.busy) return + if (Visualize.mGraph.busy) return - var self = Metamaps.JIT + var self = JIT // catch right click on mac, which is often like ctrl+click if (navigator.platform.indexOf('Mac') != -1 && e.ctrlKey) { @@ -1208,8 +1249,8 @@ Metamaps.JIT = { } // if on a topic page, let alt+click center you on a new topic - if (Metamaps.Active.Topic && e.altKey) { - Metamaps.RGraph.centerOn(node.id) + if (Active.Topic && e.altKey) { + JIT.RGraph.centerOn(node.id) return } @@ -1220,28 +1261,28 @@ Metamaps.JIT = { } else { // wait a certain length of time, then check again, then run this code setTimeout(function () { - if (!Metamaps.JIT.nodeWasDoubleClicked()) { + if (!JIT.nodeWasDoubleClicked()) { var nodeAlreadySelected = node.selected if (!e.shiftKey) { - Metamaps.Control.deselectAllNodes() - Metamaps.Control.deselectAllEdges() + Control.deselectAllNodes() + Control.deselectAllEdges() } if (nodeAlreadySelected) { - Metamaps.Control.deselectNode(node) + Control.deselectNode(node) } else { - Metamaps.Control.selectNode(node, e) + Control.selectNode(node, e) } // trigger animation to final styles - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.animate({ modes: ['edge-property:lineWidth:color:alpha'], duration: 500 }) - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } - }, Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE) + }, Mouse.DOUBLE_CLICK_TOLERANCE) } }, // selectNodeOnClickHandler selectNodeOnRightClickHandler: function (node, e) { @@ -1251,10 +1292,10 @@ Metamaps.JIT = { e.preventDefault() e.stopPropagation() - if (Metamaps.Visualize.mGraph.busy) return + if (Visualize.mGraph.busy) return // select the node - Metamaps.Control.selectNode(node, e) + Control.selectNode(node, e) // delete old right click menu $('.rightclickmenu').remove() @@ -1264,20 +1305,20 @@ Metamaps.JIT = { // add the proper options to the menu var menustring = '
    ' - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper) var disabled = authorized ? '' : 'disabled' - if (Metamaps.Active.Map) menustring += '
  • Hide until refresh
    Ctrl+H
  • ' - if (Metamaps.Active.Map && Metamaps.Active.Mapper) menustring += '
  • Remove from map
    Ctrl+M
  • ' - if (Metamaps.Active.Topic) menustring += '
  • Remove from view
    Ctrl+M
  • ' - if (Metamaps.Active.Map && Metamaps.Active.Mapper) menustring += '
  • Delete
    Ctrl+D
  • ' + if (Active.Map) menustring += '
  • Hide until refresh
    Ctrl+H
  • ' + if (Active.Map && Active.Mapper) menustring += '
  • Remove from map
    Ctrl+M
  • ' + if (Active.Topic) menustring += '
  • Remove from view
    Ctrl+M
  • ' + if (Active.Map && Active.Mapper) menustring += '
  • Delete
    Ctrl+D
  • ' - if (Metamaps.Active.Topic) { + if (Active.Topic) { menustring += '
  • Center this topic
    Alt+E
  • ' } menustring += '
  • Open in new tab
  • ' - if (Metamaps.Active.Mapper) { + if (Active.Mapper) { var options = '
    • commons
    • \
    • public
    • \
    • private
    • \ @@ -1291,8 +1332,8 @@ Metamaps.JIT = { menustring += '
    • Change metacode' + metacodeOptions + '
    • ' } - if (Metamaps.Active.Topic) { - if (!Metamaps.Active.Mapper) { + if (Active.Topic) { + if (!Active.Mapper) { menustring += '
    • ' } @@ -1350,30 +1391,30 @@ Metamaps.JIT = { if (authorized) { $('.rc-delete').click(function () { $('.rightclickmenu').remove() - Metamaps.Control.deleteSelected() + Control.deleteSelected() }) } // remove the selected things from the map - if (Metamaps.Active.Topic || authorized) { + if (Active.Topic || authorized) { $('.rc-remove').click(function () { $('.rightclickmenu').remove() - Metamaps.Control.removeSelectedEdges() - Metamaps.Control.removeSelectedNodes() + Control.removeSelectedEdges() + Control.removeSelectedNodes() }) } // hide selected nodes and synapses until refresh $('.rc-hide').click(function () { $('.rightclickmenu').remove() - Metamaps.Control.hideSelectedEdges() - Metamaps.Control.hideSelectedNodes() + Control.hideSelectedEdges() + Control.hideSelectedNodes() }) // when in radial, center on the topic you picked $('.rc-center').click(function () { $('.rightclickmenu').remove() - Metamaps.Topic.centerOn(node.id) + Topic.centerOn(node.id) }) // open the entity in a new tab @@ -1387,32 +1428,32 @@ Metamaps.JIT = { $('.rc-permission li').click(function () { $('.rightclickmenu').remove() // $(this).text() will be 'commons' 'public' or 'private' - Metamaps.Control.updateSelectedPermissions($(this).text()) + Control.updateSelectedPermissions($(this).text()) }) // change the metacode of all the selected nodes that you have edit permission for $('.rc-metacode li li').click(function () { $('.rightclickmenu').remove() // - Metamaps.Control.updateSelectedMetacodes($(this).attr('data-id')) + Control.updateSelectedMetacodes($(this).attr('data-id')) }) // fetch relatives var fetch_sent = false $('.rc-siblings').hover(function () { if (!fetch_sent) { - Metamaps.JIT.populateRightClickSiblings(node) + JIT.populateRightClickSiblings(node) fetch_sent = true } }) $('.rc-siblings .fetchAll').click(function () { $('.rightclickmenu').remove() // data-id is a metacode id - Metamaps.Topic.fetchRelatives(node) + Topic.fetchRelatives(node) }) }, // selectNodeOnRightClickHandler, populateRightClickSiblings: function (node) { - var self = Metamaps.JIT + var self = JIT // depending on how many topics are selected, do different things @@ -1440,7 +1481,7 @@ Metamaps.JIT = { $('.rc-siblings .getSiblings').click(function () { $('.rightclickmenu').remove() // data-id is a metacode id - Metamaps.Topic.fetchRelatives(node, $(this).attr('data-id')) + Topic.fetchRelatives(node, $(this).attr('data-id')) }) } @@ -1452,9 +1493,9 @@ Metamaps.JIT = { }) }, selectEdgeOnClickHandler: function (adj, e) { - if (Metamaps.Visualize.mGraph.busy) return + if (Visualize.mGraph.busy) return - var self = Metamaps.JIT + var self = JIT // catch right click on mac, which is often like ctrl+click if (navigator.platform.indexOf('Mac') != -1 && e.ctrlKey) { @@ -1469,23 +1510,23 @@ Metamaps.JIT = { } else { // wait a certain length of time, then check again, then run this code setTimeout(function () { - if (!Metamaps.JIT.nodeWasDoubleClicked()) { - var edgeAlreadySelected = Metamaps.Selected.Edges.indexOf(adj) !== -1 + if (!JIT.nodeWasDoubleClicked()) { + var edgeAlreadySelected = Selected.Edges.indexOf(adj) !== -1 if (!e.shiftKey) { - Metamaps.Control.deselectAllNodes() - Metamaps.Control.deselectAllEdges() + Control.deselectAllNodes() + Control.deselectAllEdges() } if (edgeAlreadySelected) { - Metamaps.Control.deselectEdge(adj) + Control.deselectEdge(adj) } else { - Metamaps.Control.selectEdge(adj) + Control.selectEdge(adj) } - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } - }, Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE) + }, Mouse.DOUBLE_CLICK_TOLERANCE) } }, // selectEdgeOnClickHandler selectEdgeOnRightClickHandler: function (adj, e) { @@ -1499,9 +1540,9 @@ Metamaps.JIT = { e.preventDefault() e.stopPropagation() - if (Metamaps.Visualize.mGraph.busy) return + if (Visualize.mGraph.busy) return - Metamaps.Control.selectEdge(adj) + Control.selectEdge(adj) // delete old right click menu $('.rightclickmenu').remove() @@ -1512,18 +1553,18 @@ Metamaps.JIT = { // add the proper options to the menu var menustring = '
        ' - var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper) var disabled = authorized ? '' : 'disabled' - if (Metamaps.Active.Map) menustring += '
      • Hide until refresh
        Ctrl+H
      • ' - if (Metamaps.Active.Map && Metamaps.Active.Mapper) menustring += '
      • Remove from map
        Ctrl+M
      • ' - if (Metamaps.Active.Topic) menustring += '
      • Remove from view
        Ctrl+M
      • ' - if (Metamaps.Active.Map && Metamaps.Active.Mapper) menustring += '
      • Delete
        Ctrl+D
      • ' + if (Active.Map) menustring += '
      • Hide until refresh
        Ctrl+H
      • ' + if (Active.Map && Active.Mapper) menustring += '
      • Remove from map
        Ctrl+M
      • ' + if (Active.Topic) menustring += '
      • Remove from view
        Ctrl+M
      • ' + if (Active.Map && Active.Mapper) menustring += '
      • Delete
        Ctrl+D
      • ' - if (Metamaps.Active.Map && Metamaps.Active.Mapper) menustring += '
      • ' + if (Active.Map && Active.Mapper) menustring += '
      • ' - if (Metamaps.Active.Mapper) { + if (Active.Mapper) { var permOptions = '
        • commons
        • \
        • public
        • \
        • private
        • \ @@ -1574,7 +1615,7 @@ Metamaps.JIT = { if (authorized) { $('.rc-delete').click(function () { $('.rightclickmenu').remove() - Metamaps.Control.deleteSelected() + Control.deleteSelected() }) } @@ -1582,44 +1623,44 @@ Metamaps.JIT = { if (authorized) { $('.rc-remove').click(function () { $('.rightclickmenu').remove() - Metamaps.Control.removeSelectedEdges() - Metamaps.Control.removeSelectedNodes() + Control.removeSelectedEdges() + Control.removeSelectedNodes() }) } // hide selected nodes and synapses until refresh $('.rc-hide').click(function () { $('.rightclickmenu').remove() - Metamaps.Control.hideSelectedEdges() - Metamaps.Control.hideSelectedNodes() + Control.hideSelectedEdges() + Control.hideSelectedNodes() }) // change the permission of all the selected nodes and synapses that you were the originator of $('.rc-permission li').click(function () { $('.rightclickmenu').remove() // $(this).text() will be 'commons' 'public' or 'private' - Metamaps.Control.updateSelectedPermissions($(this).text()) + Control.updateSelectedPermissions($(this).text()) }) }, // selectEdgeOnRightClickHandler SmoothPanning: function () { - var sx = Metamaps.Visualize.mGraph.canvas.scaleOffsetX, - sy = Metamaps.Visualize.mGraph.canvas.scaleOffsetY, - y_velocity = Metamaps.Mouse.changeInY, // initial y velocity - x_velocity = Metamaps.Mouse.changeInX, // initial x velocity + var sx = Visualize.mGraph.canvas.scaleOffsetX, + sy = Visualize.mGraph.canvas.scaleOffsetY, + y_velocity = Mouse.changeInY, // initial y velocity + x_velocity = Mouse.changeInX, // initial x velocity easing = 1 // frictional value easing = 1 - window.clearInterval(Metamaps.panningInt) - Metamaps.panningInt = setInterval(function () { + window.clearInterval(panningInt) + panningInt = setInterval(function () { myTimer() }, 1) function myTimer () { - Metamaps.Visualize.mGraph.canvas.translate(x_velocity * easing * 1 / sx, y_velocity * easing * 1 / sy) - $(document).trigger(Metamaps.JIT.events.pan) + Visualize.mGraph.canvas.translate(x_velocity * easing * 1 / sx, y_velocity * easing * 1 / sy) + $(document).trigger(JIT.events.pan) easing = easing * 0.75 - if (easing < 0.1) window.clearInterval(Metamaps.panningInt) + if (easing < 0.1) window.clearInterval(panningInt) } }, // SmoothPanning renderMidArrow: function (from, to, dim, swap, canvas, placement, newSynapse) { @@ -1664,7 +1705,7 @@ Metamaps.JIT = { ctx.stroke() }, // renderMidArrow renderEdgeArrows: function (edgeHelper, adj, synapse, canvas) { - var self = Metamaps.JIT + var self = JIT var directionCat = synapse.get('category') var direction = synapse.getDirection() @@ -1717,12 +1758,12 @@ Metamaps.JIT = { } }, // renderEdgeArrows zoomIn: function (event) { - Metamaps.Visualize.mGraph.canvas.scale(1.25, 1.25) - $(document).trigger(Metamaps.JIT.events.zoom, [event]) + Visualize.mGraph.canvas.scale(1.25, 1.25) + $(document).trigger(JIT.events.zoom, [event]) }, zoomOut: function (event) { - Metamaps.Visualize.mGraph.canvas.scale(0.8, 0.8) - $(document).trigger(Metamaps.JIT.events.zoom, [event]) + Visualize.mGraph.canvas.scale(0.8, 0.8) + $(document).trigger(JIT.events.zoom, [event]) }, centerMap: function (canvas) { var offsetScale = canvas.scaleOffsetX @@ -1735,13 +1776,13 @@ Metamaps.JIT = { canvas.translate(-1 * offsetX, -1 * offsetY) }, zoomToBox: function (event) { - var sX = Metamaps.Mouse.boxStartCoordinates.x, - sY = Metamaps.Mouse.boxStartCoordinates.y, - eX = Metamaps.Mouse.boxEndCoordinates.x, - eY = Metamaps.Mouse.boxEndCoordinates.y + var sX = Mouse.boxStartCoordinates.x, + sY = Mouse.boxStartCoordinates.y, + eX = Mouse.boxEndCoordinates.x, + eY = Mouse.boxEndCoordinates.y - var canvas = Metamaps.Visualize.mGraph.canvas - Metamaps.JIT.centerMap(canvas) + var canvas = Visualize.mGraph.canvas + JIT.centerMap(canvas) var height = $(document).height(), width = $(document).width() @@ -1768,22 +1809,22 @@ Metamaps.JIT = { var cogY = (sY + eY) / 2 canvas.translate(-1 * cogX, -1 * cogY) - $(document).trigger(Metamaps.JIT.events.zoom, [event]) + $(document).trigger(JIT.events.zoom, [event]) - Metamaps.Mouse.boxStartCoordinates = false - Metamaps.Mouse.boxEndCoordinates = false - Metamaps.Visualize.mGraph.plot() + Mouse.boxStartCoordinates = false + Mouse.boxEndCoordinates = false + Visualize.mGraph.plot() }, zoomExtents: function (event, canvas, denySelected) { - Metamaps.JIT.centerMap(canvas) + JIT.centerMap(canvas) var height = canvas.getSize().height, width = canvas.getSize().width, maxX, minX, maxY, minY, counter = 0 - if (!denySelected && Metamaps.Selected.Nodes.length > 0) { - var nodes = Metamaps.Selected.Nodes + if (!denySelected && Selected.Nodes.length > 0) { + var nodes = Selected.Nodes } else { - var nodes = _.values(Metamaps.Visualize.mGraph.graph.nodes) + var nodes = _.values(Visualize.mGraph.graph.nodes) } if (nodes.length > 1) { @@ -1798,7 +1839,7 @@ Metamaps.JIT = { minY = y } - var arrayOfLabelLines = Metamaps.Util.splitLine(n.name, 30).split('\n'), + var arrayOfLabelLines = Util.splitLine(n.name, 30).split('\n'), dim = n.getData('dim'), ctx = canvas.getCtx() @@ -1845,7 +1886,7 @@ Metamaps.JIT = { canvas.scale(scaleMultiplier, scaleMultiplier) } - $(document).trigger(Metamaps.JIT.events.zoom, [event]) + $(document).trigger(JIT.events.zoom, [event]) } else if (nodes.length == 1) { nodes.forEach(function (n) { @@ -1853,8 +1894,10 @@ Metamaps.JIT = { y = n.pos.y canvas.translate(-1 * x, -1 * y) - $(document).trigger(Metamaps.JIT.events.zoom, [event]) + $(document).trigger(JIT.events.zoom, [event]) }) } } } + +export default JIT diff --git a/frontend/src/Metamaps/Listeners.js b/frontend/src/Metamaps/Listeners.js new file mode 100644 index 00000000..78e881d4 --- /dev/null +++ b/frontend/src/Metamaps/Listeners.js @@ -0,0 +1,123 @@ +/* global $ */ + +import Active from './Active' +import Control from './Control' +import JIT from './JIT' +import Mobile from './Mobile' +import Realtime from './Realtime' +import Selected from './Selected' +import Topic from './Topic' +import Visualize from './Visualize' + +const Listeners = { + init: function () { + var self = this + $(document).on('keydown', function (e) { + if (!(Active.Map || Active.Topic)) return + + switch (e.which) { + case 13: // if enter key is pressed + JIT.enterKeyHandler() + e.preventDefault() + break + case 27: // if esc key is pressed + JIT.escKeyHandler() + break + case 65: // if a or A is pressed + if (e.ctrlKey) { + Control.deselectAllNodes() + Control.deselectAllEdges() + + e.preventDefault() + Visualize.mGraph.graph.eachNode(function (n) { + Control.selectNode(n, e) + }) + + Visualize.mGraph.plot() + } + + break + case 68: // if d or D is pressed + if (e.ctrlKey) { + e.preventDefault() + Control.deleteSelected() + } + break + case 69: // if e or E is pressed + if (e.ctrlKey && Active.Map) { + e.preventDefault() + JIT.zoomExtents(null, Visualize.mGraph.canvas) + break + } + if (e.altKey && Active.Topic) { + e.preventDefault() + + if (Active.Topic) { + self.centerAndReveal(Selected.Nodes, { + center: true, + reveal: false + }) + } + break + } + break + case 72: // if h or H is pressed + if (e.ctrlKey) { + e.preventDefault() + Control.hideSelectedNodes() + Control.hideSelectedEdges() + } + break + case 77: // if m or M is pressed + if (e.ctrlKey) { + e.preventDefault() + Control.removeSelectedNodes() + Control.removeSelectedEdges() + } + break + case 82: // if r or R is pressed + if (e.altKey && Active.Topic) { + e.preventDefault() + self.centerAndReveal(Selected.Nodes, { + center: false, + reveal: true + }) + } + break + case 84: // if t or T is pressed + if (e.altKey && Active.Topic) { + e.preventDefault() + self.centerAndReveal(Selected.Nodes, { + center: true, + reveal: true + }) + } + break + default: + // console.log(e.which) + break + } + }) + + $(window).resize(function () { + if (Visualize && Visualize.mGraph) Visualize.mGraph.canvas.resize($(window).width(), $(window).height()) + if (Active.Map && Realtime.inConversation) Realtime.positionVideos() + Mobile.resizeTitle() + }) + }, + centerAndReveal: function(nodes, opts) { + if (nodes.length < 1) return + var node = nodes[nodes.length - 1] + if (opts.center && opts.reveal) { + Topic.centerOn(node.id, function() { + Topic.fetchRelatives(nodes) + }) + } else if (opts.center) { + Topic.centerOn(node.id) + } else if (opts.reveal) { + Topic.fetchRelatives(nodes) + } + } +} + +export default Listeners diff --git a/frontend/src/Metamaps/Map/CheatSheet.js b/frontend/src/Metamaps/Map/CheatSheet.js new file mode 100644 index 00000000..969ee159 --- /dev/null +++ b/frontend/src/Metamaps/Map/CheatSheet.js @@ -0,0 +1,27 @@ +const CheatSheet = { + init: function () { + // tab the 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') + $(element).addClass('active') + $('#tutorialVideo').attr('src', '//player.vimeo.com/video/' + id) + } + + $('#gettingStarted').click(function () { + // switchVideo(this,'88334167') + }) + $('#upYourSkillz').click(function () { + // switchVideo(this,'100118167') + }) + $('#advancedMapping').click(function () { + // switchVideo(this,'88334167') + }) + } +} + +export default CheatSheet diff --git a/frontend/src/Metamaps/Map/InfoBox.js b/frontend/src/Metamaps/Map/InfoBox.js new file mode 100644 index 00000000..ec5c1405 --- /dev/null +++ b/frontend/src/Metamaps/Map/InfoBox.js @@ -0,0 +1,373 @@ +/* global Metamaps, $ */ + +import Active from '../Active' +import GlobalUI from '../GlobalUI' +import Router from '../Router' +import Util from '../Util' + +/* + * Metamaps.Collaborators + * Metamaps.Erb + * Metamaps.Mappers + * Metamaps.Maps + * Metamaps.Synapses + * Metamaps.Topics + */ + +const InfoBox = { + isOpen: false, + changing: false, + selectingPermission: false, + changePermissionText: "
          As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
          ", + nameHTML: '{{name}}', + descHTML: '{{desc}}', + init: function () { + var self = InfoBox + + $('.mapInfoIcon').click(self.toggleBox) + $('.mapInfoBox').click(function (event) { + event.stopPropagation() + }) + $('body').click(self.close) + + self.attachEventListeners() + + self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html()) + + var querystring = window.location.search.replace(/^\?/, '') + if (querystring == 'new') { + self.open() + $('.mapInfoBox').addClass('mapRequestTitle') + } + }, + toggleBox: function (event) { + var self = InfoBox + + if (self.isOpen) self.close() + else self.open() + + event.stopPropagation() + }, + open: function () { + var self = InfoBox + $('.mapInfoIcon div').addClass('hide') + if (!self.isOpen && !self.changing) { + self.changing = true + $('.mapInfoBox').fadeIn(200, function () { + self.changing = false + self.isOpen = true + }) + } + }, + close: function () { + var self = InfoBox + + $('.mapInfoIcon div').removeClass('hide') + if (!self.changing) { + self.changing = true + $('.mapInfoBox').fadeOut(200, function () { + self.changing = false + self.isOpen = false + self.hidePermissionSelect() + $('.mapContributors .tip').hide() + }) + } + }, + load: function () { + var self = InfoBox + + var map = Active.Map + + var obj = map.pick('permission', 'topic_count', 'synapse_count') + + var isCreator = map.authorizePermissionChange(Active.Mapper) + var canEdit = map.authorizeToEdit(Active.Mapper) + var relevantPeople = map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators + var shareable = map.get('permission') !== 'private' + + obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name') + obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc') + obj['map_creator_tip'] = isCreator ? self.changePermissionText : '' + + obj['contributor_count'] = relevantPeople.length + obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : '' + obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : '' + obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : Metamaps.Erb['user.png'] + obj['contributor_list'] = self.createContributorList() + + obj['user_name'] = isCreator ? 'You' : map.get('user_name') + obj['created_at'] = map.get('created_at_clean') + obj['updated_at'] = map.get('updated_at_clean') + + var classes = isCreator ? 'yourMap' : '' + classes += canEdit ? ' canEdit' : '' + classes += shareable ? ' shareable' : '' + $('.mapInfoBox').removeClass('shareable yourMap canEdit') + .addClass(classes) + .html(self.generateBoxHTML.render(obj)) + + self.attachEventListeners() + }, + attachEventListeners: function () { + var self = InfoBox + + $('.mapInfoBox.canEdit .best_in_place').best_in_place() + + // because anyone who can edit the map can change the map title + var bipName = $('.mapInfoBox .best_in_place_name') + bipName.unbind('best_in_place:activate').bind('best_in_place:activate', function () { + var $el = bipName.find('textarea') + var el = $el[0] + + $el.attr('maxlength', '140') + + $('.mapInfoName').append('
          ') + + var callback = function (data) { + $('.nameCounter.forMap').html(data.all + '/140') + } + Countable.live(el, callback) + }) + bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function () { + $('.nameCounter.forMap').remove() + }) + + $('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function () { + var name = $(this).html() + Active.Map.set('name', name) + Active.Map.trigger('saved') + // mobile menu + $('#header_content').html(name) + $('.mapInfoBox').removeClass('mapRequestTitle') + document.title = name + ' | Metamaps' + }) + + $('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function () { + var desc = $(this).html() + Active.Map.set('desc', desc) + Active.Map.trigger('saved') + }) + + $('.yourMap .mapPermission').unbind().click(self.onPermissionClick) + // .yourMap in the unbind/bind is just a namespace for the events + // not a reference to the class .yourMap on the .mapInfoBox + $('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect) + + $('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap) + + $('.mapContributors span, #mapContribs').unbind().click(function (event) { + $('.mapContributors .tip').toggle() + event.stopPropagation() + }) + $('.mapContributors .tip').unbind().click(function (event) { + event.stopPropagation() + }) + $('.mapContributors .tip li a').click(Router.intercept) + + $('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () { + $('.mapContributors .tip').hide() + }) + + self.addTypeahead() + }, + addTypeahead: function () { + var self = InfoBox + + if (!Active.Map) return + + // for autocomplete + var collaborators = { + name: 'collaborators', + limit: 9999, + display: function(s) { return s.label; }, + templates: { + notFound: function(s) { + return Hogan.compile($('#collaboratorSearchTemplate').html()).render({ + value: "No results", + label: "No results", + rtype: "noresult", + profile: Metamaps.Erb['user.png'], + }); + }, + suggestion: function(s) { + return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s); + }, + }, + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/mappers?term=%QUERY', + wildcard: '%QUERY', + }, + }) + } + + // for adding map collaborators, who will have edit rights + if (Active.Mapper && Active.Mapper.id === Active.Map.get('user_id')) { + $('.collaboratorSearchField').typeahead( + { + highlight: false, + }, + [collaborators] + ) + $('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick) + $('.mapContributors .removeCollaborator').click(function () { + self.removeCollaborator(parseInt($(this).data('id'))) + }) + } + }, + removeCollaborator: function (collaboratorId) { + var self = InfoBox + Metamaps.Collaborators.remove(Metamaps.Collaborators.get(collaboratorId)) + var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id }) + $.post('/maps/' + Active.Map.id + '/access', { access: mapperIds }) + self.updateNumbers() + }, + addCollaborator: function (newCollaboratorId) { + var self = InfoBox + + if (Metamaps.Collaborators.get(newCollaboratorId)) { + GlobalUI.notifyUser('That user already has access') + return + } + + function callback(mapper) { + Metamaps.Collaborators.add(mapper) + var mapperIds = Metamaps.Collaborators.models.map(function (mapper) { return mapper.id }) + $.post('/maps/' + Active.Map.id + '/access', { access: mapperIds }) + var name = Metamaps.Collaborators.get(newCollaboratorId).get('name') + GlobalUI.notifyUser(name + ' will be notified by email') + self.updateNumbers() + } + + $.getJSON('/users/' + newCollaboratorId + '.json', callback) + }, + handleResultClick: function (event, item) { + var self = InfoBox + + self.addCollaborator(item.id) + $('.collaboratorSearchField').typeahead('val', '') + }, + updateNameDescPerm: function (name, desc, perm) { + $('.mapInfoBox').removeClass('mapRequestTitle') + $('.mapInfoName .best_in_place_name').html(name) + $('.mapInfoDesc .best_in_place_desc').html(desc) + $('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm) + }, + createContributorList: function () { + var self = InfoBox + var relevantPeople = Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators + var activeMapperIsCreator = Active.Mapper && Active.Mapper.id === Active.Map.get('user_id') + var string = '' + string += '' + + if (activeMapperIsCreator) { + string += '
          ' + } + return string + }, + updateNumbers: function () { + if (!Active.Map) return + + var self = InfoBox + var mapper = Active.Mapper + var relevantPeople = Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators + + var contributors_class = '' + if (relevantPeople.length === 2) contributors_class = 'multiple mTwo' + else if (relevantPeople.length > 2) contributors_class = 'multiple' + + var contributors_image = Metamaps.Erb['user.png'] + if (relevantPeople.length > 0) { + // get the first contributor and use their image + contributors_image = relevantPeople.models[0].get('image') + } + $('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class) + $('.mapContributors span').text(relevantPeople.length) + $('.mapContributors .tip').html(self.createContributorList()) + self.addTypeahead() + $('.mapContributors .tip').unbind().click(function (event) { + event.stopPropagation() + }) + $('.mapTopics').text(Metamaps.Topics.length) + $('.mapSynapses').text(Metamaps.Synapses.length) + + $('.mapEditedAt').html('Last edited: ' + Util.nowDateFormatted()) + }, + onPermissionClick: function (event) { + var self = InfoBox + + if (!self.selectingPermission) { + self.selectingPermission = true + $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow + if ($(this).hasClass('commons')) { + $(this).append('
          ') + } else if ($(this).hasClass('public')) { + $(this).append('
          ') + } else if ($(this).hasClass('private')) { + $(this).append('
          ') + } + $('.mapPermission .permissionSelect li').click(self.selectPermission) + event.stopPropagation() + } + }, + hidePermissionSelect: function () { + var self = InfoBox + + self.selectingPermission = false + $('.mapPermission').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow + $('.mapPermission .permissionSelect').remove() + }, + selectPermission: function (event) { + var self = InfoBox + + self.selectingPermission = false + var permission = $(this).attr('class') + Active.Map.save({ + permission: permission + }) + Active.Map.updateMapWrapper() + shareable = permission === 'private' ? '' : 'shareable' + $('.mapPermission').removeClass('commons public private minimize').addClass(permission) + $('.mapPermission .permissionSelect').remove() + $('.mapInfoBox').removeClass('shareable').addClass(shareable) + event.stopPropagation() + }, + deleteActiveMap: function () { + var confirmString = 'Are you sure you want to delete this map? ' + confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.' + + var doIt = confirm(confirmString) + var map = Active.Map + var mapper = Active.Mapper + var authorized = map.authorizePermissionChange(mapper) + + if (doIt && authorized) { + InfoBox.close() + Metamaps.Maps.Active.remove(map) + Metamaps.Maps.Featured.remove(map) + Metamaps.Maps.Mine.remove(map) + Metamaps.Maps.Shared.remove(map) + map.destroy() + Router.home() + GlobalUI.notifyUser('Map eliminated!') + } + else if (!authorized) { + alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?") + } + } +} + +export default InfoBox diff --git a/frontend/src/Metamaps/Map/index.js b/frontend/src/Metamaps/Map/index.js new file mode 100644 index 00000000..3dd1c531 --- /dev/null +++ b/frontend/src/Metamaps/Map/index.js @@ -0,0 +1,365 @@ +/* global Metamaps, $ */ + +import Active from '../Active' +import AutoLayout from '../AutoLayout' +import Create from '../Create' +import Filter from '../Filter' +import GlobalUI from '../GlobalUI' +import JIT from '../JIT' +import Realtime from '../Realtime' +import Router from '../Router' +import Selected from '../Selected' +import SynapseCard from '../SynapseCard' +import TopicCard from '../TopicCard' +import Visualize from '../Visualize' + +import CheatSheet from './CheatSheet' +import InfoBox from './InfoBox' + +/* + * Metamaps.Map.js.erb + * + * Dependencies: + * - Metamaps.Backbone + * - Metamaps.Erb + * - Metamaps.Loading + * - Metamaps.Mappers + * - Metamaps.Mappings + * - Metamaps.Maps + * - Metamaps.Messages + * - Metamaps.Synapses + * - Metamaps.Topics + */ + +const Map = { + events: { + editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper' + }, + init: function () { + var self = Map + + // prevent right clicks on the main canvas, so as to not get in the way of our right clicks + $('#center-container').bind('contextmenu', function (e) { + return false + }) + + $('.starMap').click(function () { + if ($(this).is('.starred')) self.unstar() + else self.star() + }) + + $('.sidebarFork').click(function () { + self.fork() + }) + + GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html() + + self.updateStar() + self.InfoBox.init() + CheatSheet.init() + + $(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper) + }, + launch: function (id) { + var bb = Metamaps.Backbone + var start = function (data) { + Active.Map = new bb.Map(data.map) + Metamaps.Mappers = new bb.MapperCollection(data.mappers) + Metamaps.Collaborators = new bb.MapperCollection(data.collaborators) + 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.Stars = data.stars + Metamaps.Backbone.attachCollectionEvents() + + var map = Active.Map + var mapper = Active.Mapper + + // add class to .wrapper for specifying whether you can edit the map + if (map.authorizeToEdit(mapper)) { + $('.wrapper').addClass('canEditMap') + } + + // add class to .wrapper for specifying if the map can + // be collaborated on + if (map.get('permission') === 'commons') { + $('.wrapper').addClass('commonsMap') + } + + Map.updateStar() + + // set filter mapper H3 text + $('#filter_by_mapper h3').html('MAPPERS') + + // build and render the visualization + Visualize.type = 'ForceDirected' + JIT.prepareVizData() + + // update filters + Filter.reset() + + // reset selected arrays + Selected.reset() + + // set the proper mapinfobox content + Map.InfoBox.load() + + // these three update the actual filter box with the right list items + Filter.checkMetacodes() + Filter.checkSynapses() + Filter.checkMappers() + + Realtime.startActiveMap() + Metamaps.Loading.hide() + + // for mobile + $('#header_content').html(map.get('name')) + } + + $.ajax({ + url: '/maps/' + id + '/contains.json', + success: start + }) + }, + end: function () { + if (Active.Map) { + $('.wrapper').removeClass('canEditMap commonsMap') + AutoLayout.resetSpiral() + + $('.rightclickmenu').remove() + TopicCard.hideCard() + SynapseCard.hideCard() + Create.newTopic.hide(true) // true means force (and override pinned) + Create.newSynapse.hide() + Filter.close() + Map.InfoBox.close() + Realtime.endActiveMap() + } + }, + updateStar: function () { + if (!Active.Mapper || !Metamaps.Stars) return + // update the star/unstar icon + if (Metamaps.Stars.find(function (s) { return s.user_id === Active.Mapper.id })) { + $('.starMap').addClass('starred') + $('.starMap .tooltipsAbove').html('Unstar') + } else { + $('.starMap').removeClass('starred') + $('.starMap .tooltipsAbove').html('Star') + } + }, + star: function () { + var self = Map + + if (!Active.Map) return + $.post('/maps/' + Active.Map.id + '/star') + Metamaps.Stars.push({ user_id: Active.Mapper.id, map_id: Active.Map.id }) + Metamaps.Maps.Starred.add(Active.Map) + GlobalUI.notifyUser('Map is now starred') + self.updateStar() + }, + unstar: function () { + var self = Map + + if (!Active.Map) return + $.post('/maps/' + Active.Map.id + '/unstar') + Metamaps.Stars = Metamaps.Stars.filter(function (s) { return s.user_id != Active.Mapper.id }) + Metamaps.Maps.Starred.remove(Active.Map) + self.updateStar() + }, + fork: function () { + GlobalUI.openLightbox('forkmap') + + var nodes_data = '', + synapses_data = '' + var nodes_array = [] + var synapses_array = [] + // collect the unfiltered topics + Visualize.mGraph.graph.eachNode(function (n) { + // if the opacity is less than 1 then it's filtered + if (n.getData('alpha') === 1) { + var id = n.getData('topic').id + nodes_array.push(id) + var x, y + if (n.pos.x && n.pos.y) { + x = n.pos.x + y = n.pos.y + } else { + var x = Math.cos(n.pos.theta) * n.pos.rho + var y = Math.sin(n.pos.theta) * n.pos.rho + } + nodes_data += id + '/' + x + '/' + y + ',' + } + }) + // collect the unfiltered synapses + Metamaps.Synapses.each(function (synapse) { + var desc = synapse.get('desc') + + var descNotFiltered = Filter.visible.synapses.indexOf(desc) > -1 + // 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 + if (descNotFiltered && topicsNotFiltered) { + synapses_array.push(synapse.id) + } + }) + + synapses_data = synapses_array.join() + nodes_data = nodes_data.slice(0, -1) + + GlobalUI.CreateMap.topicsToMap = nodes_data + GlobalUI.CreateMap.synapsesToMap = synapses_data + }, + leavePrivateMap: function () { + var map = Active.Map + Metamaps.Maps.Active.remove(map) + Metamaps.Maps.Featured.remove(map) + Router.home() + GlobalUI.notifyUser('Sorry! That map has been changed to Private.') + }, + cantEditNow: function () { + Realtime.turnOff(true); // true is for 'silence' + GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.') + Active.Map.trigger('changeByOther') + }, + canEditNow: function () { + var confirmString = "You've been granted permission to edit this map. " + confirmString += 'Do you want to reload and enable realtime collaboration?' + var c = confirm(confirmString) + if (c) { + Router.maps(Active.Map.id) + } + }, + editedByActiveMapper: function () { + if (Active.Mapper) { + Metamaps.Mappers.add(Active.Mapper) + } + }, + exportImage: function () { + var canvas = {} + + canvas.canvas = document.createElement('canvas') + canvas.canvas.width = 1880 // 960 + canvas.canvas.height = 1260 // 630 + + canvas.scaleOffsetX = 1 + canvas.scaleOffsetY = 1 + canvas.translateOffsetY = 0 + canvas.translateOffsetX = 0 + canvas.denySelected = true + + canvas.getSize = function () { + if (this.size) return this.size + var canvas = this.canvas + return this.size = { + width: canvas.width, + height: canvas.height + } + } + canvas.scale = function (x, y) { + var px = this.scaleOffsetX * x, + py = this.scaleOffsetY * y + var dx = this.translateOffsetX * (x - 1) / px, + dy = this.translateOffsetY * (y - 1) / py + this.scaleOffsetX = px + this.scaleOffsetY = py + this.getCtx().scale(x, y) + this.translate(dx, dy) + } + canvas.translate = function (x, y) { + var sx = this.scaleOffsetX, + sy = this.scaleOffsetY + this.translateOffsetX += x * sx + this.translateOffsetY += y * sy + this.getCtx().translate(x, y) + } + canvas.getCtx = function () { + return this.canvas.getContext('2d') + } + // center it + canvas.getCtx().translate(1880 / 2, 1260 / 2) + + var mGraph = Visualize.mGraph + + var id = mGraph.root + var root = mGraph.graph.getNode(id) + var T = !!root.visited + + // pass true to avoid basing it on a selection + JIT.zoomExtents(null, canvas, true) + + var c = canvas.canvas, + ctx = canvas.getCtx(), + scale = canvas.scaleOffsetX + + // draw a grey background + ctx.fillStyle = '#d8d9da' + var xPoint = (-(c.width / scale) / 2) - (canvas.translateOffsetX / scale), + yPoint = (-(c.height / scale) / 2) - (canvas.translateOffsetY / scale) + ctx.fillRect(xPoint, yPoint, c.width / scale, c.height / scale) + + // draw the graph + mGraph.graph.eachNode(function (node) { + var nodeAlpha = node.getData('alpha') + node.eachAdjacency(function (adj) { + var nodeTo = adj.nodeTo + if (!!nodeTo.visited === T && node.drawn && nodeTo.drawn) { + mGraph.fx.plotLine(adj, canvas) + } + }) + if (node.drawn) { + mGraph.fx.plotNode(node, canvas) + } + if (!mGraph.labelsHidden) { + if (node.drawn && nodeAlpha >= 0.95) { + mGraph.labels.plotLabel(canvas, node) + } else { + mGraph.labels.hideLabel(node, false) + } + } + node.visited = !T + }) + + var imageData = { + encoded_image: canvas.canvas.toDataURL() + } + + var map = Active.Map + + var today = new Date() + var dd = today.getDate() + var mm = today.getMonth() + 1; // January is 0! + var yyyy = today.getFullYear() + if (dd < 10) { + dd = '0' + dd + } + if (mm < 10) { + mm = '0' + mm + } + today = mm + '/' + dd + '/' + yyyy + + var mapName = map.get('name').split(' ').join([separator = '-']) + var downloadMessage = '' + downloadMessage += 'Captured map screenshot! ' + downloadMessage += "DOWNLOAD" + GlobalUI.notifyUser(downloadMessage) + + $.ajax({ + type: 'POST', + dataType: 'json', + url: '/maps/' + Active.Map.id + '/upload_screenshot', + data: imageData, + success: function (data) { + console.log('successfully uploaded map screenshot') + }, + error: function () { + console.log('failed to save map screenshot') + } + }) + } +} + +export { CheatSheet, InfoBox } +export default Map diff --git a/frontend/src/Metamaps/Mapper.js b/frontend/src/Metamaps/Mapper.js new file mode 100644 index 00000000..3858101d --- /dev/null +++ b/frontend/src/Metamaps/Mapper.js @@ -0,0 +1,19 @@ +/* + * Metamaps.Backbone + */ + +const Mapper = { + // this function is to retrieve a mapper JSON object from the database + // @param id = the id of the mapper to retrieve + get: function (id, callback) { + return fetch(`/users/${id}.json`, { + }).then(response => { + if (!response.ok) throw response + return response.json() + }).then(payload => { + callback(new Metamaps.Backbone.Mapper(payload)) + }) + } +} + +export default Mapper diff --git a/app/assets/javascripts/src/Metamaps.Mobile.js b/frontend/src/Metamaps/Mobile.js similarity index 66% rename from app/assets/javascripts/src/Metamaps.Mobile.js rename to frontend/src/Metamaps/Mobile.js index 1a55f081..fddd90a4 100644 --- a/app/assets/javascripts/src/Metamaps.Mobile.js +++ b/frontend/src/Metamaps/Mobile.js @@ -1,16 +1,11 @@ -/* global Metamaps, $ */ +/* global $ */ -/* - * Metamaps.Mobile.js - * - * Dependencies: - * - Metamaps.Active - * - Metamaps.Map - */ +import Active from './Active' +import Map from './Map' -Metamaps.Mobile = { +const Mobile = { init: function () { - var self = Metamaps.Mobile + var self = Mobile $('#menu_icon').click(self.toggleMenu) $('#mobile_menu li a').click(self.liClick) @@ -22,7 +17,7 @@ Metamaps.Mobile = { $('#header_content').width($(document).width() - 70) }, liClick: function () { - var self = Metamaps.Mobile + var self = Mobile $('#header_content').html($(this).text()) self.toggleMenu() }, @@ -30,8 +25,10 @@ Metamaps.Mobile = { $('#mobile_menu').toggle() }, titleClick: function () { - if (Metamaps.Active.Map) { - Metamaps.Map.InfoBox.open() + if (Active.Map) { + Map.InfoBox.open() } } } + +export default Mobile diff --git a/frontend/src/Metamaps/Mouse.js b/frontend/src/Metamaps/Mouse.js new file mode 100644 index 00000000..9989bc20 --- /dev/null +++ b/frontend/src/Metamaps/Mouse.js @@ -0,0 +1,16 @@ +const Mouse = { + didPan: false, + didBoxZoom: false, + changeInX: 0, + changeInY: 0, + edgeHoveringOver: false, + boxStartCoordinates: false, + boxEndCoordinates: false, + synapseStartCoordinates: [], + synapseEndCoordinates: null, + lastNodeClick: 0, + lastCanvasClick: 0, + DOUBLE_CLICK_TOLERANCE: 300 +} + +export default Mouse diff --git a/app/assets/javascripts/src/Metamaps.Organize.js b/frontend/src/Metamaps/Organize.js similarity index 75% rename from app/assets/javascripts/src/Metamaps.Organize.js rename to frontend/src/Metamaps/Organize.js index b2463280..ed005d39 100644 --- a/app/assets/javascripts/src/Metamaps.Organize.js +++ b/frontend/src/Metamaps/Organize.js @@ -1,22 +1,22 @@ -/* global Metamaps, $ */ +/* global $ */ -/* - * Metamaps.Organize.js.erb - * - * Dependencies: - * - Metamaps.Visualize - */ -Metamaps.Organize = { - init: function () {}, +import _ from 'lodash' + +import $jit from '../patched/JIT' + +import Visualize from './Visualize' +import JIT from './JIT' + +const Organize = { arrange: function (layout, centerNode) { // first option for layout to implement is 'grid', will do an evenly spaced grid with its center at the 0,0 origin if (layout == 'grid') { - var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization + var numNodes = _.size(Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization var numColumns = Math.floor(Math.sqrt(numNodes)) // the number of columns to make an even grid var GRIDSPACE = 400 var row = 0 var column = 0 - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + Visualize.mGraph.graph.eachNode(function (n) { if (column == numColumns) { column = 0 row += 1 @@ -27,14 +27,14 @@ Metamaps.Organize = { n.setPos(newPos, 'end') column += 1 }) - Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout) + Visualize.mGraph.animate(JIT.ForceDirected.animateSavedLayout) } else if (layout == 'grid_full') { // this will always be an integer, the # of nodes on your graph visualization - var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes) + var numNodes = _.size(Visualize.mGraph.graph.nodes) // var numColumns = Math.floor(Math.sqrt(numNodes)) // the number of columns to make an even grid // var GRIDSPACE = 400 - var height = Metamaps.Visualize.mGraph.canvas.getSize(0).height - var width = Metamaps.Visualize.mGraph.canvas.getSize(0).width + var height = Visualize.mGraph.canvas.getSize(0).height + var width = Visualize.mGraph.canvas.getSize(0).width var totalArea = height * width var cellArea = totalArea / numNodes var ratio = height / width @@ -45,7 +45,7 @@ Metamaps.Organize = { var totalCells = row * column if (totalCells) - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + Visualize.mGraph.graph.eachNode(function (n) { if (column == numColumns) { column = 0 row += 1 @@ -56,7 +56,7 @@ Metamaps.Organize = { n.setPos(newPos, 'end') column += 1 }) - Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout) + Visualize.mGraph.animate(JIT.ForceDirected.animateSavedLayout) } else if (layout == 'radial') { var centerX = centerNode.getPos().x var centerY = centerNode.getPos().y @@ -88,16 +88,16 @@ Metamaps.Organize = { }) } radial(centerNode, 1, 0) - Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout) + Visualize.mGraph.animate(JIT.ForceDirected.animateSavedLayout) } else if (layout == 'center_viewport') { var lowX = 0, lowY = 0, highX = 0, highY = 0 - var oldOriginX = Metamaps.Visualize.mGraph.canvas.translateOffsetX - var oldOriginY = Metamaps.Visualize.mGraph.canvas.translateOffsetY + var oldOriginX = Visualize.mGraph.canvas.translateOffsetX + var oldOriginY = Visualize.mGraph.canvas.translateOffsetY - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + Visualize.mGraph.graph.eachNode(function (n) { if (n.id === 1) { lowX = n.getPos().x lowY = n.getPos().y @@ -114,4 +114,6 @@ Metamaps.Organize = { var newOriginY = (lowY + highY) / 2 } else alert('please call function with a valid layout dammit!') } -}; // end Metamaps.Organize +} + +export default Organize diff --git a/app/assets/javascripts/src/Metamaps.PasteInput.js b/frontend/src/Metamaps/PasteInput.js similarity index 83% rename from app/assets/javascripts/src/Metamaps.PasteInput.js rename to frontend/src/Metamaps/PasteInput.js index aaf848d0..e0620329 100644 --- a/app/assets/javascripts/src/Metamaps.PasteInput.js +++ b/frontend/src/Metamaps/PasteInput.js @@ -1,19 +1,16 @@ -/* global Metamaps, $ */ +/* global $ */ -/* - * Metamaps.PasteInput.js.erb - * - * Dependencies: - * - Metamaps.Import - * - Metamaps.AutoLayout - */ +import AutoLayout from './AutoLayout' +import Import from './Import' +import TopicCard from './TopicCard' +import Util from './Util' -Metamaps.PasteInput = { +const PasteInput = { // thanks to https://github.com/kevva/url-regex URL_REGEX: new RegExp('^(?:(?:(?:[a-z]+:)?//)|www\.)(?:\S+(?::\S*)?@)?(?:localhost|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#][^\s"]*)?$'), init: function () { - var self = Metamaps.PasteInput + var self = PasteInput // intercept dragged files // see http://stackoverflow.com/questions/6756583 @@ -24,7 +21,7 @@ Metamaps.PasteInput = { window.addEventListener("drop", function(e) { e = e || event; e.preventDefault(); - var coords = Metamaps.Util.pixelsToCoords({ x: e.clientX, y: e.clientY }) + var coords = Util.pixelsToCoords({ x: e.clientX, y: e.clientY }) if (e.dataTransfer.files.length > 0) { var fileReader = new FileReader() var text = fileReader.readAsText(e.dataTransfer.files[0]) @@ -58,7 +55,7 @@ Metamaps.PasteInput = { }, handle: function(text, coords) { - var self = Metamaps.PasteInput + var self = PasteInput if (text.match(self.URL_REGEX)) { self.handleURL(text, coords) @@ -74,13 +71,13 @@ Metamaps.PasteInput = { handleURL: function (text, coords) { var title = 'Link' if (!coords || !coords.x || !coords.y) { - coords = Metamaps.AutoLayout.getNextCoord() + coords = AutoLayout.getNextCoord() } var import_id = null // don't store a cidMapping var permission = null // use default - Metamaps.Import.createTopicWithParameters( + Import.createTopicWithParameters( title, 'Reference', // metacode - todo fix permission, @@ -91,7 +88,7 @@ Metamaps.PasteInput = { import_id, { success: function(topic) { - Metamaps.TopicCard.showCard(topic.get('node'), function() { + TopicCard.showCard(topic.get('node'), function() { $('#showcard #titleActivator').click() .find('textarea, input').focus() }) @@ -101,10 +98,12 @@ Metamaps.PasteInput = { }, handleJSON: function (text) { - Metamaps.Import.handleJSON(text) + Import.handleJSON(text) }, handleTSV: function (text) { - Metamaps.Import.handleTSV(text) + Import.handleTSV(text) } } + +export default PasteInput diff --git a/frontend/src/Metamaps/ReactComponents.js b/frontend/src/Metamaps/ReactComponents.js new file mode 100644 index 00000000..a2495245 --- /dev/null +++ b/frontend/src/Metamaps/ReactComponents.js @@ -0,0 +1,7 @@ +import Maps from '../components/Maps' + +const ReactComponents = { + Maps +} + +export default ReactComponents diff --git a/app/assets/javascripts/src/Metamaps.Realtime.js b/frontend/src/Metamaps/Realtime.js similarity index 69% rename from app/assets/javascripts/src/Metamaps.Realtime.js rename to frontend/src/Metamaps/Realtime.js index 620a561a..355e73f8 100644 --- a/app/assets/javascripts/src/Metamaps.Realtime.js +++ b/frontend/src/Metamaps/Realtime.js @@ -1,30 +1,32 @@ /* global Metamaps, $ */ +import _ from 'lodash' + +import Active from './Active' +import Control from './Control' +import GlobalUI from './GlobalUI' +import JIT from './JIT' +import Map from './Map' +import Mapper from './Mapper' +import Topic from './Topic' +import Util from './Util' +import Views from './Views' +import Visualize from './Visualize' + /* * Metamaps.Realtime.js * * Dependencies: - * - Metamaps.Active * - Metamaps.Backbone - * - Metamaps.Backbone - * - Metamaps.Control * - Metamaps.Erb - * - Metamaps.GlobalUI - * - Metamaps.JIT - * - Metamaps.Map - * - Metamaps.Mapper * - Metamaps.Mappers * - Metamaps.Mappings * - Metamaps.Messages * - Metamaps.Synapses - * - Metamaps.Topic * - Metamaps.Topics - * - Metamaps.Util - * - Metamaps.Views - * - Metamaps.Visualize */ -Metamaps.Realtime = { +const Realtime = { videoId: 'video-wrapper', socket: null, webrtc: null, @@ -37,7 +39,7 @@ Metamaps.Realtime = { inConversation: false, localVideo: null, init: function () { - var self = Metamaps.Realtime + var self = Realtime self.addJuntoListeners() @@ -52,7 +54,7 @@ Metamaps.Realtime = { self.disconnected = true }) - if (Metamaps.Active.Mapper) { + if (Active.Mapper) { self.webrtc = new SimpleWebRTC({ connection: self.socket, localVideoEl: self.videoId, @@ -69,23 +71,23 @@ Metamaps.Realtime = { video: true, audio: true }, - nick: Metamaps.Active.Mapper.id + nick: Active.Mapper.id }) var $video = $('').attr('id', self.videoId) self.localVideo = { $video: $video, - view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, { + view: new Views.VideoView($video[0], $('body'), 'me', true, { DOUBLE_CLICK_TOLERANCE: 200, - avatar: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '' + avatar: Active.Mapper ? Active.Mapper.get('image') : '' }) } - self.room = new Metamaps.Views.room({ + self.room = new Views.Room({ webrtc: self.webrtc, socket: self.socket, - username: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('name') : '', - image: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '', + username: Active.Mapper ? Active.Mapper.get('name') : '', + image: Active.Mapper ? Active.Mapper.get('image') : '', room: 'global', $video: self.localVideo.$video, myVideoView: self.localVideo.view, @@ -93,40 +95,40 @@ Metamaps.Realtime = { }) self.room.videoAdded(self.handleVideoAdded) - if (!Metamaps.Active.Map) { + if (!Active.Map) { self.room.chat.$container.hide() } $('body').prepend(self.room.chat.$container) - } // if Metamaps.Active.Mapper + } // if Active.Mapper }, addJuntoListeners: function () { - var self = Metamaps.Realtime + var self = Realtime - $(document).on(Metamaps.Views.chatView.events.openTray, function () { + $(document).on(Views.ChatView.events.openTray, function () { $('.main').addClass('compressed') self.chatOpen = true self.positionPeerIcons() }) - $(document).on(Metamaps.Views.chatView.events.closeTray, function () { + $(document).on(Views.ChatView.events.closeTray, function () { $('.main').removeClass('compressed') self.chatOpen = false self.positionPeerIcons() }) - $(document).on(Metamaps.Views.chatView.events.videosOn, function () { + $(document).on(Views.ChatView.events.videosOn, function () { $('#wrapper').removeClass('hideVideos') }) - $(document).on(Metamaps.Views.chatView.events.videosOff, function () { + $(document).on(Views.ChatView.events.videosOff, function () { $('#wrapper').addClass('hideVideos') }) - $(document).on(Metamaps.Views.chatView.events.cursorsOn, function () { + $(document).on(Views.ChatView.events.cursorsOn, function () { $('#wrapper').removeClass('hideCursors') }) - $(document).on(Metamaps.Views.chatView.events.cursorsOff, function () { + $(document).on(Views.ChatView.events.cursorsOff, function () { $('#wrapper').addClass('hideCursors') }) }, handleVideoAdded: function (v, id) { - var self = Metamaps.Realtime + var self = Realtime self.positionVideos() v.setParent($('#wrapper')) v.$container.find('.video-cutoff').css({ @@ -135,7 +137,7 @@ Metamaps.Realtime = { $('#wrapper').append(v.$container) }, positionVideos: function () { - var self = Metamaps.Realtime + var self = Realtime var videoIds = Object.keys(self.room.videos) var numOfVideos = videoIds.length var numOfVideosToPosition = _.filter(videoIds, function (id) { @@ -167,7 +169,7 @@ Metamaps.Realtime = { } // do self first - var myVideo = Metamaps.Realtime.localVideo.view + var myVideo = Realtime.localVideo.view if (!myVideo.manuallyPositioned) { myVideo.$container.css({ top: yFormula() + 'px', @@ -185,10 +187,10 @@ Metamaps.Realtime = { }) }, startActiveMap: function () { - var self = Metamaps.Realtime + var self = Realtime - if (Metamaps.Active.Map && Metamaps.Active.Mapper) { - if (Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)) { + if (Active.Map && Active.Mapper) { + if (Active.Map.authorizeToEdit(Active.Mapper)) { self.turnOn() self.setupSocket() } else { @@ -198,7 +200,7 @@ Metamaps.Realtime = { } }, endActiveMap: function () { - var self = Metamaps.Realtime + var self = Realtime $(document).off('.map') self.socket.removeAllListeners() @@ -213,21 +215,21 @@ Metamaps.Realtime = { } }, turnOn: function (notify) { - var self = Metamaps.Realtime + var self = Realtime if (notify) self.sendRealtimeOn() self.status = true $('.collabCompass').show() self.room.chat.$container.show() - self.room.room = 'map-' + Metamaps.Active.Map.id + self.room.room = 'map-' + Active.Map.id self.checkForACallToJoin() self.activeMapper = { - id: Metamaps.Active.Mapper.id, - name: Metamaps.Active.Mapper.get('name'), - username: Metamaps.Active.Mapper.get('name'), - image: Metamaps.Active.Mapper.get('image'), - color: Metamaps.Util.getPastelColor(), + id: Active.Mapper.id, + name: Active.Mapper.get('name'), + username: Active.Mapper.get('name'), + image: Active.Mapper.get('image'), + color: Util.getPastelColor(), self: true } self.localVideo.view.$container.find('.video-cutoff').css({ @@ -236,30 +238,30 @@ Metamaps.Realtime = { self.room.chat.addParticipant(self.activeMapper) }, checkForACallToJoin: function () { - var self = Metamaps.Realtime - self.socket.emit('checkForCall', { room: self.room.room, mapid: Metamaps.Active.Map.id }) + var self = Realtime + self.socket.emit('checkForCall', { room: self.room.room, mapid: Active.Map.id }) }, promptToJoin: function () { - var self = Metamaps.Realtime + var self = Realtime var notifyText = "There's a conversation happening, want to join?" notifyText += ' ' notifyText += ' ' - Metamaps.GlobalUI.notifyUser(notifyText, true) + GlobalUI.notifyUser(notifyText, true) self.room.conversationInProgress() }, conversationHasBegun: function () { - var self = Metamaps.Realtime + var self = Realtime if (self.inConversation) return var notifyText = "There's a conversation starting, want to join?" notifyText += ' ' notifyText += ' ' - Metamaps.GlobalUI.notifyUser(notifyText, true) + GlobalUI.notifyUser(notifyText, true) self.room.conversationInProgress() }, countOthersInConversation: function () { - var self = Metamaps.Realtime + var self = Realtime var count = 0 for (var key in self.mappersOnMap) { @@ -268,14 +270,14 @@ Metamaps.Realtime = { return count }, mapperJoinedCall: function (id) { - var self = Metamaps.Realtime + var self = Realtime var mapper = self.mappersOnMap[id] if (mapper) { if (self.inConversation) { var username = mapper.name var notifyText = username + ' joined the call' - Metamaps.GlobalUI.notifyUser(notifyText) + GlobalUI.notifyUser(notifyText) } mapper.inConversation = true @@ -283,14 +285,14 @@ Metamaps.Realtime = { } }, mapperLeftCall: function (id) { - var self = Metamaps.Realtime + var self = Realtime var mapper = self.mappersOnMap[id] if (mapper) { if (self.inConversation) { var username = mapper.name var notifyText = username + ' left the call' - Metamaps.GlobalUI.notifyUser(notifyText) + GlobalUI.notifyUser(notifyText) } mapper.inConversation = false @@ -303,7 +305,7 @@ Metamaps.Realtime = { } }, callEnded: function () { - var self = Metamaps.Realtime + var self = Realtime self.room.conversationEnding() self.room.leaveVideoOnly() @@ -322,7 +324,7 @@ Metamaps.Realtime = { self.webrtc.webrtc.localStreams = [] }, invitedToCall: function (inviter) { - var self = Metamaps.Realtime + var self = Realtime self.room.chat.sound.stop('sessioninvite') self.room.chat.sound.play('sessioninvite') @@ -332,10 +334,10 @@ Metamaps.Realtime = { notifyText += username + ' is inviting you to a conversation. Join live?' notifyText += ' ' notifyText += ' ' - Metamaps.GlobalUI.notifyUser(notifyText, true) + GlobalUI.notifyUser(notifyText, true) }, invitedToJoin: function (inviter) { - var self = Metamaps.Realtime + var self = Realtime self.room.chat.sound.stop('sessioninvite') self.room.chat.sound.play('sessioninvite') @@ -344,83 +346,83 @@ Metamaps.Realtime = { var notifyText = username + ' is inviting you to the conversation. Join?' notifyText += ' ' notifyText += ' ' - Metamaps.GlobalUI.notifyUser(notifyText, true) + GlobalUI.notifyUser(notifyText, true) }, acceptCall: function (userid) { - var self = Metamaps.Realtime + var self = Realtime self.room.chat.sound.stop('sessioninvite') self.socket.emit('callAccepted', { - mapid: Metamaps.Active.Map.id, - invited: Metamaps.Active.Mapper.id, + mapid: Active.Map.id, + invited: Active.Mapper.id, inviter: userid }) - $.post('/maps/' + Metamaps.Active.Map.id + '/events/conversation') + $.post('/maps/' + Active.Map.id + '/events/conversation') self.joinCall() - Metamaps.GlobalUI.clearNotify() + GlobalUI.clearNotify() }, denyCall: function (userid) { - var self = Metamaps.Realtime + var self = Realtime self.room.chat.sound.stop('sessioninvite') self.socket.emit('callDenied', { - mapid: Metamaps.Active.Map.id, - invited: Metamaps.Active.Mapper.id, + mapid: Active.Map.id, + invited: Active.Mapper.id, inviter: userid }) - Metamaps.GlobalUI.clearNotify() + GlobalUI.clearNotify() }, denyInvite: function (userid) { - var self = Metamaps.Realtime + var self = Realtime self.room.chat.sound.stop('sessioninvite') self.socket.emit('inviteDenied', { - mapid: Metamaps.Active.Map.id, - invited: Metamaps.Active.Mapper.id, + mapid: Active.Map.id, + invited: Active.Mapper.id, inviter: userid }) - Metamaps.GlobalUI.clearNotify() + GlobalUI.clearNotify() }, inviteACall: function (userid) { - var self = Metamaps.Realtime + var self = Realtime self.socket.emit('inviteACall', { - mapid: Metamaps.Active.Map.id, - inviter: Metamaps.Active.Mapper.id, + mapid: Active.Map.id, + inviter: Active.Mapper.id, invited: userid }) self.room.chat.invitationPending(userid) - Metamaps.GlobalUI.clearNotify() + GlobalUI.clearNotify() }, inviteToJoin: function (userid) { - var self = Metamaps.Realtime + var self = Realtime self.socket.emit('inviteToJoin', { - mapid: Metamaps.Active.Map.id, - inviter: Metamaps.Active.Mapper.id, + mapid: Active.Map.id, + inviter: Active.Mapper.id, invited: userid }) self.room.chat.invitationPending(userid) }, callAccepted: function (userid) { - var self = Metamaps.Realtime + var self = Realtime var username = self.mappersOnMap[userid].name - Metamaps.GlobalUI.notifyUser('Conversation starting...') + GlobalUI.notifyUser('Conversation starting...') self.joinCall() self.room.chat.invitationAnswered(userid) }, callDenied: function (userid) { - var self = Metamaps.Realtime + var self = Realtime var username = self.mappersOnMap[userid].name - Metamaps.GlobalUI.notifyUser(username + " didn't accept your invitation") + GlobalUI.notifyUser(username + " didn't accept your invitation") self.room.chat.invitationAnswered(userid) }, inviteDenied: function (userid) { - var self = Metamaps.Realtime + var self = Realtime var username = self.mappersOnMap[userid].name - Metamaps.GlobalUI.notifyUser(username + " didn't accept your invitation") + GlobalUI.notifyUser(username + " didn't accept your invitation") self.room.chat.invitationAnswered(userid) }, joinCall: function () { - var self = Metamaps.Realtime + var self = Realtime self.webrtc.off('readyToCall') self.webrtc.once('readyToCall', function () { @@ -436,22 +438,22 @@ Metamaps.Realtime = { }) self.inConversation = true self.socket.emit('mapperJoinedCall', { - mapid: Metamaps.Active.Map.id, - id: Metamaps.Active.Mapper.id + mapid: Active.Map.id, + id: Active.Mapper.id }) self.webrtc.startLocalVideo() - Metamaps.GlobalUI.clearNotify() - self.room.chat.mapperJoinedCall(Metamaps.Active.Mapper.id) + GlobalUI.clearNotify() + self.room.chat.mapperJoinedCall(Active.Mapper.id) }, leaveCall: function () { - var self = Metamaps.Realtime + var self = Realtime self.socket.emit('mapperLeftCall', { - mapid: Metamaps.Active.Map.id, - id: Metamaps.Active.Mapper.id + mapid: Active.Map.id, + id: Active.Mapper.id }) - self.room.chat.mapperLeftCall(Metamaps.Active.Mapper.id) + self.room.chat.mapperLeftCall(Active.Mapper.id) self.room.leaveVideoOnly() self.inConversation = false self.localVideo.view.$container.hide() @@ -463,7 +465,7 @@ Metamaps.Realtime = { } }, turnOff: function (silent) { - var self = Metamaps.Realtime + var self = Realtime if (self.status) { if (!silent) self.sendRealtimeOff() @@ -477,65 +479,65 @@ Metamaps.Realtime = { } }, setupSocket: function () { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket - var myId = Metamaps.Active.Mapper.id + var self = Realtime + var socket = Realtime.socket + var myId = Active.Mapper.id socket.emit('newMapperNotify', { userid: myId, - username: Metamaps.Active.Mapper.get('name'), - userimage: Metamaps.Active.Mapper.get('image'), - mapid: Metamaps.Active.Map.id + username: Active.Mapper.get('name'), + userimage: Active.Mapper.get('image'), + mapid: Active.Map.id }) - socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToCall', self.invitedToCall) // new call - socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToJoin', self.invitedToJoin) // call already in progress - socket.on(myId + '-' + Metamaps.Active.Map.id + '-callAccepted', self.callAccepted) - socket.on(myId + '-' + Metamaps.Active.Map.id + '-callDenied', self.callDenied) - socket.on(myId + '-' + Metamaps.Active.Map.id + '-inviteDenied', self.inviteDenied) + socket.on(myId + '-' + Active.Map.id + '-invitedToCall', self.invitedToCall) // new call + socket.on(myId + '-' + Active.Map.id + '-invitedToJoin', self.invitedToJoin) // call already in progress + socket.on(myId + '-' + Active.Map.id + '-callAccepted', self.callAccepted) + socket.on(myId + '-' + Active.Map.id + '-callDenied', self.callDenied) + socket.on(myId + '-' + Active.Map.id + '-inviteDenied', self.inviteDenied) // receive word that there's a conversation in progress - socket.on('maps-' + Metamaps.Active.Map.id + '-callInProgress', self.promptToJoin) - socket.on('maps-' + Metamaps.Active.Map.id + '-callStarting', self.conversationHasBegun) + socket.on('maps-' + Active.Map.id + '-callInProgress', self.promptToJoin) + socket.on('maps-' + Active.Map.id + '-callStarting', self.conversationHasBegun) - socket.on('maps-' + Metamaps.Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall) - socket.on('maps-' + Metamaps.Active.Map.id + '-mapperLeftCall', self.mapperLeftCall) + socket.on('maps-' + Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall) + socket.on('maps-' + Active.Map.id + '-mapperLeftCall', self.mapperLeftCall) // if you're the 'new guy' update your list with who's already online - socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList) + socket.on(myId + '-' + Active.Map.id + '-UpdateMapperList', self.updateMapperList) // receive word that there's a new mapper on the map - socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', self.newPeerOnMap) + socket.on('maps-' + Active.Map.id + '-newmapper', self.newPeerOnMap) // receive word that a mapper left the map - socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', self.lostPeerOnMap) + socket.on('maps-' + Active.Map.id + '-lostmapper', self.lostPeerOnMap) // receive word that there's a mapper turned on realtime - socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', self.newCollaborator) + socket.on('maps-' + Active.Map.id + '-newrealtime', self.newCollaborator) // receive word that there's a mapper turned on realtime - socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', self.lostCollaborator) + socket.on('maps-' + Active.Map.id + '-lostrealtime', self.lostCollaborator) // - socket.on('maps-' + Metamaps.Active.Map.id + '-topicDrag', self.topicDrag) + socket.on('maps-' + Active.Map.id + '-topicDrag', self.topicDrag) // - socket.on('maps-' + Metamaps.Active.Map.id + '-newTopic', self.newTopic) + socket.on('maps-' + Active.Map.id + '-newTopic', self.newTopic) // - socket.on('maps-' + Metamaps.Active.Map.id + '-newMessage', self.newMessage) + socket.on('maps-' + Active.Map.id + '-newMessage', self.newMessage) // - socket.on('maps-' + Metamaps.Active.Map.id + '-removeTopic', self.removeTopic) + socket.on('maps-' + Active.Map.id + '-removeTopic', self.removeTopic) // - socket.on('maps-' + Metamaps.Active.Map.id + '-newSynapse', self.newSynapse) + socket.on('maps-' + Active.Map.id + '-newSynapse', self.newSynapse) // - socket.on('maps-' + Metamaps.Active.Map.id + '-removeSynapse', self.removeSynapse) + socket.on('maps-' + Active.Map.id + '-removeSynapse', self.removeSynapse) // update mapper compass position - socket.on('maps-' + Metamaps.Active.Map.id + '-updatePeerCoords', self.updatePeerCoords) + socket.on('maps-' + Active.Map.id + '-updatePeerCoords', self.updatePeerCoords) // deletions socket.on('deleteTopicFromServer', self.removeTopic) @@ -551,7 +553,7 @@ Metamaps.Realtime = { x: event.pageX, y: event.pageY } - var coords = Metamaps.Util.pixelsToCoords(pixels) + var coords = Util.pixelsToCoords(pixels) self.sendCoords(coords) } $(document).on('mousemove.map', sendCoords) @@ -562,88 +564,88 @@ Metamaps.Realtime = { x: e.pageX, y: e.pageY } - var coords = Metamaps.Util.pixelsToCoords(pixels) + var coords = Util.pixelsToCoords(pixels) self.sendCoords(coords) } self.positionPeerIcons() } - $(document).on(Metamaps.JIT.events.zoom + '.map', zoom) + $(document).on(JIT.events.zoom + '.map', zoom) - $(document).on(Metamaps.JIT.events.pan + '.map', self.positionPeerIcons) + $(document).on(JIT.events.pan + '.map', self.positionPeerIcons) var sendTopicDrag = function (event, positions) { self.sendTopicDrag(positions) } - $(document).on(Metamaps.JIT.events.topicDrag + '.map', sendTopicDrag) + $(document).on(JIT.events.topicDrag + '.map', sendTopicDrag) var sendNewTopic = function (event, data) { self.sendNewTopic(data) } - $(document).on(Metamaps.JIT.events.newTopic + '.map', sendNewTopic) + $(document).on(JIT.events.newTopic + '.map', sendNewTopic) var sendDeleteTopic = function (event, data) { self.sendDeleteTopic(data) } - $(document).on(Metamaps.JIT.events.deleteTopic + '.map', sendDeleteTopic) + $(document).on(JIT.events.deleteTopic + '.map', sendDeleteTopic) var sendRemoveTopic = function (event, data) { self.sendRemoveTopic(data) } - $(document).on(Metamaps.JIT.events.removeTopic + '.map', sendRemoveTopic) + $(document).on(JIT.events.removeTopic + '.map', sendRemoveTopic) var sendNewSynapse = function (event, data) { self.sendNewSynapse(data) } - $(document).on(Metamaps.JIT.events.newSynapse + '.map', sendNewSynapse) + $(document).on(JIT.events.newSynapse + '.map', sendNewSynapse) var sendDeleteSynapse = function (event, data) { self.sendDeleteSynapse(data) } - $(document).on(Metamaps.JIT.events.deleteSynapse + '.map', sendDeleteSynapse) + $(document).on(JIT.events.deleteSynapse + '.map', sendDeleteSynapse) var sendRemoveSynapse = function (event, data) { self.sendRemoveSynapse(data) } - $(document).on(Metamaps.JIT.events.removeSynapse + '.map', sendRemoveSynapse) + $(document).on(JIT.events.removeSynapse + '.map', sendRemoveSynapse) var sendNewMessage = function (event, data) { self.sendNewMessage(data) } - $(document).on(Metamaps.Views.room.events.newMessage + '.map', sendNewMessage) + $(document).on(Views.Room.events.newMessage + '.map', sendNewMessage) }, attachMapListener: function () { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket socket.on('mapChangeFromServer', self.mapChange) }, sendRealtimeOn: function () { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // send this new mapper back your details, and the awareness that you're online var update = { - username: Metamaps.Active.Mapper.get('name'), - userid: Metamaps.Active.Mapper.id, - mapid: Metamaps.Active.Map.id + username: Active.Mapper.get('name'), + userid: Active.Mapper.id, + mapid: Active.Map.id } socket.emit('notifyStartRealtime', update) }, sendRealtimeOff: function () { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // send this new mapper back your details, and the awareness that you're online var update = { - username: Metamaps.Active.Mapper.get('name'), - userid: Metamaps.Active.Mapper.id, - mapid: Metamaps.Active.Map.id + username: Active.Mapper.get('name'), + userid: Active.Mapper.id, + mapid: Active.Map.id } socket.emit('notifyStopRealtime', update) }, updateMapperList: function (data) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // data.userid // data.username @@ -655,7 +657,7 @@ Metamaps.Realtime = { name: data.username, username: data.username, image: data.userimage, - color: Metamaps.Util.getPastelColor(), + color: Util.getPastelColor(), realtime: data.userrealtime, inConversation: data.userinconversation, coords: { @@ -664,7 +666,7 @@ Metamaps.Realtime = { } } - if (data.userid !== Metamaps.Active.Mapper.id) { + if (data.userid !== Active.Mapper.id) { self.room.chat.addParticipant(self.mappersOnMap[data.userid]) if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid) @@ -673,8 +675,8 @@ Metamaps.Realtime = { } }, newPeerOnMap: function (data) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // data.userid // data.username @@ -687,7 +689,7 @@ Metamaps.Realtime = { name: data.username, username: data.username, image: data.userimage, - color: Metamaps.Util.getPastelColor(), + color: Util.getPastelColor(), realtime: true, coords: { x: 0, @@ -696,7 +698,7 @@ Metamaps.Realtime = { } // create an item for them in the realtime box - if (data.userid !== Metamaps.Active.Mapper.id && self.status) { + if (data.userid !== Active.Mapper.id && self.status) { self.room.chat.sound.play('joinmap') self.room.chat.addParticipant(self.mappersOnMap[data.userid]) @@ -707,17 +709,17 @@ Metamaps.Realtime = { if (firstOtherPerson) { notifyMessage += ' ' } - Metamaps.GlobalUI.notifyUser(notifyMessage) + GlobalUI.notifyUser(notifyMessage) // send this new mapper back your details, and the awareness that you've loaded the map var update = { userToNotify: data.userid, - username: Metamaps.Active.Mapper.get('name'), - userimage: Metamaps.Active.Mapper.get('image'), - userid: Metamaps.Active.Mapper.id, + username: Active.Mapper.get('name'), + userimage: Active.Mapper.get('image'), + userid: Active.Mapper.id, userrealtime: self.status, userinconversation: self.inConversation, - mapid: Metamaps.Active.Map.id + mapid: Active.Map.id } socket.emit('updateNewMapperList', update) } @@ -741,8 +743,8 @@ Metamaps.Realtime = { }) }, lostPeerOnMap: function (data) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // data.userid // data.username @@ -753,7 +755,7 @@ Metamaps.Realtime = { $('#compass' + data.userid).remove() self.room.chat.removeParticipant(data.username) - Metamaps.GlobalUI.notifyUser(data.username + ' just left the map') + GlobalUI.notifyUser(data.username + ' just left the map') if ((self.inConversation && self.countOthersInConversation() === 0) || (!self.inConversation && self.countOthersInConversation() === 1)) { @@ -761,8 +763,8 @@ Metamaps.Realtime = { } }, newCollaborator: function (data) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // data.userid // data.username @@ -772,11 +774,11 @@ Metamaps.Realtime = { // $('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn') $('#compass' + data.userid).show() - Metamaps.GlobalUI.notifyUser(data.username + ' just turned on realtime') + GlobalUI.notifyUser(data.username + ' just turned on realtime') }, lostCollaborator: function (data) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket // data.userid // data.username @@ -786,18 +788,18 @@ Metamaps.Realtime = { // $('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff') $('#compass' + data.userid).hide() - Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime') + GlobalUI.notifyUser(data.username + ' just turned off realtime') }, updatePeerCoords: function (data) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket self.mappersOnMap[data.userid].coords = {x: data.usercoords.x,y: data.usercoords.y} self.positionPeerIcon(data.userid) }, positionPeerIcons: function () { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket if (self.status) { // if i have realtime turned on for (var key in self.mappersOnMap) { @@ -809,8 +811,8 @@ Metamaps.Realtime = { } }, positionPeerIcon: function (id) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket var boundary = self.chatOpen ? '#wrapper' : document var mapper = self.mappersOnMap[id] @@ -819,7 +821,7 @@ Metamaps.Realtime = { var compassDiameter = 56 var compassArrowSize = 24 - var origPixels = Metamaps.Util.coordsToPixels(mapper.coords) + var origPixels = Util.coordsToPixels(mapper.coords) var pixels = self.limitPixelsToScreen(origPixels) $('#compass' + id).css({ left: pixels.x + 'px', @@ -846,8 +848,8 @@ Metamaps.Realtime = { } }, limitPixelsToScreen: function (pixels) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket var boundary = self.chatOpen ? '#wrapper' : document var xLimit, yLimit @@ -864,48 +866,48 @@ Metamaps.Realtime = { return {x: xLimit,y: yLimit} }, sendCoords: function (coords) { - var self = Metamaps.Realtime - var socket = Metamaps.Realtime.socket + var self = Realtime + var socket = Realtime.socket - var map = Metamaps.Active.Map - var mapper = Metamaps.Active.Mapper + var map = Active.Map + var mapper = Active.Mapper if (self.status && map.authorizeToEdit(mapper) && socket) { var update = { usercoords: coords, - userid: Metamaps.Active.Mapper.id, - mapid: Metamaps.Active.Map.id + userid: Active.Mapper.id, + mapid: Active.Map.id } socket.emit('updateMapperCoords', update) } }, sendTopicDrag: function (positions) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map && self.status) { - positions.mapid = Metamaps.Active.Map.id + if (Active.Map && self.status) { + positions.mapid = Active.Map.id socket.emit('topicDrag', positions) } }, topicDrag: function (positions) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket var topic var node - if (Metamaps.Active.Map && self.status) { + if (Active.Map && self.status) { for (var key in positions) { topic = Metamaps.Topics.get(key) if (topic) node = topic.get('node') if (node) node.pos.setc(positions[key].x, positions[key].y) } // for - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() } }, sendTopicChange: function (topic) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket var data = { @@ -927,7 +929,7 @@ Metamaps.Realtime = { } }, sendSynapseChange: function (synapse) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket var data = { @@ -950,7 +952,7 @@ Metamaps.Realtime = { } }, sendMapChange: function (map) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket var data = { @@ -960,23 +962,23 @@ Metamaps.Realtime = { socket.emit('mapChangeFromClient', data) }, mapChange: function (data) { - var map = Metamaps.Active.Map + var map = Active.Map var isActiveMap = map && data.mapId === map.id if (isActiveMap) { - var couldEditBefore = map.authorizeToEdit(Metamaps.Active.Mapper) + var couldEditBefore = map.authorizeToEdit(Active.Mapper) var idBefore = map.id map.fetch({ success: function (model, response) { var idNow = model.id - var canEditNow = model.authorizeToEdit(Metamaps.Active.Mapper) + var canEditNow = model.authorizeToEdit(Active.Mapper) if (idNow !== idBefore) { - Metamaps.Map.leavePrivateMap() // this means the map has been changed to private + Map.leavePrivateMap() // this means the map has been changed to private } else if (couldEditBefore && !canEditNow) { - Metamaps.Map.cantEditNow() + Map.cantEditNow() } else if (!couldEditBefore && canEditNow) { - Metamaps.Map.canEditNow() + Map.canEditNow() } else { model.fetchContained() model.trigger('changeByOther') @@ -987,41 +989,41 @@ Metamaps.Realtime = { }, // newMessage sendNewMessage: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket var message = data.attributes - message.mapid = Metamaps.Active.Map.id + message.mapid = Active.Map.id socket.emit('newMessage', message) }, newMessage: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket self.room.addMessages(new Metamaps.Backbone.MessageCollection(data)) }, // newTopic sendNewTopic: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map && self.status) { - data.mapperid = Metamaps.Active.Mapper.id - data.mapid = Metamaps.Active.Map.id + if (Active.Map && self.status) { + data.mapperid = Active.Mapper.id + data.mapid = Active.Map.id socket.emit('newTopic', data) } }, newTopic: function (data) { - var topic, mapping, mapper, mapperCallback, cancel + var topic, mapping, mapper, cancel - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket if (!self.status) return function waitThenRenderTopic () { if (topic && mapping && mapper) { - Metamaps.Topic.renderTopic(mapping, topic, false, false) + Topic.renderTopic(mapping, topic, false, false) } else if (!cancel) { setTimeout(waitThenRenderTopic, 10) @@ -1030,11 +1032,10 @@ Metamaps.Realtime = { mapper = Metamaps.Mappers.get(data.mapperid) if (mapper === undefined) { - mapperCallback = function (m) { + Mapper.get(data.mapperid, function(m) { Metamaps.Mappers.add(m) mapper = m - } - Metamaps.Mapper.get(data.mapperid, mapperCallback) + }) } $.ajax({ url: '/topics/' + data.mappableid + '.json', @@ -1061,25 +1062,25 @@ Metamaps.Realtime = { }, // removeTopic sendDeleteTopic: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map) { + if (Active.Map) { socket.emit('deleteTopicFromClient', data) } }, // removeTopic sendRemoveTopic: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map) { - data.mapid = Metamaps.Active.Map.id + if (Active.Map) { + data.mapid = Active.Map.id socket.emit('removeTopic', data) } }, removeTopic: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket if (!self.status) return @@ -1088,26 +1089,26 @@ Metamaps.Realtime = { if (topic) { var node = topic.get('node') var mapping = topic.getMapping() - Metamaps.Control.hideNode(node.id) + Control.hideNode(node.id) Metamaps.Topics.remove(topic) Metamaps.Mappings.remove(mapping) } }, // newSynapse sendNewSynapse: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map) { - data.mapperid = Metamaps.Active.Mapper.id - data.mapid = Metamaps.Active.Map.id + if (Active.Map) { + data.mapperid = Active.Mapper.id + data.mapid = Active.Map.id socket.emit('newSynapse', data) } }, newSynapse: function (data) { var topic1, topic2, node1, node2, synapse, mapping, cancel - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket if (!self.status) return @@ -1119,7 +1120,7 @@ Metamaps.Realtime = { topic2 = synapse.getTopic2() node2 = topic2.get('node') - Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, false) + Synapse.renderSynapse(mapping, synapse, node1, node2, false) } else if (!cancel) { setTimeout(waitThenRenderSynapse, 10) @@ -1128,11 +1129,10 @@ Metamaps.Realtime = { mapper = Metamaps.Mappers.get(data.mapperid) if (mapper === undefined) { - mapperCallback = function (m) { + Mapper.get(data.mapperid, function(m) { Metamaps.Mappers.add(m) mapper = m - } - Metamaps.Mapper.get(data.mapperid, mapperCallback) + }) } $.ajax({ url: '/synapses/' + data.mappableid + '.json', @@ -1158,26 +1158,26 @@ Metamaps.Realtime = { }, // deleteSynapse sendDeleteSynapse: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map) { - data.mapid = Metamaps.Active.Map.id + if (Active.Map) { + data.mapid = Active.Map.id socket.emit('deleteSynapseFromClient', data) } }, // removeSynapse sendRemoveSynapse: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket - if (Metamaps.Active.Map) { - data.mapid = Metamaps.Active.Map.id + if (Active.Map) { + data.mapid = Active.Map.id socket.emit('removeSynapse', data) } }, removeSynapse: function (data) { - var self = Metamaps.Realtime + var self = Realtime var socket = self.socket if (!self.status) return @@ -1187,7 +1187,7 @@ Metamaps.Realtime = { var edge = synapse.get('edge') var mapping = synapse.getMapping() if (edge.getData('mappings').length - 1 === 0) { - Metamaps.Control.hideEdge(edge) + Control.hideEdge(edge) } var index = _.indexOf(edge.getData('synapses'), synapse) @@ -1200,4 +1200,6 @@ Metamaps.Realtime = { Metamaps.Mappings.remove(mapping) } }, -}; // end Metamaps.Realtime +} + +export default Realtime diff --git a/frontend/src/Metamaps/Router.js b/frontend/src/Metamaps/Router.js new file mode 100644 index 00000000..c5f1c9a7 --- /dev/null +++ b/frontend/src/Metamaps/Router.js @@ -0,0 +1,252 @@ +/* global Metamaps, $ */ + +import Backbone from 'backbone' +//TODO is this line good or bad? +//Backbone.$ = window.$ + +import Active from './Active' +import GlobalUI from './GlobalUI' +import JIT from './JIT' +import Map from './Map' +import Topic from './Topic' +import Views from './Views' +import Visualize from './Visualize' + +/* + * Metamaps.Router.js.erb + * + * Dependencies: + * - Metamaps.Loading + * - Metamaps.Maps + */ + +const _Router = Backbone.Router.extend({ + currentPage: '', + currentSection: '', + timeoutId: undefined, + routes: { + '': 'home', // #home + 'explore/:section': 'explore', // #explore/active + 'explore/:section/:id': 'explore', // #explore/mapper/1234 + 'maps/:id': 'maps' // #maps/7 + }, + home: function () { + let self = this + clearTimeout(this.timeoutId) + + if (Active.Mapper) document.title = 'Explore Active Maps | Metamaps' + else document.title = 'Home | Metamaps' + + this.currentSection = '' + this.currentPage = '' + $('.wrapper').removeClass('mapPage topicPage') + + var classes = Active.Mapper ? 'homePage explorePage' : 'homePage' + $('.wrapper').addClass(classes) + + var navigate = function () { + self.timeoutId = setTimeout(function () { + self.navigate('') + }, 300) + } + + // all this only for the logged in home page + if (Active.Mapper) { + $('.homeButton a').attr('href', '/') + GlobalUI.hideDiv('#yield') + + GlobalUI.showDiv('#explore') + + Views.ExploreMaps.setCollection(Metamaps.Maps.Active) + if (Metamaps.Maps.Active.length === 0) { + Metamaps.Maps.Active.getMaps(navigate) // this will trigger an explore maps render + } else { + Views.ExploreMaps.render(navigate) + } + } else { + // logged out home page + GlobalUI.hideDiv('#explore') + GlobalUI.showDiv('#yield') + this.timeoutId = setTimeout(navigate, 500) + } + + GlobalUI.hideDiv('#infovis') + GlobalUI.hideDiv('#instructions') + Map.end() + Topic.end() + Active.Map = null + Active.Topic = null + }, + explore: function (section, id) { + var self = this + clearTimeout(this.timeoutId) + + // just capitalize the variable section + // either 'featured', 'mapper', or 'active' + var capitalize = section.charAt(0).toUpperCase() + section.slice(1) + + if (section === 'shared' || section === 'featured' || section === 'active' || section === 'starred') { + document.title = 'Explore ' + capitalize + ' Maps | Metamaps' + } else if (section === 'mapper') { + $.ajax({ + url: '/users/' + id + '.json', + success: function (response) { + document.title = response.name + ' | Metamaps' + }, + error: function () {} + }) + } else if (section === 'mine') { + document.title = 'Explore My Maps | Metamaps' + } + + if (Active.Mapper && section != 'mapper') $('.homeButton a').attr('href', '/explore/' + section) + $('.wrapper').removeClass('homePage mapPage topicPage') + $('.wrapper').addClass('explorePage') + + this.currentSection = 'explore' + this.currentPage = section + + // this will mean it's a mapper page being loaded + if (id) { + if (Metamaps.Maps.Mapper.mapperId !== id) { + // empty the collection if we are trying to load the maps + // collection of a different mapper than we had previously + Metamaps.Maps.Mapper.reset() + Metamaps.Maps.Mapper.page = 1 + } + Metamaps.Maps.Mapper.mapperId = id + } + + Views.ExploreMaps.setCollection(Metamaps.Maps[capitalize]) + + var navigate = function () { + var path = '/explore/' + self.currentPage + + // alter url if for mapper profile page + if (self.currentPage === 'mapper') { + path += '/' + Metamaps.Maps.Mapper.mapperId + } + + self.navigate(path) + } + var navigateTimeout = function () { + self.timeoutId = setTimeout(navigate, 300) + } + if (Metamaps.Maps[capitalize].length === 0) { + Metamaps.Loading.show() + setTimeout(function () { + Metamaps.Maps[capitalize].getMaps(navigate) // this will trigger an explore maps render + }, 300) // wait 300 milliseconds till the other animations are done to do the fetch + } else { + if (id) { + Views.ExploreMaps.fetchUserThenRender(navigateTimeout) + } else { + Views.ExploreMaps.render(navigateTimeout) + } + } + + GlobalUI.showDiv('#explore') + GlobalUI.hideDiv('#yield') + GlobalUI.hideDiv('#infovis') + GlobalUI.hideDiv('#instructions') + Map.end() + Topic.end() + Active.Map = null + Active.Topic = null + }, + maps: function (id) { + clearTimeout(this.timeoutId) + + document.title = 'Map ' + id + ' | Metamaps' + + this.currentSection = 'map' + this.currentPage = id + + $('.wrapper').removeClass('homePage explorePage topicPage') + $('.wrapper').addClass('mapPage') + // another class will be added to wrapper if you + // can edit this map '.canEditMap' + + GlobalUI.hideDiv('#yield') + GlobalUI.hideDiv('#explore') + + // clear the visualization, if there was one, before showing its div again + if (Visualize.mGraph) { + Visualize.mGraph.graph.empty() + Visualize.mGraph.plot() + JIT.centerMap(Visualize.mGraph.canvas) + } + GlobalUI.showDiv('#infovis') + Topic.end() + Active.Topic = null + + Metamaps.Loading.show() + Map.end() + Map.launch(id) + }, + topics: function (id) { + clearTimeout(this.timeoutId) + + document.title = 'Topic ' + id + ' | Metamaps' + + this.currentSection = 'topic' + this.currentPage = id + + $('.wrapper').removeClass('homePage explorePage mapPage') + $('.wrapper').addClass('topicPage') + + GlobalUI.hideDiv('#yield') + GlobalUI.hideDiv('#explore') + + // clear the visualization, if there was one, before showing its div again + if (Visualize.mGraph) { + Visualize.mGraph.graph.empty() + Visualize.mGraph.plot() + JIT.centerMap(Visualize.mGraph.canvas) + } + GlobalUI.showDiv('#infovis') + Map.end() + Active.Map = null + + Topic.end() + Topic.launch(id) + } +}) + +const Router = new _Router() + +Router.intercept = function (evt) { + var segments + + var href = { + prop: $(this).prop('href'), + attr: $(this).attr('href') + } + var root = window.location.protocol + '//' + window.location.host + Backbone.history.options.root + + if (href.prop && href.prop === root) href.attr = '' + + if (href.prop && href.prop.slice(0, root.length) === root) { + evt.preventDefault() + + segments = href.attr.split('/') + segments.splice(0, 1) // pop off the element created by the first / + + if (href.attr === '') { + Router.home() + } else { + Router[segments[0]](segments[1], segments[2]) + } + } +} + +Router.init = function () { + Backbone.history.start({ + silent: true, + pushState: true, + root: '/' + }) + $(document).on('click', 'a[data-router="true"]', Router.intercept) +} + +export default Router diff --git a/frontend/src/Metamaps/Selected.js b/frontend/src/Metamaps/Selected.js new file mode 100644 index 00000000..396270ab --- /dev/null +++ b/frontend/src/Metamaps/Selected.js @@ -0,0 +1,11 @@ +const Selected = { + reset: function () { + var self = Metamaps.Selected + self.Nodes = [] + self.Edges = [] + }, + Nodes: [], + Edges: [] +} + +export default Selected diff --git a/frontend/src/Metamaps/Settings.js b/frontend/src/Metamaps/Settings.js new file mode 100644 index 00000000..687a6629 --- /dev/null +++ b/frontend/src/Metamaps/Settings.js @@ -0,0 +1,21 @@ +const Settings = { + embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages + sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database + colors: { + background: '#344A58', + synapses: { + normal: '#888888', + hover: '#888888', + selected: '#FFFFFF' + }, + topics: { + selected: '#FFFFFF' + }, + labels: { + background: '#18202E', + text: '#DDD' + } + }, +} + +export default Settings diff --git a/app/assets/javascripts/src/Metamaps.Synapse.js b/frontend/src/Metamaps/Synapse.js similarity index 73% rename from app/assets/javascripts/src/Metamaps.Synapse.js rename to frontend/src/Metamaps/Synapse.js index ceed219d..b50e50e6 100644 --- a/app/assets/javascripts/src/Metamaps.Synapse.js +++ b/frontend/src/Metamaps/Synapse.js @@ -1,23 +1,25 @@ /* global Metamaps, $ */ +import Active from './Active' +import Control from './Control' +import Create from './Create' +import JIT from './JIT' +import Map from './Map' +import Selected from './Selected' +import Settings from './Settings' +import Visualize from './Visualize' + /* * Metamaps.Synapse.js.erb * * Dependencies: * - Metamaps.Backbone - * - Metamaps.Control - * - Metamaps.Create - * - Metamaps.JIT - * - Metamaps.Map * - Metamaps.Mappings - * - Metamaps.Selected - * - Metamaps.Settings * - Metamaps.Synapses * - Metamaps.Topics - * - Metamaps.Visualize */ -Metamaps.Synapse = { +const Synapse = { // this function is to retrieve a synapse JSON object from the database // @param id = the id of the synapse to retrieve get: function (id, callback) { @@ -52,18 +54,18 @@ Metamaps.Synapse = { * */ renderSynapse: function (mapping, synapse, node1, node2, createNewInDB) { - var self = Metamaps.Synapse + var self = Synapse var edgeOnViz var newedge = synapse.createEdge(mapping) - Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data) - edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id) + Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data) + edgeOnViz = Visualize.mGraph.graph.getAdjacence(node1.id, node2.id) synapse.set('edge', edgeOnViz) synapse.updateEdge() // links the synapse and the mapping to the edge - Metamaps.Control.selectEdge(edgeOnViz) + Control.selectEdge(edgeOnViz) var mappingSuccessCallback = function (mappingModel, response) { var newSynapseData = { @@ -71,17 +73,17 @@ Metamaps.Synapse = { mappableid: mappingModel.get('mappable_id') } - $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]) + $(document).trigger(JIT.events.newSynapse, [newSynapseData]) } var synapseSuccessCallback = function (synapseModel, response) { - if (Metamaps.Active.Map) { + if (Active.Map) { mapping.save({ mappable_id: synapseModel.id }, { success: mappingSuccessCallback }) } } - if (!Metamaps.Settings.sandbox && createNewInDB) { + if (!Settings.sandbox && createNewInDB) { if (synapse.isNew()) { synapse.save(null, { success: synapseSuccessCallback, @@ -89,7 +91,7 @@ Metamaps.Synapse = { console.log('error saving synapse to database') } }) - } else if (!synapse.isNew() && Metamaps.Active.Map) { + } else if (!synapse.isNew() && Active.Map) { mapping.save(null, { success: mappingSuccessCallback }) @@ -97,7 +99,7 @@ Metamaps.Synapse = { } }, createSynapseLocally: function () { - var self = Metamaps.Synapse, + var self = Synapse, topic1, topic2, node1, @@ -105,27 +107,27 @@ Metamaps.Synapse = { synapse, mapping - $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + $(document).trigger(Map.events.editedByActiveMapper) // for each node in this array we will create a synapse going to the position2 node. var synapsesToCreate = [] - topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id) + topic2 = Metamaps.Topics.get(Create.newSynapse.topic2id) node2 = topic2.get('node') - var len = Metamaps.Selected.Nodes.length + var len = Selected.Nodes.length if (len == 0) { - topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id) + topic1 = Metamaps.Topics.get(Create.newSynapse.topic1id) synapsesToCreate[0] = topic1.get('node') } else if (len > 0) { - synapsesToCreate = Metamaps.Selected.Nodes + synapsesToCreate = Selected.Nodes } for (var i = 0; i < synapsesToCreate.length; i++) { node1 = synapsesToCreate[i] topic1 = node1.getData('topic') synapse = new Metamaps.Backbone.Synapse({ - desc: Metamaps.Create.newSynapse.description, + desc: Create.newSynapse.description, node1_id: topic1.isNew() ? topic1.cid : topic1.id, node2_id: topic2.isNew() ? topic2.cid : topic2.id, }) @@ -141,10 +143,10 @@ Metamaps.Synapse = { self.renderSynapse(mapping, synapse, node1, node2, true) } // for each in synapsesToCreate - Metamaps.Create.newSynapse.hide() + Create.newSynapse.hide() }, getSynapseFromAutocomplete: function (id) { - var self = Metamaps.Synapse, + var self = Synapse, topic1, topic2, node1, @@ -158,12 +160,14 @@ Metamaps.Synapse = { }) Metamaps.Mappings.add(mapping) - topic1 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic1id) + topic1 = Metamaps.Topics.get(Create.newSynapse.topic1id) node1 = topic1.get('node') - topic2 = Metamaps.Topics.get(Metamaps.Create.newSynapse.topic2id) + topic2 = Metamaps.Topics.get(Create.newSynapse.topic2id) node2 = topic2.get('node') - Metamaps.Create.newSynapse.hide() + Create.newSynapse.hide() self.renderSynapse(mapping, synapse, node1, node2, true) } -}; // end Metamaps.Synapse +} + +export default Synapse diff --git a/app/assets/javascripts/src/Metamaps.SynapseCard.js b/frontend/src/Metamaps/SynapseCard.js similarity index 90% rename from app/assets/javascripts/src/Metamaps.SynapseCard.js rename to frontend/src/Metamaps/SynapseCard.js index f71601e5..28ff1e32 100644 --- a/app/assets/javascripts/src/Metamaps.SynapseCard.js +++ b/frontend/src/Metamaps/SynapseCard.js @@ -1,18 +1,13 @@ -/* global Metamaps, $ */ +/* global $ */ +import Active from './Active' +import Control from './Control' +import Mapper from './Mapper' +import Visualize from './Visualize' -/* - * Metamaps.SynapseCard.js - * - * Dependencies: - * - Metamaps.Active - * - Metamaps.Control - * - Metamaps.Mapper - * - Metamaps.Visualize - */ -Metamaps.SynapseCard = { +const SynapseCard = { openSynapseCard: null, showCard: function (edge, e) { - var self = Metamaps.SynapseCard + var self = SynapseCard // reset so we don't interfere with other edges, but first, save its x and y var myX = $('#edit_synapse').css('left') @@ -20,7 +15,7 @@ Metamaps.SynapseCard = { $('#edit_synapse').remove() // so label is missing while editing - Metamaps.Control.deselectEdge(edge) + Control.deselectEdge(edge) var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0 var synapse = edge.getData('synapses')[index]; // for now, just get the first synapse @@ -30,9 +25,9 @@ Metamaps.SynapseCard = { var edit_div = document.createElement('div') edit_div.innerHTML = '
          ' edit_div.setAttribute('id', 'edit_synapse') - if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) { + if (synapse.authorizeToEdit(Active.Mapper)) { edit_div.className = 'permission canEdit' - edit_div.className += synapse.authorizePermissionChange(Metamaps.Active.Mapper) ? ' yourEdge' : '' + edit_div.className += synapse.authorizePermissionChange(Active.Mapper) ? ' yourEdge' : '' } else { edit_div.className = 'permission cannotEdit' } @@ -58,11 +53,11 @@ Metamaps.SynapseCard = { hideCard: function () { $('#edit_synapse').remove() - Metamaps.SynapseCard.openSynapseCard = null + SynapseCard.openSynapseCard = null }, populateShowCard: function (edge, synapse) { - var self = Metamaps.SynapseCard + var self = SynapseCard self.add_synapse_count(edge) self.add_desc_form(synapse) @@ -94,7 +89,7 @@ Metamaps.SynapseCard = { // if edge data is blank or just whitespace, populate it with data_nil if ($('#edit_synapse_desc').html().trim() == '') { - if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) { + if (synapse.authorizeToEdit(Active.Mapper)) { $('#edit_synapse_desc').html(data_nil) } else { $('#edit_synapse_desc').html('(no description)') @@ -109,8 +104,8 @@ Metamaps.SynapseCard = { synapse.set('desc', desc) } synapse.trigger('saved') - Metamaps.Control.selectEdge(synapse.get('edge')) - Metamaps.Visualize.mGraph.plot() + Control.selectEdge(synapse.get('edge')) + Visualize.mGraph.plot() }) }, add_drop_down: function (edge, synapse) { @@ -152,8 +147,8 @@ Metamaps.SynapseCard = { e.stopPropagation() var index = parseInt($(this).attr('data-synapse-index')) edge.setData('displayIndex', index) - Metamaps.Visualize.mGraph.plot() - Metamaps.SynapseCard.showCard(edge, false) + Visualize.mGraph.plot() + SynapseCard.showCard(edge, false) }) } }, @@ -167,7 +162,7 @@ Metamaps.SynapseCard = { var setMapperImage = function (mapper) { $('#edgeUser img').attr('src', mapper.get('image')) } - Metamaps.Mapper.get(synapse.get('user_id'), setMapperImage) + Mapper.get(synapse.get('user_id'), setMapperImage) }, add_perms_form: function (synapse) { @@ -210,7 +205,7 @@ Metamaps.SynapseCard = { $('#edit_synapse .permissionSelect').remove() } - if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + if (synapse.authorizePermissionChange(Active.Mapper)) { $('#edit_synapse.yourEdge .mapPerm').click(openPermissionSelect) $('#edit_synapse').click(hidePermissionSelect) } @@ -257,7 +252,7 @@ Metamaps.SynapseCard = { $('#edit_synapse_right').addClass('checked') } - if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) { + if (synapse.authorizeToEdit(Active.Mapper)) { $('#edit_synapse_left, #edit_synapse_right').click(function () { $(this).toggleClass('checked') @@ -281,8 +276,10 @@ Metamaps.SynapseCard = { node1_id: dir[0], node2_id: dir[1] }) - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() }) } // if } // add_direction_form -}; // end Metamaps.SynapseCard +} + +export default SynapseCard diff --git a/app/assets/javascripts/src/Metamaps.Topic.js b/frontend/src/Metamaps/Topic.js similarity index 66% rename from app/assets/javascripts/src/Metamaps.Topic.js rename to frontend/src/Metamaps/Topic.js index a0ebfa82..c2f3ff29 100644 --- a/app/assets/javascripts/src/Metamaps.Topic.js +++ b/frontend/src/Metamaps/Topic.js @@ -1,33 +1,35 @@ /* global Metamaps, $ */ +import $jit from '../patched/JIT' + +import Active from './Active' +import AutoLayout from './AutoLayout' +import Create from './Create' +import Filter from './Filter' +import GlobalUI from './GlobalUI' +import JIT from './JIT' +import Map from './Map' +import Router from './Router' +import Selected from './Selected' +import Settings from './Settings' +import SynapseCard from './SynapseCard' +import TopicCard from './TopicCard' +import Util from './Util' +import Visualize from './Visualize' + + /* * Metamaps.Topic.js.erb * * Dependencies: - * - Metamaps.Active * - Metamaps.Backbone - * - Metamaps.Backbone - * - Metamaps.Create * - Metamaps.Creators - * - Metamaps.Famous - * - Metamaps.Filter - * - Metamaps.GlobalUI - * - Metamaps.JIT * - Metamaps.Mappings - * - Metamaps.Selected - * - Metamaps.Settings - * - Metamaps.SynapseCard * - Metamaps.Synapses - * - Metamaps.TopicCard * - Metamaps.Topics - * - Metamaps.Util - * - Metamaps.Visualize - * - Metamaps.tempInit - * - Metamaps.tempNode - * - Metamaps.tempNode2 */ -Metamaps.Topic = { +const Topic = { // this function is to retrieve a topic JSON object from the database // @param id = the id of the topic to retrieve get: function (id, callback) { @@ -61,7 +63,7 @@ Metamaps.Topic = { launch: function (id) { var bb = Metamaps.Backbone var start = function (data) { - Metamaps.Active.Topic = new bb.Topic(data.topic) + Active.Topic = new bb.Topic(data.topic) Metamaps.Creators = new bb.MapperCollection(data.creators) Metamaps.Topics = new bb.TopicCollection([data.topic].concat(data.relatives)) Metamaps.Synapses = new bb.SynapseCollection(data.synapses) @@ -71,22 +73,22 @@ Metamaps.Topic = { $('#filter_by_mapper h3').html('CREATORS') // build and render the visualization - Metamaps.Visualize.type = 'RGraph' - Metamaps.JIT.prepareVizData() + Visualize.type = 'RGraph' + JIT.prepareVizData() // update filters - Metamaps.Filter.reset() + Filter.reset() // reset selected arrays - Metamaps.Selected.reset() + Selected.reset() // these three update the actual filter box with the right list items - Metamaps.Filter.checkMetacodes() - Metamaps.Filter.checkSynapses() - Metamaps.Filter.checkMappers() + Filter.checkMetacodes() + Filter.checkSynapses() + Filter.checkMappers() // for mobile - $('#header_content').html(Metamaps.Active.Topic.get('name')) + $('#header_content').html(Active.Topic.get('name')) } $.ajax({ @@ -95,25 +97,25 @@ Metamaps.Topic = { }) }, end: function () { - if (Metamaps.Active.Topic) { + if (Active.Topic) { $('.rightclickmenu').remove() - Metamaps.TopicCard.hideCard() - Metamaps.SynapseCard.hideCard() - Metamaps.Filter.close() + TopicCard.hideCard() + SynapseCard.hideCard() + Filter.close() } }, centerOn: function (nodeid, callback) { // don't clash with fetchRelatives - if (!Metamaps.Visualize.mGraph.busy) { - Metamaps.Visualize.mGraph.onClick(nodeid, { + if (!Visualize.mGraph.busy) { + Visualize.mGraph.onClick(nodeid, { hideLabels: false, duration: 1000, onComplete: function () { if (callback) callback() } }) - Metamaps.Router.navigate('/topics/' + nodeid) - Metamaps.Active.Topic = Metamaps.Topics.get(nodeid) + Router.navigate('/topics/' + nodeid) + Active.Topic = Metamaps.Topics.get(nodeid) } }, fetchRelatives: function (nodes, metacode_id) { @@ -131,7 +133,7 @@ Metamaps.Topic = { var successCallback; successCallback = function (data) { - if (Metamaps.Visualize.mGraph.busy) { + if (Visualize.mGraph.busy) { // don't clash with centerOn window.setTimeout(function() { successCallback(data) }, 100) return @@ -144,8 +146,8 @@ Metamaps.Topic = { topicColl.add(topic) var synapseColl = new Metamaps.Backbone.SynapseCollection(data.synapses) - var graph = Metamaps.JIT.convertModelsToJIT(topicColl, synapseColl)[0] - Metamaps.Visualize.mGraph.op.sum(graph, { + var graph = JIT.convertModelsToJIT(topicColl, synapseColl)[0] + Visualize.mGraph.op.sum(graph, { type: 'fade', duration: 500, hideLabels: false @@ -153,7 +155,7 @@ Metamaps.Topic = { var i, l, t, s - Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + Visualize.mGraph.graph.eachNode(function (n) { t = Metamaps.Topics.get(n.id) t.set({ node: n }, { silent: true }) t.updateNode() @@ -189,8 +191,8 @@ Metamaps.Topic = { // opts is additional options in a hash // TODO: move createNewInDB and permitCerateSYnapseAfter into opts - renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter, opts) { - var self = Metamaps.Topic + renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter, opts = {}) { + var self = Topic var nodeOnViz, tempPos @@ -198,56 +200,56 @@ Metamaps.Topic = { var midpoint = {}, pixelPos - if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { - Metamaps.Visualize.mGraph.graph.addNode(newnode) - nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id) + if (!$.isEmptyObject(Visualize.mGraph.graph.nodes)) { + Visualize.mGraph.graph.addNode(newnode) + nodeOnViz = Visualize.mGraph.graph.getNode(newnode.id) 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') - if (Metamaps.Visualize.type === 'RGraph') { + if (Visualize.type === 'RGraph') { tempPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')) tempPos = tempPos.toPolar() nodeOnViz.setPos(tempPos, 'current') nodeOnViz.setPos(tempPos, 'start') nodeOnViz.setPos(tempPos, 'end') - } else if (Metamaps.Visualize.type === 'ForceDirected') { + } else if (Visualize.type === 'ForceDirected') { nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current') nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start') nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end') } - if (Metamaps.Create.newTopic.addSynapse && permitCreateSynapseAfter) { - Metamaps.Create.newSynapse.topic1id = Metamaps.tempNode.getData('topic').id + if (Create.newTopic.addSynapse && permitCreateSynapseAfter) { + Create.newSynapse.topic1id = JIT.tempNode.getData('topic').id // position the form - midpoint.x = Metamaps.tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - Metamaps.tempNode.pos.getc().x) / 2 - midpoint.y = Metamaps.tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - Metamaps.tempNode.pos.getc().y) / 2 - pixelPos = Metamaps.Util.coordsToPixels(midpoint) + midpoint.x = JIT.tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - JIT.tempNode.pos.getc().x) / 2 + midpoint.y = JIT.tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - JIT.tempNode.pos.getc().y) / 2 + pixelPos = Util.coordsToPixels(midpoint) $('#new_synapse').css('left', pixelPos.x + 'px') $('#new_synapse').css('top', pixelPos.y + 'px') // show the form - Metamaps.Create.newSynapse.open() - Metamaps.Visualize.mGraph.fx.animate({ + Create.newSynapse.open() + Visualize.mGraph.fx.animate({ modes: ['node-property:dim'], duration: 500, onComplete: function () { - Metamaps.tempNode = null - Metamaps.tempNode2 = null - Metamaps.tempInit = false + JIT.tempNode = null + JIT.tempNode2 = null + JIT.tempInit = false } }) } else { - Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas) - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.plotNode(nodeOnViz, Visualize.mGraph.canvas) + Visualize.mGraph.fx.animate({ modes: ['node-property:dim'], duration: 500, onComplete: function () {} }) } } else { - Metamaps.Visualize.mGraph.loadJSON(newnode) - nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id) + Visualize.mGraph.loadJSON(newnode) + nodeOnViz = Visualize.mGraph.graph.getNode(newnode.id) topic.set('node', nodeOnViz, {silent: true}) topic.updateNode() // links the topic and the mapping to the node @@ -256,8 +258,8 @@ Metamaps.Topic = { nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current') nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start') nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end') - Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas) - Metamaps.Visualize.mGraph.fx.animate({ + Visualize.mGraph.fx.plotNode(nodeOnViz, Visualize.mGraph.canvas) + Visualize.mGraph.fx.animate({ modes: ['node-property:dim'], duration: 500, onComplete: function () {} @@ -270,14 +272,14 @@ Metamaps.Topic = { mappableid: mappingModel.get('mappable_id') } - $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]) + $(document).trigger(JIT.events.newTopic, [newTopicData]) // call a success callback if provided if (opts.success) { opts.success(topicModel) } } var topicSuccessCallback = function (topicModel, response) { - if (Metamaps.Active.Map) { + if (Active.Map) { mapping.save({ mappable_id: topicModel.id }, { success: function (model, response) { mappingSuccessCallback(model, response, topicModel) @@ -288,12 +290,12 @@ Metamaps.Topic = { }) } - if (Metamaps.Create.newTopic.addSynapse) { - Metamaps.Create.newSynapse.topic2id = topicModel.id + if (Create.newTopic.addSynapse) { + Create.newSynapse.topic2id = topicModel.id } } - if (!Metamaps.Settings.sandbox && createNewInDB) { + if (!Settings.sandbox && createNewInDB) { if (topic.isNew()) { topic.save(null, { success: topicSuccessCallback, @@ -301,7 +303,7 @@ Metamaps.Topic = { console.log('error saving topic to database') } }) - } else if (!topic.isNew() && Metamaps.Active.Map) { + } else if (!topic.isNew() && Active.Map) { mapping.save(null, { success: mappingSuccessCallback }) @@ -309,58 +311,58 @@ Metamaps.Topic = { } }, createTopicLocally: function () { - var self = Metamaps.Topic + var self = Topic - if (Metamaps.Create.newTopic.name === '') { - Metamaps.GlobalUI.notifyUser('Please enter a topic title...') + if (Create.newTopic.name === '') { + GlobalUI.notifyUser('Please enter a topic title...') return } // hide the 'double-click to add a topic' message - Metamaps.GlobalUI.hideDiv('#instructions') + GlobalUI.hideDiv('#instructions') - $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + $(document).trigger(Map.events.editedByActiveMapper) - var metacode = Metamaps.Metacodes.get(Metamaps.Create.newTopic.metacode) + var metacode = Metamaps.Metacodes.get(Create.newTopic.metacode) var topic = new Metamaps.Backbone.Topic({ - name: Metamaps.Create.newTopic.name, + name: Create.newTopic.name, metacode_id: metacode.id, - defer_to_map_id: Metamaps.Active.Map.id + defer_to_map_id: Active.Map.id }) Metamaps.Topics.add(topic) - if (Metamaps.Create.newTopic.pinned) { - var nextCoords = Metamaps.AutoLayout.getNextCoord() + if (Create.newTopic.pinned) { + var nextCoords = AutoLayout.getNextCoord() } var mapping = new Metamaps.Backbone.Mapping({ - xloc: nextCoords ? nextCoords.x : Metamaps.Create.newTopic.x, - yloc: nextCoords ? nextCoords.y : Metamaps.Create.newTopic.y, + xloc: nextCoords ? nextCoords.x : Create.newTopic.x, + yloc: nextCoords ? nextCoords.y : Create.newTopic.y, mappable_id: topic.cid, mappable_type: 'Topic', }) Metamaps.Mappings.add(mapping) // these can't happen until the value is retrieved, which happens in the line above - Metamaps.Create.newTopic.hide() + Create.newTopic.hide() self.renderTopic(mapping, topic, true, true) // this function also includes the creation of the topic in the database }, getTopicFromAutocomplete: function (id) { - var self = Metamaps.Topic + var self = Topic - $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + $(document).trigger(Map.events.editedByActiveMapper) - Metamaps.Create.newTopic.hide() + Create.newTopic.hide() var topic = self.get(id) - if (Metamaps.Create.newTopic.pinned) { - var nextCoords = Metamaps.AutoLayout.getNextCoord() + if (Create.newTopic.pinned) { + var nextCoords = AutoLayout.getNextCoord() } var mapping = new Metamaps.Backbone.Mapping({ - xloc: nextCoords ? nextCoords.x : Metamaps.Create.newTopic.x, - yloc: nextCoords ? nextCoords.y : Metamaps.Create.newTopic.y, + xloc: nextCoords ? nextCoords.x : Create.newTopic.x, + yloc: nextCoords ? nextCoords.y : Create.newTopic.y, mappable_type: 'Topic', mappable_id: topic.id, }) @@ -369,13 +371,13 @@ Metamaps.Topic = { self.renderTopic(mapping, topic, true, true) }, getTopicFromSearch: function (event, id) { - var self = Metamaps.Topic + var self = Topic - $(document).trigger(Metamaps.Map.events.editedByActiveMapper) + $(document).trigger(Map.events.editedByActiveMapper) var topic = self.get(id) - var nextCoords = Metamaps.AutoLayout.getNextCoord() + var nextCoords = AutoLayout.getNextCoord() var mapping = new Metamaps.Backbone.Mapping({ xloc: nextCoords.x, yloc: nextCoords.y, @@ -386,10 +388,12 @@ Metamaps.Topic = { self.renderTopic(mapping, topic, true, true) - Metamaps.GlobalUI.notifyUser('Topic was added to your map!') + GlobalUI.notifyUser('Topic was added to your map!') event.stopPropagation() event.preventDefault() return false } -}; // end Metamaps.Topic +} + +export default Topic diff --git a/app/assets/javascripts/src/Metamaps.TopicCard.js b/frontend/src/Metamaps/TopicCard.js similarity index 86% rename from app/assets/javascripts/src/Metamaps.TopicCard.js rename to frontend/src/Metamaps/TopicCard.js index 1453104d..dad58565 100644 --- a/app/assets/javascripts/src/Metamaps.TopicCard.js +++ b/frontend/src/Metamaps/TopicCard.js @@ -1,27 +1,28 @@ /* global Metamaps, $ */ +import Active from './Active' +import GlobalUI from './GlobalUI' +import Mapper from './Mapper' +import Router from './Router' +import Util from './Util' +import Visualize from './Visualize' + /* * Metamaps.TopicCard.js * * Dependencies: - * - Metamaps.Active - * - Metamaps.GlobalUI - * - Metamaps.Mapper * - Metamaps.Metacodes - * - Metamaps.Router - * - Metamaps.Util - * - Metamaps.Visualize */ -Metamaps.TopicCard = { +const TopicCard = { openTopicCard: null, // stores the topic that's currently open authorizedToEdit: false, // stores boolean for edit permission for open topic card init: function () { - var self = Metamaps.TopicCard + var self = TopicCard // initialize best_in_place editing $('.authenticated div.permission.canEdit .best_in_place').best_in_place() - Metamaps.TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html()) + TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html()) // initialize topic card draggability and resizability $('.showcard').draggable({ @@ -38,12 +39,12 @@ Metamaps.TopicCard = { * @param {$jit.Graph.Node} node */ showCard: function (node, opts) { - var self = Metamaps.TopicCard - + var self = TopicCard + if (!opts) opts = {} var topic = node.getData('topic') self.openTopicCard = topic - self.authorizedToEdit = topic.authorizeToEdit(Metamaps.Active.Mapper) + self.authorizedToEdit = topic.authorizeToEdit(Active.Mapper) // populate the card that's about to show with the right topics data self.populateShowCard(topic) return $('.showcard').fadeIn('fast', function() { @@ -53,14 +54,14 @@ Metamaps.TopicCard = { }) }, hideCard: function () { - var self = Metamaps.TopicCard + var self = TopicCard $('.showcard').fadeOut('fast') self.openTopicCard = null self.authorizedToEdit = false }, embedlyCardRendered: function (iframe) { - var self = Metamaps.TopicCard + var self = TopicCard $('#embedlyLinkLoader').hide() @@ -77,7 +78,7 @@ Metamaps.TopicCard = { } }, removeLink: function () { - var self = Metamaps.TopicCard + var self = TopicCard self.openTopicCard.save({ link: null }) @@ -87,7 +88,7 @@ Metamaps.TopicCard = { $('.CardOnGraph').removeClass('hasAttachment') }, bindShowCardListeners: function (topic) { - var self = Metamaps.TopicCard + var self = TopicCard var showCard = document.getElementById('showcard') var authorized = self.authorizedToEdit @@ -96,7 +97,7 @@ Metamaps.TopicCard = { var setMapperImage = function (mapper) { $('.contributorIcon').attr('src', mapper.get('image')) } - Metamaps.Mapper.get(topic.get('user_id'), setMapperImage) + Mapper.get(topic.get('user_id'), setMapperImage) // starting embed.ly var resetFunc = function () { @@ -179,24 +180,16 @@ Metamaps.TopicCard = { topic.save({ metacode_id: metacode.id }) - Metamaps.Visualize.mGraph.plot() + Visualize.mGraph.plot() $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge') $('.metacodeTitle').hide() $('.showcard .icon').css('z-index', '1') } var openMetacodeSelect = function (event) { - var windowWidth - var showcardLeft var TOPICCARD_WIDTH = 300 var METACODESELECT_WIDTH = 404 - var distanceFromEdge - var MAX_METACODELIST_HEIGHT = 270 - var windowHeight - var showcardTop - var topicTitleHeight - var distanceFromBottom if (!selectingMetacode) { selectingMetacode = true @@ -205,9 +198,9 @@ Metamaps.TopicCard = { // select is accessible onscreen, when opened // while topic card is close to the right // edge of the screen - windowWidth = $(window).width() - showcardLeft = parseInt($('.showcard').css('left')) - distanceFromEdge = windowWidth - (showcardLeft + TOPICCARD_WIDTH) + var windowWidth = $(window).width() + var showcardLeft = parseInt($('.showcard').css('left')) + var distanceFromEdge = windowWidth - (showcardLeft + TOPICCARD_WIDTH) if (distanceFromEdge < METACODESELECT_WIDTH) { $('.metacodeSelect').addClass('onRightEdge') } @@ -216,11 +209,11 @@ Metamaps.TopicCard = { // select is accessible onscreen, when opened // while topic card is close to the bottom // edge of the screen - windowHeight = $(window).height() - showcardTop = parseInt($('.showcard').css('top')) - topicTitleHeight = $('.showcard .title').height() + parseInt($('.showcard .title').css('padding-top')) + parseInt($('.showcard .title').css('padding-bottom')) - heightOfSetList = $('.showcard .metacodeSelect').height() - distanceFromBottom = windowHeight - (showcardTop + topicTitleHeight) + var windowHeight = $(window).height() + var showcardTop = parseInt($('.showcard').css('top')) + var topicTitleHeight = $('.showcard .title').height() + parseInt($('.showcard .title').css('padding-top')) + parseInt($('.showcard .title').css('padding-bottom')) + var heightOfSetList = $('.showcard .metacodeSelect').height() + var distanceFromBottom = windowHeight - (showcardTop + topicTitleHeight) if (distanceFromBottom < MAX_METACODELIST_HEIGHT) { $('.metacodeSelect').addClass('onBottomEdge') } @@ -265,7 +258,7 @@ Metamaps.TopicCard = { // bind best_in_place ajax callbacks bipName.bind('ajax:success', function () { - var name = Metamaps.Util.decodeEntities($(this).html()) + var name = Util.decodeEntities($(this).html()) topic.set('name', name) topic.trigger('saved') }) @@ -313,7 +306,7 @@ Metamaps.TopicCard = { } // ability to change permission var selectingPermission = false - if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + if (topic.authorizePermissionChange(Active.Mapper)) { $('.showcard .yourTopic .mapPerm').click(openPermissionSelect) $('.showcard').click(hidePermissionSelect) } @@ -331,7 +324,7 @@ Metamaps.TopicCard = { $('.showcard .hoverTip').removeClass('hide') }) - $('.mapCount .tip li a').click(Metamaps.Router.intercept) + $('.mapCount .tip li a').click(Router.intercept) var originalText = $('.showMore').html() $('.mapCount .tip .showMore').unbind().toggle( @@ -349,13 +342,13 @@ Metamaps.TopicCard = { }) }, handleInvalidLink: function () { - var self = Metamaps.TopicCard + var self = TopicCard self.removeLink() - Metamaps.GlobalUI.notifyUser('Invalid link') + GlobalUI.notifyUser('Invalid link') }, populateShowCard: function (topic) { - var self = Metamaps.TopicCard + var self = TopicCard var showCard = document.getElementById('showcard') @@ -364,11 +357,11 @@ Metamaps.TopicCard = { var topicForTemplate = self.buildObject(topic) var html = self.generateShowcardHTML.render(topicForTemplate) - if (topic.authorizeToEdit(Metamaps.Active.Mapper)) { + if (topic.authorizeToEdit(Active.Mapper)) { var perm = document.createElement('div') var string = 'permission canEdit' - if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) string += ' yourTopic' + if (topic.authorizePermissionChange(Active.Mapper)) string += ' yourTopic' perm.className = string perm.innerHTML = html showCard.appendChild(perm) @@ -379,16 +372,16 @@ Metamaps.TopicCard = { showCard.appendChild(perm) } - Metamaps.TopicCard.bindShowCardListeners(topic) + TopicCard.bindShowCardListeners(topic) }, generateShowcardHTML: null, // will be initialized into a Hogan template within init function // generateShowcardHTML buildObject: function (topic) { - var self = Metamaps.TopicCard + var self = TopicCard var nodeValues = {} - var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper) + var authorized = topic.authorizeToEdit(Active.Mapper) if (!authorized) { } else { @@ -421,18 +414,18 @@ Metamaps.TopicCard = { var inmapsLinks = topic.get('inmapsLinks') || [] nodeValues.inmaps = '' if (inmapsAr.length < 6) { - for (i = 0; i < inmapsAr.length; i++) { + for (let i = 0; i < inmapsAr.length; i++) { var url = '/maps/' + inmapsLinks[i] nodeValues.inmaps += '
        • ' + inmapsAr[i] + '
        • ' } } else { - for (i = 0; i < 5; i++) { + for (let i = 0; i < 5; i++) { var url = '/maps/' + inmapsLinks[i] nodeValues.inmaps += '
        • ' + inmapsAr[i] + '
        • ' } extra = inmapsAr.length - 5 nodeValues.inmaps += '
        • See ' + extra + ' more...
        • ' - for (i = 5; i < inmapsAr.length; i++) { + for (let i = 5; i < inmapsAr.length; i++) { var url = '/maps/' + inmapsLinks[i] nodeValues.inmaps += '
        • ' + inmapsAr[i] + '
        • ' } @@ -455,4 +448,6 @@ Metamaps.TopicCard = { nodeValues.desc = (topic.get('desc') == '' && authorized) ? desc_nil : topic.get('desc') return nodeValues } -}; // end Metamaps.TopicCard +} + +export default TopicCard diff --git a/app/assets/javascripts/src/Metamaps.Util.js b/frontend/src/Metamaps/Util.js similarity index 90% rename from app/assets/javascripts/src/Metamaps.Util.js rename to frontend/src/Metamaps/Util.js index e150d3bb..9eb715de 100644 --- a/app/assets/javascripts/src/Metamaps.Util.js +++ b/frontend/src/Metamaps/Util.js @@ -1,13 +1,6 @@ -/* global Metamaps */ +import Visualize from './Visualize' -/* - * Metamaps.Util.js - * - * Dependencies: - * - Metamaps.Visualize - */ - -Metamaps.Util = { +const Util = { // helper function to determine how many lines are needed // Line Splitter Function // copyright Stephen Chapman, 19th April 2006 @@ -45,8 +38,8 @@ Metamaps.Util = { return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2)) }, coordsToPixels: function (coords) { - if (Metamaps.Visualize.mGraph) { - var canvas = Metamaps.Visualize.mGraph.canvas, + if (Visualize.mGraph) { + var canvas = Visualize.mGraph.canvas, s = canvas.getSize(), p = canvas.getPos(), ox = canvas.translateOffsetX, @@ -67,8 +60,8 @@ Metamaps.Util = { }, pixelsToCoords: function (pixels) { var coords - if (Metamaps.Visualize.mGraph) { - var canvas = Metamaps.Visualize.mGraph.canvas, + if (Visualize.mGraph) { + var canvas = Visualize.mGraph.canvas, s = canvas.getSize(), p = canvas.getPos(), ox = canvas.translateOffsetX, @@ -91,7 +84,7 @@ Metamaps.Util = { var r = (Math.round(Math.random() * 127) + 127).toString(16) var g = (Math.round(Math.random() * 127) + 127).toString(16) var b = (Math.round(Math.random() * 127) + 127).toString(16) - return Metamaps.Util.colorLuminance('#' + r + g + b, -0.4) + return Util.colorLuminance('#' + r + g + b, -0.4) }, // darkens a hex value by 'lum' percentage colorLuminance: function (hex, lum) { @@ -127,4 +120,6 @@ Metamaps.Util = { checkURLisYoutubeVideo: function (url) { return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null) } -}; // end Metamaps.Util +} + +export default Util diff --git a/frontend/src/Metamaps/Views/ChatView.js b/frontend/src/Metamaps/Views/ChatView.js new file mode 100644 index 00000000..a49aaa4d --- /dev/null +++ b/frontend/src/Metamaps/Views/ChatView.js @@ -0,0 +1,343 @@ +/* global $ */ + +import Backbone from 'backbone' +import Autolinker from 'autolinker' +// TODO is this line good or bad +// Backbone.$ = window.$ + +const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false, twitter: false }); + +var Private = { + messageHTML: "
          " + + "
          " + + "
          {{ message }}
          " + + "
          {{ timestamp }}
          " + + "
          " + + "
          ", + participantHTML: "
          " + + "
          " + + "
          {{ username }} {{ selfName }}
          " + + "" + + "" + + "
          " + + "
          " + + "
          ", + templates: function() { + _.templateSettings = { + interpolate: /\{\{(.+?)\}\}/g + }; + this.messageTemplate = _.template(Private.messageHTML); + + this.participantTemplate = _.template(Private.participantHTML); + }, + createElements: function() { + this.$unread = $('
          '); + this.$button = $('
          Chat
          '); + this.$messageInput = $(''); + this.$juntoHeader = $('
          PARTICIPANTS
          '); + this.$videoToggle = $('
          '); + this.$cursorToggle = $('
          '); + this.$participants = $('
          '); + this.$conversationInProgress = $('
          LIVE LEAVEJOIN
          '); + this.$chatHeader = $('
          CHAT
          '); + this.$soundToggle = $('
          '); + this.$messages = $('
          '); + this.$container = $('
          '); + }, + attachElements: function() { + this.$button.append(this.$unread); + + this.$juntoHeader.append(this.$videoToggle); + this.$juntoHeader.append(this.$cursorToggle); + + this.$chatHeader.append(this.$soundToggle); + + this.$participants.append(this.$conversationInProgress); + + this.$container.append(this.$juntoHeader); + this.$container.append(this.$participants); + this.$container.append(this.$chatHeader); + this.$container.append(this.$button); + this.$container.append(this.$messages); + this.$container.append(this.$messageInput); + }, + addEventListeners: function() { + var self = this; + + this.participants.on('add', function (participant) { + Private.addParticipant.call(self, participant); + }); + + this.participants.on('remove', function (participant) { + Private.removeParticipant.call(self, participant); + }); + + this.$button.on('click', function () { + Handlers.buttonClick.call(self); + }); + this.$videoToggle.on('click', function () { + Handlers.videoToggleClick.call(self); + }); + this.$cursorToggle.on('click', function () { + Handlers.cursorToggleClick.call(self); + }); + this.$soundToggle.on('click', function () { + Handlers.soundToggleClick.call(self); + }); + this.$messageInput.on('keyup', function (event) { + Handlers.keyUp.call(self, event); + }); + this.$messageInput.on('focus', function () { + Handlers.inputFocus.call(self); + }); + this.$messageInput.on('blur', function () { + Handlers.inputBlur.call(self); + }); + }, + initializeSounds: function() { + this.sound = new Howl({ + urls: [Metamaps.Erb['sounds/MM_sounds.mp3'], Metamaps.Erb['sounds/MM_sounds.ogg']], + sprite: { + joinmap: [0, 561], + leavemap: [1000, 592], + receivechat: [2000, 318], + sendchat: [3000, 296], + sessioninvite: [4000, 5393, true] + } + }); + }, + incrementUnread: function() { + this.unreadMessages++; + this.$unread.html(this.unreadMessages); + this.$unread.show(); + }, + addMessage: function(message, isInitial, wasMe) { + + if (!this.isOpen && !isInitial) Private.incrementUnread.call(this); + + function addZero(i) { + if (i < 10) { + i = "0" + i; + } + return i; + } + var m = _.clone(message.attributes); + + var today = new Date(); + 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.user_image || 'http://www.hotpepper.ca/wp-content/uploads/2014/11/default_profile_1_200x200.png'; // TODO: remove + m.message = linker.link(m.message); + var $html = $(this.messageTemplate(m)); + this.$messages.append($html); + if (!isInitial) this.scrollMessages(200); + + if (!wasMe && !isInitial && this.alertSound) this.sound.play('receivechat'); + }, + initialMessages: function() { + var messages = this.messages.models; + for (var i = 0; i < messages.length; i++) { + Private.addMessage.call(this, messages[i], true); + } + }, + handleInputMessage: function() { + var message = { + message: this.$messageInput.val(), + }; + this.$messageInput.val(''); + $(document).trigger(ChatView.events.message + '-' + this.room, [message]); + }, + addParticipant: function(participant) { + var p = _.clone(participant.attributes); + if (p.self) { + p.selfClass = 'is-self'; + p.selfName = '(me)'; + } else { + p.selfClass = ''; + p.selfName = ''; + } + var html = this.participantTemplate(p); + this.$participants.append(html); + }, + removeParticipant: function(participant) { + this.$container.find('.participant-' + participant.get('id')).remove(); + } +}; + +var Handlers = { + buttonClick: function() { + if (this.isOpen) this.close(); + else if (!this.isOpen) this.open(); + }, + videoToggleClick: function() { + this.$videoToggle.toggleClass('active'); + this.videosShowing = !this.videosShowing; + $(document).trigger(this.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff); + }, + cursorToggleClick: function() { + this.$cursorToggle.toggleClass('active'); + this.cursorsShowing = !this.cursorsShowing; + $(document).trigger(this.cursorsShowing ? ChatView.events.cursorsOn : ChatView.events.cursorsOff); + }, + soundToggleClick: function() { + this.alertSound = !this.alertSound; + this.$soundToggle.toggleClass('active'); + }, + keyUp: function(event) { + switch(event.which) { + case 13: // enter + Private.handleInputMessage.call(this); + break; + } + }, + inputFocus: function() { + $(document).trigger(ChatView.events.inputFocus); + }, + inputBlur: function() { + $(document).trigger(ChatView.events.inputBlur); + } +}; + +const ChatView = function(messages, mapper, room) { + var self = this; + + this.room = room; + this.mapper = mapper; + this.messages = messages; // backbone collection + + this.isOpen = false; + this.alertSound = true; // whether to play sounds on arrival of new messages or not + this.cursorsShowing = true; + this.videosShowing = true; + this.unreadMessages = 0; + this.participants = new Backbone.Collection(); + + Private.templates.call(this); + Private.createElements.call(this); + Private.attachElements.call(this); + Private.addEventListeners.call(this); + Private.initialMessages.call(this); + Private.initializeSounds.call(this); + this.$container.css({ + right: '-300px' + }); +}; + +ChatView.prototype.conversationInProgress = function (participating) { + this.$conversationInProgress.show(); + this.$participants.addClass('is-live'); + if (participating) this.$participants.addClass('is-participating'); + this.$button.addClass('active'); + + // hide invite to call buttons +} + +ChatView.prototype.conversationEnded = function () { + this.$conversationInProgress.hide(); + this.$participants.removeClass('is-live'); + this.$participants.removeClass('is-participating'); + this.$button.removeClass('active'); + this.$participants.find('.participant').removeClass('active'); + this.$participants.find('.participant').removeClass('pending'); +} + +ChatView.prototype.leaveConversation = function () { + this.$participants.removeClass('is-participating'); +} + +ChatView.prototype.mapperJoinedCall = function (id) { + this.$participants.find('.participant-' + id).addClass('active'); +} + +ChatView.prototype.mapperLeftCall = function (id) { + this.$participants.find('.participant-' + id).removeClass('active'); +} + +ChatView.prototype.invitationPending = function (id) { + this.$participants.find('.participant-' + id).addClass('pending'); +} + +ChatView.prototype.invitationAnswered = function (id) { + this.$participants.find('.participant-' + id).removeClass('pending'); +} + +ChatView.prototype.addParticipant = function (participant) { + this.participants.add(participant); +} + +ChatView.prototype.removeParticipant = function (username) { + var p = this.participants.find(function (p) { return p.get('username') === username; }); + if (p) { + this.participants.remove(p); + } +} + +ChatView.prototype.removeParticipants = function () { + this.participants.remove(this.participants.models); +} + +ChatView.prototype.open = function () { + this.$container.css({ + right: '0' + }); + this.$messageInput.focus(); + this.isOpen = true; + this.unreadMessages = 0; + this.$unread.hide(); + this.scrollMessages(0); + $(document).trigger(ChatView.events.openTray); +} + +ChatView.prototype.addMessage = function(message, isInitial, wasMe) { + this.messages.add(message); + Private.addMessage.call(this, message, isInitial, wasMe); +} + +ChatView.prototype.scrollMessages = function(duration) { + duration = duration || 0; + + this.$messages.animate({ + scrollTop: this.$messages[0].scrollHeight + }, duration); +} + +ChatView.prototype.clearMessages = function () { + this.unreadMessages = 0; + this.$unread.hide(); + this.$messages.empty(); +} + +ChatView.prototype.close = function () { + this.$container.css({ + right: '-300px' + }); + this.$messageInput.blur(); + this.isOpen = false; + $(document).trigger(ChatView.events.closeTray); +} + +ChatView.prototype.remove = function () { + this.$button.off(); + this.$container.remove(); +} + +/** + * @class + * @static + */ +ChatView.events = { + message: 'ChatView:message', + openTray: 'ChatView:openTray', + closeTray: 'ChatView:closeTray', + inputFocus: 'ChatView:inputFocus', + inputBlur: 'ChatView:inputBlur', + cursorsOff: 'ChatView:cursorsOff', + cursorsOn: 'ChatView:cursorsOn', + videosOff: 'ChatView:videosOff', + videosOn: 'ChatView:videosOn' +}; + +export default ChatView diff --git a/frontend/src/Metamaps/Views/ExploreMaps.js b/frontend/src/Metamaps/Views/ExploreMaps.js new file mode 100644 index 00000000..155e8453 --- /dev/null +++ b/frontend/src/Metamaps/Views/ExploreMaps.js @@ -0,0 +1,88 @@ +/* global Metamaps, $ */ + +import React from 'react' +import ReactDOM from 'react-dom' // TODO ensure this isn't a double import + +import Active from '../Active' +import ReactComponents from '../ReactComponents' + +/* + * - Metamaps.Loading + */ + +const ExploreMaps = { + setCollection: function (collection) { + var self = ExploreMaps + + if (self.collection) { + self.collection.off('add', self.render) + self.collection.off('successOnFetch', self.handleSuccess) + self.collection.off('errorOnFetch', self.handleError) + } + self.collection = collection + self.collection.on('add', self.render) + self.collection.on('successOnFetch', self.handleSuccess) + self.collection.on('errorOnFetch', self.handleError) + }, + render: function (mapperObj, cb) { + var self = ExploreMaps + + if (typeof mapperObj === 'function') { + cb = mapperObj + mapperObj = null + } + + var exploreObj = { + currentUser: Active.Mapper, + section: self.collection.id, + displayStyle: 'grid', + maps: self.collection, + moreToLoad: self.collection.page != 'loadedAll', + user: mapperObj, + loadMore: self.loadMore + } + ReactDOM.render( + React.createElement(ReactComponents.Maps, exploreObj), + document.getElementById('explore') + ) + + if (cb) cb() + Metamaps.Loading.hide() + }, + loadMore: function () { + var self = ExploreMaps + + if (self.collection.page != "loadedAll") { + self.collection.getMaps() + } + else self.render() + }, + handleSuccess: function (cb) { + var self = ExploreMaps + + if (self.collection && self.collection.id === 'mapper') { + self.fetchUserThenRender(cb) + } else { + self.render(cb) + } + }, + handleError: function () { + console.log('error loading maps!') // TODO + }, + fetchUserThenRender: function (cb) { + var self = ExploreMaps + + // first load the mapper object and then call the render function + $.ajax({ + url: '/users/' + self.collection.mapperId + '/details.json', + success: function (response) { + self.render(response, cb) + }, + error: function () { + self.render(cb) + } + }) + } +} + +export default ExploreMaps diff --git a/frontend/src/Metamaps/Views/Room.js b/frontend/src/Metamaps/Views/Room.js new file mode 100644 index 00000000..5b70ee7c --- /dev/null +++ b/frontend/src/Metamaps/Views/Room.js @@ -0,0 +1,204 @@ +/* global Metamaps, $ */ + +import Backbone from 'backbone' +// TODO is this line good or bad +// Backbone.$ = window.$ + +import Active from '../Active' +import Realtime from '../Realtime' + +import ChatView from './ChatView' +import VideoView from './VideoView' + +/* + * Dependencies: + * Metamaps.Backbone + */ + +const Room = function(opts) { + var self = this + + this.isActiveRoom = false + this.socket = opts.socket + this.webrtc = opts.webrtc + //this.roomRef = opts.firebase + this.room = opts.room + this.config = opts.config + this.peopleCount = 0 + + this.$myVideo = opts.$video + this.myVideo = opts.myVideoView + + this.messages = new Backbone.Collection() + this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image }) + this.chat = new ChatView(this.messages, this.currentMapper, this.room) + + this.videos = {} + + this.init() +} + +Room.prototype.join = function(cb) { + this.isActiveRoom = true + this.webrtc.joinRoom(this.room, cb) + this.chat.conversationInProgress(true) // true indicates participation +} + +Room.prototype.conversationInProgress = function() { + this.chat.conversationInProgress(false) // false indicates not participating +} + +Room.prototype.conversationEnding = function() { + this.chat.conversationEnded() +} + +Room.prototype.leaveVideoOnly = function() { + this.chat.leaveConversation() // the conversation will carry on without you + for (var id in this.videos) { + this.removeVideo(id) + } + this.isActiveRoom = false + this.webrtc.leaveRoom() +} + +Room.prototype.leave = function() { + for (var id in this.videos) { + this.removeVideo(id) + } + this.isActiveRoom = false + this.webrtc.leaveRoom() + this.chat.conversationEnded() + this.chat.removeParticipants() + this.chat.clearMessages() + this.messages.reset() +} + +Room.prototype.setPeopleCount = function(count) { + this.peopleCount = count +} + +Room.prototype.init = function () { + var self = this + + $(document).on(VideoView.events.audioControlClick, function (event, videoView) { + if (!videoView.audioStatus) self.webrtc.mute() + else if (videoView.audioStatus) self.webrtc.unmute() + }) + $(document).on(VideoView.events.videoControlClick, function (event, videoView) { + if (!videoView.videoStatus) self.webrtc.pauseVideo() + else if (videoView.videoStatus) self.webrtc.resumeVideo() + }) + + this.webrtc.webrtc.off('peerStreamAdded') + this.webrtc.webrtc.off('peerStreamRemoved') + this.webrtc.on('peerStreamAdded', function (peer) { + var mapper = Realtime.mappersOnMap[peer.nick] + peer.avatar = mapper.image + peer.username = mapper.name + if (self.isActiveRoom) { + self.addVideo(peer) + } + }) + + this.webrtc.on('peerStreamRemoved', function (peer) { + if (self.isActiveRoom) { + self.removeVideo(peer) + } + }) + + 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() + }) + + var sendChatMessage = function (event, data) { + self.sendChatMessage(data) + } + $(document).on(ChatView.events.message + '-' + this.room, sendChatMessage) + } + + Room.prototype.videoAdded = function (callback) { + this._videoAdded = callback + } + + Room.prototype.addVideo = function (peer) { + var + id = this.webrtc.getDomId(peer), + video = attachMediaStream(peer.stream) + + var + v = new VideoView(video, null, id, false, { DOUBLE_CLICK_TOLERANCE: 200, avatar: peer.avatar, username: peer.username }) + + this.videos[peer.id] = v + if (this._videoAdded) this._videoAdded(v, peer.nick) + } + + Room.prototype.removeVideo = function (peer) { + var id = typeof peer == 'string' ? peer : peer.id + if (this.videos[id]) { + this.videos[id].remove() + delete this.videos[id] + } + } + + Room.prototype.sendChatMessage = function (data) { + var self = this + //this.roomRef.child('messages').push(data) + if (self.chat.alertSound) self.chat.sound.play('sendchat') + var m = new Metamaps.Backbone.Message({ + message: data.message, + resource_id: Active.Map.id, + resource_type: "Map" + }) + m.save(null, { + success: function (model, response) { + self.addMessages(new Metamaps.Backbone.MessageCollection(model), false, true) + $(document).trigger(Room.events.newMessage, [model]) + }, + error: function (model, response) { + console.log('error!', response) + } + }) + } + + // they should be instantiated as backbone models before they get + // passed to this function + Room.prototype.addMessages = function (messages, isInitial, wasMe) { + var self = this + + messages.models.forEach(function (message) { + self.chat.addMessage(message, isInitial, wasMe) + }) + } + +/** + * @class + * @static + */ +Room.events = { + newMessage: "Room:newMessage" +} + +export default Room diff --git a/frontend/src/Metamaps/Views/VideoView.js b/frontend/src/Metamaps/Views/VideoView.js new file mode 100644 index 00000000..401ece54 --- /dev/null +++ b/frontend/src/Metamaps/Views/VideoView.js @@ -0,0 +1,202 @@ +/* global $ */ + +var Private = { + addControls: function() { + var self = this; + + this.$audioControl = $('
          '); + this.$videoControl = $('
          '); + + this.$audioControl.on('click', function () { + Handlers.audioControlClick.call(self); + }); + + this.$videoControl.on('click', function () { + Handlers.videoControlClick.call(self); + }); + + this.$container.append(this.$audioControl); + this.$container.append(this.$videoControl); + }, + cancelClick: function() { + this.mouseIsDown = false; + + if (this.hasMoved) { + + } + + $(document).trigger(VideoView.events.dragEnd); + } +}; + +var Handlers = { + mousedown: function(event) { + this.mouseIsDown = true; + this.hasMoved = false; + this.mouseMoveStart = { + x: event.pageX, + y: event.pageY + }; + this.posStart = { + x: parseInt(this.$container.css('left'), '10'), + y: parseInt(this.$container.css('top'), '10') + } + + $(document).trigger(VideoView.events.mousedown); + }, + mouseup: function(event) { + $(document).trigger(VideoView.events.mouseup, [this]); + + var storedTime = this.lastClick; + var now = Date.now(); + this.lastClick = now; + + if (now - storedTime < this.config.DOUBLE_CLICK_TOLERANCE) { + $(document).trigger(VideoView.events.doubleClick, [this]); + } + }, + mousemove: function(event) { + var + diffX, + diffY, + newX, + newY; + + if (this.$parent && this.mouseIsDown) { + this.manuallyPositioned = true; + this.hasMoved = true; + diffX = event.pageX - this.mouseMoveStart.x; + diffY = this.mouseMoveStart.y - event.pageY; + newX = this.posStart.x + diffX; + newY = this.posStart.y - diffY; + this.$container.css({ + top: newY, + left: newX + }); + } + }, + audioControlClick: function() { + if (this.audioStatus) { + this.audioOff(); + } else { + this.audioOn(); + } + $(document).trigger(VideoView.events.audioControlClick, [this]); + }, + videoControlClick: function() { + if (this.videoStatus) { + this.videoOff(); + } else { + this.videoOn(); + } + $(document).trigger(VideoView.events.videoControlClick, [this]); + }, +}; + +var VideoView = function(video, $parent, id, isMyself, config) { + var self = this; + + this.$parent = $parent; // mapView + + this.video = video; + this.id = id; + + this.config = config; + + this.mouseIsDown = false; + this.mouseDownOffset = { x: 0, y: 0 }; + this.lastClick = null; + this.hasMoved = false; + + this.audioStatus = true; + this.videoStatus = true; + + this.$container = $('
          '); + this.$container.addClass('collaborator-video' + (isMyself ? ' my-video' : '')); + this.$container.attr('id', 'container_' + id); + + + var $vidContainer = $('
          '); + $vidContainer.addClass('video-cutoff'); + $vidContainer.append(this.video); + + this.avatar = config.avatar; + this.$avatar = $(''); + $vidContainer.append(this.$avatar); + + this.$container.append($vidContainer); + + this.$container.on('mousedown', function (event) { + Handlers.mousedown.call(self, event); + }); + + if (isMyself) { + Private.addControls.call(this); + } + + // suppress contextmenu + this.video.oncontextmenu = function () { return false; }; + + if (this.$parent) this.setParent(this.$parent); +}; + +VideoView.prototype.setParent = function($parent) { + var self = this; + this.$parent = $parent; + this.$parent.off('.video' + this.id); + this.$parent.on('mouseup.video' + this.id, function (event) { + Handlers.mouseup.call(self, event); + Private.cancelClick.call(self); + }); + this.$parent.on('mousemove.video' + this.id, function (event) { + Handlers.mousemove.call(self, event); + }); +} + +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); + this.$container.remove(); +} + +VideoView.prototype.videoOff = function () { + this.$videoControl.addClass('active'); + this.$avatar.show(); + this.videoStatus = false; +} + +VideoView.prototype.videoOn = function () { + this.$videoControl.removeClass('active'); + this.$avatar.hide(); + this.videoStatus = true; +} + +VideoView.prototype.audioOff = function () { + this.$audioControl.addClass('active'); + this.audioStatus = false; +} + +VideoView.prototype.audioOn = function () { + this.$audioControl.removeClass('active'); + this.audioStatus = true; +} + +/** + * @class + * @static + */ +VideoView.events = { + mousedown: "VideoView:mousedown", + mouseup: "VideoView:mouseup", + doubleClick: "VideoView:doubleClick", + dragEnd: "VideoView:dragEnd", + audioControlClick: "VideoView:audioControlClick", + videoControlClick: "VideoView:videoControlClick", +}; + +export default VideoView diff --git a/frontend/src/Metamaps/Views/index.js b/frontend/src/Metamaps/Views/index.js new file mode 100644 index 00000000..d13482d0 --- /dev/null +++ b/frontend/src/Metamaps/Views/index.js @@ -0,0 +1,7 @@ +import ExploreMaps from './ExploreMaps' +import ChatView from './ChatView' +import VideoView from './VideoView' +import Room from './Room' + +const Views = { ExploreMaps, ChatView, VideoView, Room } +export default Views diff --git a/app/assets/javascripts/src/Metamaps.Visualize.js b/frontend/src/Metamaps/Visualize.js similarity index 73% rename from app/assets/javascripts/src/Metamaps.Visualize.js rename to frontend/src/Metamaps/Visualize.js index 7168c03a..df5bab99 100644 --- a/app/assets/javascripts/src/Metamaps.Visualize.js +++ b/frontend/src/Metamaps/Visualize.js @@ -1,27 +1,32 @@ /* global Metamaps, $ */ + +import _ from 'lodash' + +import $jit from '../patched/JIT' + +import Active from './Active' +import JIT from './JIT' +import Router from './Router' +import TopicCard from './TopicCard' + /* * Metamaps.Visualize * * Dependencies: - * - Metamaps.Active - * - Metamaps.JIT * - Metamaps.Loading * - Metamaps.Metacodes - * - Metamaps.Router * - Metamaps.Synapses - * - Metamaps.TopicCard * - Metamaps.Topics - * - Metamaps.Touch - * - Metamaps.Visualize */ -Metamaps.Visualize = { +const Visualize = { mGraph: null, // a reference to the graph object. cameraPosition: null, // stores the camera position when using a 3D visualization type: 'ForceDirected', // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D" loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created + touchDragNode: null, init: function () { - var self = Metamaps.Visualize + var self = Visualize // disable awkward dragging of the canvas element that would sometimes happen $('#infovis-canvas').on('dragstart', function (event) { event.preventDefault() @@ -35,19 +40,19 @@ Metamaps.Visualize = { // prevent touch events on the canvas from default behaviour $('#infovis-canvas').bind('touchmove', function (event) { - // Metamaps.JIT.touchPanZoomHandler(event) + // JIT.touchPanZoomHandler(event) }) // prevent touch events on the canvas from default behaviour $('#infovis-canvas').bind('touchend touchcancel', function (event) { lastDist = 0 - if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard() + if (!self.mGraph.events.touchMoved && !Visualize.touchDragNode) TopicCard.hideCurrentCard() self.mGraph.events.touched = self.mGraph.events.touchMoved = false - Metamaps.Touch.touchDragNode = false + Visualize.touchDragNode = false }) }, computePositions: function () { - var self = Metamaps.Visualize, + var self = Visualize, mapping if (self.type == 'RGraph') { @@ -111,31 +116,31 @@ Metamaps.Visualize = { * */ render: function () { - var self = Metamaps.Visualize, RGraphSettings, FDSettings + var self = Visualize, RGraphSettings, FDSettings if (self.type == 'RGraph' && (!self.mGraph || self.mGraph instanceof $jit.ForceDirected)) { // clear the previous canvas from #infovis $('#infovis').empty() - RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings) + RGraphSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings) - $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings) - $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings) + $jit.RGraph.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings) + $jit.RGraph.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings) RGraphSettings.width = $(document).width() RGraphSettings.height = $(document).height() - RGraphSettings.background = Metamaps.JIT.RGraph.background - RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance + RGraphSettings.background = JIT.RGraph.background + RGraphSettings.levelDistance = JIT.RGraph.levelDistance self.mGraph = new $jit.RGraph(RGraphSettings) } else if (self.type == 'ForceDirected' && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) { // clear the previous canvas from #infovis $('#infovis').empty() - FDSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings) + FDSettings = $.extend(true, {}, JIT.ForceDirected.graphSettings) - $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings) - $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings) + $jit.ForceDirected.Plot.NodeTypes.implement(JIT.ForceDirected.nodeSettings) + $jit.ForceDirected.Plot.EdgeTypes.implement(JIT.ForceDirected.edgeSettings) FDSettings.width = $('body').width() FDSettings.height = $('body').height() @@ -146,14 +151,14 @@ Metamaps.Visualize = { $('#infovis').empty() // init ForceDirected3D - self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings) + self.mGraph = new $jit.ForceDirected3D(JIT.ForceDirected3D.graphSettings) self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position } else { self.mGraph.graph.empty() } - if (self.type == 'ForceDirected' && Metamaps.Active.Mapper) $.post('/maps/' + Metamaps.Active.Map.id + '/events/user_presence') + if (self.type == 'ForceDirected' && Active.Mapper) $.post('/maps/' + Active.Map.id + '/events/user_presence') function runAnimation () { Metamaps.Loading.hide() @@ -161,22 +166,22 @@ Metamaps.Visualize = { if (!self.loadLater) { // load JSON data. var rootIndex = 0 - if (Metamaps.Active.Topic) { - var node = _.find(Metamaps.JIT.vizData, function (node) { - return node.id === Metamaps.Active.Topic.id + if (Active.Topic) { + var node = _.find(JIT.vizData, function (node) { + return node.id === Active.Topic.id }) - rootIndex = _.indexOf(Metamaps.JIT.vizData, node) + rootIndex = _.indexOf(JIT.vizData, node) } - self.mGraph.loadJSON(Metamaps.JIT.vizData, rootIndex) + self.mGraph.loadJSON(JIT.vizData, rootIndex) // compute positions and plot. self.computePositions() self.mGraph.busy = true if (self.type == 'RGraph') { - self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate) + self.mGraph.fx.animate(JIT.RGraph.animate) } else if (self.type == 'ForceDirected') { - self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout) + self.mGraph.animate(JIT.ForceDirected.animateSavedLayout) } else if (self.type == 'ForceDirected3D') { - self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout) + self.mGraph.animate(JIT.ForceDirected.animateFDLayout) } } } @@ -203,17 +208,19 @@ Metamaps.Visualize = { hold() // update the url now that the map is ready - clearTimeout(Metamaps.Router.timeoutId) - Metamaps.Router.timeoutId = setTimeout(function () { - var m = Metamaps.Active.Map - var t = Metamaps.Active.Topic + clearTimeout(Router.timeoutId) + Router.timeoutId = setTimeout(function () { + var m = Active.Map + var t = Active.Topic if (m && window.location.pathname !== '/maps/' + m.id) { - Metamaps.Router.navigate('/maps/' + m.id) + Router.navigate('/maps/' + m.id) } else if (t && window.location.pathname !== '/topics/' + t.id) { - Metamaps.Router.navigate('/topics/' + t.id) + Router.navigate('/topics/' + t.id) } }, 800) } -}; // end Metamaps.Visualize +} + +export default Visualize diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js new file mode 100644 index 00000000..5d15559c --- /dev/null +++ b/frontend/src/Metamaps/index.js @@ -0,0 +1,107 @@ +/* global $ */ + +import Account from './Account' +import Active from './Active' +import Admin from './Admin' +import AutoLayout from './AutoLayout' +import Backbone from './Backbone' +import Control from './Control' +import Create from './Create' +import Debug from './Debug' +import Filter from './Filter' +import GlobalUI from './GlobalUI' +import Import from './Import' +import JIT from './JIT' +import Listeners from './Listeners' +import Map, { CheatSheet, InfoBox } from './Map' +import Mapper from './Mapper' +import Mobile from './Mobile' +import Mouse from './Mouse' +import Organize from './Organize' +import PasteInput from './PasteInput' +import Realtime from './Realtime' +import Router from './Router' +import Selected from './Selected' +import Settings from './Settings' +import Synapse from './Synapse' +import SynapseCard from './SynapseCard' +import Topic from './Topic' +import TopicCard from './TopicCard' +import Util from './Util' +import Views from './Views' +import Visualize from './Visualize' +import ReactComponents from './ReactComponents' + +Metamaps.Account = Account +Metamaps.Active = Active +Metamaps.Admin = Admin +Metamaps.AutoLayout = AutoLayout +Metamaps.Backbone = Backbone +Metamaps.Control = Control +Metamaps.Create = Create +Metamaps.Debug = Debug +Metamaps.Filter = Filter +Metamaps.GlobalUI = GlobalUI +Metamaps.Import = Import +Metamaps.JIT = JIT +Metamaps.Listeners = Listeners +Metamaps.Map = Map +Metamaps.Map.CheatSheet = CheatSheet +Metamaps.Map.InfoBox = InfoBox +Metamaps.Maps = {} +Metamaps.Mapper = Mapper +Metamaps.Mobile = Mobile +Metamaps.Mouse = Mouse +Metamaps.Organize = Organize +Metamaps.PasteInput = PasteInput +Metamaps.Realtime = Realtime +Metamaps.ReactComponents = ReactComponents +Metamaps.Router = Router +Metamaps.Selected = Selected +Metamaps.Settings = Settings +Metamaps.Synapse = Synapse +Metamaps.SynapseCard = SynapseCard +Metamaps.Topic = Topic +Metamaps.TopicCard = TopicCard +Metamaps.Util = Util +Metamaps.Views = Views +Metamaps.Visualize = Visualize + +document.addEventListener("DOMContentLoaded", function() { + // initialize all the modules + for (const prop in Metamaps) { + // this runs the init function within each sub-object on the Metamaps one + if (Metamaps.hasOwnProperty(prop) && + Metamaps[prop] != null && + Metamaps[prop].hasOwnProperty('init') && + typeof (Metamaps[prop].init) == 'function' + ) { + Metamaps[prop].init() + } + } + // load whichever page you are on + if (Metamaps.currentSection === "explore") { + const capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1) + + Metamaps.Views.ExploreMaps.setCollection( Metamaps.Maps[capitalize] ) + if (Metamaps.currentPage === "mapper") { + Views.ExploreMaps.fetchUserThenRender() + } + else { + Views.ExploreMaps.render() + } + GlobalUI.showDiv('#explore') + } + else if (Metamaps.currentSection === "" && Active.Mapper) { + Views.ExploreMaps.setCollection(Metamaps.Maps.Active) + Views.ExploreMaps.render() + GlobalUI.showDiv('#explore') + } + else if (Active.Map || Active.Topic) { + Metamaps.Loading.show() + JIT.prepareVizData() + GlobalUI.showDiv('#infovis') + } +}); + +export default Metamaps diff --git a/frontend/src/index.js b/frontend/src/index.js index 0556f4c1..176ac329 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,18 +1,8 @@ -import React from 'react' +// create global references to some utility libraries import ReactDOM from 'react-dom' -import Backbone from 'backbone' import _ from 'underscore' -import Maps from './components/Maps.js' - -// this is optional really, if we import components directly React will be -// in the bundle, so we won't need a global reference -window.React = React window.ReactDOM = ReactDOM -Backbone.$ = window.$ -window.Backbone = Backbone window._ = _ -window.Metamaps = window.Metamaps || {} -window.Metamaps.ReactComponents = { - Maps -} +import Metamaps from './Metamaps' +window.Metamaps = Metamaps diff --git a/app/assets/javascripts/src/JIT.js.erb b/frontend/src/patched/JIT.js similarity index 99% rename from app/assets/javascripts/src/JIT.js.erb rename to frontend/src/patched/JIT.js index 2cb202dc..af7311be 100644 --- a/app/assets/javascripts/src/JIT.js.erb +++ b/frontend/src/patched/JIT.js @@ -20,7 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - (function () { /* File: Core.js @@ -34,7 +33,11 @@ THE SOFTWARE. This variable is the *only* global variable defined in the Toolkit. There are also other interesting properties attached to this variable described below. */ -window.$jit = function(w) { +// START METAMAPS CODE +const $jit = function(w) { +// ORIGINAL: +// window.$jit = function(w) { +// END METAMAPS CODE w = w || window; for(var k in $jit) { if($jit[k].$extend) { @@ -3122,9 +3125,15 @@ var Canvas; }; }, translateToCenter: function(ps) { - var size = this.getSize(), - width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width; - height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height; + // START METAMAPS CODE + var size = this.getSize(); + var width = ps ? (size.width - ps.width - this.translateOffsetX*2) : size.width; + var height = ps ? (size.height - ps.height - this.translateOffsetY*2) : size.height; + // ORIGINAL CODE + // var size = this.getSize(), + // width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width; + // height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height; + // END METAMAPS CODE var ctx = this.getCtx(); ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY); ctx.translate(width/2, height/2); @@ -3232,7 +3241,7 @@ var Canvas; ctx = base.getCtx(), scale = base.scaleOffsetX; //var pattern = new Image(); - //pattern.src = "<%= asset_path('cubes.png') %>"; + //pattern.src = Metamaps.Erb['cubes.png'] //var ptrn = ctx.createPattern(pattern, 'repeat'); //ctx.fillStyle = ptrn; ctx.fillStyle = Metamaps.Settings.colors.background; @@ -5634,7 +5643,11 @@ Graph.Op = { break; case 'fade:seq': case 'fade': case 'fade:con': - that = this; + // START METAMAPS CODE + var that = this; + // ORIGINAL CODE: + // that = this; + // END METAMAPS CODE graph = viz.construct(json); //set alpha to 0 for nodes to add. @@ -5770,7 +5783,11 @@ Graph.Op = { break; case 'fade:seq': case 'fade': case 'fade:con': - that = this; + // START METAMAPS CODE + var that = this; + // ORIGINAL CODE: + // that = this; + // END METAMAPS CODE graph = viz.construct(json); //preprocessing for nodes to delete. //get node property modes to interpolate @@ -11312,7 +11329,4 @@ $jit.ForceDirected3D.$extend = true; })($jit.ForceDirected3D); - - - - })(); +export default $jit diff --git a/frontend/test/Metamaps.Import.spec.js b/frontend/test/Metamaps.Import.spec.js index 8dcf8e97..c8ee33b1 100644 --- a/frontend/test/Metamaps.Import.spec.js +++ b/frontend/test/Metamaps.Import.spec.js @@ -1,13 +1,14 @@ /* global describe, it */ -const chai = require('chai') -const expect = chai.expect -Metamaps = {} -require('../../app/assets/javascripts/src/Metamaps.Import') +import chai from 'chai' + +import Import from '../src/Metamaps/Import' + +const { expect } = chai describe('Metamaps.Import.js', function () { it('has a topic whitelist', function () { - expect(Metamaps.Import.topicWhitelist).to.deep.equal( + expect(Import.topicWhitelist).to.deep.equal( ['id', 'name', 'metacode', 'x', 'y', 'description', 'link', 'permission'] ) }) diff --git a/package.json b/package.json index a2227300..d15d248e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "webpack", "build:watch": "webpack --watch", - "test": "mocha frontend/test || (echo 'Run `npm install` to setup testing' && false)" + "test": "mocha --compilers js:babel-core/register frontend/test || (echo 'Run `npm install` to setup testing' && false)" }, "repository": { "type": "git", @@ -18,6 +18,7 @@ }, "homepage": "https://github.com/metamaps/metamaps#readme", "dependencies": { + "autolinker": "^0.17.1", "babel-cli": "^6.11.4", "babel-loader": "^6.2.4", "babel-plugin-transform-class-properties": "^6.11.5", @@ -27,9 +28,12 @@ "chai": "^3.5.0", "jquery": "1.12.1", "mocha": "^3.0.2", + "mocha-jsdom": "^1.1.0", + "node-uuid": "1.2.0", "react": "^15.3.0", "react-dom": "^15.3.0", "requirejs": "^2.1.1", + "socket.io": "0.9.12", "underscore": "^1.4.4", "webpack": "^1.13.1" } diff --git a/realtime/package.json b/realtime/package.json deleted file mode 100644 index 5b5b08f4..00000000 --- a/realtime/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "RoR-real-time", - "description": "providing real-time sychronization for ruby on rails", - "version": "0.0.1", - "private": true, - "dependencies": { - "socket.io": "0.9.12", - "node-uuid": "1.2.0" - } -}