client/js/paper-renderer.js
changeset 199 aff7eb118f7d
parent 198 d3737b90a66b
child 207 583dc519c5fa
equal deleted inserted replaced
198:d3737b90a66b 199:aff7eb118f7d
     6 
     6 
     7 var Rkns = root.Rkns,
     7 var Rkns = root.Rkns,
     8     _ = Rkns._,
     8     _ = Rkns._,
     9     $ = Rkns.$;
     9     $ = Rkns.$;
    10 
    10 
       
    11 /* Rkns.Renderer Object */
       
    12 
       
    13 /* This object contains constants, utility functions and classes for Renkan's Graph Manipulation GUI */
       
    14 
    11 var Renderer = Rkns.Renderer = {},
    15 var Renderer = Rkns.Renderer = {},
       
    16         /* The minimum distance (in pixels) the mouse has to move to consider an element was dragged */
    12     _MIN_DRAG_DISTANCE = 2,
    17     _MIN_DRAG_DISTANCE = 2,
       
    18         /* Distance between the inner and outer radius of buttons that appear when hovering on a node */
    13     _NODE_BUTTON_WIDTH = 40,
    19     _NODE_BUTTON_WIDTH = 40,
    14     _EDGE_BUTTON_INNER = 2,
    20     _EDGE_BUTTON_INNER = 2,
    15     _EDGE_BUTTON_OUTER = 40,
    21     _EDGE_BUTTON_OUTER = 40,
       
    22         /* Constants used to know if a specific action is to be performed when clicking on the canvas */
    16     _CLICKMODE_ADDNODE = 1,
    23     _CLICKMODE_ADDNODE = 1,
    17     _CLICKMODE_STARTEDGE = 2,
    24     _CLICKMODE_STARTEDGE = 2,
    18     _CLICKMODE_ENDEDGE = 3,
    25     _CLICKMODE_ENDEDGE = 3,
       
    26         /* Node size step: Used to calculate the size change when clicking the +/- buttons */
    19     _NODE_SIZE_STEP = Math.LN2/4,
    27     _NODE_SIZE_STEP = Math.LN2/4,
    20     _MIN_SCALE = 1/20,
    28     _MIN_SCALE = 1/20,
    21     _MAX_SCALE = 20,
    29     _MAX_SCALE = 20,
    22     _MOUSEMOVE_RATE = 80,
    30     _MOUSEMOVE_RATE = 80,
    23     _DOUBLETAP_DELAY = 800,
    31     _DOUBLETAP_DELAY = 800,
       
    32         /* Maximum distance in pixels (squared, to reduce calculations)
       
    33          * between two taps when double-tapping on a touch terminal */
    24     _DOUBLETAP_DISTANCE = 20*20,
    34     _DOUBLETAP_DISTANCE = 20*20,
       
    35         /* A placeholder so a default colour is displayed when a node has a null value for its user property */
    25     _USER_PLACEHOLDER = function(_renkan) {
    36     _USER_PLACEHOLDER = function(_renkan) {
    26         return {
    37         return {
    27             color: _renkan.options.default_user_color,
    38             color: _renkan.options.default_user_color,
    28             title: _renkan.translate("(unknown user)"),
    39             title: _renkan.translate("(unknown user)"),
    29             get: function(attr) {
    40             get: function(attr) {
    30                 return this[attr] || false;
    41                 return this[attr] || false;
    31             }
    42             }
    32         };
    43         };
    33     },
    44     },
       
    45         /* The code for the "Drag and Add Bookmarklet", slightly minified and with whitespaces removed, though
       
    46          * it doesn't seem that it's still a requirement in newer browsers (i.e. the ones compatibles with canvas drawing)
       
    47          */
    34     _BOOKMARKLET_CODE = function(_renkan) {
    48     _BOOKMARKLET_CODE = function(_renkan) {
    35         return "(function(a,b,c,d,e,f,h,i,j,k,l,m,n,o,p,q,r){a=document;b=a.body;c=a.location.href;j='draggable';m='text/x-iri-';d=a.createElement('div');d.innerHTML='<p_style=\"position:fixed;top:0;right:0;font:bold_18px_sans-serif;color:#fff;background:#909;padding:10px;z-index:100000;\">"
    49         return "(function(a,b,c,d,e,f,h,i,j,k,l,m,n,o,p,q,r){a=document;b=a.body;c=a.location.href;j='draggable';m='text/x-iri-';d=a.createElement('div');d.innerHTML='<p_style=\"position:fixed;top:0;right:0;font:bold_18px_sans-serif;color:#fff;background:#909;padding:10px;z-index:100000;\">"
    36         + _renkan.translate("Drag items from this website, drop them in Renkan").replace(/ /g,"_")
    50         + _renkan.translate("Drag items from this website, drop them in Renkan").replace(/ /g,"_")
    37         + "</p>'.replace(/_/g,String.fromCharCode(32));b.appendChild(d);e=[{r:/https?:\\/\\/[^\\/]*twitter\\.com\\//,s:'.tweet',n:'twitter'},{r:/https?:\\/\\/[^\\/]*google\\.[^\\/]+\\//,s:'.g',n:'google'},{r:/https?:\\/\\/[^\\/]*lemonde\\.fr\\//,s:'[data-vr-contentbox]',n:'lemonde'}];f=false;e.forEach(function(g){if(g.r.test(c)){f=g;}});if(f){h=function(){Array.prototype.forEach.call(a.querySelectorAll(f.s),function(i){i[j]=true;k=i.style;k.borderWidth='2px';k.borderColor='#909';k.borderStyle='solid';k.backgroundColor='rgba(200,0,180,.1)';})};window.setInterval(h,500);h();};a.addEventListener('dragstart',function(k){l=k.dataTransfer;l.setData(m+'source-uri',c);l.setData(m+'source-title',a.title);n=k.target;if(f){o=n;while(!o.attributes[j]){o=o.parentNode;if(o==b){break;}}}if(f&&o.attributes[j]){p=o.cloneNode(true);l.setData(m+'specific-site',f.n)}else{q=a.getSelection();if(q.type==='Range'||!q.type){p=q.getRangeAt(0).cloneContents();}else{p=n.cloneNode();}}r=a.createElement('div');r.appendChild(p);l.setData('text/x-iri-selected-text',r.textContent.trim());l.setData('text/x-iri-selected-html',r.innerHTML);},false);})();";
    51         + "</p>'.replace(/_/g,String.fromCharCode(32));b.appendChild(d);e=[{r:/https?:\\/\\/[^\\/]*twitter\\.com\\//,s:'.tweet',n:'twitter'},{r:/https?:\\/\\/[^\\/]*google\\.[^\\/]+\\//,s:'.g',n:'google'},{r:/https?:\\/\\/[^\\/]*lemonde\\.fr\\//,s:'[data-vr-contentbox]',n:'lemonde'}];f=false;e.forEach(function(g){if(g.r.test(c)){f=g;}});if(f){h=function(){Array.prototype.forEach.call(a.querySelectorAll(f.s),function(i){i[j]=true;k=i.style;k.borderWidth='2px';k.borderColor='#909';k.borderStyle='solid';k.backgroundColor='rgba(200,0,180,.1)';})};window.setInterval(h,500);h();};a.addEventListener('dragstart',function(k){l=k.dataTransfer;l.setData(m+'source-uri',c);l.setData(m+'source-title',a.title);n=k.target;if(f){o=n;while(!o.attributes[j]){o=o.parentNode;if(o==b){break;}}}if(f&&o.attributes[j]){p=o.cloneNode(true);l.setData(m+'specific-site',f.n)}else{q=a.getSelection();if(q.type==='Range'||!q.type){p=q.getRangeAt(0).cloneContents();}else{p=n.cloneNode();}}r=a.createElement('div');r.appendChild(p);l.setData('text/x-iri-selected-text',r.textContent.trim());l.setData('text/x-iri-selected-html',r.innerHTML);},false);})();";
    38     },
    52     },
       
    53         /* Shortens text to the required length then adds ellipsis */
    39     shortenText = function(_text, _maxlength) {
    54     shortenText = function(_text, _maxlength) {
    40         return (_text.length > _maxlength ? (_text.substr(0,_maxlength) + '…') : _text);
    55         return (_text.length > _maxlength ? (_text.substr(0,_maxlength) + '…') : _text);
    41     },
    56     },
       
    57         /* Drawing an edit box with an arrow and positioning the edit box according to the position of the node/edge being edited
       
    58          * Called by Rkns.Renderer.NodeEditor and Rkns.Renderer.EdgeEditor */
    42     drawEditBox = function(_options, _coords, _path, _xmargin, _selector) {
    59     drawEditBox = function(_options, _coords, _path, _xmargin, _selector) {
    43         _selector.css({
    60         _selector.css({
    44             width: ( _options.tooltip_width - 2* _options.tooltip_padding ),
    61             width: ( _options.tooltip_width - 2* _options.tooltip_padding ),
    45         });
    62         });
    46         var _height = _selector.outerHeight() + 2* _options.tooltip_padding,
    63         var _height = _selector.outerHeight() + 2* _options.tooltip_padding,
    81             top: (_options.tooltip_padding + _top)
    98             top: (_options.tooltip_padding + _top)
    82         });
    99         });
    83         return _path;
   100         return _path;
    84     };
   101     };
    85 
   102 
       
   103 /* Rkns.Renderer._BaseRepresentation Class */
       
   104 
       
   105 /* In Renkan, a "Representation" is a sort of ViewModel (in the MVVM paradigm) and bridges the gap between
       
   106  * models (written with Backbone.js) and the view (written with Paper.js)
       
   107  * Renkan's representations all inherit from Rkns.Renderer._BaseRepresentation '*/
       
   108 
    86 var _BaseRepresentation = Renderer._BaseRepresentation = function(_renderer, _model) {
   109 var _BaseRepresentation = Renderer._BaseRepresentation = function(_renderer, _model) {
    87     if (typeof _renderer !== "undefined") {
   110     if (typeof _renderer !== "undefined") {
    88         this.renderer = _renderer;
   111         this.renderer = _renderer;
    89         this.renkan = _renderer.renkan;
   112         this.renkan = _renderer.renkan;
    90         this.project = _renderer.renkan.project;
   113         this.project = _renderer.renkan.project;
   113             this.model.on("unselect", this._unselectBinding );
   136             this.model.on("unselect", this._unselectBinding );
   114         }
   137         }
   115     }
   138     }
   116 };
   139 };
   117 
   140 
       
   141 /* Rkns.Renderer._BaseRepresentation Methods */
       
   142 
   118 _(_BaseRepresentation.prototype).extend({
   143 _(_BaseRepresentation.prototype).extend({
   119     _super: function(_func) {
   144     _super: function(_func) {
   120         return _BaseRepresentation.prototype[_func].apply(this, Array.prototype.slice.call(arguments, 1));
   145         return _BaseRepresentation.prototype[_func].apply(this, Array.prototype.slice.call(arguments, 1));
   121     },
   146     },
   122     redraw: function() {},
   147     redraw: function() {},
   149             this.model.off("unselect", this._unselectBinding );
   174             this.model.off("unselect", this._unselectBinding );
   150         }
   175         }
   151     }
   176     }
   152 });
   177 });
   153 
   178 
   154 /* */
   179 /* End of Rkns.Renderer._BaseRepresentation Class */
       
   180 
       
   181 /* Rkns.Renderer._BaseButton Class */
       
   182 
       
   183 /* BaseButton is extended by contextual buttons that appear when hovering on nodes and edges */
   155 
   184 
   156 var _BaseButton = Renderer._BaseButton = Rkns.Utils.inherit(_BaseRepresentation);
   185 var _BaseButton = Renderer._BaseButton = Rkns.Utils.inherit(_BaseRepresentation);
   157 
   186 
   158 _(_BaseButton.prototype).extend({
   187 _(_BaseButton.prototype).extend({
   159 moveTo: function(_pos) {
   188 moveTo: function(_pos) {
   177 destroy: function() {
   206 destroy: function() {
   178     this.sector.destroy();
   207     this.sector.destroy();
   179 }
   208 }
   180 });
   209 });
   181 
   210 
   182 /* */
   211 /* End of Rkns.Renderer._BaseButton Class */
       
   212 
       
   213 /* Rkns.Renderer.Node Class */
       
   214 
       
   215 /* The representation for the node : A circle, with an image inside and a text label underneath.
       
   216  * The circle and the image are drawn on canvas and managed by Paper.js.
       
   217  * The text label is an HTML node, managed by jQuery. */
   183 
   218 
   184 var NodeRepr = Renderer.Node = Rkns.Utils.inherit(_BaseRepresentation);
   219 var NodeRepr = Renderer.Node = Rkns.Utils.inherit(_BaseRepresentation);
   185 
   220 
   186 _(NodeRepr.prototype).extend({
   221 _(NodeRepr.prototype).extend({
   187 _init: function() {
   222 _init: function() {
   383                     }
   418                     }
   384                     return res;
   419                     return res;
   385                 });
   420                 });
   386                 lastCoords = newCoords.slice(-2);
   421                 lastCoords = newCoords.slice(-2);
   387                 return newCoords;
   422                 return newCoords;
   388             }
   423             };
   389             
   424             
   390             instructions.forEach(function(instr) {
   425             instructions.forEach(function(instr) {
   391                 var coords = instr.match(/([a-z]|[0-9.-]+)/ig) || [""];
   426                 var coords = instr.match(/([a-z]|[0-9.-]+)/ig) || [""];
   392                 switch(coords[0]) {
   427                 switch(coords[0]) {
   393                     case "M":
   428                     case "M":
  1084                 _model.set("size", _newsize);
  1119                 _model.set("size", _newsize);
  1085                 paper.view.draw();
  1120                 paper.view.draw();
  1086             } else {
  1121             } else {
  1087                 closeEditor();
  1122                 closeEditor();
  1088             }
  1123             }
  1089         }
  1124         };
  1090         
  1125         
  1091         this.editor_$.find(".Rk-Edit-Size-Down").click(function() {
  1126         this.editor_$.find(".Rk-Edit-Size-Down").click(function() {
  1092             shiftSize(-1);
  1127             shiftSize(-1);
  1093             return false;
  1128             return false;
  1094         });
  1129         });
  1099     } else {
  1134     } else {
  1100         if (typeof this.source_representation.highlighted === "object") {
  1135         if (typeof this.source_representation.highlighted === "object") {
  1101             var titlehtml = this.source_representation.highlighted.replace(_(_model.get("title")).escape(),'<span class="Rk-Highlighted">$1</span>');
  1136             var titlehtml = this.source_representation.highlighted.replace(_(_model.get("title")).escape(),'<span class="Rk-Highlighted">$1</span>');
  1102             this.editor_$.find(".Rk-Display-Title" + (_model.get("uri") ? " a" : "")).html(titlehtml);
  1137             this.editor_$.find(".Rk-Display-Title" + (_model.get("uri") ? " a" : "")).html(titlehtml);
  1103             if (this.options.show_node_tooltip_description) {
  1138             if (this.options.show_node_tooltip_description) {
  1104                 this.editor_$.find(".Rk-Display-Description").html(this.source_representation.highlighted.replace(_(_model.get("description")).escape(),'<span class="Rk-Highlighted">$1</span>'))
  1139                 this.editor_$.find(".Rk-Display-Description").html(this.source_representation.highlighted.replace(_(_model.get("description")).escape(),'<span class="Rk-Highlighted">$1</span>'));
  1105             }
  1140             }
  1106         }
  1141         }
  1107     }
  1142     }
  1108     this.editor_$.find("img").load(function() {
  1143     this.editor_$.find("img").load(function() {
  1109         _this.redraw();
  1144         _this.redraw();
  1746     var bindClick = function(selector, fname) {
  1781     var bindClick = function(selector, fname) {
  1747         _this.$.find(selector).click(function(evt) {
  1782         _this.$.find(selector).click(function(evt) {
  1748             _this[fname](evt);
  1783             _this[fname](evt);
  1749             return false;
  1784             return false;
  1750         });
  1785         });
  1751     }
  1786     };
  1752     
  1787     
  1753     bindClick(".Rk-ZoomOut", "zoomOut");
  1788     bindClick(".Rk-ZoomOut", "zoomOut");
  1754     bindClick(".Rk-ZoomIn", "zoomIn");
  1789     bindClick(".Rk-ZoomIn", "zoomIn");
  1755     this.$.find(".Rk-CurrentUser").mouseenter(
  1790     this.$.find(".Rk-CurrentUser").mouseenter(
  1756         function() { _this.$.find(".Rk-UserList").slideDown(); }
  1791         function() { _this.$.find(".Rk-UserList").slideDown(); }