├── .jshintrc ├── .gitignore ├── test ├── demo.css ├── demo.html └── test.js ├── browserify.js ├── component.json ├── bower.json ├── LICENSE ├── package.json ├── examples ├── 3_progress.html ├── 2_calories.html └── 1_week_activity.html ├── README.md ├── dist ├── radial-progress-chart.min.js └── radial-progress-chart.js └── index.js /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "laxcomma": true, 3 | "browser": true, 4 | "node": true 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | sample/ 3 | node_modules/ 4 | bower_components/ 5 | *.log 6 | build/ 7 | -------------------------------------------------------------------------------- /test/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: black; 3 | } 4 | 5 | svg { 6 | padding: 25px; 7 | } 8 | 9 | .rbc-label-start { 10 | font-family: fontawesome; 11 | font-weight: bold; 12 | font-size: 30px; 13 | 14 | } 15 | 16 | .rbc-center-text { 17 | font-family: 'Roboto', 'Myriad Set Pro', 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; 18 | fill: white; 19 | font-size: 95px; 20 | } -------------------------------------------------------------------------------- /browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var browserify = require('browserify'); 5 | 6 | if (!fs.existsSync('./dist')) { 7 | fs.mkdirSync('./dist'); 8 | } 9 | 10 | browserify({'debug': true, 'standalone': 'Radial Progress Chart'}) 11 | .require('./index.js', {'entry': true}) 12 | .exclude('d3') 13 | .bundle() 14 | .on('error', function(err) { console.log('Error : ' + err.message); }) 15 | .pipe(fs.createWriteStream('dist/radial-progress-chart.js')); -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "radial-progress-chart", 3 | "description": "Radial Progress Chart started as a weekend project. It's written on the top of D3.js and was heavily inspired by and D3 stub of Polar Clock", 4 | "version": "0.0.4", 5 | "repo": "git@github.com:pablomolnar/radial-progress-chart.git", 6 | "scripts":[ 7 | "index.js" 8 | ], 9 | "dependencies": { 10 | "mbostock/d3": "3.5.0" 11 | }, 12 | "keywords": [ 13 | "apple", 14 | "watch", 15 | "radial", 16 | "bar", 17 | "progress", 18 | "chart", 19 | "polar", 20 | "d3" 21 | ], 22 | "author": "Pablo Molnar", 23 | "development": {}, 24 | "license": "MIT", 25 | "readmeFilename": "README.md" 26 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "radial-progress-chart", 3 | "main": "dist/radial-progress-chart.js", 4 | "version": "0.0.4", 5 | "homepage": "https://github.com/pablomolnar/radial-progress-chart", 6 | "authors": [ 7 | "Pablo Molnar " 8 | ], 9 | "description": "Radial Progress Chart started as a weekend project. It's written on the top of D3.js and was heavily inspired by and D3 stub of Polar Clock", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "apple", 17 | "watch", 18 | "radial", 19 | "bar", 20 | "progress", 21 | "chart", 22 | "polar", 23 | "d3" 24 | ], 25 | "license": "MIT", 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "test", 31 | "tests" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pablo Molnar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "radial-progress-chart", 3 | "description": "A customizable Radial Progress Chart written on the top of D3.js.", 4 | "version": "0.0.4", 5 | "repository": "git@github.com:pablomolnar/radial-progress-chart.git", 6 | "scripts": { 7 | "test": "mocha", 8 | "build": "node browserify.js && ./node_modules/uglify-js/bin/uglifyjs ./dist/radial-progress-chart.js -m -o ./dist/radial-progress-chart.min.js", 9 | "hint": "./node_modules/jshint/bin/jshint index.js" 10 | }, 11 | "dependencies": { 12 | "d3": "^3.5.0" 13 | }, 14 | "devDependencies": { 15 | "browserify": "~10.2.4", 16 | "mocha": "^2.2.5", 17 | "uglify-js": "2.4.15", 18 | "jshint": "2.8.0" 19 | }, 20 | "main": "index.js", 21 | "filename": "radial-progress-chart.min.js", 22 | "keywords": [ 23 | "apple", 24 | "watch", 25 | "radial", 26 | "bar", 27 | "progress", 28 | "chart", 29 | "polar", 30 | "d3" 31 | ], 32 | "author": "Pablo Molnar", 33 | "license": "MIT", 34 | "readmeFilename": "README.md", 35 | "spm": { 36 | "dependencies": { 37 | "d3": "^3.5.0" 38 | }, 39 | "main": "index.js" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/3_progress.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 |
45 | 46 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note: Sorry, I've been super busy learning and doing other stuff.This was done in 2015 so you can imagine that the code got a little bit outdated by now...* 2 | 3 | 4 | # Radial Progress Chart 5 | 6 | > Radial Progress Chart started as a weekend project. It’s written on the top of D3.js and was heavily inspired by Apple Watch Activity and D3 stub of Polar Clock 7 | 8 | ## Features 9 | 10 | I tried to make it highly customizable for serve multiple purposes. Some of the features available are: 11 | 12 | - Add many progress rings as you want 13 | - Place static or dynamic text in the center 14 | - Customize diameter, stroke width, gap, colors 15 | - CSS customizable 16 | - Chart data can be update 17 | - 275 bytes minified & gzipped 18 | 19 | ## Dependencies 20 | 21 | D3.js = ^3.5.0 22 | 23 | ## Demo 24 | 25 | Check out project page [http://pablomolnar.github.io/radial-progress-chart](http://pablomolnar.github.io/radial-progress-chart) 26 | 27 | ## Installation 28 | 29 | Download the latest version from Github or use your favorite package manager to include it in your project. 30 | 31 | $ npm install radial-progress-chart 32 | 33 | $ bower install radial-progress-chart 34 | 35 | $ component install pablomolnar/radial-progress-chart 36 | 37 | $ spm install radial-progress-chart 38 | 39 | Check out project page [http://pablomolnar.github.io/radial-progress-chart](http://pablomolnar.github.io/radial-progress-chart) 40 | 41 | ## API & Usage 42 | 43 | Check out project page [http://pablomolnar.github.io/radial-progress-chart](http://pablomolnar.github.io/radial-progress-chart) 44 | 45 | ## Browser Support 46 | 47 | - Modern browsers (Chrome, Firefox, Safari, Opera, iOS, Android) 48 | - IE9+ 49 | 50 | ## Contribution 51 | Sure, pull request are welcome. Build the project locally using the following steps: 52 | 53 | $ npm install 54 | $ npm run-script build 55 | 56 | Remember to test and hint your code with: 57 | 58 | $ npm test 59 | $ npm run-script hint 60 | 61 | ##Publishing 62 | 63 | - Bump version 64 | - Tag vX.X.X 65 | - npm version patch 66 | - spm publish 67 | 68 | ## License 69 | This library is free and open source software, distributed under the MIT License. 70 | Copyright © 2015 Pablo Molnar @pmolnar; 71 | -------------------------------------------------------------------------------- /examples/2_calories.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 |
55 |
56 |
57 | 58 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /test/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /examples/1_week_activity.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 |
    95 |
    96 |
    97 |
    98 |
    99 | 100 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /dist/radial-progress-chart.min.js: -------------------------------------------------------------------------------- 1 | (function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.RadialProgressChart=e()}})(function(){var e,t,r;return function n(e,t,r){function i(a,s){if(!t[a]){if(!e[a]){var l=typeof require=="function"&&require;if(!s&&l)return l(a,!0);if(o)return o(a,!0);var d=new Error("Cannot find module '"+a+"'");throw d.code="MODULE_NOT_FOUND",d}var f=t[a]={exports:{}};e[a][0].call(f.exports,function(t){var r=e[a][1][t];return i(r?r:t)},f,f.exports,n,e,t,r)}return t[a].exports}var o=typeof require=="function"&&require;for(var a=0;a=90?(100-e.percentage)*.1:1;return o.options.stroke.width/2*t});var h=n.svg.arc().startAngle(0).endAngle(f).innerRadius(u).outerRadius(g);o.svg=n.select(t).append("svg").attr("preserveAspectRatio","xMinYMin meet").attr("viewBox",d).append("g").attr("transform","translate("+s/2+","+l/2+")");var v=o.svg.append("svg:defs");a.forEach(function(e){if(e.color.linearGradient||e.color.radialGradient){var t=i.Gradient.toSVGElement("gradient"+e.index,e.color);v.node().appendChild(t)}});v=o.svg.append("svg:defs");var y="dropshadow-"+Math.random();var x=v.append("filter").attr("id",y);if(o.options.shadow.width>0){x.append("feGaussianBlur").attr("in","SourceAlpha").attr("stdDeviation",o.options.shadow.width).attr("result","blur");x.append("feOffset").attr("in","blur").attr("dx",1).attr("dy",1).attr("result","offsetBlur")}var m=x.append("feMerge");m.append("feMergeNode").attr("in","offsetBlur");m.append("feMergeNode").attr("in","SourceGraphic");if(o.options.center){o.svg.append("text").attr("class","rbc-center-text").attr("text-anchor","middle").attr("x",o.options.center.x+"px").attr("y",o.options.center.y+"px").selectAll("tspan").data(o.options.center.content).enter().append("tspan").attr("dominant-baseline",function(){if(o.options.center.content.length===1){return"central"}}).attr("class",function(e,t){return"rbc-center-text-line"+t}).attr("x",0).attr("dy",function(e,t){if(t>0){return"1.1em"}}).each(function(e){if(typeof e==="function"){this.callback=e}}).text(function(e){if(typeof e==="string"){return e}return""})}o.field=o.svg.selectAll("g").data(a).enter().append("g");o.field.append("path").attr("class","progress").attr("filter","url(#"+y+")");o.field.append("path").attr("class","bg").style("fill",function(e){return e.color.background}).style("opacity",.2).attr("d",h);o.field.append("text").classed("rbc-label rbc-label-start",true).attr("dominant-baseline","central").attr("x","10").attr("y",function(e){return-(o.options.diameter/2+e.index*(o.options.stroke.gap+o.options.stroke.width)+o.options.stroke.width/2)}).text(function(e){return e.labelStart});o.update()}i.prototype.update=function(e){var t=this;if(e){if(typeof e==="number"){e=[e]}var r;if(Array.isArray(e)){r=e}else if(typeof e==="object"){r=e.series||[]}for(var i=0;i= 90 ? (100 - d.percentage) * 0.1 : 1; 55 | return (self.options.stroke.width / 2) * m; 56 | }); 57 | 58 | var background = d3.svg.arc() 59 | .startAngle(0) 60 | .endAngle(τ) 61 | .innerRadius(innerRadius) 62 | .outerRadius(outerRadius); 63 | 64 | // create svg 65 | self.svg = d3.select(query).append("svg") 66 | .attr("preserveAspectRatio","xMinYMin meet") 67 | .attr("viewBox", dim) 68 | .append("g") 69 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); 70 | 71 | // add gradients defs 72 | var defs = self.svg.append("svg:defs"); 73 | series.forEach(function (item) { 74 | if (item.color.linearGradient || item.color.radialGradient) { 75 | var gradient = RadialProgressChart.Gradient.toSVGElement('gradient' + item.index, item.color); 76 | defs.node().appendChild(gradient); 77 | } 78 | }); 79 | 80 | // add shadows defs 81 | defs = self.svg.append("svg:defs"); 82 | var dropshadowId = "dropshadow-" + Math.random(); 83 | var filter = defs.append("filter").attr("id", dropshadowId); 84 | if(self.options.shadow.width > 0) { 85 | 86 | filter.append("feGaussianBlur") 87 | .attr("in", "SourceAlpha") 88 | .attr("stdDeviation", self.options.shadow.width) 89 | .attr("result", "blur"); 90 | 91 | filter.append("feOffset") 92 | .attr("in", "blur") 93 | .attr("dx", 1) 94 | .attr("dy", 1) 95 | .attr("result", "offsetBlur"); 96 | } 97 | 98 | var feMerge = filter.append("feMerge"); 99 | feMerge.append("feMergeNode").attr("in", "offsetBlur"); 100 | feMerge.append("feMergeNode").attr("in", "SourceGraphic"); 101 | 102 | // add inner text 103 | if (self.options.center) { 104 | self.svg.append("text") 105 | .attr('class', 'rbc-center-text') 106 | .attr("text-anchor", "middle") 107 | .attr('x', self.options.center.x + 'px') 108 | .attr('y', self.options.center.y + 'px') 109 | .selectAll('tspan') 110 | .data(self.options.center.content).enter() 111 | .append('tspan') 112 | .attr("dominant-baseline", function () { 113 | 114 | // Single lines can easily centered in the middle using dominant-baseline, multiline need to use y 115 | if (self.options.center.content.length === 1) { 116 | return 'central'; 117 | } 118 | }) 119 | .attr('class', function (d, i) { 120 | return 'rbc-center-text-line' + i; 121 | }) 122 | .attr('x', 0) 123 | .attr('dy', function (d, i) { 124 | if (i > 0) { 125 | return '1.1em'; 126 | } 127 | }) 128 | .each(function (d) { 129 | if (typeof d === 'function') { 130 | this.callback = d; 131 | } 132 | }) 133 | .text(function (d) { 134 | if (typeof d === 'string') { 135 | return d; 136 | } 137 | 138 | return ''; 139 | }); 140 | } 141 | 142 | // add ring structure 143 | self.field = self.svg.selectAll("g") 144 | .data(series) 145 | .enter().append("g"); 146 | 147 | self.field.append("path").attr("class", "progress").attr("filter", "url(#" + dropshadowId +")"); 148 | 149 | self.field.append("path").attr("class", "bg") 150 | .style("fill", function (item) { 151 | return item.color.background; 152 | }) 153 | .style("opacity", 0.2) 154 | .attr("d", background); 155 | 156 | self.field.append("text") 157 | .classed('rbc-label rbc-label-start', true) 158 | .attr("dominant-baseline", "central") 159 | .attr("x", "10") 160 | .attr("y", function (item) { 161 | return -( 162 | self.options.diameter / 2 + 163 | item.index * (self.options.stroke.gap + self.options.stroke.width) + 164 | self.options.stroke.width / 2 165 | ); 166 | }) 167 | .text(function (item) { 168 | return item.labelStart; 169 | }); 170 | 171 | self.update(); 172 | } 173 | 174 | /** 175 | * Update data to be visualized in the chart. 176 | * 177 | * @param {Object|Array} data Optional data you'd like to set for the chart before it will update. If not specified the update method will use the data that is already configured with the chart. 178 | * @example update([70, 10, 45]) 179 | * @example update({series: [{value: 70}, 10, 45]}) 180 | * 181 | */ 182 | RadialProgressChart.prototype.update = function (data) { 183 | var self = this; 184 | 185 | // parse new data 186 | if (data) { 187 | if (typeof data === 'number') { 188 | data = [data]; 189 | } 190 | 191 | var series; 192 | 193 | if (Array.isArray(data)) { 194 | series = data; 195 | } else if (typeof data === 'object') { 196 | series = data.series || []; 197 | } 198 | 199 | for (var i = 0; i < series.length; i++) { 200 | this.options.series[i].previousValue = this.options.series[i].value; 201 | 202 | var item = series[i]; 203 | if (typeof item === 'number') { 204 | this.options.series[i].value = item; 205 | } else if (typeof item === 'object') { 206 | this.options.series[i].value = item.value; 207 | } 208 | } 209 | } 210 | 211 | // calculate from percentage and new percentage for the progress animation 212 | self.options.series.forEach(function (item) { 213 | item.fromPercentage = item.percentage ? item.percentage : 5; 214 | item.percentage = (item.value - self.options.min) * 100 / (self.options.max - self.options.min); 215 | }); 216 | 217 | var center = self.svg.select("text.rbc-center-text"); 218 | 219 | // progress 220 | self.field.select("path.progress") 221 | .interrupt() 222 | .transition() 223 | .duration(self.options.animation.duration) 224 | .delay(function (d, i) { 225 | // delay between each item 226 | return i * self.options.animation.delay; 227 | }) 228 | .ease("elastic") 229 | .attrTween("d", function (item) { 230 | var interpolator = d3.interpolateNumber(item.fromPercentage, item.percentage); 231 | return function (t) { 232 | item.percentage = interpolator(t); 233 | return self.progress(item); 234 | }; 235 | }) 236 | .tween("center", function (item) { 237 | // Execute callbacks on each line 238 | if (self.options.center) { 239 | var interpolate = self.options.round ? d3.interpolateRound : d3.interpolateNumber; 240 | var interpolator = interpolate(item.previousValue || 0, item.value); 241 | return function (t) { 242 | center 243 | .selectAll('tspan') 244 | .each(function () { 245 | if (this.callback) { 246 | d3.select(this).text(this.callback(interpolator(t), item.index, item)); 247 | } 248 | }); 249 | }; 250 | } 251 | }) 252 | .tween("interpolate-color", function (item) { 253 | if (item.color.interpolate && item.color.interpolate.length == 2) { 254 | var colorInterpolator = d3.interpolateHsl(item.color.interpolate[0], item.color.interpolate[1]); 255 | 256 | return function (t) { 257 | var color = colorInterpolator(item.percentage / 100); 258 | d3.select(this).style('fill', color); 259 | d3.select(this.parentNode).select('path.bg').style('fill', color); 260 | }; 261 | } 262 | }) 263 | .style("fill", function (item) { 264 | if (item.color.solid) { 265 | return item.color.solid; 266 | } 267 | 268 | if (item.color.linearGradient || item.color.radialGradient) { 269 | return "url(#gradient" + item.index + ')'; 270 | } 271 | }); 272 | }; 273 | 274 | /** 275 | * Remove svg and clean some references 276 | */ 277 | RadialProgressChart.prototype.destroy = function () { 278 | this.svg.remove(); 279 | delete this.svg; 280 | }; 281 | 282 | /** 283 | * Detach and normalize user's options input. 284 | */ 285 | RadialProgressChart.normalizeOptions = function (options) { 286 | if (!options || typeof options !== 'object') { 287 | options = {}; 288 | } 289 | 290 | var _options = { 291 | diameter: options.diameter || 100, 292 | stroke: { 293 | width: options.stroke && options.stroke.width || 40, 294 | gap: (!options.stroke || options.stroke.gap === undefined) ? 2 : options.stroke.gap 295 | }, 296 | shadow: { 297 | width: (!options.shadow || options.shadow.width === null) ? 4 : options.shadow.width 298 | }, 299 | animation: { 300 | duration: options.animation && options.animation.duration || 1750, 301 | delay: options.animation && options.animation.delay || 200 302 | }, 303 | min: options.min || 0, 304 | max: options.max || 100, 305 | round: options.round !== undefined ? !!options.round : true, 306 | series: options.series || [], 307 | center: RadialProgressChart.normalizeCenter(options.center) 308 | }; 309 | 310 | var defaultColorsIterator = new RadialProgressChart.ColorsIterator(); 311 | for (var i = 0, length = _options.series.length; i < length; i++) { 312 | var item = options.series[i]; 313 | 314 | // convert number to object 315 | if (typeof item === 'number') { 316 | item = {value: item}; 317 | } 318 | 319 | _options.series[i] = { 320 | index: i, 321 | value: item.value, 322 | labelStart: item.labelStart, 323 | color: RadialProgressChart.normalizeColor(item.color, defaultColorsIterator) 324 | }; 325 | } 326 | 327 | return _options; 328 | }; 329 | 330 | /** 331 | * Normalize different notations of color property 332 | * 333 | * @param {String|Array|Object} color 334 | * @example '#fe08b5' 335 | * @example { solid: '#fe08b5', background: '#000000' } 336 | * @example ['#000000', '#ff0000'] 337 | * @example { 338 | linearGradient: { x1: '0%', y1: '100%', x2: '50%', y2: '0%'}, 339 | stops: [ 340 | {offset: '0%', 'stop-color': '#fe08b5', 'stop-opacity': 1}, 341 | {offset: '100%', 'stop-color': '#ff1410', 'stop-opacity': 1} 342 | ] 343 | } 344 | * @example { 345 | radialGradient: {cx: '60', cy: '60', r: '50'}, 346 | stops: [ 347 | {offset: '0%', 'stop-color': '#fe08b5', 'stop-opacity': 1}, 348 | {offset: '100%', 'stop-color': '#ff1410', 'stop-opacity': 1} 349 | ] 350 | } 351 | * 352 | */ 353 | RadialProgressChart.normalizeColor = function (color, defaultColorsIterator) { 354 | 355 | if (!color) { 356 | color = {solid: defaultColorsIterator.next()}; 357 | } else if (typeof color === 'string') { 358 | color = {solid: color}; 359 | } else if (Array.isArray(color)) { 360 | color = {interpolate: color}; 361 | } else if (typeof color === 'object') { 362 | if (!color.solid && !color.interpolate && !color.linearGradient && !color.radialGradient) { 363 | color.solid = defaultColorsIterator.next(); 364 | } 365 | } 366 | 367 | // Validate interpolate syntax 368 | if (color.interpolate) { 369 | if (color.interpolate.length !== 2) { 370 | throw new Error('interpolate array should contain two colors'); 371 | } 372 | } 373 | 374 | // Validate gradient syntax 375 | if (color.linearGradient || color.radialGradient) { 376 | if (!color.stops || !Array.isArray(color.stops) || color.stops.length !== 2) { 377 | throw new Error('gradient syntax is malformed'); 378 | } 379 | } 380 | 381 | // Set background when is not provided 382 | if (!color.background) { 383 | if (color.solid) { 384 | color.background = color.solid; 385 | } else if (color.interpolate) { 386 | color.background = color.interpolate[0]; 387 | } else if (color.linearGradient || color.radialGradient) { 388 | color.background = color.stops[0]['stop-color']; 389 | } 390 | } 391 | 392 | return color; 393 | 394 | }; 395 | 396 | 397 | /** 398 | * Normalize different notations of center property 399 | * 400 | * @param {String|Array|Function|Object} center 401 | * @example 'foo bar' 402 | * @example { content: 'foo bar', x: 10, y: 4 } 403 | * @example function(value, index, item) {} 404 | * @example ['foo bar', function(value, index, item) {}] 405 | */ 406 | RadialProgressChart.normalizeCenter = function (center) { 407 | if (!center) return null; 408 | 409 | // Convert to object notation 410 | if (center.constructor !== Object) { 411 | center = {content: center}; 412 | } 413 | 414 | // Defaults 415 | center.content = center.content || []; 416 | center.x = center.x || 0; 417 | center.y = center.y || 0; 418 | 419 | // Convert content to array notation 420 | if (!Array.isArray(center.content)) { 421 | center.content = [center.content]; 422 | } 423 | 424 | return center; 425 | }; 426 | 427 | // Linear or Radial Gradient internal object 428 | RadialProgressChart.Gradient = (function () { 429 | function Gradient() { 430 | } 431 | 432 | Gradient.toSVGElement = function (id, options) { 433 | var gradientType = options.linearGradient ? 'linearGradient' : 'radialGradient'; 434 | var gradient = d3.select(document.createElementNS(d3.ns.prefix.svg, gradientType)) 435 | .attr(options[gradientType]) 436 | .attr('id', id); 437 | 438 | options.stops.forEach(function (stopAttrs) { 439 | gradient.append("svg:stop").attr(stopAttrs); 440 | }); 441 | 442 | this.background = options.stops[0]['stop-color']; 443 | 444 | return gradient.node(); 445 | }; 446 | 447 | return Gradient; 448 | })(); 449 | 450 | // Default colors iterator 451 | RadialProgressChart.ColorsIterator = (function () { 452 | 453 | ColorsIterator.DEFAULT_COLORS = ["#1ad5de", "#a0ff03", "#e90b3a", '#ff9500', '#007aff', '#ffcc00', '#5856d6', '#8e8e93']; 454 | 455 | function ColorsIterator() { 456 | this.index = 0; 457 | } 458 | 459 | ColorsIterator.prototype.next = function () { 460 | if (this.index === ColorsIterator.DEFAULT_COLORS.length) { 461 | this.index = 0; 462 | } 463 | 464 | return ColorsIterator.DEFAULT_COLORS[this.index++]; 465 | }; 466 | 467 | return ColorsIterator; 468 | })(); 469 | 470 | 471 | // Export RadialProgressChart object 472 | if (typeof module !== "undefined")module.exports = RadialProgressChart; -------------------------------------------------------------------------------- /dist/radial-progress-chart.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.RadialProgressChart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 90 ? (100 - d.percentage) * 0.1 : 1; 56 | return (self.options.stroke.width / 2) * m; 57 | }); 58 | 59 | var background = d3.svg.arc() 60 | .startAngle(0) 61 | .endAngle(τ) 62 | .innerRadius(innerRadius) 63 | .outerRadius(outerRadius); 64 | 65 | // create svg 66 | self.svg = d3.select(query).append("svg") 67 | .attr("preserveAspectRatio","xMinYMin meet") 68 | .attr("viewBox", dim) 69 | .append("g") 70 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); 71 | 72 | // add gradients defs 73 | var defs = self.svg.append("svg:defs"); 74 | series.forEach(function (item) { 75 | if (item.color.linearGradient || item.color.radialGradient) { 76 | var gradient = RadialProgressChart.Gradient.toSVGElement('gradient' + item.index, item.color); 77 | defs.node().appendChild(gradient); 78 | } 79 | }); 80 | 81 | // add shadows defs 82 | defs = self.svg.append("svg:defs"); 83 | var dropshadowId = "dropshadow-" + Math.random(); 84 | var filter = defs.append("filter").attr("id", dropshadowId); 85 | if(self.options.shadow.width > 0) { 86 | 87 | filter.append("feGaussianBlur") 88 | .attr("in", "SourceAlpha") 89 | .attr("stdDeviation", self.options.shadow.width) 90 | .attr("result", "blur"); 91 | 92 | filter.append("feOffset") 93 | .attr("in", "blur") 94 | .attr("dx", 1) 95 | .attr("dy", 1) 96 | .attr("result", "offsetBlur"); 97 | } 98 | 99 | var feMerge = filter.append("feMerge"); 100 | feMerge.append("feMergeNode").attr("in", "offsetBlur"); 101 | feMerge.append("feMergeNode").attr("in", "SourceGraphic"); 102 | 103 | // add inner text 104 | if (self.options.center) { 105 | self.svg.append("text") 106 | .attr('class', 'rbc-center-text') 107 | .attr("text-anchor", "middle") 108 | .attr('x', self.options.center.x + 'px') 109 | .attr('y', self.options.center.y + 'px') 110 | .selectAll('tspan') 111 | .data(self.options.center.content).enter() 112 | .append('tspan') 113 | .attr("dominant-baseline", function () { 114 | 115 | // Single lines can easily centered in the middle using dominant-baseline, multiline need to use y 116 | if (self.options.center.content.length === 1) { 117 | return 'central'; 118 | } 119 | }) 120 | .attr('class', function (d, i) { 121 | return 'rbc-center-text-line' + i; 122 | }) 123 | .attr('x', 0) 124 | .attr('dy', function (d, i) { 125 | if (i > 0) { 126 | return '1.1em'; 127 | } 128 | }) 129 | .each(function (d) { 130 | if (typeof d === 'function') { 131 | this.callback = d; 132 | } 133 | }) 134 | .text(function (d) { 135 | if (typeof d === 'string') { 136 | return d; 137 | } 138 | 139 | return ''; 140 | }); 141 | } 142 | 143 | // add ring structure 144 | self.field = self.svg.selectAll("g") 145 | .data(series) 146 | .enter().append("g"); 147 | 148 | self.field.append("path").attr("class", "progress").attr("filter", "url(#" + dropshadowId +")"); 149 | 150 | self.field.append("path").attr("class", "bg") 151 | .style("fill", function (item) { 152 | return item.color.background; 153 | }) 154 | .style("opacity", 0.2) 155 | .attr("d", background); 156 | 157 | self.field.append("text") 158 | .classed('rbc-label rbc-label-start', true) 159 | .attr("dominant-baseline", "central") 160 | .attr("x", "10") 161 | .attr("y", function (item) { 162 | return -( 163 | self.options.diameter / 2 + 164 | item.index * (self.options.stroke.gap + self.options.stroke.width) + 165 | self.options.stroke.width / 2 166 | ); 167 | }) 168 | .text(function (item) { 169 | return item.labelStart; 170 | }); 171 | 172 | self.update(); 173 | } 174 | 175 | /** 176 | * Update data to be visualized in the chart. 177 | * 178 | * @param {Object|Array} data Optional data you'd like to set for the chart before it will update. If not specified the update method will use the data that is already configured with the chart. 179 | * @example update([70, 10, 45]) 180 | * @example update({series: [{value: 70}, 10, 45]}) 181 | * 182 | */ 183 | RadialProgressChart.prototype.update = function (data) { 184 | var self = this; 185 | 186 | // parse new data 187 | if (data) { 188 | if (typeof data === 'number') { 189 | data = [data]; 190 | } 191 | 192 | var series; 193 | 194 | if (Array.isArray(data)) { 195 | series = data; 196 | } else if (typeof data === 'object') { 197 | series = data.series || []; 198 | } 199 | 200 | for (var i = 0; i < series.length; i++) { 201 | this.options.series[i].previousValue = this.options.series[i].value; 202 | 203 | var item = series[i]; 204 | if (typeof item === 'number') { 205 | this.options.series[i].value = item; 206 | } else if (typeof item === 'object') { 207 | this.options.series[i].value = item.value; 208 | } 209 | } 210 | } 211 | 212 | // calculate from percentage and new percentage for the progress animation 213 | self.options.series.forEach(function (item) { 214 | item.fromPercentage = item.percentage ? item.percentage : 5; 215 | item.percentage = (item.value - self.options.min) * 100 / (self.options.max - self.options.min); 216 | }); 217 | 218 | var center = self.svg.select("text.rbc-center-text"); 219 | 220 | // progress 221 | self.field.select("path.progress") 222 | .interrupt() 223 | .transition() 224 | .duration(self.options.animation.duration) 225 | .delay(function (d, i) { 226 | // delay between each item 227 | return i * self.options.animation.delay; 228 | }) 229 | .ease("elastic") 230 | .attrTween("d", function (item) { 231 | var interpolator = d3.interpolateNumber(item.fromPercentage, item.percentage); 232 | return function (t) { 233 | item.percentage = interpolator(t); 234 | return self.progress(item); 235 | }; 236 | }) 237 | .tween("center", function (item) { 238 | // Execute callbacks on each line 239 | if (self.options.center) { 240 | var interpolate = self.options.round ? d3.interpolateRound : d3.interpolateNumber; 241 | var interpolator = interpolate(item.previousValue || 0, item.value); 242 | return function (t) { 243 | center 244 | .selectAll('tspan') 245 | .each(function () { 246 | if (this.callback) { 247 | d3.select(this).text(this.callback(interpolator(t), item.index, item)); 248 | } 249 | }); 250 | }; 251 | } 252 | }) 253 | .tween("interpolate-color", function (item) { 254 | if (item.color.interpolate && item.color.interpolate.length == 2) { 255 | var colorInterpolator = d3.interpolateHsl(item.color.interpolate[0], item.color.interpolate[1]); 256 | 257 | return function (t) { 258 | var color = colorInterpolator(item.percentage / 100); 259 | d3.select(this).style('fill', color); 260 | d3.select(this.parentNode).select('path.bg').style('fill', color); 261 | }; 262 | } 263 | }) 264 | .style("fill", function (item) { 265 | if (item.color.solid) { 266 | return item.color.solid; 267 | } 268 | 269 | if (item.color.linearGradient || item.color.radialGradient) { 270 | return "url(#gradient" + item.index + ')'; 271 | } 272 | }); 273 | }; 274 | 275 | /** 276 | * Remove svg and clean some references 277 | */ 278 | RadialProgressChart.prototype.destroy = function () { 279 | this.svg.remove(); 280 | delete this.svg; 281 | }; 282 | 283 | /** 284 | * Detach and normalize user's options input. 285 | */ 286 | RadialProgressChart.normalizeOptions = function (options) { 287 | if (!options || typeof options !== 'object') { 288 | options = {}; 289 | } 290 | 291 | var _options = { 292 | diameter: options.diameter || 100, 293 | stroke: { 294 | width: options.stroke && options.stroke.width || 40, 295 | gap: (!options.stroke || options.stroke.gap === undefined) ? 2 : options.stroke.gap 296 | }, 297 | shadow: { 298 | width: (!options.shadow || options.shadow.width === null) ? 4 : options.shadow.width 299 | }, 300 | animation: { 301 | duration: options.animation && options.animation.duration || 1750, 302 | delay: options.animation && options.animation.delay || 200 303 | }, 304 | min: options.min || 0, 305 | max: options.max || 100, 306 | round: options.round !== undefined ? !!options.round : true, 307 | series: options.series || [], 308 | center: RadialProgressChart.normalizeCenter(options.center) 309 | }; 310 | 311 | var defaultColorsIterator = new RadialProgressChart.ColorsIterator(); 312 | for (var i = 0, length = _options.series.length; i < length; i++) { 313 | var item = options.series[i]; 314 | 315 | // convert number to object 316 | if (typeof item === 'number') { 317 | item = {value: item}; 318 | } 319 | 320 | _options.series[i] = { 321 | index: i, 322 | value: item.value, 323 | labelStart: item.labelStart, 324 | color: RadialProgressChart.normalizeColor(item.color, defaultColorsIterator) 325 | }; 326 | } 327 | 328 | return _options; 329 | }; 330 | 331 | /** 332 | * Normalize different notations of color property 333 | * 334 | * @param {String|Array|Object} color 335 | * @example '#fe08b5' 336 | * @example { solid: '#fe08b5', background: '#000000' } 337 | * @example ['#000000', '#ff0000'] 338 | * @example { 339 | linearGradient: { x1: '0%', y1: '100%', x2: '50%', y2: '0%'}, 340 | stops: [ 341 | {offset: '0%', 'stop-color': '#fe08b5', 'stop-opacity': 1}, 342 | {offset: '100%', 'stop-color': '#ff1410', 'stop-opacity': 1} 343 | ] 344 | } 345 | * @example { 346 | radialGradient: {cx: '60', cy: '60', r: '50'}, 347 | stops: [ 348 | {offset: '0%', 'stop-color': '#fe08b5', 'stop-opacity': 1}, 349 | {offset: '100%', 'stop-color': '#ff1410', 'stop-opacity': 1} 350 | ] 351 | } 352 | * 353 | */ 354 | RadialProgressChart.normalizeColor = function (color, defaultColorsIterator) { 355 | 356 | if (!color) { 357 | color = {solid: defaultColorsIterator.next()}; 358 | } else if (typeof color === 'string') { 359 | color = {solid: color}; 360 | } else if (Array.isArray(color)) { 361 | color = {interpolate: color}; 362 | } else if (typeof color === 'object') { 363 | if (!color.solid && !color.interpolate && !color.linearGradient && !color.radialGradient) { 364 | color.solid = defaultColorsIterator.next(); 365 | } 366 | } 367 | 368 | // Validate interpolate syntax 369 | if (color.interpolate) { 370 | if (color.interpolate.length !== 2) { 371 | throw new Error('interpolate array should contain two colors'); 372 | } 373 | } 374 | 375 | // Validate gradient syntax 376 | if (color.linearGradient || color.radialGradient) { 377 | if (!color.stops || !Array.isArray(color.stops) || color.stops.length !== 2) { 378 | throw new Error('gradient syntax is malformed'); 379 | } 380 | } 381 | 382 | // Set background when is not provided 383 | if (!color.background) { 384 | if (color.solid) { 385 | color.background = color.solid; 386 | } else if (color.interpolate) { 387 | color.background = color.interpolate[0]; 388 | } else if (color.linearGradient || color.radialGradient) { 389 | color.background = color.stops[0]['stop-color']; 390 | } 391 | } 392 | 393 | return color; 394 | 395 | }; 396 | 397 | 398 | /** 399 | * Normalize different notations of center property 400 | * 401 | * @param {String|Array|Function|Object} center 402 | * @example 'foo bar' 403 | * @example { content: 'foo bar', x: 10, y: 4 } 404 | * @example function(value, index, item) {} 405 | * @example ['foo bar', function(value, index, item) {}] 406 | */ 407 | RadialProgressChart.normalizeCenter = function (center) { 408 | if (!center) return null; 409 | 410 | // Convert to object notation 411 | if (center.constructor !== Object) { 412 | center = {content: center}; 413 | } 414 | 415 | // Defaults 416 | center.content = center.content || []; 417 | center.x = center.x || 0; 418 | center.y = center.y || 0; 419 | 420 | // Convert content to array notation 421 | if (!Array.isArray(center.content)) { 422 | center.content = [center.content]; 423 | } 424 | 425 | return center; 426 | }; 427 | 428 | // Linear or Radial Gradient internal object 429 | RadialProgressChart.Gradient = (function () { 430 | function Gradient() { 431 | } 432 | 433 | Gradient.toSVGElement = function (id, options) { 434 | var gradientType = options.linearGradient ? 'linearGradient' : 'radialGradient'; 435 | var gradient = d3.select(document.createElementNS(d3.ns.prefix.svg, gradientType)) 436 | .attr(options[gradientType]) 437 | .attr('id', id); 438 | 439 | options.stops.forEach(function (stopAttrs) { 440 | gradient.append("svg:stop").attr(stopAttrs); 441 | }); 442 | 443 | this.background = options.stops[0]['stop-color']; 444 | 445 | return gradient.node(); 446 | }; 447 | 448 | return Gradient; 449 | })(); 450 | 451 | // Default colors iterator 452 | RadialProgressChart.ColorsIterator = (function () { 453 | 454 | ColorsIterator.DEFAULT_COLORS = ["#1ad5de", "#a0ff03", "#e90b3a", '#ff9500', '#007aff', '#ffcc00', '#5856d6', '#8e8e93']; 455 | 456 | function ColorsIterator() { 457 | this.index = 0; 458 | } 459 | 460 | ColorsIterator.prototype.next = function () { 461 | if (this.index === ColorsIterator.DEFAULT_COLORS.length) { 462 | this.index = 0; 463 | } 464 | 465 | return ColorsIterator.DEFAULT_COLORS[this.index++]; 466 | }; 467 | 468 | return ColorsIterator; 469 | })(); 470 | 471 | 472 | // Export RadialProgressChart object 473 | if (typeof module !== "undefined")module.exports = RadialProgressChart; 474 | 475 | },{"d3":undefined}]},{},[1])(1) 476 | }); 477 | //# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJpbmRleC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiJ3VzZSBzdHJpY3QnO1xyXG5cclxudmFyIGQzO1xyXG5cclxuLy8gUmFkaWFsUHJvZ3Jlc3NDaGFydCBvYmplY3RcclxuZnVuY3Rpb24gUmFkaWFsUHJvZ3Jlc3NDaGFydChxdWVyeSwgb3B0aW9ucykge1xyXG5cclxuICAvLyB2ZXJpZnkgZDMgaXMgbG9hZGVkXHJcbiAgZDMgPSAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LmQzKSA/IHdpbmRvdy5kMyA6IHR5cGVvZiByZXF1aXJlICE9PSAndW5kZWZpbmVkJyA/IHJlcXVpcmUoXCJkM1wiKSA6IHVuZGVmaW5lZDtcclxuICBpZighZDMpIHRocm93IG5ldyBFcnJvcignZDMgb2JqZWN0IGlzIG1pc3NpbmcuIEQzLmpzIGxpYnJhcnkgaGFzIHRvIGJlIGxvYWRlZCBiZWZvcmUuJyk7XHJcblxyXG4gIHZhciBzZWxmID0gdGhpcztcclxuICBzZWxmLm9wdGlvbnMgPSBSYWRpYWxQcm9ncmVzc0NoYXJ0Lm5vcm1hbGl6ZU9wdGlvbnMob3B0aW9ucyk7XHJcblxyXG4gIC8vIGludGVybmFsICB2YXJpYWJsZXNcclxuICB2YXIgc2VyaWVzID0gc2VsZi5vcHRpb25zLnNlcmllc1xyXG4gICAgLCB3aWR0aCA9IDE1ICsgKChzZWxmLm9wdGlvbnMuZGlhbWV0ZXIgLyAyKSArIChzZWxmLm9wdGlvbnMuc3Ryb2tlLndpZHRoICogc2VsZi5vcHRpb25zLnNlcmllcy5sZW5ndGgpICsgKHNlbGYub3B0aW9ucy5zdHJva2UuZ2FwICogc2VsZi5vcHRpb25zLnNlcmllcy5sZW5ndGggLSAxKSkgKiAyXHJcbiAgICAsIGhlaWdodCA9IHdpZHRoXHJcbiAgICAsIGRpbSA9IFwiMCAwIFwiICsgaGVpZ2h0ICsgXCIgXCIgKyB3aWR0aFxyXG4gICAgLCDPhCA9IDIgKiBNYXRoLlBJXHJcbiAgICAsIGlubmVyID0gW11cclxuICAgICwgb3V0ZXIgPSBbXTtcclxuXHJcbiAgZnVuY3Rpb24gaW5uZXJSYWRpdXMoaXRlbSkge1xyXG4gICAgdmFyIHJhZGl1cyA9IGlubmVyW2l0ZW0uaW5kZXhdO1xyXG4gICAgaWYgKHJhZGl1cykgcmV0dXJuIHJhZGl1cztcclxuXHJcbiAgICAvLyBmaXJzdCByaW5nIGJhc2VkIG9uIGRpYW1ldGVyIGFuZCB0aGUgcmVzdCBiYXNlZCBvbiB0aGUgcHJldmlvdXMgb3V0ZXIgcmFkaXVzIHBsdXMgZ2FwXHJcbiAgICByYWRpdXMgPSBpdGVtLmluZGV4ID09PSAwID8gc2VsZi5vcHRpb25zLmRpYW1ldGVyIC8gMiA6IG91dGVyW2l0ZW0uaW5kZXggLSAxXSArIHNlbGYub3B0aW9ucy5zdHJva2UuZ2FwO1xyXG4gICAgaW5uZXJbaXRlbS5pbmRleF0gPSByYWRpdXM7XHJcbiAgICByZXR1cm4gcmFkaXVzO1xyXG4gIH1cclxuXHJcbiAgZnVuY3Rpb24gb3V0ZXJSYWRpdXMoaXRlbSkge1xyXG4gICAgdmFyIHJhZGl1cyA9IG91dGVyW2l0ZW0uaW5kZXhdO1xyXG4gICAgaWYgKHJhZGl1cykgcmV0dXJuIHJhZGl1cztcclxuXHJcbiAgICAvLyBiYXNlZCBvbiB0aGUgcHJldmlvdXMgaW5uZXIgcmFkaXVzICsgc3Ryb2tlIHdpZHRoXHJcbiAgICByYWRpdXMgPSBpbm5lcltpdGVtLmluZGV4XSArIHNlbGYub3B0aW9ucy5zdHJva2Uud2lkdGg7XHJcbiAgICBvdXRlcltpdGVtLmluZGV4XSA9IHJhZGl1cztcclxuICAgIHJldHVybiByYWRpdXM7XHJcbiAgfVxyXG5cclxuICBzZWxmLnByb2dyZXNzID0gZDMuc3ZnLmFyYygpXHJcbiAgICAuc3RhcnRBbmdsZSgwKVxyXG4gICAgLmVuZEFuZ2xlKGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICAgIHJldHVybiBpdGVtLnBlcmNlbnRhZ2UgLyAxMDAgKiDPhDtcclxuICAgIH0pXHJcbiAgICAuaW5uZXJSYWRpdXMoaW5uZXJSYWRpdXMpXHJcbiAgICAub3V0ZXJSYWRpdXMob3V0ZXJSYWRpdXMpXHJcbiAgICAuY29ybmVyUmFkaXVzKGZ1bmN0aW9uIChkKSB7XHJcbiAgICAgIC8vIFdvcmthcm91bmQgZm9yIGQzIGJ1ZyBodHRwczovL2dpdGh1Yi5jb20vbWJvc3RvY2svZDMvaXNzdWVzLzIyNDlcclxuICAgICAgLy8gUmVkdWNlIGNvcm5lciByYWRpdXMgd2hlbiBjb3JuZXJzIGFyZSBjbG9zZSBlYWNoIG90aGVyXHJcbiAgICAgIHZhciBtID0gZC5wZXJjZW50YWdlID49IDkwID8gKDEwMCAtIGQucGVyY2VudGFnZSkgKiAwLjEgOiAxO1xyXG4gICAgICByZXR1cm4gKHNlbGYub3B0aW9ucy5zdHJva2Uud2lkdGggLyAyKSAqIG07XHJcbiAgICB9KTtcclxuXHJcbiAgdmFyIGJhY2tncm91bmQgPSBkMy5zdmcuYXJjKClcclxuICAgIC5zdGFydEFuZ2xlKDApXHJcbiAgICAuZW5kQW5nbGUoz4QpXHJcbiAgICAuaW5uZXJSYWRpdXMoaW5uZXJSYWRpdXMpXHJcbiAgICAub3V0ZXJSYWRpdXMob3V0ZXJSYWRpdXMpO1xyXG5cclxuICAvLyBjcmVhdGUgc3ZnXHJcbiAgc2VsZi5zdmcgPSBkMy5zZWxlY3QocXVlcnkpLmFwcGVuZChcInN2Z1wiKVxyXG4gICAgLmF0dHIoXCJwcmVzZXJ2ZUFzcGVjdFJhdGlvXCIsXCJ4TWluWU1pbiBtZWV0XCIpXHJcbiAgICAuYXR0cihcInZpZXdCb3hcIiwgZGltKVxyXG4gICAgLmFwcGVuZChcImdcIilcclxuICAgIC5hdHRyKFwidHJhbnNmb3JtXCIsIFwidHJhbnNsYXRlKFwiICsgd2lkdGggLyAyICsgXCIsXCIgKyBoZWlnaHQgLyAyICsgXCIpXCIpO1xyXG5cclxuICAvLyBhZGQgZ3JhZGllbnRzIGRlZnNcclxuICB2YXIgZGVmcyA9IHNlbGYuc3ZnLmFwcGVuZChcInN2ZzpkZWZzXCIpO1xyXG4gIHNlcmllcy5mb3JFYWNoKGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICBpZiAoaXRlbS5jb2xvci5saW5lYXJHcmFkaWVudCB8fCBpdGVtLmNvbG9yLnJhZGlhbEdyYWRpZW50KSB7XHJcbiAgICAgIHZhciBncmFkaWVudCA9IFJhZGlhbFByb2dyZXNzQ2hhcnQuR3JhZGllbnQudG9TVkdFbGVtZW50KCdncmFkaWVudCcgKyBpdGVtLmluZGV4LCBpdGVtLmNvbG9yKTtcclxuICAgICAgZGVmcy5ub2RlKCkuYXBwZW5kQ2hpbGQoZ3JhZGllbnQpO1xyXG4gICAgfVxyXG4gIH0pO1xyXG5cclxuICAvLyBhZGQgc2hhZG93cyBkZWZzXHJcbiAgZGVmcyA9IHNlbGYuc3ZnLmFwcGVuZChcInN2ZzpkZWZzXCIpO1xyXG4gIHZhciBkcm9wc2hhZG93SWQgPSBcImRyb3BzaGFkb3ctXCIgKyBNYXRoLnJhbmRvbSgpO1xyXG4gIHZhciBmaWx0ZXIgPSBkZWZzLmFwcGVuZChcImZpbHRlclwiKS5hdHRyKFwiaWRcIiwgZHJvcHNoYWRvd0lkKTtcclxuICBpZihzZWxmLm9wdGlvbnMuc2hhZG93LndpZHRoID4gMCkge1xyXG5cclxuICAgIGZpbHRlci5hcHBlbmQoXCJmZUdhdXNzaWFuQmx1clwiKVxyXG4gICAgICAuYXR0cihcImluXCIsIFwiU291cmNlQWxwaGFcIilcclxuICAgICAgLmF0dHIoXCJzdGREZXZpYXRpb25cIiwgc2VsZi5vcHRpb25zLnNoYWRvdy53aWR0aClcclxuICAgICAgLmF0dHIoXCJyZXN1bHRcIiwgXCJibHVyXCIpO1xyXG5cclxuICAgIGZpbHRlci5hcHBlbmQoXCJmZU9mZnNldFwiKVxyXG4gICAgICAuYXR0cihcImluXCIsIFwiYmx1clwiKVxyXG4gICAgICAuYXR0cihcImR4XCIsIDEpXHJcbiAgICAgIC5hdHRyKFwiZHlcIiwgMSlcclxuICAgICAgLmF0dHIoXCJyZXN1bHRcIiwgXCJvZmZzZXRCbHVyXCIpO1xyXG4gIH1cclxuXHJcbiAgdmFyIGZlTWVyZ2UgPSBmaWx0ZXIuYXBwZW5kKFwiZmVNZXJnZVwiKTtcclxuICBmZU1lcmdlLmFwcGVuZChcImZlTWVyZ2VOb2RlXCIpLmF0dHIoXCJpblwiLCBcIm9mZnNldEJsdXJcIik7XHJcbiAgZmVNZXJnZS5hcHBlbmQoXCJmZU1lcmdlTm9kZVwiKS5hdHRyKFwiaW5cIiwgXCJTb3VyY2VHcmFwaGljXCIpO1xyXG5cclxuICAvLyBhZGQgaW5uZXIgdGV4dFxyXG4gIGlmIChzZWxmLm9wdGlvbnMuY2VudGVyKSB7XHJcbiAgICBzZWxmLnN2Zy5hcHBlbmQoXCJ0ZXh0XCIpXHJcbiAgICAgIC5hdHRyKCdjbGFzcycsICdyYmMtY2VudGVyLXRleHQnKVxyXG4gICAgICAuYXR0cihcInRleHQtYW5jaG9yXCIsIFwibWlkZGxlXCIpXHJcbiAgICAgIC5hdHRyKCd4Jywgc2VsZi5vcHRpb25zLmNlbnRlci54ICsgJ3B4JylcclxuICAgICAgLmF0dHIoJ3knLCBzZWxmLm9wdGlvbnMuY2VudGVyLnkgKyAncHgnKVxyXG4gICAgICAuc2VsZWN0QWxsKCd0c3BhbicpXHJcbiAgICAgIC5kYXRhKHNlbGYub3B0aW9ucy5jZW50ZXIuY29udGVudCkuZW50ZXIoKVxyXG4gICAgICAuYXBwZW5kKCd0c3BhbicpXHJcbiAgICAgIC5hdHRyKFwiZG9taW5hbnQtYmFzZWxpbmVcIiwgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAvLyBTaW5nbGUgbGluZXMgY2FuIGVhc2lseSBjZW50ZXJlZCBpbiB0aGUgbWlkZGxlIHVzaW5nIGRvbWluYW50LWJhc2VsaW5lLCBtdWx0aWxpbmUgbmVlZCB0byB1c2UgeVxyXG4gICAgICAgIGlmIChzZWxmLm9wdGlvbnMuY2VudGVyLmNvbnRlbnQubGVuZ3RoID09PSAxKSB7XHJcbiAgICAgICAgICByZXR1cm4gJ2NlbnRyYWwnO1xyXG4gICAgICAgIH1cclxuICAgICAgfSlcclxuICAgICAgLmF0dHIoJ2NsYXNzJywgZnVuY3Rpb24gKGQsIGkpIHtcclxuICAgICAgICByZXR1cm4gJ3JiYy1jZW50ZXItdGV4dC1saW5lJyArIGk7XHJcbiAgICAgIH0pXHJcbiAgICAgIC5hdHRyKCd4JywgMClcclxuICAgICAgLmF0dHIoJ2R5JywgZnVuY3Rpb24gKGQsIGkpIHtcclxuICAgICAgICBpZiAoaSA+IDApIHtcclxuICAgICAgICAgIHJldHVybiAnMS4xZW0nO1xyXG4gICAgICAgIH1cclxuICAgICAgfSlcclxuICAgICAgLmVhY2goZnVuY3Rpb24gKGQpIHtcclxuICAgICAgICBpZiAodHlwZW9mIGQgPT09ICdmdW5jdGlvbicpIHtcclxuICAgICAgICAgIHRoaXMuY2FsbGJhY2sgPSBkO1xyXG4gICAgICAgIH1cclxuICAgICAgfSlcclxuICAgICAgLnRleHQoZnVuY3Rpb24gKGQpIHtcclxuICAgICAgICBpZiAodHlwZW9mIGQgPT09ICdzdHJpbmcnKSB7XHJcbiAgICAgICAgICByZXR1cm4gZDtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIHJldHVybiAnJztcclxuICAgICAgfSk7XHJcbiAgfVxyXG5cclxuICAvLyBhZGQgcmluZyBzdHJ1Y3R1cmVcclxuICBzZWxmLmZpZWxkID0gc2VsZi5zdmcuc2VsZWN0QWxsKFwiZ1wiKVxyXG4gICAgLmRhdGEoc2VyaWVzKVxyXG4gICAgLmVudGVyKCkuYXBwZW5kKFwiZ1wiKTtcclxuXHJcbiAgc2VsZi5maWVsZC5hcHBlbmQoXCJwYXRoXCIpLmF0dHIoXCJjbGFzc1wiLCBcInByb2dyZXNzXCIpLmF0dHIoXCJmaWx0ZXJcIiwgXCJ1cmwoI1wiICsgZHJvcHNoYWRvd0lkICtcIilcIik7XHJcblxyXG4gIHNlbGYuZmllbGQuYXBwZW5kKFwicGF0aFwiKS5hdHRyKFwiY2xhc3NcIiwgXCJiZ1wiKVxyXG4gICAgLnN0eWxlKFwiZmlsbFwiLCBmdW5jdGlvbiAoaXRlbSkge1xyXG4gICAgICByZXR1cm4gaXRlbS5jb2xvci5iYWNrZ3JvdW5kO1xyXG4gICAgfSlcclxuICAgIC5zdHlsZShcIm9wYWNpdHlcIiwgMC4yKVxyXG4gICAgLmF0dHIoXCJkXCIsIGJhY2tncm91bmQpO1xyXG5cclxuICBzZWxmLmZpZWxkLmFwcGVuZChcInRleHRcIilcclxuICAgIC5jbGFzc2VkKCdyYmMtbGFiZWwgcmJjLWxhYmVsLXN0YXJ0JywgdHJ1ZSlcclxuICAgIC5hdHRyKFwiZG9taW5hbnQtYmFzZWxpbmVcIiwgXCJjZW50cmFsXCIpXHJcbiAgICAuYXR0cihcInhcIiwgXCIxMFwiKVxyXG4gICAgLmF0dHIoXCJ5XCIsIGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICAgIHJldHVybiAtKFxyXG4gICAgICAgIHNlbGYub3B0aW9ucy5kaWFtZXRlciAvIDIgK1xyXG4gICAgICAgIGl0ZW0uaW5kZXggKiAoc2VsZi5vcHRpb25zLnN0cm9rZS5nYXAgKyBzZWxmLm9wdGlvbnMuc3Ryb2tlLndpZHRoKSArXHJcbiAgICAgICAgc2VsZi5vcHRpb25zLnN0cm9rZS53aWR0aCAvIDJcclxuICAgICAgICApO1xyXG4gICAgfSlcclxuICAgIC50ZXh0KGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICAgIHJldHVybiBpdGVtLmxhYmVsU3RhcnQ7XHJcbiAgICB9KTtcclxuXHJcbiAgc2VsZi51cGRhdGUoKTtcclxufVxyXG5cclxuLyoqXHJcbiAqIFVwZGF0ZSBkYXRhIHRvIGJlIHZpc3VhbGl6ZWQgaW4gdGhlIGNoYXJ0LlxyXG4gKlxyXG4gKiBAcGFyYW0ge09iamVjdHxBcnJheX0gZGF0YSBPcHRpb25hbCBkYXRhIHlvdSdkIGxpa2UgdG8gc2V0IGZvciB0aGUgY2hhcnQgYmVmb3JlIGl0IHdpbGwgdXBkYXRlLiBJZiBub3Qgc3BlY2lmaWVkIHRoZSB1cGRhdGUgbWV0aG9kIHdpbGwgdXNlIHRoZSBkYXRhIHRoYXQgaXMgYWxyZWFkeSBjb25maWd1cmVkIHdpdGggdGhlIGNoYXJ0LlxyXG4gKiBAZXhhbXBsZSB1cGRhdGUoWzcwLCAxMCwgNDVdKVxyXG4gKiBAZXhhbXBsZSB1cGRhdGUoe3NlcmllczogW3t2YWx1ZTogNzB9LCAxMCwgNDVdfSlcclxuICpcclxuICovXHJcblJhZGlhbFByb2dyZXNzQ2hhcnQucHJvdG90eXBlLnVwZGF0ZSA9IGZ1bmN0aW9uIChkYXRhKSB7XHJcbiAgdmFyIHNlbGYgPSB0aGlzO1xyXG5cclxuICAvLyBwYXJzZSBuZXcgZGF0YVxyXG4gIGlmIChkYXRhKSB7XHJcbiAgICBpZiAodHlwZW9mIGRhdGEgPT09ICdudW1iZXInKSB7XHJcbiAgICAgIGRhdGEgPSBbZGF0YV07XHJcbiAgICB9XHJcblxyXG4gICAgdmFyIHNlcmllcztcclxuXHJcbiAgICBpZiAoQXJyYXkuaXNBcnJheShkYXRhKSkge1xyXG4gICAgICBzZXJpZXMgPSBkYXRhO1xyXG4gICAgfSBlbHNlIGlmICh0eXBlb2YgZGF0YSA9PT0gJ29iamVjdCcpIHtcclxuICAgICAgc2VyaWVzID0gZGF0YS5zZXJpZXMgfHwgW107XHJcbiAgICB9XHJcblxyXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzZXJpZXMubGVuZ3RoOyBpKyspIHtcclxuICAgICAgdGhpcy5vcHRpb25zLnNlcmllc1tpXS5wcmV2aW91c1ZhbHVlID0gdGhpcy5vcHRpb25zLnNlcmllc1tpXS52YWx1ZTtcclxuXHJcbiAgICAgIHZhciBpdGVtID0gc2VyaWVzW2ldO1xyXG4gICAgICBpZiAodHlwZW9mIGl0ZW0gPT09ICdudW1iZXInKSB7XHJcbiAgICAgICAgdGhpcy5vcHRpb25zLnNlcmllc1tpXS52YWx1ZSA9IGl0ZW07XHJcbiAgICAgIH0gZWxzZSBpZiAodHlwZW9mIGl0ZW0gPT09ICdvYmplY3QnKSB7XHJcbiAgICAgICAgdGhpcy5vcHRpb25zLnNlcmllc1tpXS52YWx1ZSA9IGl0ZW0udmFsdWU7XHJcbiAgICAgIH1cclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8vIGNhbGN1bGF0ZSBmcm9tIHBlcmNlbnRhZ2UgYW5kIG5ldyBwZXJjZW50YWdlIGZvciB0aGUgcHJvZ3Jlc3MgYW5pbWF0aW9uXHJcbiAgc2VsZi5vcHRpb25zLnNlcmllcy5mb3JFYWNoKGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICBpdGVtLmZyb21QZXJjZW50YWdlID0gaXRlbS5wZXJjZW50YWdlID8gaXRlbS5wZXJjZW50YWdlIDogNTtcclxuICAgIGl0ZW0ucGVyY2VudGFnZSA9IChpdGVtLnZhbHVlIC0gc2VsZi5vcHRpb25zLm1pbikgKiAxMDAgLyAoc2VsZi5vcHRpb25zLm1heCAtIHNlbGYub3B0aW9ucy5taW4pO1xyXG4gIH0pO1xyXG5cclxuICB2YXIgY2VudGVyID0gc2VsZi5zdmcuc2VsZWN0KFwidGV4dC5yYmMtY2VudGVyLXRleHRcIik7XHJcblxyXG4gIC8vIHByb2dyZXNzXHJcbiAgc2VsZi5maWVsZC5zZWxlY3QoXCJwYXRoLnByb2dyZXNzXCIpXHJcbiAgICAuaW50ZXJydXB0KClcclxuICAgIC50cmFuc2l0aW9uKClcclxuICAgIC5kdXJhdGlvbihzZWxmLm9wdGlvbnMuYW5pbWF0aW9uLmR1cmF0aW9uKVxyXG4gICAgLmRlbGF5KGZ1bmN0aW9uIChkLCBpKSB7XHJcbiAgICAgIC8vIGRlbGF5IGJldHdlZW4gZWFjaCBpdGVtXHJcbiAgICAgIHJldHVybiBpICogc2VsZi5vcHRpb25zLmFuaW1hdGlvbi5kZWxheTtcclxuICAgIH0pXHJcbiAgICAuZWFzZShcImVsYXN0aWNcIilcclxuICAgIC5hdHRyVHdlZW4oXCJkXCIsIGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICAgIHZhciBpbnRlcnBvbGF0b3IgPSBkMy5pbnRlcnBvbGF0ZU51bWJlcihpdGVtLmZyb21QZXJjZW50YWdlLCBpdGVtLnBlcmNlbnRhZ2UpO1xyXG4gICAgICByZXR1cm4gZnVuY3Rpb24gKHQpIHtcclxuICAgICAgICBpdGVtLnBlcmNlbnRhZ2UgPSBpbnRlcnBvbGF0b3IodCk7XHJcbiAgICAgICAgcmV0dXJuIHNlbGYucHJvZ3Jlc3MoaXRlbSk7XHJcbiAgICAgIH07XHJcbiAgICB9KVxyXG4gICAgLnR3ZWVuKFwiY2VudGVyXCIsIGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICAgIC8vIEV4ZWN1dGUgY2FsbGJhY2tzIG9uIGVhY2ggbGluZVxyXG4gICAgICBpZiAoc2VsZi5vcHRpb25zLmNlbnRlcikge1xyXG4gICAgICAgIHZhciBpbnRlcnBvbGF0ZSA9IHNlbGYub3B0aW9ucy5yb3VuZCA/IGQzLmludGVycG9sYXRlUm91bmQgOiBkMy5pbnRlcnBvbGF0ZU51bWJlcjtcclxuICAgICAgICB2YXIgaW50ZXJwb2xhdG9yID0gaW50ZXJwb2xhdGUoaXRlbS5wcmV2aW91c1ZhbHVlIHx8IDAsIGl0ZW0udmFsdWUpO1xyXG4gICAgICAgIHJldHVybiBmdW5jdGlvbiAodCkge1xyXG4gICAgICAgICAgY2VudGVyXHJcbiAgICAgICAgICAgIC5zZWxlY3RBbGwoJ3RzcGFuJylcclxuICAgICAgICAgICAgLmVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgIGlmICh0aGlzLmNhbGxiYWNrKSB7XHJcbiAgICAgICAgICAgICAgICBkMy5zZWxlY3QodGhpcykudGV4dCh0aGlzLmNhbGxiYWNrKGludGVycG9sYXRvcih0KSwgaXRlbS5pbmRleCwgaXRlbSkpO1xyXG4gICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfTtcclxuICAgICAgfVxyXG4gICAgfSlcclxuICAgIC50d2VlbihcImludGVycG9sYXRlLWNvbG9yXCIsIGZ1bmN0aW9uIChpdGVtKSB7XHJcbiAgICAgIGlmIChpdGVtLmNvbG9yLmludGVycG9sYXRlICYmIGl0ZW0uY29sb3IuaW50ZXJwb2xhdGUubGVuZ3RoID09IDIpIHtcclxuICAgICAgICB2YXIgY29sb3JJbnRlcnBvbGF0b3IgPSBkMy5pbnRlcnBvbGF0ZUhzbChpdGVtLmNvbG9yLmludGVycG9sYXRlWzBdLCBpdGVtLmNvbG9yLmludGVycG9sYXRlWzFdKTtcclxuXHJcbiAgICAgICAgcmV0dXJuIGZ1bmN0aW9uICh0KSB7XHJcbiAgICAgICAgICB2YXIgY29sb3IgPSBjb2xvckludGVycG9sYXRvcihpdGVtLnBlcmNlbnRhZ2UgLyAxMDApO1xyXG4gICAgICAgICAgZDMuc2VsZWN0KHRoaXMpLnN0eWxlKCdmaWxsJywgY29sb3IpO1xyXG4gICAgICAgICAgZDMuc2VsZWN0KHRoaXMucGFyZW50Tm9kZSkuc2VsZWN0KCdwYXRoLmJnJykuc3R5bGUoJ2ZpbGwnLCBjb2xvcik7XHJcbiAgICAgICAgfTtcclxuICAgICAgfVxyXG4gICAgfSlcclxuICAgIC5zdHlsZShcImZpbGxcIiwgZnVuY3Rpb24gKGl0ZW0pIHtcclxuICAgICAgaWYgKGl0ZW0uY29sb3Iuc29saWQpIHtcclxuICAgICAgICByZXR1cm4gaXRlbS5jb2xvci5zb2xpZDtcclxuICAgICAgfVxyXG5cclxuICAgICAgaWYgKGl0ZW0uY29sb3IubGluZWFyR3JhZGllbnQgfHwgaXRlbS5jb2xvci5yYWRpYWxHcmFkaWVudCkge1xyXG4gICAgICAgIHJldHVybiBcInVybCgjZ3JhZGllbnRcIiArIGl0ZW0uaW5kZXggKyAnKSc7XHJcbiAgICAgIH1cclxuICAgIH0pO1xyXG59O1xyXG5cclxuLyoqXHJcbiAqIFJlbW92ZSBzdmcgYW5kIGNsZWFuIHNvbWUgcmVmZXJlbmNlc1xyXG4gKi9cclxuUmFkaWFsUHJvZ3Jlc3NDaGFydC5wcm90b3R5cGUuZGVzdHJveSA9IGZ1bmN0aW9uICgpIHtcclxuICB0aGlzLnN2Zy5yZW1vdmUoKTtcclxuICBkZWxldGUgdGhpcy5zdmc7XHJcbn07XHJcblxyXG4vKipcclxuICogRGV0YWNoIGFuZCBub3JtYWxpemUgdXNlcidzIG9wdGlvbnMgaW5wdXQuXHJcbiAqL1xyXG5SYWRpYWxQcm9ncmVzc0NoYXJ0Lm5vcm1hbGl6ZU9wdGlvbnMgPSBmdW5jdGlvbiAob3B0aW9ucykge1xyXG4gIGlmICghb3B0aW9ucyB8fCB0eXBlb2Ygb3B0aW9ucyAhPT0gJ29iamVjdCcpIHtcclxuICAgIG9wdGlvbnMgPSB7fTtcclxuICB9XHJcblxyXG4gIHZhciBfb3B0aW9ucyA9IHtcclxuICAgIGRpYW1ldGVyOiBvcHRpb25zLmRpYW1ldGVyIHx8IDEwMCxcclxuICAgIHN0cm9rZToge1xyXG4gICAgICB3aWR0aDogb3B0aW9ucy5zdHJva2UgJiYgb3B0aW9ucy5zdHJva2Uud2lkdGggfHwgNDAsXHJcbiAgICAgIGdhcDogKCFvcHRpb25zLnN0cm9rZSB8fCBvcHRpb25zLnN0cm9rZS5nYXAgPT09IHVuZGVmaW5lZCkgPyAyIDogb3B0aW9ucy5zdHJva2UuZ2FwXHJcbiAgICB9LFxyXG4gICAgc2hhZG93OiB7XHJcbiAgICAgIHdpZHRoOiAoIW9wdGlvbnMuc2hhZG93IHx8IG9wdGlvbnMuc2hhZG93LndpZHRoID09PSBudWxsKSA/IDQgOiBvcHRpb25zLnNoYWRvdy53aWR0aFxyXG4gICAgfSxcclxuICAgIGFuaW1hdGlvbjoge1xyXG4gICAgICBkdXJhdGlvbjogb3B0aW9ucy5hbmltYXRpb24gJiYgb3B0aW9ucy5hbmltYXRpb24uZHVyYXRpb24gfHwgMTc1MCxcclxuICAgICAgZGVsYXk6IG9wdGlvbnMuYW5pbWF0aW9uICYmIG9wdGlvbnMuYW5pbWF0aW9uLmRlbGF5IHx8IDIwMFxyXG4gICAgfSxcclxuICAgIG1pbjogb3B0aW9ucy5taW4gfHwgMCxcclxuICAgIG1heDogb3B0aW9ucy5tYXggfHwgMTAwLFxyXG4gICAgcm91bmQ6IG9wdGlvbnMucm91bmQgIT09IHVuZGVmaW5lZCA/ICEhb3B0aW9ucy5yb3VuZCA6IHRydWUsXHJcbiAgICBzZXJpZXM6IG9wdGlvbnMuc2VyaWVzIHx8IFtdLFxyXG4gICAgY2VudGVyOiBSYWRpYWxQcm9ncmVzc0NoYXJ0Lm5vcm1hbGl6ZUNlbnRlcihvcHRpb25zLmNlbnRlcilcclxuICB9O1xyXG5cclxuICB2YXIgZGVmYXVsdENvbG9yc0l0ZXJhdG9yID0gbmV3IFJhZGlhbFByb2dyZXNzQ2hhcnQuQ29sb3JzSXRlcmF0b3IoKTtcclxuICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gX29wdGlvbnMuc2VyaWVzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XHJcbiAgICB2YXIgaXRlbSA9IG9wdGlvbnMuc2VyaWVzW2ldO1xyXG5cclxuICAgIC8vIGNvbnZlcnQgbnVtYmVyIHRvIG9iamVjdFxyXG4gICAgaWYgKHR5cGVvZiBpdGVtID09PSAnbnVtYmVyJykge1xyXG4gICAgICBpdGVtID0ge3ZhbHVlOiBpdGVtfTtcclxuICAgIH1cclxuXHJcbiAgICBfb3B0aW9ucy5zZXJpZXNbaV0gPSB7XHJcbiAgICAgIGluZGV4OiBpLFxyXG4gICAgICB2YWx1ZTogaXRlbS52YWx1ZSxcclxuICAgICAgbGFiZWxTdGFydDogaXRlbS5sYWJlbFN0YXJ0LFxyXG4gICAgICBjb2xvcjogUmFkaWFsUHJvZ3Jlc3NDaGFydC5ub3JtYWxpemVDb2xvcihpdGVtLmNvbG9yLCBkZWZhdWx0Q29sb3JzSXRlcmF0b3IpXHJcbiAgICB9O1xyXG4gIH1cclxuXHJcbiAgcmV0dXJuIF9vcHRpb25zO1xyXG59O1xyXG5cclxuLyoqXHJcbiAqIE5vcm1hbGl6ZSBkaWZmZXJlbnQgbm90YXRpb25zIG9mIGNvbG9yIHByb3BlcnR5XHJcbiAqXHJcbiAqIEBwYXJhbSB7U3RyaW5nfEFycmF5fE9iamVjdH0gY29sb3JcclxuICogQGV4YW1wbGUgJyNmZTA4YjUnXHJcbiAqIEBleGFtcGxlIHsgc29saWQ6ICcjZmUwOGI1JywgYmFja2dyb3VuZDogJyMwMDAwMDAnIH1cclxuICogQGV4YW1wbGUgWycjMDAwMDAwJywgJyNmZjAwMDAnXVxyXG4gKiBAZXhhbXBsZSB7XHJcbiAgICAgICAgICAgICAgICBsaW5lYXJHcmFkaWVudDogeyB4MTogJzAlJywgeTE6ICcxMDAlJywgeDI6ICc1MCUnLCB5MjogJzAlJ30sXHJcbiAgICAgICAgICAgICAgICBzdG9wczogW1xyXG4gICAgICAgICAgICAgICAgICB7b2Zmc2V0OiAnMCUnLCAnc3RvcC1jb2xvcic6ICcjZmUwOGI1JywgJ3N0b3Atb3BhY2l0eSc6IDF9LFxyXG4gICAgICAgICAgICAgICAgICB7b2Zmc2V0OiAnMTAwJScsICdzdG9wLWNvbG9yJzogJyNmZjE0MTAnLCAnc3RvcC1vcGFjaXR5JzogMX1cclxuICAgICAgICAgICAgICAgIF1cclxuICAgICAgICAgICAgICB9XHJcbiAqIEBleGFtcGxlIHtcclxuICAgICAgICAgICAgICAgIHJhZGlhbEdyYWRpZW50OiB7Y3g6ICc2MCcsIGN5OiAnNjAnLCByOiAnNTAnfSxcclxuICAgICAgICAgICAgICAgIHN0b3BzOiBbXHJcbiAgICAgICAgICAgICAgICAgIHtvZmZzZXQ6ICcwJScsICdzdG9wLWNvbG9yJzogJyNmZTA4YjUnLCAnc3RvcC1vcGFjaXR5JzogMX0sXHJcbiAgICAgICAgICAgICAgICAgIHtvZmZzZXQ6ICcxMDAlJywgJ3N0b3AtY29sb3InOiAnI2ZmMTQxMCcsICdzdG9wLW9wYWNpdHknOiAxfVxyXG4gICAgICAgICAgICAgICAgXVxyXG4gICAgICAgICAgICAgIH1cclxuICpcclxuICovXHJcblJhZGlhbFByb2dyZXNzQ2hhcnQubm9ybWFsaXplQ29sb3IgPSBmdW5jdGlvbiAoY29sb3IsIGRlZmF1bHRDb2xvcnNJdGVyYXRvcikge1xyXG5cclxuICBpZiAoIWNvbG9yKSB7XHJcbiAgICBjb2xvciA9IHtzb2xpZDogZGVmYXVsdENvbG9yc0l0ZXJhdG9yLm5leHQoKX07XHJcbiAgfSBlbHNlIGlmICh0eXBlb2YgY29sb3IgPT09ICdzdHJpbmcnKSB7XHJcbiAgICBjb2xvciA9IHtzb2xpZDogY29sb3J9O1xyXG4gIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShjb2xvcikpIHtcclxuICAgIGNvbG9yID0ge2ludGVycG9sYXRlOiBjb2xvcn07XHJcbiAgfSBlbHNlIGlmICh0eXBlb2YgY29sb3IgPT09ICdvYmplY3QnKSB7XHJcbiAgICBpZiAoIWNvbG9yLnNvbGlkICYmICFjb2xvci5pbnRlcnBvbGF0ZSAmJiAhY29sb3IubGluZWFyR3JhZGllbnQgJiYgIWNvbG9yLnJhZGlhbEdyYWRpZW50KSB7XHJcbiAgICAgIGNvbG9yLnNvbGlkID0gZGVmYXVsdENvbG9yc0l0ZXJhdG9yLm5leHQoKTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8vIFZhbGlkYXRlIGludGVycG9sYXRlIHN5bnRheFxyXG4gIGlmIChjb2xvci5pbnRlcnBvbGF0ZSkge1xyXG4gICAgaWYgKGNvbG9yLmludGVycG9sYXRlLmxlbmd0aCAhPT0gMikge1xyXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2ludGVycG9sYXRlIGFycmF5IHNob3VsZCBjb250YWluIHR3byBjb2xvcnMnKTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8vIFZhbGlkYXRlIGdyYWRpZW50IHN5bnRheFxyXG4gIGlmIChjb2xvci5saW5lYXJHcmFkaWVudCB8fCBjb2xvci5yYWRpYWxHcmFkaWVudCkge1xyXG4gICAgaWYgKCFjb2xvci5zdG9wcyB8fCAhQXJyYXkuaXNBcnJheShjb2xvci5zdG9wcykgfHwgY29sb3Iuc3RvcHMubGVuZ3RoICE9PSAyKSB7XHJcbiAgICAgIHRocm93IG5ldyBFcnJvcignZ3JhZGllbnQgc3ludGF4IGlzIG1hbGZvcm1lZCcpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgLy8gU2V0IGJhY2tncm91bmQgd2hlbiBpcyBub3QgcHJvdmlkZWRcclxuICBpZiAoIWNvbG9yLmJhY2tncm91bmQpIHtcclxuICAgIGlmIChjb2xvci5zb2xpZCkge1xyXG4gICAgICBjb2xvci5iYWNrZ3JvdW5kID0gY29sb3Iuc29saWQ7XHJcbiAgICB9IGVsc2UgaWYgKGNvbG9yLmludGVycG9sYXRlKSB7XHJcbiAgICAgIGNvbG9yLmJhY2tncm91bmQgPSBjb2xvci5pbnRlcnBvbGF0ZVswXTtcclxuICAgIH0gZWxzZSBpZiAoY29sb3IubGluZWFyR3JhZGllbnQgfHwgY29sb3IucmFkaWFsR3JhZGllbnQpIHtcclxuICAgICAgY29sb3IuYmFja2dyb3VuZCA9IGNvbG9yLnN0b3BzWzBdWydzdG9wLWNvbG9yJ107XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICByZXR1cm4gY29sb3I7XHJcblxyXG59O1xyXG5cclxuXHJcbi8qKlxyXG4gKiBOb3JtYWxpemUgZGlmZmVyZW50IG5vdGF0aW9ucyBvZiBjZW50ZXIgcHJvcGVydHlcclxuICpcclxuICogQHBhcmFtIHtTdHJpbmd8QXJyYXl8RnVuY3Rpb258T2JqZWN0fSBjZW50ZXJcclxuICogQGV4YW1wbGUgJ2ZvbyBiYXInXHJcbiAqIEBleGFtcGxlIHsgY29udGVudDogJ2ZvbyBiYXInLCB4OiAxMCwgeTogNCB9XHJcbiAqIEBleGFtcGxlIGZ1bmN0aW9uKHZhbHVlLCBpbmRleCwgaXRlbSkge31cclxuICogQGV4YW1wbGUgWydmb28gYmFyJywgZnVuY3Rpb24odmFsdWUsIGluZGV4LCBpdGVtKSB7fV1cclxuICovXHJcblJhZGlhbFByb2dyZXNzQ2hhcnQubm9ybWFsaXplQ2VudGVyID0gZnVuY3Rpb24gKGNlbnRlcikge1xyXG4gIGlmICghY2VudGVyKSByZXR1cm4gbnVsbDtcclxuXHJcbiAgLy8gQ29udmVydCB0byBvYmplY3Qgbm90YXRpb25cclxuICBpZiAoY2VudGVyLmNvbnN0cnVjdG9yICE9PSBPYmplY3QpIHtcclxuICAgIGNlbnRlciA9IHtjb250ZW50OiBjZW50ZXJ9O1xyXG4gIH1cclxuXHJcbiAgLy8gRGVmYXVsdHNcclxuICBjZW50ZXIuY29udGVudCA9IGNlbnRlci5jb250ZW50IHx8IFtdO1xyXG4gIGNlbnRlci54ID0gY2VudGVyLnggfHwgMDtcclxuICBjZW50ZXIueSA9IGNlbnRlci55IHx8IDA7XHJcblxyXG4gIC8vIENvbnZlcnQgY29udGVudCB0byBhcnJheSBub3RhdGlvblxyXG4gIGlmICghQXJyYXkuaXNBcnJheShjZW50ZXIuY29udGVudCkpIHtcclxuICAgIGNlbnRlci5jb250ZW50ID0gW2NlbnRlci5jb250ZW50XTtcclxuICB9XHJcblxyXG4gIHJldHVybiBjZW50ZXI7XHJcbn07XHJcblxyXG4vLyBMaW5lYXIgb3IgUmFkaWFsIEdyYWRpZW50IGludGVybmFsIG9iamVjdFxyXG5SYWRpYWxQcm9ncmVzc0NoYXJ0LkdyYWRpZW50ID0gKGZ1bmN0aW9uICgpIHtcclxuICBmdW5jdGlvbiBHcmFkaWVudCgpIHtcclxuICB9XHJcblxyXG4gIEdyYWRpZW50LnRvU1ZHRWxlbWVudCA9IGZ1bmN0aW9uIChpZCwgb3B0aW9ucykge1xyXG4gICAgdmFyIGdyYWRpZW50VHlwZSA9IG9wdGlvbnMubGluZWFyR3JhZGllbnQgPyAnbGluZWFyR3JhZGllbnQnIDogJ3JhZGlhbEdyYWRpZW50JztcclxuICAgIHZhciBncmFkaWVudCA9IGQzLnNlbGVjdChkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMoZDMubnMucHJlZml4LnN2ZywgZ3JhZGllbnRUeXBlKSlcclxuICAgICAgLmF0dHIob3B0aW9uc1tncmFkaWVudFR5cGVdKVxyXG4gICAgICAuYXR0cignaWQnLCBpZCk7XHJcblxyXG4gICAgb3B0aW9ucy5zdG9wcy5mb3JFYWNoKGZ1bmN0aW9uIChzdG9wQXR0cnMpIHtcclxuICAgICAgZ3JhZGllbnQuYXBwZW5kKFwic3ZnOnN0b3BcIikuYXR0cihzdG9wQXR0cnMpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgdGhpcy5iYWNrZ3JvdW5kID0gb3B0aW9ucy5zdG9wc1swXVsnc3RvcC1jb2xvciddO1xyXG5cclxuICAgIHJldHVybiBncmFkaWVudC5ub2RlKCk7XHJcbiAgfTtcclxuXHJcbiAgcmV0dXJuIEdyYWRpZW50O1xyXG59KSgpO1xyXG5cclxuLy8gRGVmYXVsdCBjb2xvcnMgaXRlcmF0b3JcclxuUmFkaWFsUHJvZ3Jlc3NDaGFydC5Db2xvcnNJdGVyYXRvciA9IChmdW5jdGlvbiAoKSB7XHJcblxyXG4gIENvbG9yc0l0ZXJhdG9yLkRFRkFVTFRfQ09MT1JTID0gW1wiIzFhZDVkZVwiLCBcIiNhMGZmMDNcIiwgXCIjZTkwYjNhXCIsICcjZmY5NTAwJywgJyMwMDdhZmYnLCAnI2ZmY2MwMCcsICcjNTg1NmQ2JywgJyM4ZThlOTMnXTtcclxuXHJcbiAgZnVuY3Rpb24gQ29sb3JzSXRlcmF0b3IoKSB7XHJcbiAgICB0aGlzLmluZGV4ID0gMDtcclxuICB9XHJcblxyXG4gIENvbG9yc0l0ZXJhdG9yLnByb3RvdHlwZS5uZXh0ID0gZnVuY3Rpb24gKCkge1xyXG4gICAgaWYgKHRoaXMuaW5kZXggPT09IENvbG9yc0l0ZXJhdG9yLkRFRkFVTFRfQ09MT1JTLmxlbmd0aCkge1xyXG4gICAgICB0aGlzLmluZGV4ID0gMDtcclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4gQ29sb3JzSXRlcmF0b3IuREVGQVVMVF9DT0xPUlNbdGhpcy5pbmRleCsrXTtcclxuICB9O1xyXG5cclxuICByZXR1cm4gQ29sb3JzSXRlcmF0b3I7XHJcbn0pKCk7XHJcblxyXG5cclxuLy8gRXhwb3J0IFJhZGlhbFByb2dyZXNzQ2hhcnQgb2JqZWN0XHJcbmlmICh0eXBlb2YgbW9kdWxlICE9PSBcInVuZGVmaW5lZFwiKW1vZHVsZS5leHBvcnRzID0gUmFkaWFsUHJvZ3Jlc3NDaGFydDtcclxuIl19 478 | --------------------------------------------------------------------------------