[TASK] Make SVG category tree use JS Icon API 68/54468/7
authorTymoteusz Motylewski <t.motylewski@gmail.com>
Tue, 24 Oct 2017 11:27:08 +0000 (13:27 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Thu, 2 Nov 2017 16:51:05 +0000 (17:51 +0100)
This change make SVG tree expect icon identifier instead of icon source
in JSON node data.
Tree fetches icon source via JS Icon API only once per icon type.
This makes JSON response few times smaller.

Resolves: #82849
Releases: master
Change-Id: I8954837f68cee3bcc316bc56e30788e0d16c81d6
Reviewed-on: https://review.typo3.org/54468
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/backend/Classes/Tree/Renderer/ExtJsJsonTreeRenderer.php
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTree.js
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SvgTree.js

index 12bd862..7ec8def 100644 (file)
@@ -14,7 +14,6 @@ namespace TYPO3\CMS\Backend\Tree\Renderer;
  * The TYPO3 project - inspiring people to share!
  */
 use TYPO3\CMS\Backend\Tree\TreeNodeCollection;
-use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
 use TYPO3\CMS\Core\Tree\TableConfiguration\DatabaseTreeNode;
 
 /**
@@ -59,21 +58,21 @@ class ExtJsJsonTreeRenderer extends \TYPO3\CMS\Backend\Tree\Renderer\AbstractTre
      */
     protected function getNodeArray(\TYPO3\CMS\Backend\Tree\TreeRepresentationNode $node)
     {
-        $overlayIconMarkup  = '';
+        $overlayIconName  = '';
         if (is_object($node->getIcon())) {
-            $iconMarkup = $node->getIcon()->getMarkup(SvgIconProvider::MARKUP_IDENTIFIER_INLINE);
+            $iconName = $node->getIcon()->getIdentifier();
             if (is_object($node->getIcon()->getOverlayIcon())) {
-                $overlayIconMarkup = $node->getIcon()->getOverlayIcon()->getMarkup(SvgIconProvider::MARKUP_IDENTIFIER_INLINE);
+                $overlayIconName = $node->getIcon()->getOverlayIcon()->getIdentifier();
             }
         } else {
-            $iconMarkup = $node->getIcon();
+            $iconName = $node->getIcon();
         }
         $nodeArray = [
             'identifier' => htmlspecialchars($node->getId()),
             // No need for htmlspecialchars() here as d3 is using 'textContent' property of the HTML DOM node
             'name' => $node->getLabel(),
-            'icon' => $iconMarkup,
-            'overlayIcon' => $overlayIconMarkup,
+            'icon' => $iconName,
+            'overlayIcon' => $overlayIconName,
             'depth' => $this->recursionLevel,
             'hasChildren' => (bool)$node->hasChildNodes(),
             'selectable' => true,
index d38a990..6ea3ef7 100644 (file)
@@ -196,39 +196,21 @@ define(['d3',
      */
     SelectTree.prototype.addIcons = function () {
 
-        var iconsData = [
-            {
-                identifier: 'icon-check',
+        this.data.icons = {
+            'check': {
+                identifier: 'check',
                 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">' +
                 '<rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1312 256h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-832q0-66-47-113t-113-47zm288 160v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>'
             },
-            {
-                identifier: 'icon-checked',
+            'checked': {
+                identifier: 'checked',
                 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M813 1299l614-614q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-467 467-211-211q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l358 358q19 19 45 19t45-19zm851-883v960q0 119-84.5 203.5t-203.5 84.5h-960q-119 0-203.5-84.5t-84.5-203.5v-960q0-119 84.5-203.5t203.5-84.5h960q119 0 203.5 84.5t84.5 203.5z"></path></g>'
             },
-            {
-                identifier: 'icon-indeterminate',
+            'indeterminate': {
+                identifier: 'indeterminate',
                 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1344 800v64q0 14-9 23t-23 9h-832q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14 0 23 9t9 23zm128 448v-832q0-66-47-113t-113-47h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>'
             }
-        ];
-
-        var icons = this.iconsContainer
-            .selectAll('.icon-def')
-            .data(iconsData);
-        icons
-            .enter()
-            .append('g')
-            .attr('class', 'icon-def')
-            .attr('id', function (i) {
-                return i.identifier;
-            })
-            .append(function (i) {
-                //workaround for IE11 where you can't simply call .html(content) on svg
-                var parser = new DOMParser();
-                var dom = parser.parseFromString(i.icon, "image/svg+xml");
-                return dom.documentElement;
-            });
-
+        };
     };
 
     return SelectTree;
index 72e243e..401710c 100644 (file)
@@ -14,7 +14,7 @@
 /**
  * Module: TYPO3/CMS/Backend/FormEngine/Element/SvgTree
  */
-define(['jquery', 'd3'], function ($, d3) {
+define(['jquery', 'd3', 'TYPO3/CMS/Backend/Icons'], function ($, d3, Icons) {
     'use strict';
 
     /**
@@ -136,6 +136,7 @@ define(['jquery', 'd3'], function ($, d3) {
                 .attr('class', 'nodes');
             if (this.settings.showIcons) {
                 this.iconsContainer = this.svg.append('defs');
+                this.data.icons = {};
             }
 
             this.updateScrollPosition();
@@ -219,9 +220,7 @@ define(['jquery', 'd3'], function ($, d3) {
                 });
             });
 
-            var iconHashes = [];
             this.data.links = [];
-            this.data.icons = [];
             this.data.nodes.forEach(function (n, i) {
                 //delete n.children;
                 n.x = n.depth * me.settings.indentWidth;
@@ -232,33 +231,38 @@ define(['jquery', 'd3'], function ($, d3) {
                         target: n
                     });
                 }
-                if (!n.iconHash && me.settings.showIcons && n.icon) {
-                    n.iconHash = Math.abs(me.hashCode(n.icon));
-                    if (iconHashes.indexOf(n.iconHash) === -1) {
-                        iconHashes.push(n.iconHash);
-                        me.data.icons.push({
-                            identifier: n.iconHash,
-                            icon: n.icon
-                        });
-                    }
-                    delete n.icon;
-                }
-                if (!n.iconOverlayHash && me.settings.showIcons && n.overlayIcon) {
-                    n.iconOverlayHash = Math.abs(me.hashCode(n.overlayIcon));
-                    if (iconHashes.indexOf(n.iconOverlayHash) === -1) {
-                        iconHashes.push(n.iconOverlayHash);
-                        me.data.icons.push({
-                            identifier: n.iconOverlayHash,
-                            icon: n.overlayIcon
-                        });
-                    }
-                    delete n.overlayIcon;
+
+                if (me.settings.showIcons) {
+                    me.fetchIcon(n.icon);
+                    me.fetchIcon(n.overlayIcon);
                 }
             });
             this.svg.attr('height', this.data.nodes.length * this.settings.nodeHeight);
         },
 
         /**
+         * Fetch icon from Icon API and store it in data.icons
+         *
+         * @param {String} iconName
+         */
+        fetchIcon: function (iconName) {
+            if (!iconName) {
+                return;
+            }
+            var me = this;
+            if (!(iconName in this.data.icons)) {
+                this.data.icons[iconName] = {
+                    identifier: iconName,
+                    icon: ''
+                };
+                Icons.getIcon(iconName, Icons.sizes.small, null, null, 'inline').done(function(icon) {
+                    me.data.icons[iconName].icon = icon.match(/<svg.*<\/svg>/im)[0];
+                    me.update();
+                });
+            }
+        },
+
+        /**
          * Renders the subset of the tree nodes fitting the viewport (adding, modifying and removing SVG nodes)
          */
         update: function () {
@@ -339,9 +343,10 @@ define(['jquery', 'd3'], function ($, d3) {
             me.textPosition = 10;
 
             if (me.settings.showIcons) {
+                var iconsArray = $.map(me.data.icons, function(value) {if (value.icon !== '') return value});
                 var icons = this.iconsContainer
                     .selectAll('.icon-def')
-                    .data(this.data.icons, function (i) {
+                    .data(iconsArray, function (i) {
                         return i.identifier;
                     });
                 icons
@@ -475,7 +480,7 @@ define(['jquery', 'd3'], function ($, d3) {
          * @returns {String}
          */
         getIconId: function (node) {
-            return '#icon-' + node.iconHash;
+            return '#icon-' + node.icon;
         },
         /**
          * Returns icon's href attribute value
@@ -484,7 +489,7 @@ define(['jquery', 'd3'], function ($, d3) {
          * @returns {String}
          */
         getIconOverlayId: function (node) {
-            return '#icon-' + node.iconOverlayHash;
+            return '#icon-' + node.overlayIcon;
         },
 
         /**
@@ -521,20 +526,6 @@ define(['jquery', 'd3'], function ($, d3) {
         },
 
         /**
-         * Simple hash function used to create icon href's
-         *
-         * @param {String} s
-         * @returns {String}
-         */
-        hashCode: function (s) {
-            return s.split('')
-                .reduce(function (a, b) {
-                    a = ((a << 5) - a) + b.charCodeAt(0);
-                    return a & a
-                }, 0);
-        },
-
-        /**
          * Node selection logic (triggered by different events)
          *
          * @param {Node} node