eGonomy image fragments support
authorveltr
Tue, 14 May 2013 12:15:15 +0200
changeset 175 f636e2dcfb2c
parent 174 756cfa6570d2
child 176 f3da1f26f949
eGonomy image fragments support
client/css/renkan.css
client/js/defaults.js
client/js/models.js
client/js/paper-renderer.js
client/render-test.html
--- a/client/css/renkan.css	Mon May 13 15:28:52 2013 +0200
+++ b/client/css/renkan.css	Tue May 14 12:15:15 2013 +0200
@@ -286,9 +286,26 @@
     font-size: 12px; width: 230px;
 }
 
+.Rk-Edit-ImgWrap {
+    text-align: center;
+}
+
 .Rk-Edit-ImgPreview {
-    border: 1px solid #666; margin: 5px auto; display: block;
-    max-width: 253px !important; max-height: 200px !important;
+    display: inline-block;
+    border: 1px solid #666; margin: 5px auto;
+    position: relative;
+}
+
+.Rk-Edit-ImgPreview img {
+    display: inline-block; max-width: 253px !important; max-height: 200px !important;
+}
+
+.Rk-Edit-ImgPreview svg {
+    height: 100%;
+    left: 0;
+    position: absolute;
+    top: 0;
+    width: 100%;
 }
 
 .Rk-Editor textarea {
--- a/client/js/defaults.js	Mon May 13 15:28:52 2013 +0200
+++ b/client/js/defaults.js	Tue May 14 12:15:15 2013 +0200
@@ -28,7 +28,7 @@
     force_resize: false,
     allow_double_click: true,
         /* Allows Double Click to create a node on an empty background */
-    element_delete_delay: 8000,
+    element_delete_delay: 5000,
         /* Delay between clicking on the bin on an element and really deleting it
            Set to 0 for delete confirm */
     autoscale_padding: 50,
--- a/client/js/models.js	Mon May 13 15:28:52 2013 +0200
+++ b/client/js/models.js	Tue May 14 12:15:15 2013 +0200
@@ -90,7 +90,8 @@
                 image: this.get("image"),
                 color: this.get("color"),
                 created_by: this.get("created_by") ? this.get("created_by").get("_id") : null,
-                size: this.get("size")
+                size: this.get("size"),
+                "clip-path": this.get("clip-path")
             };
         },
     });
--- a/client/js/paper-renderer.js	Mon May 13 15:28:52 2013 +0200
+++ b/client/js/paper-renderer.js	Tue May 14 12:15:15 2013 +0200
@@ -242,15 +242,14 @@
         var square = new paper.Size(this.circle_radius, this.circle_radius),
             topleft = this.paper_coords.subtract(square),
             bounds = new paper.Rectangle(topleft, square.multiply(2));
-        this.circle.fitBounds(bounds);
+        this.circle.scale(this.circle_radius / this.last_circle_radius);
         if (this.node_image) {
-            this.node_image.fitBounds(bounds);
+            this.node_image.scale(this.circle_radius / this.last_circle_radius);
         }
-    } else {
-        this.circle.position = this.paper_coords;
-        if (this.node_image) {
-            this.node_image.position = this.paper_coords;
-        }
+    }
+    this.circle.position = this.paper_coords;
+    if (this.node_image) {
+        this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
     }
     this.last_circle_radius = this.circle_radius;
     
@@ -299,44 +298,11 @@
     this.all_buttons.forEach(function(b) {
         b.moveTo(_pc);
     });
