From dd0a369d0d5ccf814a5a25e389338cdb4abc432c Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Tue, 19 Dec 2017 19:03:52 -0800 Subject: [PATCH 01/10] Add tsc command and fix imports --- package.json | 3 ++- src/index.ts | 5 ++--- src/ipanel.ts | 2 +- src/toolbar.ts | 2 +- tsconfig.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index fb32337..5b5237f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "test": "node .\\node_modules\\karma\\bin\\karma start karma.conf.js", "build": "webpack", - "demo": "npm run build && node .\\node_modules\\http-server\\bin\\http-server -o -c-1" + "demo": "npm run build && node .\\node_modules\\http-server\\bin\\http-server -o -c-1", + "tsc": "node D:\\repos\\webperftoolbar\\node_modules\\typescript\\bin\\tsc --p tsconfig.json" }, "keywords": [], "author": "", diff --git a/src/index.ts b/src/index.ts index 99d313e..5b4551d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ -import "Toolbar"; import { Toolbar } from "toolbar"; -import "IPanel"; import { IPanel } from 'ipanel'; -import "Button"; import { Button } from 'button'; +import "./toolbar"; import { Toolbar } from "toolbar"; +import "./button"; import { Button } from 'button'; export {Toolbar, Button}; diff --git a/src/ipanel.ts b/src/ipanel.ts index 4a33d10..d0f498d 100644 --- a/src/ipanel.ts +++ b/src/ipanel.ts @@ -1,4 +1,4 @@ -import "Button"; import { Button } from './button'; +import "./button"; import { Button } from './button'; /** Describes a panel within the opened toolbar. */ export interface IPanel diff --git a/src/toolbar.ts b/src/toolbar.ts index e53ae6d..52efc70 100644 --- a/src/toolbar.ts +++ b/src/toolbar.ts @@ -1,4 +1,4 @@ -import "IPanel"; import { IPanel } from './ipanel'; +import "./ipanel"; import { IPanel } from './ipanel'; /** Describes the toolbar. */ export class Toolbar diff --git a/tsconfig.json b/tsconfig.json index 5beeb26..b3767d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "outDir": "dist", + "outDir": "dist/tsc", "noImplicitAny": true, "module": "es6", "target": "es5", From d7b003900e9409e9542b3e6dded7ef69b38a4295 Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Tue, 19 Dec 2017 19:05:00 -0800 Subject: [PATCH 02/10] Add navigation timing button and render --- dist/bundle.js | 246 +++++++++++++------------------- index.html | 25 +--- src/index.ts | 10 +- src/panels/navigation-timing.ts | 72 ++++++++++ 4 files changed, 181 insertions(+), 172 deletions(-) create mode 100644 src/panels/navigation-timing.ts diff --git a/dist/bundle.js b/dist/bundle.js index be67912..b09d9b6 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -61,158 +61,13 @@ var PerfToolbar = /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 2); +/******/ return __webpack_require__(__webpack_require__.s = 3); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { -"use strict"; -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_Button__ = __webpack_require__(1); - - - -/***/ }), -/* 1 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* unused harmony export Button */ -/** Describes a button to be displayed in the collapsed toolbar. */ -var Button = /** @class */ (function () { - /** - * Create the button. - * @param emoji The icon for the button. The intention is to use a single character emoji - * but it's just a string, so anything goes. - * @param getValue Gets the displayed value for the button. - * @param getColor Gets the background color for the button. - */ - function Button(emoji, getValue, getColor) { - this.emoji = emoji; - this.getValue = getValue; - this.getColor = getColor; - } - /** - * Renders the button by adding it as a new child. - * @param container The DOM node that should contain this button. - */ - Button.prototype.render = function (container) { - var li = document.createElement("li"); - li.setAttribute("style", "background-color:" + this.getColor()); - li.innerText = this.emoji + " " + this.getValue(); - container.appendChild(li); - }; - return Button; -}()); - - - -/***/ }), -/* 2 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_Toolbar__ = __webpack_require__(3); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_toolbar__ = __webpack_require__(4); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_IPanel__ = __webpack_require__(0); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_Button__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_button__ = __webpack_require__(5); -/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "Toolbar", function() { return __WEBPACK_IMPORTED_MODULE_1_toolbar__["a"]; }); -/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "Button", function() { return __WEBPACK_IMPORTED_MODULE_4_button__["a"]; }); - - - - - - - - -/***/ }), -/* 3 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* unused harmony export Toolbar */ -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_IPanel__ = __webpack_require__(0); - -/** Describes the toolbar. */ -var Toolbar = /** @class */ (function () { - /** - * Creates the toolbar. - * @param panels The panels to be displayed when the toolbar is opened. - * @param container Optional parameter that defaults to the body of the HTML page. - */ - function Toolbar(panels, container) { - if (container === void 0) { container = window.document.body; } - this.panels = panels; - this.container = container; - this.root = document.createElement("div"); - container.appendChild(this.root); - } - Toolbar.prototype.render = function () { - // clear all children - this.container.innerHTML = ""; - var ul = document.createElement("ul"); - for (var _i = 0, _a = this.panels; _i < _a.length; _i++) { - var p = _a[_i]; - for (var _b = 0, _c = p.getButtons(); _b < _c.length; _b++) { - var b = _c[_b]; - b.render(ul); - } - } - this.container.appendChild(ul); - }; - return Toolbar; -}()); - - - -/***/ }), -/* 4 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Toolbar; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_IPanel__ = __webpack_require__(0); - -/** Describes the toolbar. */ -var Toolbar = /** @class */ (function () { - /** - * Creates the toolbar. - * @param panels The panels to be displayed when the toolbar is opened. - * @param container Optional parameter that defaults to the body of the HTML page. - */ - function Toolbar(panels, container) { - if (container === void 0) { container = window.document.body; } - this.panels = panels; - this.container = container; - this.root = document.createElement("div"); - container.appendChild(this.root); - } - Toolbar.prototype.render = function () { - // clear all children - this.container.innerHTML = ""; - var ul = document.createElement("ul"); - for (var _i = 0, _a = this.panels; _i < _a.length; _i++) { - var p = _a[_i]; - for (var _b = 0, _c = p.getButtons(); _b < _c.length; _b++) { - var b = _c[_b]; - b.render(ul); - } - } - this.container.appendChild(ul); - }; - return Toolbar; -}()); - - - -/***/ }), -/* 5 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Button; }); /** Describes a button to be displayed in the collapsed toolbar. */ @@ -244,6 +99,103 @@ var Button = /** @class */ (function () { +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Toolbar; }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ipanel__ = __webpack_require__(4); + +/** Describes the toolbar. */ +var Toolbar = /** @class */ (function () { + /** + * Creates the toolbar. + * @param panels The panels to be displayed when the toolbar is opened. + * @param container Optional parameter that defaults to the body of the HTML page. + */ + function Toolbar(panels, container) { + if (container === void 0) { container = window.document.body; } + this.panels = panels; + this.container = container; + this.root = document.createElement("div"); + container.appendChild(this.root); + } + Toolbar.prototype.render = function () { + // clear all children + this.container.innerHTML = ""; + var ul = document.createElement("ul"); + for (var _i = 0, _a = this.panels; _i < _a.length; _i++) { + var p = _a[_i]; + for (var _b = 0, _c = p.getButtons(); _b < _c.length; _b++) { + var b = _c[_b]; + b.render(ul); + } + } + this.container.appendChild(ul); + }; + return Toolbar; +}()); + + + +/***/ }), +/* 2 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return NavigationTimingsPanel; }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_button__ = __webpack_require__(0); + +var NavigationTimingsPanel = /** @class */ (function () { + function NavigationTimingsPanel() { + } + NavigationTimingsPanel.prototype.render = function (target) { + var t = performance.timing; + target.innerHTML = "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Get Connected" + (t.connectEnd - t.domainLookupStart).toFixed(2) + " ms
DNS Lookup" + (t.domainLookupEnd - t.domainLookupStart).toFixed(2) + " ms
SSL" + (t.connectEnd - t.connectStart).toFixed(2) + " ms
Get Content" + (t.responseEnd - t.requestStart).toFixed(2) + " ms
Waiting for Server" + (t.responseStart - t.requestStart).toFixed(2) + " ms
Time To Download" + (t.responseEnd - t.responseStart).toFixed(2) + " ms
Get Ready
Parse Content" + (t.domInteractive - t.responseEnd).toFixed(2) + " ms
Deferred Scripts" + (t.domContentLoadedEventEnd - t.domInteractive).toFixed(2) + " ms
DOM Complete" + (t.domComplete - t.domContentLoadedEventEnd).toFixed(2) + " ms
Load Event" + (t.loadEventEnd - t.loadEventStart).toFixed(2) + " ms
Total Load" + (t.loadEventEnd - t.navigationStart).toFixed(2) + " ms
\n "; + }; + NavigationTimingsPanel.prototype.getButtons = function () { + return [new __WEBPACK_IMPORTED_MODULE_0_button__["a" /* Button */]('⏱️', function () { return (performance.timing.loadEventEnd - performance.timing.navigationStart).toFixed(2) + " ms"; }, function () { return performance.timing.loadEventEnd - performance.timing.navigationStart < 500 ? "green" : "red"; })]; + }; + return NavigationTimingsPanel; +}()); + + + +/***/ }), +/* 3 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__toolbar__ = __webpack_require__(1); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_toolbar__ = __webpack_require__(1); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__button__ = __webpack_require__(0); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_button__ = __webpack_require__(0); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__panels_navigation_timing__ = __webpack_require__(2); +/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "Toolbar", function() { return __WEBPACK_IMPORTED_MODULE_1_toolbar__["a"]; }); +/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "Button", function() { return __WEBPACK_IMPORTED_MODULE_3_button__["a"]; }); +/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "NavigationTimingsPanel", function() { return __WEBPACK_IMPORTED_MODULE_4__panels_navigation_timing__["a"]; }); +// Core + + + + +// Panels + + + + + +/***/ }), +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__button__ = __webpack_require__(0); + + + /***/ }) /******/ ]); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgZDJiMjI2MzU1ZjVkMDc3NWI4NjUiLCJ3ZWJwYWNrOi8vLy4vc3JjL0lQYW5lbC50cyIsIndlYnBhY2s6Ly8vLi9zcmMvQnV0dG9uLnRzIiwid2VicGFjazovLy8uL3NyYy9pbmRleC50cyIsIndlYnBhY2s6Ly8vLi9zcmMvVG9vbGJhci50cyIsIndlYnBhY2s6Ly8vLi9zcmMvdG9vbGJhci50cyIsIndlYnBhY2s6Ly8vLi9zcmMvYnV0dG9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLG1DQUEyQiwwQkFBMEIsRUFBRTtBQUN2RCx5Q0FBaUMsZUFBZTtBQUNoRDtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw4REFBc0QsK0RBQStEOztBQUVySDtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7OztBQzdEZ0I7Ozs7Ozs7O0FDQWhCO0FBQUEsbUVBQW1FO0FBQ25FO0lBRUk7Ozs7OztPQU1HO0lBQ0gsZ0JBQW1CLEtBQWEsRUFBUyxRQUFzQixFQUFTLFFBQXNCO1FBQTNFLFVBQUssR0FBTCxLQUFLLENBQVE7UUFBUyxhQUFRLEdBQVIsUUFBUSxDQUFjO1FBQVMsYUFBUSxHQUFSLFFBQVEsQ0FBYztJQUc5RixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksdUJBQU0sR0FBYixVQUFjLFNBQXNCO1FBRWhDLElBQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEMsRUFBRSxDQUFDLFlBQVksQ0FBQyxPQUFP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UyxhQUFRLEdBQVIsUUFBUSxDQUFjO0lBRzlGLENBQUM7SUFFRDs7O09BR0c7SUFDSSx1QkFBTSxHQUFiLFVBQWMsU0FBc0I7UUFFaEMsSUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN4QyxFQUFFLENBQUMsWUFBWSxDQUFDLE9BQU8sRUFBRSxzQkFBb0IsSUFBSSxDQUFDLFFBQVEsRUFBSSxDQUFDLENBQUM7UUFDaEUsRUFBRSxDQUFDLFNBQVMsR0FBTSxJQUFJLENBQUMsS0FBSyxTQUFJLElBQUksQ0FBQyxRQUFRLEVBQUksQ0FBQztRQUVsRCxTQUFTLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzlCLENBQUM7SUFDTCxhQUFDO0FBQUQsQ0FBQyIsImZpbGUiOiJidW5kbGUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSAyKTtcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyB3ZWJwYWNrL2Jvb3RzdHJhcCBkMmIyMjYzNTVmNWQwNzc1Yjg2NSIsImltcG9ydCBcIkJ1dHRvblwiOyBpbXBvcnQgeyBCdXR0b24gfSBmcm9tICcuL2J1dHRvbic7XHJcblxyXG4vKiogRGVzY3JpYmVzIGEgcGFuZWwgd2l0aGluIHRoZSBvcGVuZWQgdG9vbGJhci4gKi9cclxuZXhwb3J0IGludGVyZmFjZSBJUGFuZWxcclxue1xyXG4gICAgbmFtZTogc3RyaW5nO1xyXG5cclxuICAgIC8qKlxyXG4gICAgICogUmVuZGVycyB0aGUgcGFuZWwuXHJcbiAgICAgKiBAcGFyYW0gdGFyZ2V0IFRoZSBIVE1MIGVsZW1lbnQgdG8gY29udGFpbiB0aGlzIHBhbmVsLlxyXG4gICAgICovXHJcbiAgICByZW5kZXIodGFyZ2V0OiBIVE1MRWxlbWVudCk6IHZvaWQ7XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBHZXRzIHRoZSBidXR0b25zIHByb3ZpZGVkIGJ5IHRoaXMgcGFuZWwgdG8gYmUgZGlzcGxheWVkIGluIHRoZSBjb2xsYXBzZWQgdG9vbGJhci5cclxuICAgICAqL1xyXG4gICAgZ2V0QnV0dG9ucygpOiBCdXR0b25bXTtcclxufVxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy9JUGFuZWwudHMiLCIvKiogRGVzY3JpYmVzIGEgYnV0dG9uIHRvIGJlIGRpc3BsYXllZCBpbiB0aGUgY29sbGFwc2VkIHRvb2xiYXIuICovXHJcbmV4cG9ydCBjbGFzcyBCdXR0b25cclxue1xyXG4gICAgLyoqXHJcbiAgICAgKiBDcmVhdGUgdGhlIGJ1dHRvbi5cclxuICAgICAqIEBwYXJhbSBlbW9qaSBUaGUgaWNvbiBmb3IgdGhlIGJ1dHRvbi4gVGhlIGludGVudGlvbiBpcyB0byB1c2UgYSBzaW5nbGUgY2hhcmFjdGVyIGVtb2ppXHJcbiAgICAgKiAgIGJ1dCBpdCdzIGp1c3QgYSBzdHJpbmcsIHNvIGFueXRoaW5nIGdvZXMuXHJcbiAgICAgKiBAcGFyYW0gZ2V0VmFsdWUgR2V0cyB0aGUgZGlzcGxheWVkIHZhbHVlIGZvciB0aGUgYnV0dG9uLlxyXG4gICAgICogQHBhcmFtIGdldENvbG9yIEdldHMgdGhlIGJhY2tncm91bmQgY29sb3IgZm9yIHRoZSBidXR0b24uXHJcbiAgICAgKi9cclxuICAgIGNvbnN0cnVjdG9yKHB1YmxpYyBlbW9qaTogc3RyaW5nLCBwdWJsaWMgZ2V0VmFsdWU6ICgpID0+IHN0cmluZywgcHVibGljIGdldENvbG9yOiAoKSA9PiBzdHJpbmcpXHJcbiAgICB7XHJcblxyXG4gICAgfVxyXG5cclxuICAgIC8qKlxyXG4gICAgICogUmVuZGVycyB0aGUgYnV0dG9uIGJ5IGFkZGluZyBpdCBhcyBhIG5ldyBjaGlsZC5cclxuICAgICAqIEBwYXJhbSBjb250YWluZXIgVGhlIERPTSBub2RlIHRoYXQgc2hvdWxkIGNvbnRhaW4gdGhpcyBidXR0b24uXHJcbiAgICAgKi9cclxuICAgIHB1YmxpYyByZW5kZXIoY29udGFpbmVyOiBIVE1MRWxlbWVudCk6IHZvaWRcclxuICAgIHtcclxuICAgICAgICBjb25zdCBsaSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJsaVwiKTtcclxuICAgICAgICBsaS5zZXRBdHRyaWJ1dGUoXCJzdHlsZVwiLCBgYmFja2dyb3VuZC1jb2xvcjoke3RoaXMuZ2V0Q29sb3IoKX1gKTtcclxuICAgICAgICBsaS5pbm5lclRleHQgPSBgJHt0aGlzLmVtb2ppfSAke3RoaXMuZ2V0VmFsdWUoKX1gO1xyXG5cclxuICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQobGkpO1xyXG4gICAgfVxyXG59XG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL0J1dHRvbi50cyIsImltcG9ydCBcIlRvb2xiYXJcIjsgaW1wb3J0IHsgVG9vbGJhciB9IGZyb20gXCJ0b29sYmFyXCI7XHJcbmltcG9ydCBcIklQYW5lbFwiOyBpbXBvcnQgeyBJUGFuZWwgfSBmcm9tICdpcGFuZWwnO1xyXG5pbXBvcnQgXCJCdXR0b25cIjsgaW1wb3J0IHsgQnV0dG9uIH0gZnJvbSAnYnV0dG9uJztcclxuXHJcbmV4cG9ydCB7VG9vbGJhciwgQnV0dG9ufTtcclxuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL2luZGV4LnRzIiwiaW1wb3J0IFwiSVBhbmVsXCI7IGltcG9ydCB7IElQYW5lbCB9IGZyb20gJy4vaXBhbmVsJztcclxuXHJcbi8qKiBEZXNjcmliZXMgdGhlIHRvb2xiYXIuICovXHJcbmV4cG9ydCBjbGFzcyBUb29sYmFyXHJcbntcclxuICAgIHByaXZhdGUgcm9vdDogSFRNTEVsZW1lbnQ7XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBDcmVhdGVzIHRoZSB0b29sYmFyLlxyXG4gICAgICogQHBhcmFtIHBhbmVscyBUaGUgcGFuZWxzIHRvIGJlIGRpc3BsYXllZCB3aGVuIHRoZSB0b29sYmFyIGlzIG9wZW5lZC5cclxuICAgICAqIEBwYXJhbSBjb250YWluZXIgT3B0aW9uYWwgcGFyYW1ldGVyIHRoYXQgZGVmYXVsdHMgdG8gdGhlIGJvZHkgb2YgdGhlIEhUTUwgcGFnZS5cclxuICAgICAqL1xyXG4gICAgY29uc3RydWN0b3IocHJpdmF0ZSBwYW5lbHM6IElQYW5lbFtdLCBwcml2YXRlIGNvbnRhaW5lcjogSFRNTEVsZW1lbnQgPSB3aW5kb3cuZG9jdW1lbnQuYm9keSlcclxuICAgIHtcclxuICAgICAgICB0aGlzLnJvb3QgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiZGl2XCIpO1xyXG4gICAgICAgIGNvbnRhaW5lci5hcHBlbmRDaGlsZCh0aGlzLnJvb3QpO1xyXG4gICAgfVxyXG5cclxuICAgIHB1YmxpYyByZW5kZXIoKTogdm9pZFxyXG4gICAge1xyXG4gICAgICAgIC8vIGNsZWFyIGFsbCBjaGlsZHJlblxyXG4gICAgICAgIHRoaXMuY29udGFpbmVyLmlubmVySFRNTCA9IFwiXCI7XHJcblxyXG4gICAgICAgIGNvbnN0IHVsID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcInVsXCIpO1xyXG4gICAgICAgIGZvcihsZXQgcCBvZiB0aGlzLnBhbmVscylcclxuICAgICAgICB7XHJcbiAgICAgICAgICAgIGZvcihsZXQgYiBvZiBwLmdldEJ1dHRvbnMoKSlcclxuICAgICAgICAgICAge1xyXG4gICAgICAgICAgICAgICAgYi5yZW5kZXIodWwpO1xyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgfVxyXG5cclxuICAgICAgICB0aGlzLmNvbnRhaW5lci5hcHBlbmRDaGlsZCh1bCk7XHJcbiAgICB9XHJcbn1cclxuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL1Rvb2xiYXIudHMiLCJpbXBvcnQgXCJJUGFuZWxcIjsgaW1wb3J0IHsgSVBhbmVsIH0gZnJvbSAnLi9pcGFuZWwnO1xyXG5cclxuLyoqIERlc2NyaWJlcyB0aGUgdG9vbGJhci4gKi9cclxuZXhwb3J0IGNsYXNzIFRvb2xiYXJcclxue1xyXG4gICAgcHJpdmF0ZSByb290OiBIVE1MRWxlbWVudDtcclxuXHJcbiAgICAvKipcclxuICAgICAqIENyZWF0ZXMgdGhlIHRvb2xiYXIuXHJcbiAgICAgKiBAcGFyYW0gcGFuZWxzIFRoZSBwYW5lbHMgdG8gYmUgZGlzcGxheWVkIHdoZW4gdGhlIHRvb2xiYXIgaXMgb3BlbmVkLlxyXG4gICAgICogQHBhcmFtIGNvbnRhaW5lciBPcHRpb25hbCBwYXJhbWV0ZXIgdGhhdCBkZWZhdWx0cyB0byB0aGUgYm9keSBvZiB0aGUgSFRNTCBwYWdlLlxyXG4gICAgICovXHJcbiAgICBjb25zdHJ1Y3Rvcihwcml2YXRlIHBhbmVsczogSVBhbmVsW10sIHByaXZhdGUgY29udGFpbmVyOiBIVE1MRWxlbWVudCA9IHdpbmRvdy5kb2N1bWVudC5ib2R5KVxyXG4gICAge1xyXG4gICAgICAgIHRoaXMucm9vdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XHJcbiAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKHRoaXMucm9vdCk7XHJcbiAgICB9XHJcblxyXG4gICAgcHVibGljIHJlbmRlcigpOiB2b2lkXHJcbiAgICB7XHJcbiAgICAgICAgLy8gY2xlYXIgYWxsIGNoaWxkcmVuXHJcbiAgICAgICAgdGhpcy5jb250YWluZXIuaW5uZXJIVE1MID0gXCJcIjtcclxuXHJcbiAgICAgICAgY29uc3QgdWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwidWxcIik7XHJcbiAgICAgICAgZm9yKGxldCBwIG9mIHRoaXMucGFuZWxzKVxyXG4gICAgICAgIHtcclxuICAgICAgICAgICAgZm9yKGxldCBiIG9mIHAuZ2V0QnV0dG9ucygpKVxyXG4gICAgICAgICAgICB7XHJcbiAgICAgICAgICAgICAgICBiLnJlbmRlcih1bCk7XHJcbiAgICAgICAgICAgIH1cclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIHRoaXMuY29udGFpbmVyLmFwcGVuZENoaWxkKHVsKTtcclxuICAgIH1cclxufVxyXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gLi9zcmMvdG9vbGJhci50cyIsIi8qKiBEZXNjcmliZXMgYSBidXR0b24gdG8gYmUgZGlzcGxheWVkIGluIHRoZSBjb2xsYXBzZWQgdG9vbGJhci4gKi9cclxuZXhwb3J0IGNsYXNzIEJ1dHRvblxyXG57XHJcbiAgICAvKipcclxuICAgICAqIENyZWF0ZSB0aGUgYnV0dG9uLlxyXG4gICAgICogQHBhcmFtIGVtb2ppIFRoZSBpY29uIGZvciB0aGUgYnV0dG9uLiBUaGUgaW50ZW50aW9uIGlzIHRvIHVzZSBhIHNpbmdsZSBjaGFyYWN0ZXIgZW1vamlcclxuICAgICAqICAgYnV0IGl0J3MganVzdCBhIHN0cmluZywgc28gYW55dGhpbmcgZ29lcy5cclxuICAgICAqIEBwYXJhbSBnZXRWYWx1ZSBHZXRzIHRoZSBkaXNwbGF5ZWQgdmFsdWUgZm9yIHRoZSBidXR0b24uXHJcbiAgICAgKiBAcGFyYW0gZ2V0Q29sb3IgR2V0cyB0aGUgYmFja2dyb3VuZCBjb2xvciBmb3IgdGhlIGJ1dHRvbi5cclxuICAgICAqL1xyXG4gICAgY29uc3RydWN0b3IocHVibGljIGVtb2ppOiBzdHJpbmcsIHB1YmxpYyBnZXRWYWx1ZTogKCkgPT4gc3RyaW5nLCBwdWJsaWMgZ2V0Q29sb3I6ICgpID0+IHN0cmluZylcclxuICAgIHtcclxuXHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBSZW5kZXJzIHRoZSBidXR0b24gYnkgYWRkaW5nIGl0IGFzIGEgbmV3IGNoaWxkLlxyXG4gICAgICogQHBhcmFtIGNvbnRhaW5lciBUaGUgRE9NIG5vZGUgdGhhdCBzaG91bGQgY29udGFpbiB0aGlzIGJ1dHRvbi5cclxuICAgICAqL1xyXG4gICAgcHVibGljIHJlbmRlcihjb250YWluZXI6IEhUTUxFbGVtZW50KTogdm9pZFxyXG4gICAge1xyXG4gICAgICAgIGNvbnN0IGxpID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImxpXCIpO1xyXG4gICAgICAgIGxpLnNldEF0dHJpYnV0ZShcInN0eWxlXCIsIGBiYWNrZ3JvdW5kLWNvbG9yOiR7dGhpcy5nZXRDb2xvcigpfWApO1xyXG4gICAgICAgIGxpLmlubmVyVGV4dCA9IGAke3RoaXMuZW1vaml9ICR7dGhpcy5nZXRWYWx1ZSgpfWA7XHJcblxyXG4gICAgICAgIGNvbnRhaW5lci5hcHBlbmRDaGlsZChsaSk7XHJcbiAgICB9XHJcbn1cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gLi9zcmMvYnV0dG9uLnRzIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgM2UwZWJmZmMwNjgxMDM3MDJlNzIiLCJ3ZWJwYWNrOi8vLy4vc3JjL2J1dHRvbi50cyIsIndlYnBhY2s6Ly8vLi9zcmMvdG9vbGJhci50cyIsIndlYnBhY2s6Ly8vLi9zcmMvcGFuZWxzL25hdmlnYXRpb24tdGltaW5nLnRzIiwid2VicGFjazovLy8uL3NyYy9pbmRleC50cyIsIndlYnBhY2s6Ly8vLi9zcmMvaXBhbmVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLG1DQUEyQiwwQkFBMEIsRUFBRTtBQUN2RCx5Q0FBaUMsZUFBZTtBQUNoRDtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw4REFBc0QsK0RBQStEOztBQUVySDtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7O0FDN0RBO0FBQUEsbUVBQW1FO0FBQ25FO0lBRUk7Ozs7OztPQU1HO0lBQ0gsZ0JBQW1CLEtBQWEsRUFBUyxRQUFzQixFQUFTLFFBQXNCO1FBQTNFLFVBQUssR0FBTCxLQUFLLENBQVE7UUFBUyxhQUFRLEdBQVIsUUFBUSxDQUFjO1FBQVMsYUFBUSxHQUFSLFFBQVEsQ0FBYztJQUc5RixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksdUJBQU0sR0FBYixVQUFjLFNBQXNCO1FBRWhDLElBQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEMsRUFBRSxDQUFDLFlBQVksQ0FBQyxPQUFPLEVBQUUsc0JBQW9CLElBQUksQ0FBQyxRQUFR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Q0FBQyxZQUFZLEdBQUcsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsNERBRzVELENBQUM7SUFDTixDQUFDO0lBRUQsMkNBQVUsR0FBVjtRQUNJLE1BQU0sQ0FBQyxDQUFDLElBQUksc0RBQU0sQ0FDZCxJQUFJLEVBQ0osY0FBTSxPQUFHLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFFBQUssRUFBekYsQ0FBeUYsRUFDL0YsY0FBTSxrQkFBVyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxlQUFlLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBNUYsQ0FBNEYsQ0FDckcsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQUVMLDZCQUFDO0FBQUQsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7Ozs7QUN2RUQ7QUFBQSxPQUFPO0FBQ1k7QUFBbUM7QUFDcEM7QUFBaUM7QUFFbkQsU0FBUztBQUMyQjtBQUFxRTtBQU12Rzs7Ozs7Ozs7O0FDWGdCIiwiZmlsZSI6ImJ1bmRsZS5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwge1xuIFx0XHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcbiBcdFx0XHRcdGVudW1lcmFibGU6IHRydWUsXG4gXHRcdFx0XHRnZXQ6IGdldHRlclxuIFx0XHRcdH0pO1xuIFx0XHR9XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IDMpO1xuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIHdlYnBhY2svYm9vdHN0cmFwIDNlMGViZmZjMDY4MTAzNzAyZTcyIiwiLyoqIERlc2NyaWJlcyBhIGJ1dHRvbiB0byBiZSBkaXNwbGF5ZWQgaW4gdGhlIGNvbGxhcHNlZCB0b29sYmFyLiAqL1xyXG5leHBvcnQgY2xhc3MgQnV0dG9uXHJcbntcclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlIHRoZSBidXR0b24uXHJcbiAgICAgKiBAcGFyYW0gZW1vamkgVGhlIGljb24gZm9yIHRoZSBidXR0b24uIFRoZSBpbnRlbnRpb24gaXMgdG8gdXNlIGEgc2luZ2xlIGNoYXJhY3RlciBlbW9qaVxyXG4gICAgICogICBidXQgaXQncyBqdXN0IGEgc3RyaW5nLCBzbyBhbnl0aGluZyBnb2VzLlxyXG4gICAgICogQHBhcmFtIGdldFZhbHVlIEdldHMgdGhlIGRpc3BsYXllZCB2YWx1ZSBmb3IgdGhlIGJ1dHRvbi5cclxuICAgICAqIEBwYXJhbSBnZXRDb2xvciBHZXRzIHRoZSBiYWNrZ3JvdW5kIGNvbG9yIGZvciB0aGUgYnV0dG9uLlxyXG4gICAgICovXHJcbiAgICBjb25zdHJ1Y3RvcihwdWJsaWMgZW1vamk6IHN0cmluZywgcHVibGljIGdldFZhbHVlOiAoKSA9PiBzdHJpbmcsIHB1YmxpYyBnZXRDb2xvcjogKCkgPT4gc3RyaW5nKVxyXG4gICAge1xyXG5cclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFJlbmRlcnMgdGhlIGJ1dHRvbiBieSBhZGRpbmcgaXQgYXMgYSBuZXcgY2hpbGQuXHJcbiAgICAgKiBAcGFyYW0gY29udGFpbmVyIFRoZSBET00gbm9kZSB0aGF0IHNob3VsZCBjb250YWluIHRoaXMgYnV0dG9uLlxyXG4gICAgICovXHJcbiAgICBwdWJsaWMgcmVuZGVyKGNvbnRhaW5lcjogSFRNTEVsZW1lbnQpOiB2b2lkXHJcbiAgICB7XHJcbiAgICAgICAgY29uc3QgbGkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwibGlcIik7XHJcbiAgICAgICAgbGkuc2V0QXR0cmlidXRlKFwic3R5bGVcIiwgYGJhY2tncm91bmQtY29sb3I6JHt0aGlzLmdldENvbG9yKCl9YCk7XHJcbiAgICAgICAgbGkuaW5uZXJUZXh0ID0gYCR7dGhpcy5lbW9qaX0gJHt0aGlzLmdldFZhbHVlKCl9YDtcclxuXHJcbiAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKGxpKTtcclxuICAgIH1cclxufVxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy9idXR0b24udHMiLCJpbXBvcnQgXCIuL2lwYW5lbFwiOyBpbXBvcnQgeyBJUGFuZWwgfSBmcm9tICcuL2lwYW5lbCc7XHJcblxyXG4vKiogRGVzY3JpYmVzIHRoZSB0b29sYmFyLiAqL1xyXG5leHBvcnQgY2xhc3MgVG9vbGJhclxyXG57XHJcbiAgICBwcml2YXRlIHJvb3Q6IEhUTUxFbGVtZW50O1xyXG5cclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlcyB0aGUgdG9vbGJhci5cclxuICAgICAqIEBwYXJhbSBwYW5lbHMgVGhlIHBhbmVscyB0byBiZSBkaXNwbGF5ZWQgd2hlbiB0aGUgdG9vbGJhciBpcyBvcGVuZWQuXHJcbiAgICAgKiBAcGFyYW0gY29udGFpbmVyIE9wdGlvbmFsIHBhcmFtZXRlciB0aGF0IGRlZmF1bHRzIHRvIHRoZSBib2R5IG9mIHRoZSBIVE1MIHBhZ2UuXHJcbiAgICAgKi9cclxuICAgIGNvbnN0cnVjdG9yKHByaXZhdGUgcGFuZWxzOiBJUGFuZWxbXSwgcHJpdmF0ZSBjb250YWluZXI6IEhUTUxFbGVtZW50ID0gd2luZG93LmRvY3VtZW50LmJvZHkpXHJcbiAgICB7XHJcbiAgICAgICAgdGhpcy5yb290ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKTtcclxuICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQodGhpcy5yb290KTtcclxuICAgIH1cclxuXHJcbiAgICBwdWJsaWMgcmVuZGVyKCk6IHZvaWRcclxuICAgIHtcclxuICAgICAgICAvLyBjbGVhciBhbGwgY2hpbGRyZW5cclxuICAgICAgICB0aGlzLmNvbnRhaW5lci5pbm5lckhUTUwgPSBcIlwiO1xyXG5cclxuICAgICAgICBjb25zdCB1bCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJ1bFwiKTtcclxuICAgICAgICBmb3IobGV0IHAgb2YgdGhpcy5wYW5lbHMpXHJcbiAgICAgICAge1xyXG4gICAgICAgICAgICBmb3IobGV0IGIgb2YgcC5nZXRCdXR0b25zKCkpXHJcbiAgICAgICAgICAgIHtcclxuICAgICAgICAgICAgICAgIGIucmVuZGVyKHVsKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgdGhpcy5jb250YWluZXIuYXBwZW5kQ2hpbGQodWwpO1xyXG4gICAgfVxyXG59XHJcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy90b29sYmFyLnRzIiwiaW1wb3J0IHsgSVBhbmVsIH0gZnJvbSAnaXBhbmVsJztcclxuaW1wb3J0IHsgQnV0dG9uIH0gZnJvbSAnYnV0dG9uJztcclxuXHJcbmV4cG9ydCBjbGFzcyBOYXZpZ2F0aW9uVGltaW5nc1BhbmVsIGltcGxlbWVudHMgSVBhbmVsXHJcbntcclxuICAgIG5hbWU6IFwiTmF2aWdhdGlvbiBUaW1pbmdzXCI7XHJcblxyXG4gICAgcmVuZGVyKHRhcmdldDogSFRNTEVsZW1lbnQpOiB2b2lkIHtcclxuICAgICAgICBjb25zdCB0ID0gcGVyZm9ybWFuY2UudGltaW5nO1xyXG5cclxuICAgICAgICB0YXJnZXQuaW5uZXJIVE1MID0gYFxyXG4gICAgICAgIDx0YWJsZT5cclxuICAgICAgICAgICAgPHRyPlxyXG4gICAgICAgICAgICAgICAgPHRoPkdldCBDb25uZWN0ZWQ8L3RoPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQuY29ubmVjdEVuZCAtIHQuZG9tYWluTG9va3VwU3RhcnQpLnRvRml4ZWQoMil9IG1zPC90ZD5cclxuICAgICAgICAgICAgPC90cj5cclxuICAgICAgICAgICAgPHRyPlxyXG4gICAgICAgICAgICAgICAgPHRkPkROUyBMb29rdXA8L3RkPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQuZG9tYWluTG9va3VwRW5kIC0gdC5kb21haW5Mb29rdXBTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+U1NMPC90ZD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LmNvbm5lY3RFbmQgLSB0LmNvbm5lY3RTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGg+R2V0IENvbnRlbnQ8L3RoPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQucmVzcG9uc2VFbmQgLSB0LnJlcXVlc3RTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+V2FpdGluZyBmb3IgU2VydmVyPC90ZD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LnJlc3BvbnNlU3RhcnQgLSB0LnJlcXVlc3RTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+VGltZSBUbyBEb3dubG9hZDwvdGQ+XHJcbiAgICAgICAgICAgICAgICA8dGQ+JHsodC5yZXNwb25zZUVuZCAtIHQucmVzcG9uc2VTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGggY29sc3Bhbj0yPkdldCBSZWFkeTwvdGg+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgICAgIDx0cj5cclxuICAgICAgICAgICAgICAgIDx0ZD5QYXJzZSBDb250ZW50PC90ZD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LmRvbUludGVyYWN0aXZlIC0gdC5yZXNwb25zZUVuZCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+RGVmZXJyZWQgU2NyaXB0czwvdGQ+XHJcbiAgICAgICAgICAgICAgICA8dGQ+JHsodC5kb21Db250ZW50TG9hZGVkRXZlbnRFbmQgLSB0LmRvbUludGVyYWN0aXZlKS50b0ZpeGVkKDIpfSBtczwvdGQ+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgICAgIDx0cj5cclxuICAgICAgICAgICAgICAgIDx0ZD5ET00gQ29tcGxldGU8L3RkPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQuZG9tQ29tcGxldGUgLSB0LmRvbUNvbnRlbnRMb2FkZWRFdmVudEVuZCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+TG9hZCBFdmVudDwvdGQ+XHJcbiAgICAgICAgICAgICAgICA8dGQ+JHsodC5sb2FkRXZlbnRFbmQgLSB0LmxvYWRFdmVudFN0YXJ0KS50b0ZpeGVkKDIpfSBtczwvdGQ+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgICAgIDx0cj5cclxuICAgICAgICAgICAgICAgIDx0aD5Ub3RhbCBMb2FkPC90aD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LmxvYWRFdmVudEVuZCAtIHQubmF2aWdhdGlvblN0YXJ0KS50b0ZpeGVkKDIpfSBtczwvdGQ+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgPC90YWJsZT5cclxuICAgICAgICBgO1xyXG4gICAgfVxyXG5cclxuICAgIGdldEJ1dHRvbnMoKTogQnV0dG9uW10ge1xyXG4gICAgICAgIHJldHVybiBbbmV3IEJ1dHRvbihcclxuICAgICAgICAgICAgJ+KPse+4jycsXHJcbiAgICAgICAgICAgICgpID0+IGAkeyhwZXJmb3JtYW5jZS50aW1pbmcubG9hZEV2ZW50RW5kIC0gcGVyZm9ybWFuY2UudGltaW5nLm5hdmlnYXRpb25TdGFydCkudG9GaXhlZCgyKX0gbXNgLFxyXG4gICAgICAgICAgICAoKSA9PiBwZXJmb3JtYW5jZS50aW1pbmcubG9hZEV2ZW50RW5kIC0gcGVyZm9ybWFuY2UudGltaW5nLm5hdmlnYXRpb25TdGFydCA8IDUwMCA/IFwiZ3JlZW5cIiA6IFwicmVkXCJcclxuICAgICAgICApXTtcclxuICAgIH1cclxuXHJcbn1cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gLi9zcmMvcGFuZWxzL25hdmlnYXRpb24tdGltaW5nLnRzIiwiLy8gQ29yZVxyXG5pbXBvcnQgXCIuL3Rvb2xiYXJcIjsgaW1wb3J0IHsgVG9vbGJhciB9IGZyb20gXCJ0b29sYmFyXCI7XHJcbmltcG9ydCBcIi4vYnV0dG9uXCI7IGltcG9ydCB7IEJ1dHRvbiB9IGZyb20gJ2J1dHRvbic7XHJcblxyXG4vLyBQYW5lbHNcclxuaW1wb3J0IFwiLi9wYW5lbHMvbmF2aWdhdGlvbi10aW1pbmdcIjsgaW1wb3J0IHsgTmF2aWdhdGlvblRpbWluZ3NQYW5lbCB9IGZyb20gXCIuL3BhbmVscy9uYXZpZ2F0aW9uLXRpbWluZ1wiO1xyXG5cclxuZXhwb3J0IHtcclxuICAgIFRvb2xiYXIsXHJcbiAgICBCdXR0b24sXHJcbiAgICBOYXZpZ2F0aW9uVGltaW5nc1BhbmVsXHJcbn07XHJcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy9pbmRleC50cyIsImltcG9ydCBcIi4vYnV0dG9uXCI7IGltcG9ydCB7IEJ1dHRvbiB9IGZyb20gJy4vYnV0dG9uJztcclxuXHJcbi8qKiBEZXNjcmliZXMgYSBwYW5lbCB3aXRoaW4gdGhlIG9wZW5lZCB0b29sYmFyLiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIElQYW5lbFxyXG57XHJcbiAgICBuYW1lOiBzdHJpbmc7XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBSZW5kZXJzIHRoZSBwYW5lbC5cclxuICAgICAqIEBwYXJhbSB0YXJnZXQgVGhlIEhUTUwgZWxlbWVudCB0byBjb250YWluIHRoaXMgcGFuZWwuXHJcbiAgICAgKi9cclxuICAgIHJlbmRlcih0YXJnZXQ6IEhUTUxFbGVtZW50KTogdm9pZDtcclxuXHJcbiAgICAvKipcclxuICAgICAqIEdldHMgdGhlIGJ1dHRvbnMgcHJvdmlkZWQgYnkgdGhpcyBwYW5lbCB0byBiZSBkaXNwbGF5ZWQgaW4gdGhlIGNvbGxhcHNlZCB0b29sYmFyLlxyXG4gICAgICovXHJcbiAgICBnZXRCdXR0b25zKCk6IEJ1dHRvbltdO1xyXG59XG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL2lwYW5lbC50cyJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file diff --git a/index.html b/index.html index 17a0960..06018aa 100644 --- a/index.html +++ b/index.html @@ -15,32 +15,9 @@ } var startFunc = function() { - /** Fake a panel with a single button */ - var fakePanel = { - getButtons: function () { - return [new PerfToolbar.Button( - '#', - function () { return '1'}, - function () { return 'gray'} - )]; - } - }; - - /** Fake another panel with a single button */ - var fakePanel2 = { - getButtons: function () { - return [new PerfToolbar.Button( - '#', - function () { return '2'}, - function () { return 'lightgray'} - )]; - } - }; - /** Configure this to include the panels you need */ (new PerfToolbar.Toolbar([ - fakePanel, - fakePanel2 + new PerfToolbar.NavigationTimingsPanel() ])).render(); /** End configuration */ } diff --git a/src/index.ts b/src/index.ts index 5b4551d..d83ab36 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,12 @@ +// Core import "./toolbar"; import { Toolbar } from "toolbar"; import "./button"; import { Button } from 'button'; -export {Toolbar, Button}; +// Panels +import "./panels/navigation-timing"; import { NavigationTimingsPanel } from "./panels/navigation-timing"; + +export { + Toolbar, + Button, + NavigationTimingsPanel +}; diff --git a/src/panels/navigation-timing.ts b/src/panels/navigation-timing.ts new file mode 100644 index 0000000..ddf4b1a --- /dev/null +++ b/src/panels/navigation-timing.ts @@ -0,0 +1,72 @@ +import { IPanel } from 'ipanel'; +import { Button } from 'button'; + +export class NavigationTimingsPanel implements IPanel +{ + name: "Navigation Timings"; + + render(target: HTMLElement): void { + const t = performance.timing; + + target.innerHTML = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Get Connected${(t.connectEnd - t.domainLookupStart).toFixed(2)} ms
DNS Lookup${(t.domainLookupEnd - t.domainLookupStart).toFixed(2)} ms
SSL${(t.connectEnd - t.connectStart).toFixed(2)} ms
Get Content${(t.responseEnd - t.requestStart).toFixed(2)} ms
Waiting for Server${(t.responseStart - t.requestStart).toFixed(2)} ms
Time To Download${(t.responseEnd - t.responseStart).toFixed(2)} ms
Get Ready
Parse Content${(t.domInteractive - t.responseEnd).toFixed(2)} ms
Deferred Scripts${(t.domContentLoadedEventEnd - t.domInteractive).toFixed(2)} ms
DOM Complete${(t.domComplete - t.domContentLoadedEventEnd).toFixed(2)} ms
Load Event${(t.loadEventEnd - t.loadEventStart).toFixed(2)} ms
Total Load${(t.loadEventEnd - t.navigationStart).toFixed(2)} ms
+ `; + } + + getButtons(): Button[] { + return [new Button( + '⏱️', + () => `${(performance.timing.loadEventEnd - performance.timing.navigationStart).toFixed(2)} ms`, + () => performance.timing.loadEventEnd - performance.timing.navigationStart < 500 ? "green" : "red" + )]; + } + +} \ No newline at end of file From 245a0db47d06116408272d85d1dc4f001203e4a4 Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Thu, 21 Dec 2017 14:06:37 -0800 Subject: [PATCH 03/10] get bundle out of this branch --- dist/bundle.js | 201 ------------------------------------------------- 1 file changed, 201 deletions(-) delete mode 100644 dist/bundle.js diff --git a/dist/bundle.js b/dist/bundle.js deleted file mode 100644 index b09d9b6..0000000 --- a/dist/bundle.js +++ /dev/null @@ -1,201 +0,0 @@ -var PerfToolbar = -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 3); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Button; }); -/** Describes a button to be displayed in the collapsed toolbar. */ -var Button = /** @class */ (function () { - /** - * Create the button. - * @param emoji The icon for the button. The intention is to use a single character emoji - * but it's just a string, so anything goes. - * @param getValue Gets the displayed value for the button. - * @param getColor Gets the background color for the button. - */ - function Button(emoji, getValue, getColor) { - this.emoji = emoji; - this.getValue = getValue; - this.getColor = getColor; - } - /** - * Renders the button by adding it as a new child. - * @param container The DOM node that should contain this button. - */ - Button.prototype.render = function (container) { - var li = document.createElement("li"); - li.setAttribute("style", "background-color:" + this.getColor()); - li.innerText = this.emoji + " " + this.getValue(); - container.appendChild(li); - }; - return Button; -}()); - - - -/***/ }), -/* 1 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Toolbar; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ipanel__ = __webpack_require__(4); - -/** Describes the toolbar. */ -var Toolbar = /** @class */ (function () { - /** - * Creates the toolbar. - * @param panels The panels to be displayed when the toolbar is opened. - * @param container Optional parameter that defaults to the body of the HTML page. - */ - function Toolbar(panels, container) { - if (container === void 0) { container = window.document.body; } - this.panels = panels; - this.container = container; - this.root = document.createElement("div"); - container.appendChild(this.root); - } - Toolbar.prototype.render = function () { - // clear all children - this.container.innerHTML = ""; - var ul = document.createElement("ul"); - for (var _i = 0, _a = this.panels; _i < _a.length; _i++) { - var p = _a[_i]; - for (var _b = 0, _c = p.getButtons(); _b < _c.length; _b++) { - var b = _c[_b]; - b.render(ul); - } - } - this.container.appendChild(ul); - }; - return Toolbar; -}()); - - - -/***/ }), -/* 2 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return NavigationTimingsPanel; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_button__ = __webpack_require__(0); - -var NavigationTimingsPanel = /** @class */ (function () { - function NavigationTimingsPanel() { - } - NavigationTimingsPanel.prototype.render = function (target) { - var t = performance.timing; - target.innerHTML = "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Get Connected" + (t.connectEnd - t.domainLookupStart).toFixed(2) + " ms
DNS Lookup" + (t.domainLookupEnd - t.domainLookupStart).toFixed(2) + " ms
SSL" + (t.connectEnd - t.connectStart).toFixed(2) + " ms
Get Content" + (t.responseEnd - t.requestStart).toFixed(2) + " ms
Waiting for Server" + (t.responseStart - t.requestStart).toFixed(2) + " ms
Time To Download" + (t.responseEnd - t.responseStart).toFixed(2) + " ms
Get Ready
Parse Content" + (t.domInteractive - t.responseEnd).toFixed(2) + " ms
Deferred Scripts" + (t.domContentLoadedEventEnd - t.domInteractive).toFixed(2) + " ms
DOM Complete" + (t.domComplete - t.domContentLoadedEventEnd).toFixed(2) + " ms
Load Event" + (t.loadEventEnd - t.loadEventStart).toFixed(2) + " ms
Total Load" + (t.loadEventEnd - t.navigationStart).toFixed(2) + " ms
\n "; - }; - NavigationTimingsPanel.prototype.getButtons = function () { - return [new __WEBPACK_IMPORTED_MODULE_0_button__["a" /* Button */]('⏱️', function () { return (performance.timing.loadEventEnd - performance.timing.navigationStart).toFixed(2) + " ms"; }, function () { return performance.timing.loadEventEnd - performance.timing.navigationStart < 500 ? "green" : "red"; })]; - }; - return NavigationTimingsPanel; -}()); - - - -/***/ }), -/* 3 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__toolbar__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_toolbar__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__button__ = __webpack_require__(0); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_button__ = __webpack_require__(0); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__panels_navigation_timing__ = __webpack_require__(2); -/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "Toolbar", function() { return __WEBPACK_IMPORTED_MODULE_1_toolbar__["a"]; }); -/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "Button", function() { return __WEBPACK_IMPORTED_MODULE_3_button__["a"]; }); -/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "NavigationTimingsPanel", function() { return __WEBPACK_IMPORTED_MODULE_4__panels_navigation_timing__["a"]; }); -// Core - - - - -// Panels - - - - - -/***/ }), -/* 4 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__button__ = __webpack_require__(0); - - - -/***/ }) -/******/ ]); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgM2UwZWJmZmMwNjgxMDM3MDJlNzIiLCJ3ZWJwYWNrOi8vLy4vc3JjL2J1dHRvbi50cyIsIndlYnBhY2s6Ly8vLi9zcmMvdG9vbGJhci50cyIsIndlYnBhY2s6Ly8vLi9zcmMvcGFuZWxzL25hdmlnYXRpb24tdGltaW5nLnRzIiwid2VicGFjazovLy8uL3NyYy9pbmRleC50cyIsIndlYnBhY2s6Ly8vLi9zcmMvaXBhbmVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLG1DQUEyQiwwQkFBMEIsRUFBRTtBQUN2RCx5Q0FBaUMsZUFBZTtBQUNoRDtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw4REFBc0QsK0RBQStEOztBQUVySDtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7O0FDN0RBO0FBQUEsbUVBQW1FO0FBQ25FO0lBRUk7Ozs7OztPQU1HO0lBQ0gsZ0JBQW1CLEtBQWEsRUFBUyxRQUFzQixFQUFTLFFBQXNCO1FBQTNFLFVBQUssR0FBTCxLQUFLLENBQVE7UUFBUyxhQUFRLEdBQVIsUUFBUSxDQUFjO1FBQVMsYUFBUSxHQUFSLFFBQVEsQ0FBYztJQUc5RixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksdUJBQU0sR0FBYixVQUFjLFNBQXNCO1FBRWhDLElBQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEMsRUFBRSxDQUFDLFlBQVksQ0FBQyxPQUFPLEVBQUUsc0JBQW9CLElBQUksQ0FBQyxRQUFR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Q0FBQyxZQUFZLEdBQUcsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsNERBRzVELENBQUM7SUFDTixDQUFDO0lBRUQsMkNBQVUsR0FBVjtRQUNJLE1BQU0sQ0FBQyxDQUFDLElBQUksc0RBQU0sQ0FDZCxJQUFJLEVBQ0osY0FBTSxPQUFHLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFFBQUssRUFBekYsQ0FBeUYsRUFDL0YsY0FBTSxrQkFBVyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxlQUFlLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBNUYsQ0FBNEYsQ0FDckcsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQUVMLDZCQUFDO0FBQUQsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7Ozs7QUN2RUQ7QUFBQSxPQUFPO0FBQ1k7QUFBbUM7QUFDcEM7QUFBaUM7QUFFbkQsU0FBUztBQUMyQjtBQUFxRTtBQU12Rzs7Ozs7Ozs7O0FDWGdCIiwiZmlsZSI6ImJ1bmRsZS5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKSB7XG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG4gXHRcdH1cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGk6IG1vZHVsZUlkLFxuIFx0XHRcdGw6IGZhbHNlLFxuIFx0XHRcdGV4cG9ydHM6IHt9XG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmwgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb24gZm9yIGhhcm1vbnkgZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kID0gZnVuY3Rpb24oZXhwb3J0cywgbmFtZSwgZ2V0dGVyKSB7XG4gXHRcdGlmKCFfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZXhwb3J0cywgbmFtZSkpIHtcbiBcdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgbmFtZSwge1xuIFx0XHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcbiBcdFx0XHRcdGVudW1lcmFibGU6IHRydWUsXG4gXHRcdFx0XHRnZXQ6IGdldHRlclxuIFx0XHRcdH0pO1xuIFx0XHR9XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IDMpO1xuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIHdlYnBhY2svYm9vdHN0cmFwIDNlMGViZmZjMDY4MTAzNzAyZTcyIiwiLyoqIERlc2NyaWJlcyBhIGJ1dHRvbiB0byBiZSBkaXNwbGF5ZWQgaW4gdGhlIGNvbGxhcHNlZCB0b29sYmFyLiAqL1xyXG5leHBvcnQgY2xhc3MgQnV0dG9uXHJcbntcclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlIHRoZSBidXR0b24uXHJcbiAgICAgKiBAcGFyYW0gZW1vamkgVGhlIGljb24gZm9yIHRoZSBidXR0b24uIFRoZSBpbnRlbnRpb24gaXMgdG8gdXNlIGEgc2luZ2xlIGNoYXJhY3RlciBlbW9qaVxyXG4gICAgICogICBidXQgaXQncyBqdXN0IGEgc3RyaW5nLCBzbyBhbnl0aGluZyBnb2VzLlxyXG4gICAgICogQHBhcmFtIGdldFZhbHVlIEdldHMgdGhlIGRpc3BsYXllZCB2YWx1ZSBmb3IgdGhlIGJ1dHRvbi5cclxuICAgICAqIEBwYXJhbSBnZXRDb2xvciBHZXRzIHRoZSBiYWNrZ3JvdW5kIGNvbG9yIGZvciB0aGUgYnV0dG9uLlxyXG4gICAgICovXHJcbiAgICBjb25zdHJ1Y3RvcihwdWJsaWMgZW1vamk6IHN0cmluZywgcHVibGljIGdldFZhbHVlOiAoKSA9PiBzdHJpbmcsIHB1YmxpYyBnZXRDb2xvcjogKCkgPT4gc3RyaW5nKVxyXG4gICAge1xyXG5cclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFJlbmRlcnMgdGhlIGJ1dHRvbiBieSBhZGRpbmcgaXQgYXMgYSBuZXcgY2hpbGQuXHJcbiAgICAgKiBAcGFyYW0gY29udGFpbmVyIFRoZSBET00gbm9kZSB0aGF0IHNob3VsZCBjb250YWluIHRoaXMgYnV0dG9uLlxyXG4gICAgICovXHJcbiAgICBwdWJsaWMgcmVuZGVyKGNvbnRhaW5lcjogSFRNTEVsZW1lbnQpOiB2b2lkXHJcbiAgICB7XHJcbiAgICAgICAgY29uc3QgbGkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwibGlcIik7XHJcbiAgICAgICAgbGkuc2V0QXR0cmlidXRlKFwic3R5bGVcIiwgYGJhY2tncm91bmQtY29sb3I6JHt0aGlzLmdldENvbG9yKCl9YCk7XHJcbiAgICAgICAgbGkuaW5uZXJUZXh0ID0gYCR7dGhpcy5lbW9qaX0gJHt0aGlzLmdldFZhbHVlKCl9YDtcclxuXHJcbiAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKGxpKTtcclxuICAgIH1cclxufVxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy9idXR0b24udHMiLCJpbXBvcnQgXCIuL2lwYW5lbFwiOyBpbXBvcnQgeyBJUGFuZWwgfSBmcm9tICcuL2lwYW5lbCc7XHJcblxyXG4vKiogRGVzY3JpYmVzIHRoZSB0b29sYmFyLiAqL1xyXG5leHBvcnQgY2xhc3MgVG9vbGJhclxyXG57XHJcbiAgICBwcml2YXRlIHJvb3Q6IEhUTUxFbGVtZW50O1xyXG5cclxuICAgIC8qKlxyXG4gICAgICogQ3JlYXRlcyB0aGUgdG9vbGJhci5cclxuICAgICAqIEBwYXJhbSBwYW5lbHMgVGhlIHBhbmVscyB0byBiZSBkaXNwbGF5ZWQgd2hlbiB0aGUgdG9vbGJhciBpcyBvcGVuZWQuXHJcbiAgICAgKiBAcGFyYW0gY29udGFpbmVyIE9wdGlvbmFsIHBhcmFtZXRlciB0aGF0IGRlZmF1bHRzIHRvIHRoZSBib2R5IG9mIHRoZSBIVE1MIHBhZ2UuXHJcbiAgICAgKi9cclxuICAgIGNvbnN0cnVjdG9yKHByaXZhdGUgcGFuZWxzOiBJUGFuZWxbXSwgcHJpdmF0ZSBjb250YWluZXI6IEhUTUxFbGVtZW50ID0gd2luZG93LmRvY3VtZW50LmJvZHkpXHJcbiAgICB7XHJcbiAgICAgICAgdGhpcy5yb290ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImRpdlwiKTtcclxuICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQodGhpcy5yb290KTtcclxuICAgIH1cclxuXHJcbiAgICBwdWJsaWMgcmVuZGVyKCk6IHZvaWRcclxuICAgIHtcclxuICAgICAgICAvLyBjbGVhciBhbGwgY2hpbGRyZW5cclxuICAgICAgICB0aGlzLmNvbnRhaW5lci5pbm5lckhUTUwgPSBcIlwiO1xyXG5cclxuICAgICAgICBjb25zdCB1bCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJ1bFwiKTtcclxuICAgICAgICBmb3IobGV0IHAgb2YgdGhpcy5wYW5lbHMpXHJcbiAgICAgICAge1xyXG4gICAgICAgICAgICBmb3IobGV0IGIgb2YgcC5nZXRCdXR0b25zKCkpXHJcbiAgICAgICAgICAgIHtcclxuICAgICAgICAgICAgICAgIGIucmVuZGVyKHVsKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgdGhpcy5jb250YWluZXIuYXBwZW5kQ2hpbGQodWwpO1xyXG4gICAgfVxyXG59XHJcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy90b29sYmFyLnRzIiwiaW1wb3J0IHsgSVBhbmVsIH0gZnJvbSAnaXBhbmVsJztcclxuaW1wb3J0IHsgQnV0dG9uIH0gZnJvbSAnYnV0dG9uJztcclxuXHJcbmV4cG9ydCBjbGFzcyBOYXZpZ2F0aW9uVGltaW5nc1BhbmVsIGltcGxlbWVudHMgSVBhbmVsXHJcbntcclxuICAgIG5hbWU6IFwiTmF2aWdhdGlvbiBUaW1pbmdzXCI7XHJcblxyXG4gICAgcmVuZGVyKHRhcmdldDogSFRNTEVsZW1lbnQpOiB2b2lkIHtcclxuICAgICAgICBjb25zdCB0ID0gcGVyZm9ybWFuY2UudGltaW5nO1xyXG5cclxuICAgICAgICB0YXJnZXQuaW5uZXJIVE1MID0gYFxyXG4gICAgICAgIDx0YWJsZT5cclxuICAgICAgICAgICAgPHRyPlxyXG4gICAgICAgICAgICAgICAgPHRoPkdldCBDb25uZWN0ZWQ8L3RoPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQuY29ubmVjdEVuZCAtIHQuZG9tYWluTG9va3VwU3RhcnQpLnRvRml4ZWQoMil9IG1zPC90ZD5cclxuICAgICAgICAgICAgPC90cj5cclxuICAgICAgICAgICAgPHRyPlxyXG4gICAgICAgICAgICAgICAgPHRkPkROUyBMb29rdXA8L3RkPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQuZG9tYWluTG9va3VwRW5kIC0gdC5kb21haW5Mb29rdXBTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+U1NMPC90ZD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LmNvbm5lY3RFbmQgLSB0LmNvbm5lY3RTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGg+R2V0IENvbnRlbnQ8L3RoPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQucmVzcG9uc2VFbmQgLSB0LnJlcXVlc3RTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+V2FpdGluZyBmb3IgU2VydmVyPC90ZD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LnJlc3BvbnNlU3RhcnQgLSB0LnJlcXVlc3RTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+VGltZSBUbyBEb3dubG9hZDwvdGQ+XHJcbiAgICAgICAgICAgICAgICA8dGQ+JHsodC5yZXNwb25zZUVuZCAtIHQucmVzcG9uc2VTdGFydCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGggY29sc3Bhbj0yPkdldCBSZWFkeTwvdGg+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgICAgIDx0cj5cclxuICAgICAgICAgICAgICAgIDx0ZD5QYXJzZSBDb250ZW50PC90ZD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LmRvbUludGVyYWN0aXZlIC0gdC5yZXNwb25zZUVuZCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+RGVmZXJyZWQgU2NyaXB0czwvdGQ+XHJcbiAgICAgICAgICAgICAgICA8dGQ+JHsodC5kb21Db250ZW50TG9hZGVkRXZlbnRFbmQgLSB0LmRvbUludGVyYWN0aXZlKS50b0ZpeGVkKDIpfSBtczwvdGQ+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgICAgIDx0cj5cclxuICAgICAgICAgICAgICAgIDx0ZD5ET00gQ29tcGxldGU8L3RkPlxyXG4gICAgICAgICAgICAgICAgPHRkPiR7KHQuZG9tQ29tcGxldGUgLSB0LmRvbUNvbnRlbnRMb2FkZWRFdmVudEVuZCkudG9GaXhlZCgyKX0gbXM8L3RkPlxyXG4gICAgICAgICAgICA8L3RyPlxyXG4gICAgICAgICAgICA8dHI+XHJcbiAgICAgICAgICAgICAgICA8dGQ+TG9hZCBFdmVudDwvdGQ+XHJcbiAgICAgICAgICAgICAgICA8dGQ+JHsodC5sb2FkRXZlbnRFbmQgLSB0LmxvYWRFdmVudFN0YXJ0KS50b0ZpeGVkKDIpfSBtczwvdGQ+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgICAgIDx0cj5cclxuICAgICAgICAgICAgICAgIDx0aD5Ub3RhbCBMb2FkPC90aD5cclxuICAgICAgICAgICAgICAgIDx0ZD4keyh0LmxvYWRFdmVudEVuZCAtIHQubmF2aWdhdGlvblN0YXJ0KS50b0ZpeGVkKDIpfSBtczwvdGQ+XHJcbiAgICAgICAgICAgIDwvdHI+XHJcbiAgICAgICAgPC90YWJsZT5cclxuICAgICAgICBgO1xyXG4gICAgfVxyXG5cclxuICAgIGdldEJ1dHRvbnMoKTogQnV0dG9uW10ge1xyXG4gICAgICAgIHJldHVybiBbbmV3IEJ1dHRvbihcclxuICAgICAgICAgICAgJ+KPse+4jycsXHJcbiAgICAgICAgICAgICgpID0+IGAkeyhwZXJmb3JtYW5jZS50aW1pbmcubG9hZEV2ZW50RW5kIC0gcGVyZm9ybWFuY2UudGltaW5nLm5hdmlnYXRpb25TdGFydCkudG9GaXhlZCgyKX0gbXNgLFxyXG4gICAgICAgICAgICAoKSA9PiBwZXJmb3JtYW5jZS50aW1pbmcubG9hZEV2ZW50RW5kIC0gcGVyZm9ybWFuY2UudGltaW5nLm5hdmlnYXRpb25TdGFydCA8IDUwMCA/IFwiZ3JlZW5cIiA6IFwicmVkXCJcclxuICAgICAgICApXTtcclxuICAgIH1cclxuXHJcbn1cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gLi9zcmMvcGFuZWxzL25hdmlnYXRpb24tdGltaW5nLnRzIiwiLy8gQ29yZVxyXG5pbXBvcnQgXCIuL3Rvb2xiYXJcIjsgaW1wb3J0IHsgVG9vbGJhciB9IGZyb20gXCJ0b29sYmFyXCI7XHJcbmltcG9ydCBcIi4vYnV0dG9uXCI7IGltcG9ydCB7IEJ1dHRvbiB9IGZyb20gJ2J1dHRvbic7XHJcblxyXG4vLyBQYW5lbHNcclxuaW1wb3J0IFwiLi9wYW5lbHMvbmF2aWdhdGlvbi10aW1pbmdcIjsgaW1wb3J0IHsgTmF2aWdhdGlvblRpbWluZ3NQYW5lbCB9IGZyb20gXCIuL3BhbmVscy9uYXZpZ2F0aW9uLXRpbWluZ1wiO1xyXG5cclxuZXhwb3J0IHtcclxuICAgIFRvb2xiYXIsXHJcbiAgICBCdXR0b24sXHJcbiAgICBOYXZpZ2F0aW9uVGltaW5nc1BhbmVsXHJcbn07XHJcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyAuL3NyYy9pbmRleC50cyIsImltcG9ydCBcIi4vYnV0dG9uXCI7IGltcG9ydCB7IEJ1dHRvbiB9IGZyb20gJy4vYnV0dG9uJztcclxuXHJcbi8qKiBEZXNjcmliZXMgYSBwYW5lbCB3aXRoaW4gdGhlIG9wZW5lZCB0b29sYmFyLiAqL1xyXG5leHBvcnQgaW50ZXJmYWNlIElQYW5lbFxyXG57XHJcbiAgICBuYW1lOiBzdHJpbmc7XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBSZW5kZXJzIHRoZSBwYW5lbC5cclxuICAgICAqIEBwYXJhbSB0YXJnZXQgVGhlIEhUTUwgZWxlbWVudCB0byBjb250YWluIHRoaXMgcGFuZWwuXHJcbiAgICAgKi9cclxuICAgIHJlbmRlcih0YXJnZXQ6IEhUTUxFbGVtZW50KTogdm9pZDtcclxuXHJcbiAgICAvKipcclxuICAgICAqIEdldHMgdGhlIGJ1dHRvbnMgcHJvdmlkZWQgYnkgdGhpcyBwYW5lbCB0byBiZSBkaXNwbGF5ZWQgaW4gdGhlIGNvbGxhcHNlZCB0b29sYmFyLlxyXG4gICAgICovXHJcbiAgICBnZXRCdXR0b25zKCk6IEJ1dHRvbltdO1xyXG59XG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIC4vc3JjL2lwYW5lbC50cyJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file From 222f20f059c2a2e338de45c40c4dfd4b6afe880d Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Fri, 22 Dec 2017 16:00:32 -0800 Subject: [PATCH 04/10] Work in progress on adding a panel frame --- src/button.ts | 15 +++++++ src/formatter.ts | 23 ++++++++++ src/ipanel.ts | 12 ++++- src/panelframe.ts | 8 ++++ src/panels/navigation-timing.ts | 77 ++++++++++++++++++++++----------- test/formatter.spec.ts | 27 ++++++++++++ test/mock/panel.mock.ts | 4 ++ tslint.json | 3 +- 8 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 src/formatter.ts create mode 100644 src/panelframe.ts create mode 100644 test/formatter.spec.ts diff --git a/src/button.ts b/src/button.ts index 3528778..bfbb68f 100644 --- a/src/button.ts +++ b/src/button.ts @@ -1,12 +1,18 @@ +import { IPanel } from "./ipanel"; + export interface IButtonConfiguration { /** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes */ emoji?: string; + /** The panel that owns this button */ + parent?: IPanel; + /** Gets the background color for the button. */ getColor?(): string; /** Gets the displayed value for the button. */ getValue?(): string; + } /** Describes a button to be displayed in the collapsed toolbar. */ @@ -20,11 +26,14 @@ export class Button { /** Gets the displayed value for the button. */ public readonly getValue: (() => string); + public readonly parent: IPanel | undefined; + /** * Create the button. */ public constructor(config: IButtonConfiguration = {}) { this.emoji = config.emoji !== undefined ? config.emoji : ""; + this.parent = config.parent; /* tslint:disable no-unbound-method */ this.getValue = config.getValue !== undefined ? config.getValue : (): string => ""; this.getColor = config.getColor !== undefined ? config.getColor : (): string => ""; @@ -40,6 +49,12 @@ export class Button { li.setAttribute("style", `background-color:${this.getColor()}`); li.innerText = `${this.emoji} ${this.getValue()}`; + li.addEventListener("click", () => { + if (this.parent) { + this.parent.toggle(); + } + }); + container.appendChild(li); } } diff --git a/src/formatter.ts b/src/formatter.ts new file mode 100644 index 0000000..5752b9d --- /dev/null +++ b/src/formatter.ts @@ -0,0 +1,23 @@ +/** + * A class for formatting strings or numbers. + */ +export class Formatter { + /** The level of precision we want to see for numbers */ + public static readonly decimalPlaces: number = 2; + + /** + * Formats a duration for output. Makes sure the numbers are valid and only returns a certain number of decimal places. + * Invalid input returns a dash. + * @param end The end timestamp + * @param start The start timestamp + * @param decimalPlaces The number of decimal places to show. + */ + public static duration(end: number, start: number, decimalPlaces: number = Formatter.decimalPlaces): string { + if (isNaN(end) || isNaN(start)) { + return "-"; + } + + return (end - start).toFixed(decimalPlaces); + } + +} diff --git a/src/ipanel.ts b/src/ipanel.ts index 6530f15..0f8b829 100644 --- a/src/ipanel.ts +++ b/src/ipanel.ts @@ -1,7 +1,12 @@ import { Button } from "./button"; +export interface IPanelConstructor { + new (frame: PanelFrame): IPanel; +} + /** Describes a panel within the opened toolbar. */ export interface IPanel { + name: string; /** @@ -14,4 +19,9 @@ export interface IPanel { * @param target The HTML element to contain this panel. */ render(target: HTMLElement): void; -} \ No newline at end of file + + /** Toggles the visibility of this panel */ + toggle(): void; +} + +const createPanel: (ctor: IPanelConstructor, frame: PanelFrame) => IPanel = (ctor: IPanelConstructor, frame: PanelFrame): IPanel => new ctor(frame); diff --git a/src/panelframe.ts b/src/panelframe.ts new file mode 100644 index 0000000..a92aff5 --- /dev/null +++ b/src/panelframe.ts @@ -0,0 +1,8 @@ +/** + * Responsible for holding and displaying panels + */ +class PanelFrame { + public toggle(): void { + throw new Error("NYI"); + } +} diff --git a/src/panels/navigation-timing.ts b/src/panels/navigation-timing.ts index ddf4b1a..e233982 100644 --- a/src/panels/navigation-timing.ts +++ b/src/panels/navigation-timing.ts @@ -1,72 +1,97 @@ -import { IPanel } from 'ipanel'; -import { Button } from 'button'; +import { Button } from "../button"; +import { Formatter } from "../formatter"; +import { IPanel } from "../ipanel"; -export class NavigationTimingsPanel implements IPanel -{ - name: "Navigation Timings"; +/** + * Provides a panel that shows the navigation timings for a page + */ +export class NavigationTimingsPanel implements IPanel { + /** The name of the panel */ + public name: "Navigation Timings"; - render(target: HTMLElement): void { - const t = performance.timing; + public constructor(frame: PanelFrame) { + + } + + /** + * Gets the buttons to be displayed + * @see IPanel.getButtons + */ + public getButtons(): Button[] { + const goalMs: number = 500; + + return [new Button({ + parent: this, + emoji: "⏱️", + getValue: (): string => `${Formatter.duration(performance.timing.loadEventEnd, performance.timing.navigationStart)} ms`, + getColor: (): string => performance.timing.loadEventEnd - performance.timing.navigationStart < goalMs ? "green" : "red", + })]; + } + + /** + * Renders the contents of the panel + * @see IPanel.render + */ + public render(target: HTMLElement): void { + const t: PerformanceTiming = performance.timing; target.innerHTML = ` - + - + - + - + - + - + - + - + - + - + - +
Get Connected${(t.connectEnd - t.domainLookupStart).toFixed(2)} ms${Formatter.duration(t.connectEnd, t.domainLookupStart)} ms
DNS Lookup${(t.domainLookupEnd - t.domainLookupStart).toFixed(2)} ms${Formatter.duration(t.domainLookupEnd, t.domainLookupStart)} ms
SSL${(t.connectEnd - t.connectStart).toFixed(2)} ms${Formatter.duration(t.connectEnd, t.connectStart)} ms
Get Content${(t.responseEnd - t.requestStart).toFixed(2)} ms${Formatter.duration(t.responseEnd, t.requestStart)} ms
Waiting for Server${(t.responseStart - t.requestStart).toFixed(2)} ms${Formatter.duration(t.responseStart, t.requestStart)} ms
Time To Download${(t.responseEnd - t.responseStart).toFixed(2)} ms${Formatter.duration(t.responseEnd, t.responseStart)} ms
Get Ready
Parse Content${(t.domInteractive - t.responseEnd).toFixed(2)} ms${Formatter.duration(t.domInteractive, t.responseEnd)} ms
Deferred Scripts${(t.domContentLoadedEventEnd - t.domInteractive).toFixed(2)} ms${Formatter.duration(t.domContentLoadedEventEnd, t.domInteractive)} ms
DOM Complete${(t.domComplete - t.domContentLoadedEventEnd).toFixed(2)} ms${Formatter.duration(t.domComplete, t.domContentLoadedEventEnd)} ms
Load Event${(t.loadEventEnd - t.loadEventStart).toFixed(2)} ms${Formatter.duration(t.loadEventEnd, t.loadEventStart)} ms
Total Load${(t.loadEventEnd - t.navigationStart).toFixed(2)} ms${Formatter.duration(t.loadEventEnd, t.navigationStart)} ms
`; } - getButtons(): Button[] { - return [new Button( - '⏱️', - () => `${(performance.timing.loadEventEnd - performance.timing.navigationStart).toFixed(2)} ms`, - () => performance.timing.loadEventEnd - performance.timing.navigationStart < 500 ? "green" : "red" - )]; + /** + * Toggles the display of this panel. + */ + public toggle(): void { + throw new Error("Method not implemented."); } - -} \ No newline at end of file +} diff --git a/test/formatter.spec.ts b/test/formatter.spec.ts new file mode 100644 index 0000000..ff9091d --- /dev/null +++ b/test/formatter.spec.ts @@ -0,0 +1,27 @@ +import { assert, expect } from "chai"; +import "mocha"; +import * as sinon from "sinon"; + +import { Formatter } from "../src/formatter"; + +describe("Formatter class", () => { + describe("duration method", () => { + it("should return a dash for invalid input", () => { + expect(Formatter.duration(undefined, 0)).to.equal("-", "invalid input in first parameter generates a dash"); + expect(Formatter.duration(0, undefined)).to.equal("-", "invalid input in second parameter generates a dash"); + }); + + it("should default to two decimal places", () => { + expect(Formatter.duration(0, 0)).to.equal("0.00"); + }); + + it("should allow custom number of decimals", () => { + expect(Formatter.duration(0, 0, 1)).to.equal("0.0"); + }); + + it("should do subtract the start from the end", () => { + expect(Formatter.duration(1, 0, 0)).to.equal("1"); + expect(Formatter.duration(0, 1, 0)).to.equal("-1"); + }); + }); +}); diff --git a/test/mock/panel.mock.ts b/test/mock/panel.mock.ts index 2fd080f..aad7c24 100644 --- a/test/mock/panel.mock.ts +++ b/test/mock/panel.mock.ts @@ -15,4 +15,8 @@ export class MockPanel implements IPanel { public render(target: HTMLElement): void { throw new Error("Method not implemented."); } + + public toggle(): void { + throw new Error("Method not implemented."); + } } diff --git a/tslint.json b/tslint.json index 229701d..3d5924e 100644 --- a/tslint.json +++ b/tslint.json @@ -25,7 +25,8 @@ "prefer-function-over-method": false, "no-implicit-dependencies": [true, "dev"], "no-import-side-effect": false, - "strict-boolean-expressions": [true, "allow-undefined-union"] + "strict-boolean-expressions": [true, "allow-undefined-union"], + "no-unnecessary-class": false }, "jsRules": { "max-line-length": { From 974e5894e414eacdf10fecde9c7c8d7d9bc87018 Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Tue, 2 Jan 2018 14:36:36 -0800 Subject: [PATCH 05/10] navigation timings basic --- index.html | 4 +--- src/button.ts | 10 ++++++--- src/ipanel.ts | 3 +-- src/panelframe.ts | 33 ++++++++++++++++++++++++++--- src/panels/navigation-timing.ts | 8 +++++-- src/toolbar.ts | 37 +++++++++++++++------------------ tslint.json | 3 ++- 7 files changed, 64 insertions(+), 34 deletions(-) diff --git a/index.html b/index.html index 06018aa..b8dd522 100644 --- a/index.html +++ b/index.html @@ -16,9 +16,7 @@ var startFunc = function() { /** Configure this to include the panels you need */ - (new PerfToolbar.Toolbar([ - new PerfToolbar.NavigationTimingsPanel() - ])).render(); + (new PerfToolbar.Toolbar([PerfToolbar.NavigationTimingsPanel])).render(); /** End configuration */ } diff --git a/src/button.ts b/src/button.ts index bfbb68f..c7c76e3 100644 --- a/src/button.ts +++ b/src/button.ts @@ -1,10 +1,13 @@ import { IPanel } from "./ipanel"; +/** + * The configuration options for constructing a button. + */ export interface IButtonConfiguration { - /** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes */ + /** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes. */ emoji?: string; - /** The panel that owns this button */ + /** The panel that owns this button. */ parent?: IPanel; /** Gets the background color for the button. */ @@ -17,7 +20,7 @@ export interface IButtonConfiguration { /** Describes a button to be displayed in the collapsed toolbar. */ export class Button { - /** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes */ + /** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes. */ public readonly emoji: string; /** Gets the background color for the button. */ @@ -26,6 +29,7 @@ export class Button { /** Gets the displayed value for the button. */ public readonly getValue: (() => string); + /** The panel that provides this button. */ public readonly parent: IPanel | undefined; /** diff --git a/src/ipanel.ts b/src/ipanel.ts index 0f8b829..16fd5dd 100644 --- a/src/ipanel.ts +++ b/src/ipanel.ts @@ -1,4 +1,5 @@ import { Button } from "./button"; +import { PanelFrame } from "./panelframe"; export interface IPanelConstructor { new (frame: PanelFrame): IPanel; @@ -23,5 +24,3 @@ export interface IPanel { /** Toggles the visibility of this panel */ toggle(): void; } - -const createPanel: (ctor: IPanelConstructor, frame: PanelFrame) => IPanel = (ctor: IPanelConstructor, frame: PanelFrame): IPanel => new ctor(frame); diff --git a/src/panelframe.ts b/src/panelframe.ts index a92aff5..343d9d1 100644 --- a/src/panelframe.ts +++ b/src/panelframe.ts @@ -1,8 +1,35 @@ +import { IPanel } from "./ipanel"; + /** * Responsible for holding and displaying panels */ -class PanelFrame { - public toggle(): void { - throw new Error("NYI"); +export class PanelFrame { + + /** The element that represents the frame in the DOM. */ + private frame: HTMLDivElement; + + /** Tracks the visible state of the frame. */ + private isVisible: boolean; + + /** + * Creates the panel frame. + * @param toolbarRoot The DOM element to contain the frame. Should be the root of the toolbar. + */ + public constructor(toolbarRoot: HTMLElement) { + this.frame = document.createElement("div"); + toolbarRoot.appendChild(this.frame); + + this.isVisible = false; + } + + /** Show the provided panel, or hide the displayed panel. */ + public toggle(panel: IPanel): void { + if (this.isVisible) { + this.frame.innerHTML = ""; + } else { + panel.render(this.frame); + } + + this.isVisible = !this.isVisible; } } diff --git a/src/panels/navigation-timing.ts b/src/panels/navigation-timing.ts index e233982..916e5c5 100644 --- a/src/panels/navigation-timing.ts +++ b/src/panels/navigation-timing.ts @@ -1,6 +1,7 @@ import { Button } from "../button"; import { Formatter } from "../formatter"; import { IPanel } from "../ipanel"; +import { PanelFrame } from "../panelframe"; /** * Provides a panel that shows the navigation timings for a page @@ -9,8 +10,11 @@ export class NavigationTimingsPanel implements IPanel { /** The name of the panel */ public name: "Navigation Timings"; - public constructor(frame: PanelFrame) { + /** The frame that displays this panel. */ + private frame: PanelFrame; + public constructor(frame: PanelFrame) { + this.frame = frame; } /** @@ -92,6 +96,6 @@ export class NavigationTimingsPanel implements IPanel { * Toggles the display of this panel. */ public toggle(): void { - throw new Error("Method not implemented."); + this.frame.toggle(this); } } diff --git a/src/toolbar.ts b/src/toolbar.ts index 81287c1..ffa0804 100644 --- a/src/toolbar.ts +++ b/src/toolbar.ts @@ -1,42 +1,39 @@ -import { IPanel } from "./ipanel"; +import { IPanel, IPanelConstructor } from "./ipanel"; +import { PanelFrame } from "./panelframe"; /** Describes the toolbar. */ export class Toolbar { - /** The container that will hold the toolbar */ - private container: HTMLElement; - /** The panels that will be displayed in the toolbar */ private panels: IPanel[]; /** The root element of the toolbar. */ - private root: HTMLElement; + private toolbarRoot: HTMLElement; /** * Creates the toolbar. - * @param panels The panels to be displayed when the toolbar is opened. - * @param container Optional parameter that defaults to the body of the HTML page. + * @param panels Classes for the panels to be displayed when the toolbar is opened. + * @param container Optional parameter for the element that contains the toolbar. It defaults to the body of the HTML page. */ - public constructor(panels: IPanel[], container: HTMLElement = window.document.body) { - this.panels = panels; - this.container = container; - this.root = document.createElement("div"); - container.appendChild(this.root); + public constructor(panels: IPanelConstructor[], container: HTMLElement = window.document.body) { + this.toolbarRoot = document.createElement("div"); + container.appendChild(this.toolbarRoot); + + // Construct the frame and the panels that use it + const frame: PanelFrame = new PanelFrame(this.toolbarRoot); + this.panels = panels.map((panel: IPanelConstructor): IPanel => new panel(frame)); } /** * Renders the toolbar. */ public render(): void { - // Clear all children - this.container.innerHTML = ""; - - const ul: HTMLUListElement = document.createElement("ul"); - for (const p of this.panels) { - for (const b of p.getButtons()) { - b.render(ul); + const listOfButtons: HTMLUListElement = document.createElement("ul"); + for (const panel of this.panels) { + for (const button of panel.getButtons()) { + button.render(listOfButtons); } } - this.container.appendChild(ul); + this.toolbarRoot.appendChild(listOfButtons); } } diff --git a/tslint.json b/tslint.json index 3d5924e..43eb403 100644 --- a/tslint.json +++ b/tslint.json @@ -26,7 +26,8 @@ "no-implicit-dependencies": [true, "dev"], "no-import-side-effect": false, "strict-boolean-expressions": [true, "allow-undefined-union"], - "no-unnecessary-class": false + "no-unnecessary-class": false, + "interface-over-type-literal": false }, "jsRules": { "max-line-length": { From 1d05ea77b1cc702088564f794485986da97d02b0 Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Tue, 2 Jan 2018 17:56:20 -0800 Subject: [PATCH 06/10] Construct panels with configs --- index.html | 11 +++++++++-- src/ipanel.ts | 14 ++++++++++++-- src/panelframe.ts | 1 + src/panels/navigation-timing.ts | 23 ++++++++++++++++++----- src/toolbar.ts | 9 ++++++--- test/mock/panel.mock.ts | 15 ++++++++++++--- test/toolbar.spec.ts | 15 ++++++++++----- 7 files changed, 68 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index b8dd522..cc3a10b 100644 --- a/index.html +++ b/index.html @@ -15,9 +15,16 @@ } var startFunc = function() { - /** Configure this to include the panels you need */ - (new PerfToolbar.Toolbar([PerfToolbar.NavigationTimingsPanel])).render(); + (new PerfToolbar.Toolbar([ + /** Configure this to include the panels you need */ + { + panel: PerfToolbar.NavigationTimingsPanel, + config: { + goalMs: 20 + } + }, /** End configuration */ + ])).render(); } if('requestIdleCallback' in window) diff --git a/src/ipanel.ts b/src/ipanel.ts index 16fd5dd..dea3ec9 100644 --- a/src/ipanel.ts +++ b/src/ipanel.ts @@ -1,8 +1,18 @@ import { Button } from "./button"; import { PanelFrame } from "./panelframe"; -export interface IPanelConstructor { - new (frame: PanelFrame): IPanel; +export interface IPanelWithConfiguration { + config: C; + panel: IPanelConstructor; +} + +export interface IPanelConstructor { + new (frame: PanelFrame, config: C): P; +} + +// tslint:disable-next-line:no-empty-interface +export interface IPanelConfig { + } /** Describes a panel within the opened toolbar. */ diff --git a/src/panelframe.ts b/src/panelframe.ts index 343d9d1..368c8cf 100644 --- a/src/panelframe.ts +++ b/src/panelframe.ts @@ -17,6 +17,7 @@ export class PanelFrame { */ public constructor(toolbarRoot: HTMLElement) { this.frame = document.createElement("div"); + this.frame.setAttribute("id", "PTB_frame"); toolbarRoot.appendChild(this.frame); this.isVisible = false; diff --git a/src/panels/navigation-timing.ts b/src/panels/navigation-timing.ts index 916e5c5..a82a064 100644 --- a/src/panels/navigation-timing.ts +++ b/src/panels/navigation-timing.ts @@ -1,8 +1,19 @@ import { Button } from "../button"; import { Formatter } from "../formatter"; -import { IPanel } from "../ipanel"; +import { IPanel, IPanelConfig } from "../ipanel"; import { PanelFrame } from "../panelframe"; +/** Describes the configuration options available for the network panel */ +export interface INavigationTimingsPanelConfig extends IPanelConfig { + /** The goal for the load duration */ + goalMs: number; +} + +/** A set of default configuration options for the navigation timings panel */ +const navigationTimingsPanelDefaultConfig: INavigationTimingsPanelConfig = { + goalMs: 500, +}; + /** * Provides a panel that shows the navigation timings for a page */ @@ -10,11 +21,15 @@ export class NavigationTimingsPanel implements IPanel { /** The name of the panel */ public name: "Navigation Timings"; + /** The settings for this panel. */ + private config: INavigationTimingsPanelConfig; + /** The frame that displays this panel. */ private frame: PanelFrame; - public constructor(frame: PanelFrame) { + public constructor(frame: PanelFrame, config?: INavigationTimingsPanelConfig) { this.frame = frame; + this.config = config !== undefined ? config : navigationTimingsPanelDefaultConfig; } /** @@ -22,13 +37,11 @@ export class NavigationTimingsPanel implements IPanel { * @see IPanel.getButtons */ public getButtons(): Button[] { - const goalMs: number = 500; - return [new Button({ parent: this, emoji: "⏱️", getValue: (): string => `${Formatter.duration(performance.timing.loadEventEnd, performance.timing.navigationStart)} ms`, - getColor: (): string => performance.timing.loadEventEnd - performance.timing.navigationStart < goalMs ? "green" : "red", + getColor: (): string => performance.timing.loadEventEnd - performance.timing.navigationStart < this.config.goalMs ? "green" : "red", })]; } diff --git a/src/toolbar.ts b/src/toolbar.ts index ffa0804..337df84 100644 --- a/src/toolbar.ts +++ b/src/toolbar.ts @@ -1,4 +1,4 @@ -import { IPanel, IPanelConstructor } from "./ipanel"; +import { IPanel, IPanelConfig, IPanelWithConfiguration } from "./ipanel"; import { PanelFrame } from "./panelframe"; /** Describes the toolbar. */ @@ -14,13 +14,15 @@ export class Toolbar { * @param panels Classes for the panels to be displayed when the toolbar is opened. * @param container Optional parameter for the element that contains the toolbar. It defaults to the body of the HTML page. */ - public constructor(panels: IPanelConstructor[], container: HTMLElement = window.document.body) { + public constructor(panels: Array>, container: HTMLElement = window.document.body) { this.toolbarRoot = document.createElement("div"); + this.toolbarRoot.setAttribute("id", "PTB_root"); container.appendChild(this.toolbarRoot); // Construct the frame and the panels that use it const frame: PanelFrame = new PanelFrame(this.toolbarRoot); - this.panels = panels.map((panel: IPanelConstructor): IPanel => new panel(frame)); + this.panels = panels.map((panelWithConfig: IPanelWithConfiguration): IPanel => + new panelWithConfig.panel(frame, panelWithConfig.config)); } /** @@ -28,6 +30,7 @@ export class Toolbar { */ public render(): void { const listOfButtons: HTMLUListElement = document.createElement("ul"); + listOfButtons.setAttribute("id", "PTB_buttons"); for (const panel of this.panels) { for (const button of panel.getButtons()) { button.render(listOfButtons); diff --git a/test/mock/panel.mock.ts b/test/mock/panel.mock.ts index aad7c24..25a9d96 100644 --- a/test/mock/panel.mock.ts +++ b/test/mock/panel.mock.ts @@ -1,15 +1,24 @@ import { Button } from "../../src/button"; import { IPanel } from "../../src/ipanel"; +import { PanelFrame } from "../../src/panelframe"; + +export interface IMockPanelConfig { + getButtons(): Button[]; +} + +export const mockPanelConfig: IMockPanelConfig = { + getButtons: (): Button[] => [new Button({})], +}; export class MockPanel implements IPanel { public name: string = "Mock Panel"; - public constructor(getButtons: () => Button[]) { - this.getButtons = getButtons; + public constructor(frame: PanelFrame, config: IMockPanelConfig) { + this.getButtons = config.getButtons; } public getButtons(): Button[] { - return undefined; + throw new Error("Method must be overridden by passing a new getter in the constructor."); } public render(target: HTMLElement): void { diff --git a/test/toolbar.spec.ts b/test/toolbar.spec.ts index 8ffdcf8..b221652 100644 --- a/test/toolbar.spec.ts +++ b/test/toolbar.spec.ts @@ -3,9 +3,9 @@ import "mocha"; import * as sinon from "sinon"; import { Button } from "../src/button"; -import { IPanel } from "../src/ipanel"; +import { IPanel, IPanelConstructor, IPanelWithConfiguration } from "../src/ipanel"; import { Toolbar } from "../src/toolbar"; -import { MockPanel } from "./mock/panel.mock"; +import { IMockPanelConfig, MockPanel, mockPanelConfig } from "./mock/panel.mock"; describe("Toolbar class", () => { @@ -17,13 +17,18 @@ describe("Toolbar class", () => { }); it("can render buttons", () => { - const panel: IPanel = new MockPanel((): Button[] => [new Button({})]); const container: HTMLElement = document.createElement("div"); - const toolbar: Toolbar = new Toolbar([panel], container); + + const mockPanelWithConfig: IPanelWithConfiguration = { + panel: MockPanel, + config: mockPanelConfig, + }; + + const toolbar: Toolbar = new Toolbar([mockPanelWithConfig], container); toolbar.render(); - const expectedList: Element = container.firstElementChild; + const expectedList: Element = container.firstElementChild.children.item(1); expect(expectedList).instanceof(HTMLUListElement, "We expect the toolbar to be a list"); expect(expectedList.childElementCount).equals(1, "We expect that list to have one item"); }); From baebdcfcc2f4899cfabd623fbc048b70e13ee4c3 Mon Sep 17 00:00:00 2001 From: Adam Reineke Date: Tue, 2 Jan 2018 18:46:51 -0800 Subject: [PATCH 07/10] Some initial styling --- index.html | 37 ++++++++++++++++++++++++++++++++++++- src/panelframe.ts | 15 ++++++++++----- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index cc3a10b..7279d4f 100644 --- a/index.html +++ b/index.html @@ -2,6 +2,41 @@ Demo for WebPerfToolbar +