diff --git a/app/javascript/components/AppIcon.vue b/app/javascript/components/AppIcon.vue
index 1c22293..bc115db 100644
--- a/app/javascript/components/AppIcon.vue
+++ b/app/javascript/components/AppIcon.vue
@@ -1,6 +1,6 @@
-
-
+
+
@@ -19,16 +19,102 @@
import Star from "../iconic/svg/smart/star";
import StarEmpty from "../iconic/svg/smart/star-empty";
import X from "../iconic/svg/smart/x";
-
- const iconicInstance = IconicJs({
- autoInjectSelector: null
- });
+
+ const SVG_CACHE = {};
+ let REMAP_COUNT = 0;
class IconData {
- constructor(url, dataAttributes) {
- this.url = url;
+ constructor(svgData, dataAttributes) {
+ this.svgData = svgData;
this.dataAttributes = dataAttributes || {};
- this.dataAttributes['src'] = url;
+ this.svgTemplate = null;
+ }
+
+ getSvg() {
+ if (this.svgTemplate === null) {
+ const iconicName = this.svgData.attributes.class;
+
+ this.svgTemplate = SVG_CACHE[iconicName] || null;
+
+ if (this.svgTemplate === null) {
+ this.svgTemplate = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ SVG_CACHE[iconicName] = this.svgTemplate;
+
+ for (let attr in this.svgData.attributes) {
+ this.svgTemplate.setAttribute(attr, this.svgData.attributes[attr]);
+ }
+ this.svgTemplate.innerHTML = this.svgData.content;
+
+
+ // Find then prune the scripts
+ const scripts = [...this.svgTemplate.querySelectorAll('script')];
+
+ scripts.filter(s => {
+ let scriptType = s.getAttribute("type");
+ return !scriptType || scriptType === 'application/ecmascript' || scriptType === 'application/javascript'
+ }).forEach(s => {
+ new Function(s.innerText || s.textContent)(window);
+ this.svgTemplate.removeChild(s);
+ });
+ }
+ }
+
+ const clone = this.svgTemplate.cloneNode(true);
+ this.remapIds(clone);
+ this.attachIconicApi(clone);
+ for (let attr in this.dataAttributes) {
+ clone.setAttribute(`data-${attr}`, this.dataAttributes[attr]);
+ }
+ return clone;
+ }
+
+ remapIds(svg) {
+ const iriElementsAndProperties = {
+ 'clipPath': ['clip-path'],
+ 'color-profile': ['color-profile'],
+ 'cursor': ['cursor'],
+ 'filter': ['filter'],
+ 'linearGradient': ['fill', 'stroke'],
+ 'marker': ['marker', 'marker-start', 'marker-mid', 'marker-end'],
+ 'mask': ['mask'],
+ 'pattern': ['fill', 'stroke'],
+ 'radialGradient': ['fill', 'stroke']
+ };
+
+ REMAP_COUNT += 1;
+
+ let element, elementDefs, properties, currentId, newId;
+ Object.keys(iriElementsAndProperties).forEach(function (key) {
+ element = key;
+ properties = iriElementsAndProperties[key];
+
+ elementDefs = svg.querySelectorAll('defs ' + element + '[id]');
+ for (var i = 0, elementsLen = elementDefs.length; i < elementsLen; i++) {
+ currentId = elementDefs[i].id;
+ newId = currentId + '-' + REMAP_COUNT;
+
+ // All of the properties that can reference this element type
+ var referencingElements;
+ properties.forEach(function (property) {
+ // :NOTE: using a substring match attr selector here to deal with IE "adding extra quotes in url() attrs"
+ referencingElements = svg.querySelectorAll('[' + property + '*="' + currentId + '"]');
+ for (var j = 0, referencingElementLen = referencingElements.length; j < referencingElementLen; j++) {
+ referencingElements[j].setAttribute(property, 'url(#' + newId + ')');
+ }
+ });
+
+ elementDefs[i].id = newId;
+ }
+ });
+ }
+
+ attachIconicApi(svg) {
+ const key = svg.getAttribute("data-icon");
+ const apis = window.iconicSmartIconApis;
+ if (key && apis[key]) {
+ const iconApi = apis[key](svg);
+ for (let func in iconApi) svg[func] = iconApi[func]
+ }
}
}
@@ -38,6 +124,43 @@
this.defaultPadding = defaultPadding || null;
}
}
+
+ class IconicQueue {
+ constructor(iconicInstance) {
+ this.iconic = iconicInstance;
+ this.updateQueue = [];
+ this.isTimerRunning = false;
+ }
+
+ processQueues() {
+ if (this.updateQueue.length > 0) {
+ console.log(`updating ${this.updateQueue.length}`);
+ this.iconic.update(this.updateQueue);
+ this.updateQueue.forEach(s => (s.style.visibility = null));
+ this.updateQueue = [];
+ }
+
+ this.isTimerRunning = false;
+ }
+
+ ensureTimer() {
+ if (!this.isTimerRunning) {
+ this.isTimerRunning = true;
+ setTimeout(() => this.processQueues());
+ }
+ }
+
+ updateSvg(svgElem) {
+ this.updateQueue.push(svgElem);
+ this.ensureTimer();
+ }
+ }
+
+ const iconicInstance = IconicJs({
+ autoInjectSelector: "#notreal-noinjection"
+ });
+
+ const iconicQueue = new IconicQueue(iconicInstance);
const iconMap = {
'caret-bottom': new IconData(Caret, {direction: 'bottom'}),
@@ -97,15 +220,6 @@
return sizeMap[this.size];
},
- extraIconAttributes() {
- const attrs = {};
-
- for (let attr in this.iconData.dataAttributes) {
- attrs[`data-${attr}`] = this.iconData.dataAttributes[attr];
- }
- return attrs;
- },
-
sizeClass() {
return this.sizeData.bulmaIconClass;
},
@@ -115,22 +229,42 @@
}
},
- mounted() {
- const self = this;
- setTimeout(() => {
- iconicInstance.inject(this.$refs.img, {
- each: function(svg) { self.injectedSvg = svg; }
+ methods: {
+ insertSvg() {
+ const parent = this.$refs.container;
+
+ while (parent.firstChild) {
+ parent.removeChild(parent.firstChild);
+ }
+
+ const svg = this.iconData.getSvg();
+ this.injectedSvg = svg;
+
+ svg.style.padding = this.svgPadding;
+ svg.style.visibility = "hidden";
+ svg.classList.add("iconic-fluid");
+ parent.appendChild(svg);
+
+
+ setTimeout(() => {
+ //svg.style.visibility = "visible";
+ //iconicInstance.update(svg);
+ iconicQueue.updateSvg(svg);
});
- });
+ }
},
- updated() {
- if (this.injectedSvg) {
- for(let attr in this.extraIconAttributes) {
- this.injectedSvg.setAttribute(attr, this.extraIconAttributes[attr]);
+ mounted() {
+ let self = this;
+ this.$watch(
+ function() { return this.icon + this.size + this.padding },
+ function() {
+ this.insertSvg();
+ },
+ {
+ immediate: true
}
- iconicInstance.update(this.injectedSvg);
- }
+ )
}
}
diff --git a/config/webpack/environment.js b/config/webpack/environment.js
index 3b91fd3..dc70989 100644
--- a/config/webpack/environment.js
+++ b/config/webpack/environment.js
@@ -3,9 +3,9 @@ const vue = require('./loaders/vue');
const svg = require('./loaders/svg');
environment.loaders.append('vue', vue);
-//environment.loaders.prepend('svg', svg);
+environment.loaders.prepend('svg', svg);
-//const fileLoader = environment.loaders.get('file');
-//fileLoader.exclude = /\.(svg)$/i;
+const fileLoader = environment.loaders.get('file');
+fileLoader.exclude = /\.(svg)$/i;
module.exports = environment;
diff --git a/config/webpack/loaders/svg.js b/config/webpack/loaders/svg.js
index 7b00dcc..b1da427 100644
--- a/config/webpack/loaders/svg.js
+++ b/config/webpack/loaders/svg.js
@@ -1,10 +1,18 @@
+const path = require("path");
module.exports = {
test: /\.svg$/,
use: [{
- loader: 'url-loader',
- options: {
- limit: 10000
- }
+ loader: path.join(__dirname, 'svg_loader.js')
}]
};
+
+// module.exports = {
+// test: /\.svg$/,
+// use: [{
+// loader: 'url-loader',
+// options: {
+// limit: 10000
+// }
+// }]
+// };
diff --git a/config/webpack/loaders/svg_loader.js b/config/webpack/loaders/svg_loader.js
new file mode 100644
index 0000000..619b321
--- /dev/null
+++ b/config/webpack/loaders/svg_loader.js
@@ -0,0 +1,27 @@
+module.exports = function(content) {
+ this.cacheable && this.cacheable();
+
+ var match = content.match(/