-    var _img = this.model.get("image");
-    if (_img && _img !== this.img) {
-        var _image = new Image(),
-            _this = this;
-        _image.onload = function() {
-            if (_this.node_image) {
-                _this.node_image.remove();
-            }
-            _this.renderer.node_layer.activate();
-            var _ratio = Math.min(2 / _image.width, 2 / _image.height );
-            if (!_this.options.show_node_circles) {
-                _this.h_ratio = Math.min(1, _image.height / _image.width);
-            }
-            var _raster = new paper.Raster(_image);
-            if (_this.options.clip_node_images) {
-                var _clip = new paper.Path.Circle([0, 0], 1);
-                _raster.scale(_ratio);
-                _this.node_image = new paper.Group(_clip, _raster);
-                _this.node_image.opacity = .99;
-                /* This is a workaround to allow clipping at group level
-                 * If opacity was set to 1, paper.js would merge all clipping groups in one (known bug).
-                */
-                _this.node_image.clipped = true;
-                _clip.__representation = _this;
-            } else {
-                _this.node_image = _raster;
-            }
-            _this.node_image.__representation = _this;
-            var square = new paper.Size(_this.circle_radius, _this.circle_radius),
-                topleft = _this.paper_coords.subtract(square),
-                bounds = new paper.Rectangle(topleft, square.multiply(2));
-            _this.node_image.fitBounds(bounds);
-            _this.redraw();
-            paper.view.draw();
-        };
-        _image.src = _img;
+    var lastImage = this.img;
+    this.img = this.model.get("image");
+    if (this.img && this.img !== lastImage) {
+        this.showImage();
     }
-    this.img = _img;
     if (this.node_image && !this.img) {
         this.node_image.remove();
         delete this.node_image;
@@ -361,6 +327,127 @@
 
 };
 
+Rkns.Renderer.Node.prototype.showImage = function() {
+    if (typeof this.renderer.image_cache[this.img] === "undefined") {
+        var _image = new Image()
+        this.renderer.image_cache[this.img] = _image;
+        _image.src = this.img;
+    } else {
+        var _image = this.renderer.image_cache[this.img];
+    }
+    if (_image.width) {
+        if (this.node_image) {
+            this.node_image.remove();
+        }
+        this.renderer.node_layer.activate();
+        var width = _image.width,
+            height = _image.height,
+            clipPath = this.model.get("clip-path"),
+            hasClipPath = (typeof clipPath !== "undefined" && clipPath);
+        if (hasClipPath) {
+            var _clip = new paper.Path(),
+                instructions = clipPath.match(/[a-z][^a-z]+/gi) || [],
+                lastCoords = [0,0],
+                minX = Infinity,
+                minY = Infinity,
+                maxX = -Infinity,
+                maxY = -Infinity;
+                
+            function transformCoords(tabc, relative) {
+                var newCoords = tabc.slice(1).map(function(v, k) {
+                    var res = parseFloat(v),
+                        isY = k % 2;
+                    if (isY) {
+                        res = ( res - .5 ) * height;
+                    } else {
+                        res = ( res - .5 ) * width;
+                    }
+                    if (relative) {
+                        res += lastCoords[isY];
+                    }
+                    if (isY) {
+                        minY = Math.min(minY, res);
+                        maxY = Math.max(maxY, res);
+                    } else {
+                        minX = Math.min(minX, res);
+                        maxX = Math.max(maxX, res);
+                    }
+                    return res;
+                });
+                lastCoords = newCoords.slice(-2);
+                return newCoords;
+            }
+            
+            instructions.forEach(function(instr) {
+                var coords = instr.match(/([a-z]|[0-9.-]+)/ig) || [""];
+                switch(coords[0]) {
+                    case "M":
+                        _clip.moveTo(transformCoords(coords));
+                    break;
+                    case "m":
+                        _clip.moveTo(transformCoords(coords, true));
+                    break;
+                    case "L":
+                        _clip.lineTo(transformCoords(coords));
+                    break;
+                    case "l":
+                        _clip.lineTo(transformCoords(coords, true));
+                    break;
+                    case "C":
+                        _clip.cubicCurveTo(transformCoords(coords));
+                    break;
+                    case "c":
+                        _clip.cubicCurveTo(transformCoords(coords, true));
+                    break;
+                    case "Q":
+                        _clip.quadraticCurveTo(transformCoords(coords));
+                    break;
+                    case "q":
+                        _clip.quadraticCurveTo(transformCoords(coords, true));
+                    break;
+                }
+            });
+            
+            var baseScale = Math.max(maxX - minX, maxY - minY) / 2;
+            this.image_delta = new paper.Point((maxX + minX) / (2 * baseScale), (maxY + minY) / (2 * baseScale));
+            if (!this.options.show_node_circles) {
+                this.h_ratio = (maxY - minY) / (2 * baseScale);
+            }
+        } else {
+            var baseScale = Math.max(width, height) / 2;
+            this.image_delta = new paper.Point(0,0);
+            if (!this.options.show_node_circles) {
+                this.h_ratio = height / (2 * baseScale);
+            }
+        }
+        var _raster = new paper.Raster(_image);
+        if (this.options.clip_node_images || hasClipPath) {
+            if (!hasClipPath) {
+                var _clip = new paper.Path.Circle([0, 0], baseScale);
+            }
+            this.node_image = new paper.Group(_clip, _raster);
+            this.node_image.opacity = .99;
+            /* This is a workaround to allow clipping at group level
+             * If opacity was set to 1, paper.js would merge all clipping groups in one (known bug).
+            */
+            this.node_image.clipped = true;
+            _clip.__representation = this;
+        } else {
+            this.node_image = _raster;
+        }
+        this.node_image.__representation = _this;
+        this.node_image.scale(this.circle_radius / baseScale);
+        this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
+        this.redraw();
+        this.renderer.throttledPaperDraw();
+    } else {
+        var _this = this;
+        Rkns.$(_image).on("load", function() {
+            _this.showImage();
+        });
+    }
+}
+
 Rkns.Renderer.Node.prototype.paperShift = function(_delta) {
     if (this.options.editor_mode) {
         if (!this.renkan.read_only) {
@@ -841,8 +928,9 @@
     + '<% if (options.show_node_editor_size) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Size:")%></span><a href="#" class="Rk-Edit-Size-Down">-</a><span class="Rk-Edit-Size-Value"><%-node.size%></span><a href="#" class="Rk-Edit-Size-Up">+</a></p><% } %>'
     + '<% if (options.show_node_editor_color) { %><div class="Rk-Editor-p"><span class="Rk-Editor-Label"><%-renkan.translate("Node color:")%></span><div class="Rk-Edit-ColorPicker-Wrapper"><span class="Rk-Edit-Color" style="background:<%-node.color%>;"><span class="Rk-Edit-ColorTip"></span></span><ul class="Rk-Edit-ColorPicker">'
     + '<% _(Rkns.pickerColors).each(function(c) { %><li data-color="<%=c%>" style="background: <%=c%>"></li><% }); %></ul><span class="Rk-Edit-ColorPicker-Text"><%- renkan.translate("Choose color") %></span></div></div><% } %>'
-    + '<% if (options.show_node_editor_image) { %><img class="Rk-Edit-ImgPreview" src="<%-node.image || node.image_placeholder%>" />'
-    + '<p><label><%-renkan.translate("Image URL:")%></label><input class="Rk-Edit-Image" type="text" value="<%-node.image%>"/></p>'
+    + '<% if (options.show_node_editor_image) { %><div class="Rk-Edit-ImgWrap"><div class="Rk-Edit-ImgPreview"><img src="<%-node.image || node.image_placeholder%>" />'
+    + '<% if (node.clip_path) { %><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 1 1" preserveAspectRatio="none"><path style="stroke-width: .02; stroke:red; fill-opacity:.3; fill:red;" d="<%- node.clip_path %>"/></svg><% }%>'
+    + '</div></div><p><label><%-renkan.translate("Image URL:")%></label><input class="Rk-Edit-Image" type="text" value="<%-node.image%>"/></p>'
     + '<p><label><%-renkan.translate("Choose Image File:")%></label><input class="Rk-Edit-Image-File" type="file" accept="image/*"/></p><% } %>'    
     + '<% if (options.show_node_editor_creator && node.has_creator) { %><p><span class="Rk-Editor-Label"><%-renkan.translate("Created by:")%></span> <span class="Rk-UserColor" style="background:<%-node.created_by_color%>;"></span><%- Rkns.Renderer.shortenText(node.created_by_title, 25) %></p><% } %>'
 );
@@ -873,6 +961,7 @@
                 image: _model.get("image") || "",
                 image_placeholder: _image_placeholder,
                 color: _model.get("color") || _created_by.get("color"),
+                clip_path: _model.get("clip-path") || false,
                 created_by_color: _created_by.get("color"),
                 created_by_title: _created_by.get("title"),
                 size: (_size > 0 ? "+" : "") + _size
@@ -1474,12 +1563,13 @@
         _lastTapX,
         _lastTapY;
     
-    this.imageCache = {};
+    this.image_cache = {};
+    this.icon_cache = {};
     
     ['edit', 'remove', 'link', 'enlarge', 'shrink', 'revert' ].forEach(function(imgname) {
         var img = new Image();
         img.src = _renkan.options.static_url + 'img/' + imgname + '.png';
-        _this.imageCache[imgname] = img;
+        _this.icon_cache[imgname] = img;
     });
     
     var throttledMouseMove = _.throttle(function(_event, _isTouch) {
@@ -1853,7 +1943,7 @@
     var _options = this.renkan.options,
         _startRads = _startAngle * Math.PI / 180,
         _endRads = _endAngle * Math.PI / 180,
-        _img = this.imageCache[_imgname],
+        _img = this.icon_cache[_imgname],
         _span = _endRads - _startRads,
         _startdx = - Math.sin(_startRads),
         _startdy = Math.cos(_startRads),
@@ -2361,6 +2451,14 @@
             }
             if (_data["text/html"] || _data["text/x-iri-selected-html"]) {
                 var snippet = Rkns.$('<div>').html(_data["text/html"] || _data["text/x-iri-selected-html"]);
+                var _svgimgs = snippet.find("image");
+                if (_svgimgs.length) {
+                    newNode.image = _svgimgs.attr("xlink:href");
+                }
+                var _svgpaths = snippet.find("path");
+                if (_svgpaths.length) {
+                    newNode.clipPath = _svgpaths.attr("d");
+                }
                 var _imgs = snippet.find("img");
                 if (_imgs.length) {
                     newNode.image = _imgs[0].src;
@@ -2389,6 +2487,7 @@
                 newNode.uri = snippet.find("[data-uri]").attr("data-uri") || newNode.uri;
                 newNode.title = snippet.find("[data-title]").attr("data-title") || newNode.title;
                 newNode.description = snippet.find("[data-description]").attr("data-description") || newNode.description;
+                newNode.description = snippet.find("[data-clip-path]").attr("data-clip-path") || newNode.description;
             }
     }
     if (!newNode.title) {
@@ -2418,6 +2517,7 @@
             description: newNode.description || "",
             image: newNode.image || "",
             color: newNode.color || undefined,
+            "clip-path": newNode.clipPath || undefined,
             position: {
                 x: _coords.x,
                 y: _coords.y
--- a/client/render-test.html	Mon May 13 15:28:52 2013 +0200
+++ b/client/render-test.html	Tue May 14 12:15:15 2013 +0200
@@ -26,6 +26,8 @@
             var _renkan;
             $(function() {
                 _renkan = new Rkns.Renkan({
+                    show_node_circles: false,
+                    clip_node_images: false,
                     bins: [
                     	{
                             title: "Projet Lignes de Temps",