├── shells ├── .DS_Store ├── chrome │ ├── 128.png │ ├── devtool.html │ ├── devtool.js │ ├── manifest.json │ ├── content.js │ ├── panel.html │ ├── panel.js │ ├── background.js │ └── json-formatter.js └── firefox │ ├── data │ ├── 128.png │ ├── panel.css │ ├── content.js │ ├── panel.html │ ├── panel.js │ └── json-formatter.js │ ├── package.json │ └── index.js ├── dist └── firefox │ ├── .DS_Store │ ├── pux_devtool-1.0.2-an+fx.xpi │ └── update.rdf ├── .gitignore └── README.md /shells/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmingoia/pux-devtool/HEAD/shells/.DS_Store -------------------------------------------------------------------------------- /dist/firefox/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmingoia/pux-devtool/HEAD/dist/firefox/.DS_Store -------------------------------------------------------------------------------- /shells/chrome/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmingoia/pux-devtool/HEAD/shells/chrome/128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **DS_Store 2 | dist/chrome 3 | bower_components/ 4 | node_modules/ 5 | npm-debug.log 6 | output/ 7 | -------------------------------------------------------------------------------- /shells/firefox/data/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmingoia/pux-devtool/HEAD/shells/firefox/data/128.png -------------------------------------------------------------------------------- /dist/firefox/pux_devtool-1.0.2-an+fx.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmingoia/pux-devtool/HEAD/dist/firefox/pux_devtool-1.0.2-an+fx.xpi -------------------------------------------------------------------------------- /shells/chrome/devtool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pux Devtool 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /shells/firefox/data/panel.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: #fcfcfc; 3 | padding: 12px 36px; 4 | color: #667380; 5 | font-family: "Source Sans Pro", sans-serif; 6 | font-size: 13px; 7 | } 8 | 9 | pre { 10 | color: #667380; 11 | font-size: 13px; 12 | } 13 | 14 | #states { 15 | display: inline-block; 16 | margin-left: .5em; 17 | } 18 | -------------------------------------------------------------------------------- /shells/firefox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Pux Devtool", 3 | "name": "pux-devtool", 4 | "version": "1.0.2", 5 | "description": "Pux devtool.", 6 | "main": "index.js", 7 | "author": "Alexander C. Mingoia", 8 | "engines": { 9 | "firefox": ">=38.0a1", 10 | "fennec": ">=38.0a1" 11 | }, 12 | "multiprocess": true, 13 | "updateURL": "https://raw.githubusercontent.com/alexmingoia/pux-devtool/master/dist/firefox/update.rdf", 14 | "updateLink": "https://raw.githubusercontent.com/alexmingoia/pux-devtool/master/dist/firefox/pux-devtool.xpi", 15 | "license": "MIT", 16 | "keywords": [ 17 | "jetpack" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /shells/chrome/devtool.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create('Pux', '128.png', 'panel.html', function (panel) { 2 | const backgroundPort = chrome.runtime.connect({name: 'pux'}); 3 | 4 | panel.onShown.addListener(function (panelWindow) { 5 | backgroundPort.postMessage({ 6 | name: 'init', 7 | tabId: chrome.devtools.inspectedWindow.tabId 8 | }); 9 | 10 | panelWindow.addEventListener('message', function (ev) { 11 | backgroundPort.postMessage({ 12 | name: ev.data, 13 | tabId: chrome.devtools.inspectedWindow.tabId 14 | }) 15 | }) 16 | 17 | backgroundPort.onMessage.addListener(function(message) { 18 | panelWindow.postMessage(message, '*') 19 | }) 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /shells/firefox/data/content.js: -------------------------------------------------------------------------------- 1 | self.port.on('init', function () { 2 | window.addEventListener('pux:state:change', function (ev) { 3 | self.port.emit('pux:state:change', ev.detail); 4 | }); 5 | 6 | window.dispatchEvent(new CustomEvent('pux:devtool:init')); 7 | }); 8 | 9 | self.port.on('first', function () { 10 | window.dispatchEvent(new CustomEvent('pux:state:first')) 11 | }); 12 | 13 | self.port.on('prev', function () { 14 | window.dispatchEvent(new CustomEvent('pux:state:prev')) 15 | }); 16 | 17 | self.port.on('next', function () { 18 | window.dispatchEvent(new CustomEvent('pux:state:next')) 19 | }); 20 | 21 | self.port.on('last', function () { 22 | window.dispatchEvent(new CustomEvent('pux:state:last')) 23 | }); 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pux-devtool 2 | 3 | > DevTools extension for 4 | > [Pux](https://github.com/alexmingoia/purescript-pux) applications. 5 | 6 | ![Pux Devtool animation](support/pux-devtool.gif) 7 | 8 | Visualize app state and events. 9 | 10 | - Show current event 11 | - Inspect current state 12 | - Time-travel between application states 13 | 14 | ## Chrome Extension 15 | 16 | Install the Chrome extension from the [Chrome Web Store](https://chrome.google.com/webstore/detail/pux-devtool/ecolgfgmnimnllbgllbpfmnehejockmk). 17 | 18 | ## Firefox Extension 19 | 20 | Install the FireFox extension by going to Add-Ons -> Install Add-On From File... 21 | -> then choose [pux_devtool-1.0.2-an+fx.xpi](https://github.com/alexmingoia/pux-devtool/tree/master/dist/firefox). 22 | -------------------------------------------------------------------------------- /shells/firefox/data/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pux 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 |
Waiting to connect to Pux application...
18 |
19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /shells/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Pux DevTool", 4 | "description": "Pux DevTool for visualizing app state and events.", 5 | "version": "1.1.0", 6 | "minimum_chrome_version": "43", 7 | "icons": { 8 | "128": "128.png" 9 | }, 10 | "devtools_page": "devtool.html", 11 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 12 | "web_accessible_resources": [ "devtool.html", "panel.html"], 13 | "background": { 14 | "scripts": [ 15 | "background.js" 16 | ], 17 | "persistent": false 18 | }, 19 | "permissions": [ 20 | "tabs", 21 | "contextMenus", 22 | "" 23 | ], 24 | "content_scripts": [ 25 | { 26 | "matches": [""], 27 | "js": ["content.js"], 28 | "run_at": "document_end", 29 | "all_frames": true 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /shells/chrome/content.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('pux:state:change', function (ev) { 2 | chrome.runtime.sendMessage({ 3 | name: 'pux:state:change', 4 | detail: ev.detail 5 | }) 6 | }); 7 | 8 | window.dispatchEvent(new CustomEvent('pux:devtool:init')); 9 | 10 | chrome.runtime.onMessage.addListener(function (message) { 11 | if (message === 'init') { 12 | window.dispatchEvent(new CustomEvent('pux:devtool:init')); 13 | } 14 | 15 | if (message === 'first') { 16 | window.dispatchEvent(new CustomEvent('pux:state:first')); 17 | } 18 | 19 | if (message === 'prev') { 20 | window.dispatchEvent(new CustomEvent('pux:state:prev')); 21 | } 22 | 23 | if (message === 'next') { 24 | window.dispatchEvent(new CustomEvent('pux:state:next')); 25 | } 26 | 27 | if (message === 'last') { 28 | window.dispatchEvent(new CustomEvent('pux:state:last')); 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /shells/chrome/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Pux 8 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 |
34 |
Waiting to connect to Pux application...
35 |
36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /shells/firefox/data/panel.js: -------------------------------------------------------------------------------- 1 | var eventEl = document.getElementById('event'); 2 | var stateEl = document.getElementById('state'); 3 | var statesEl = document.getElementById('states'); 4 | var prevEl = document.getElementById('prev'); 5 | var nextEl = document.getElementById('next'); 6 | var firstEl = document.getElementById('first'); 7 | var lastEl = document.getElementById('last'); 8 | 9 | firstEl.addEventListener('click', function (ev) { 10 | window.port.postMessage('first'); 11 | }); 12 | 13 | prevEl.addEventListener('click', function (ev) { 14 | window.port.postMessage('prev'); 15 | }); 16 | 17 | nextEl.addEventListener('click', function (ev) { 18 | window.port.postMessage('next'); 19 | }); 20 | 21 | lastEl.addEventListener('click', function (ev) { 22 | window.port.postMessage('last'); 23 | }); 24 | 25 | window.addEventListener('message', function (ev) { 26 | if (ev.data === 'init') { 27 | window.port = ev.ports[0]; 28 | window.port.start(); 29 | } else { 30 | var data = JSON.parse(ev.data); 31 | 32 | if (data.event) { 33 | eventEl.textContent = data.event 34 | statesEl.textContent = (data.index + 1) + ' / ' + data.length; 35 | stateEl.innerHTML = ''; 36 | stateEl.appendChild((new JSONFormatter(JSON.parse(data.state))).render()); 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /shells/chrome/panel.js: -------------------------------------------------------------------------------- 1 | var eventEl = document.getElementById("event"); 2 | var stateEl = document.getElementById("state"); 3 | var statesEl = document.getElementById("states"); 4 | var prevEl = document.getElementById("prev"); 5 | var nextEl = document.getElementById("next"); 6 | var firstEl = document.getElementById("first"); 7 | var lastEl = document.getElementById("last"); 8 | 9 | if (!window.connected) { 10 | window.connected = true; 11 | 12 | firstEl.addEventListener("click", function (ev) { 13 | window.postMessage('first', '*'); 14 | }); 15 | 16 | prevEl.addEventListener("click", function (ev) { 17 | window.postMessage('prev', '*'); 18 | }); 19 | 20 | nextEl.addEventListener("click", function (ev) { 21 | window.postMessage('next', '*'); 22 | }); 23 | 24 | lastEl.addEventListener("click", function (ev) { 25 | window.postMessage('last', '*'); 26 | }); 27 | 28 | window.addEventListener('message', function (ev) { 29 | if (ev.data && ev.data.name) { 30 | if (ev.data.name === 'pux:state:change') { 31 | eventEl.textContent = ev.data.detail.event 32 | statesEl.textContent = (ev.data.detail.index + 1) + ' / ' + ev.data.detail.length; 33 | stateEl.innerHTML = ''; 34 | stateEl.appendChild((new JSONFormatter(JSON.parse(ev.data.detail.state))).render()); 35 | } 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /shells/firefox/index.js: -------------------------------------------------------------------------------- 1 | const { Panel } = require('dev/panel'); 2 | const { Tool } = require('dev/toolbox'); 3 | const { Class } = require('sdk/core/heritage'); 4 | const self = require('sdk/self'); 5 | const tabs = require('sdk/tabs'); 6 | 7 | const { MessageChannel } = require('sdk/messaging'); 8 | const channel = new MessageChannel(); 9 | const addonSide = channel.port1; 10 | const panelSide = channel.port2; 11 | 12 | const PuxPanel = Class({ 13 | extends: Panel, 14 | label: 'Pux', 15 | tooltip: 'Pux debugger', 16 | icon: self.data.url('128.png'), 17 | invertIconForDarkTheme: true, 18 | url: self.data.url('panel.html'), 19 | onReady: function() { 20 | var worker; 21 | var _ = this; 22 | 23 | function makeWorker (tab) { 24 | worker = tab.attach({ 25 | contentScriptFile: self.data.url('content.js') 26 | }) 27 | 28 | worker.port.on('pux:state:change', function (ev) { 29 | _.postMessage(JSON.stringify(ev)); 30 | }); 31 | 32 | worker.port.emit('init'); 33 | } 34 | 35 | tabs.on('pageshow', makeWorker); 36 | 37 | addonSide.onmessage = function (ev) { 38 | if (worker) { 39 | worker.port.emit(ev.data); 40 | } 41 | }; 42 | 43 | _.postMessage('init', [panelSide]); 44 | 45 | makeWorker(tabs.activeTab); 46 | } 47 | }); 48 | 49 | exports.PuxPanel = PuxPanel; 50 | 51 | const puxTool = new Tool({ 52 | panels: { puxPanel: PuxPanel } 53 | }); 54 | -------------------------------------------------------------------------------- /dist/firefox/update.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
  • 7 | 8 | 1.0.2 9 | 10 | 11 | 12 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 13 | 38.0a1 14 | * 15 | https://raw.githubusercontent.com/alexmingoia/pux-devtool/master/dist/firefox/pux-devtool.xpi 16 | 17 | 18 | 19 | 20 | 21 | {aa3c5121-dab2-40e2-81ca-7ea25febc110} 22 | 38.0a1 23 | * 24 | https://raw.githubusercontent.com/alexmingoia/pux-devtool/master/dist/firefox/pux-devtool.xpi 25 | 26 | 27 | 28 | 29 | 30 | 31 |
  • 32 | 33 |
    34 | 35 |
    36 | 37 |
    38 | 39 |
    40 | -------------------------------------------------------------------------------- /shells/chrome/background.js: -------------------------------------------------------------------------------- 1 | var connections = {}; 2 | 3 | chrome.runtime.onConnect.addListener(function (port) { 4 | var extensionListener = function (message, sender, sendResponse) { 5 | // The original connection event doesn't include the tab ID of the 6 | // DevTools page, so we need to send it explicitly. 7 | if (message.name == "init") { 8 | connections[message.tabId] = port; 9 | chrome.tabs.sendMessage(message.tabId, 'init') 10 | return; 11 | } 12 | 13 | // Send message to content script 14 | chrome.tabs.sendMessage(message.tabId, message.name) 15 | } 16 | 17 | // Listen to messages sent from the DevTools page 18 | port.onMessage.addListener(extensionListener); 19 | 20 | port.onDisconnect.addListener(function(port) { 21 | port.onMessage.removeListener(extensionListener); 22 | 23 | var tabs = Object.keys(connections); 24 | for (var i=0, len=tabs.length; i < len; i++) { 25 | if (connections[tabs[i]] == port) { 26 | delete connections[tabs[i]] 27 | break; 28 | } 29 | } 30 | }); 31 | }); 32 | 33 | // Receive message from content script and relay to the devTools page for the 34 | // current tab 35 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 36 | // Messages from content scripts should have sender.tab set 37 | if (sender.tab) { 38 | var tabId = sender.tab.id; 39 | if (tabId in connections) { 40 | connections[tabId].postMessage(request); 41 | } else { 42 | console.log("Tab not found in connection list."); 43 | } 44 | } else { 45 | console.log("sender.tab not defined."); 46 | } 47 | return true; 48 | }); 49 | -------------------------------------------------------------------------------- /shells/chrome/json-formatter.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define("JSONFormatter", [], factory); 6 | else if(typeof exports === 'object') 7 | exports["JSONFormatter"] = factory(); 8 | else 9 | root["JSONFormatter"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = "dist"; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | module.exports = __webpack_require__(1); 58 | 59 | 60 | /***/ }, 61 | /* 1 */ 62 | /***/ function(module, exports, __webpack_require__) { 63 | 64 | "use strict"; 65 | __webpack_require__(2); 66 | var helpers_ts_1 = __webpack_require__(6); 67 | var DATE_STRING_REGEX = /(^\d{1,4}[\.|\\/|-]\d{1,2}[\.|\\/|-]\d{1,4})(\s*(?:0?[1-9]:[0-5]|1(?=[012])\d:[0-5])\d\s*[ap]m)?$/; 68 | var PARTIAL_DATE_REGEX = /\d{2}:\d{2}:\d{2} GMT-\d{4}/; 69 | var JSON_DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; 70 | // When toggleing, don't animated removal or addition of more than a few items 71 | var MAX_ANIMATED_TOGGLE_ITEMS = 10; 72 | var requestAnimationFrame = window.requestAnimationFrame || function (cb) { cb(); return 0; }; 73 | ; 74 | var _defaultConfig = { 75 | hoverPreviewEnabled: false, 76 | hoverPreviewArrayCount: 100, 77 | hoverPreviewFieldCount: 5, 78 | animateOpen: true, 79 | animateClose: true, 80 | theme: null 81 | }; 82 | module.exports = (function () { 83 | /** 84 | * @param {object} json The JSON object you want to render. It has to be an 85 | * object or array. Do NOT pass raw JSON string. 86 | * 87 | * @param {number} [open=1] his number indicates up to how many levels the 88 | * rendered tree should expand. Set it to `0` to make the whole tree collapsed 89 | * or set it to `Infinity` to expand the tree deeply 90 | * 91 | * @param {object} [config=defaultConfig] - 92 | * defaultConfig = { 93 | * hoverPreviewEnabled: false, 94 | * hoverPreviewArrayCount: 100, 95 | * hoverPreviewFieldCount: 5 96 | * } 97 | * 98 | * Available configurations: 99 | * #####Hover Preview 100 | * * `hoverPreviewEnabled`: enable preview on hover 101 | * * `hoverPreviewArrayCount`: number of array items to show in preview Any 102 | * array larger than this number will be shown as `Array[XXX]` where `XXX` 103 | * is length of the array. 104 | * * `hoverPreviewFieldCount`: number of object properties to show for object 105 | * preview. Any object with more properties that thin number will be 106 | * truncated. 107 | * 108 | * @param {string} [key=undefined] The key that this object in it's parent 109 | * context 110 | */ 111 | function JSONFormatter(json, open, config, key) { 112 | if (open === void 0) { open = 1; } 113 | if (config === void 0) { config = _defaultConfig; } 114 | this.json = json; 115 | this.open = open; 116 | this.config = config; 117 | this.key = key; 118 | // Hold the open state after the toggler is used 119 | this._isOpen = null; 120 | // Setting default values for config object 121 | if (this.config.hoverPreviewEnabled === undefined) { 122 | this.config.hoverPreviewEnabled = _defaultConfig.hoverPreviewEnabled; 123 | } 124 | if (this.config.hoverPreviewArrayCount === undefined) { 125 | this.config.hoverPreviewArrayCount = _defaultConfig.hoverPreviewArrayCount; 126 | } 127 | if (this.config.hoverPreviewFieldCount === undefined) { 128 | this.config.hoverPreviewFieldCount = _defaultConfig.hoverPreviewFieldCount; 129 | } 130 | } 131 | Object.defineProperty(JSONFormatter.prototype, "isOpen", { 132 | /* 133 | * is formatter open? 134 | */ 135 | get: function () { 136 | if (this._isOpen !== null) { 137 | return this._isOpen; 138 | } 139 | else { 140 | return this.open > 0; 141 | } 142 | }, 143 | /* 144 | * set open state (from toggler) 145 | */ 146 | set: function (value) { 147 | this._isOpen = value; 148 | }, 149 | enumerable: true, 150 | configurable: true 151 | }); 152 | Object.defineProperty(JSONFormatter.prototype, "isDate", { 153 | /* 154 | * is this a date string? 155 | */ 156 | get: function () { 157 | return (this.type === 'string') && 158 | (DATE_STRING_REGEX.test(this.json) || 159 | JSON_DATE_REGEX.test(this.json) || 160 | PARTIAL_DATE_REGEX.test(this.json)); 161 | }, 162 | enumerable: true, 163 | configurable: true 164 | }); 165 | Object.defineProperty(JSONFormatter.prototype, "isUrl", { 166 | /* 167 | * is this a URL string? 168 | */ 169 | get: function () { 170 | return this.type === 'string' && (this.json.indexOf('http') === 0); 171 | }, 172 | enumerable: true, 173 | configurable: true 174 | }); 175 | Object.defineProperty(JSONFormatter.prototype, "isArray", { 176 | /* 177 | * is this an array? 178 | */ 179 | get: function () { 180 | return Array.isArray(this.json); 181 | }, 182 | enumerable: true, 183 | configurable: true 184 | }); 185 | Object.defineProperty(JSONFormatter.prototype, "isObject", { 186 | /* 187 | * is this an object? 188 | * Note: In this context arrays are object as well 189 | */ 190 | get: function () { 191 | return helpers_ts_1.isObject(this.json); 192 | }, 193 | enumerable: true, 194 | configurable: true 195 | }); 196 | Object.defineProperty(JSONFormatter.prototype, "isEmptyObject", { 197 | /* 198 | * is this an empty object with no properties? 199 | */ 200 | get: function () { 201 | return !this.keys.length && !this.isArray; 202 | }, 203 | enumerable: true, 204 | configurable: true 205 | }); 206 | Object.defineProperty(JSONFormatter.prototype, "isEmpty", { 207 | /* 208 | * is this an empty object or array? 209 | */ 210 | get: function () { 211 | return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray); 212 | }, 213 | enumerable: true, 214 | configurable: true 215 | }); 216 | Object.defineProperty(JSONFormatter.prototype, "hasKey", { 217 | /* 218 | * did we recieve a key argument? 219 | * This means that the formatter was called as a sub formatter of a parent formatter 220 | */ 221 | get: function () { 222 | return typeof this.key !== 'undefined'; 223 | }, 224 | enumerable: true, 225 | configurable: true 226 | }); 227 | Object.defineProperty(JSONFormatter.prototype, "constructorName", { 228 | /* 229 | * if this is an object, get constructor function name 230 | */ 231 | get: function () { 232 | return helpers_ts_1.getObjectName(this.json); 233 | }, 234 | enumerable: true, 235 | configurable: true 236 | }); 237 | Object.defineProperty(JSONFormatter.prototype, "type", { 238 | /* 239 | * get type of this value 240 | * Possible values: all JavaScript primitive types plus "array" and "null" 241 | */ 242 | get: function () { 243 | return helpers_ts_1.getType(this.json); 244 | }, 245 | enumerable: true, 246 | configurable: true 247 | }); 248 | Object.defineProperty(JSONFormatter.prototype, "keys", { 249 | /* 250 | * get object keys 251 | * If there is an empty key we pad it wit quotes to make it visible 252 | */ 253 | get: function () { 254 | if (this.isObject) { 255 | return Object.keys(this.json).map(function (key) { return key ? key : '""'; }); 256 | } 257 | else { 258 | return []; 259 | } 260 | }, 261 | enumerable: true, 262 | configurable: true 263 | }); 264 | /** 265 | * Toggles `isOpen` state 266 | * 267 | */ 268 | JSONFormatter.prototype.toggleOpen = function () { 269 | this.isOpen = !this.isOpen; 270 | if (this.element) { 271 | if (this.isOpen) { 272 | this.appendChildren(this.config.animateOpen); 273 | } 274 | else { 275 | this.removeChildren(this.config.animateClose); 276 | } 277 | this.element.classList.toggle(helpers_ts_1.cssClass('open')); 278 | } 279 | }; 280 | /** 281 | * Open all children up to a certain depth. 282 | * Allows actions such as expand all/collapse all 283 | * 284 | */ 285 | JSONFormatter.prototype.openAtDepth = function (depth) { 286 | if (depth === void 0) { depth = 1; } 287 | if (depth < 0) { 288 | return; 289 | } 290 | this.open = depth; 291 | this.isOpen = (depth !== 0); 292 | if (this.element) { 293 | this.removeChildren(false); 294 | if (depth === 0) { 295 | this.element.classList.remove(helpers_ts_1.cssClass('open')); 296 | } 297 | else { 298 | this.appendChildren(this.config.animateOpen); 299 | this.element.classList.add(helpers_ts_1.cssClass('open')); 300 | } 301 | } 302 | }; 303 | /** 304 | * Generates inline preview 305 | * 306 | * @returns {string} 307 | */ 308 | JSONFormatter.prototype.getInlinepreview = function () { 309 | var _this = this; 310 | if (this.isArray) { 311 | // if array length is greater then 100 it shows "Array[101]" 312 | if (this.json.length > this.config.hoverPreviewArrayCount) { 313 | return "Array[" + this.json.length + "]"; 314 | } 315 | else { 316 | return "[" + this.json.map(helpers_ts_1.getPreview).join(', ') + "]"; 317 | } 318 | } 319 | else { 320 | var keys = this.keys; 321 | // the first five keys (like Chrome Developer Tool) 322 | var narrowKeys = keys.slice(0, this.config.hoverPreviewFieldCount); 323 | // json value schematic information 324 | var kvs = narrowKeys.map(function (key) { return (key + ":" + helpers_ts_1.getPreview(_this.json[key])); }); 325 | // if keys count greater then 5 then show ellipsis 326 | var ellipsis = keys.length >= this.config.hoverPreviewFieldCount ? '…' : ''; 327 | return "{" + kvs.join(', ') + ellipsis + "}"; 328 | } 329 | }; 330 | /** 331 | * Renders an HTML element and installs event listeners 332 | * 333 | * @returns {HTMLDivElement} 334 | */ 335 | JSONFormatter.prototype.render = function () { 336 | // construct the root element and assign it to this.element 337 | this.element = helpers_ts_1.createElement('div', 'row'); 338 | // construct the toggler link 339 | var togglerLink = helpers_ts_1.createElement('a', 'toggler-link'); 340 | // if this is an object we need a wrapper span (toggler) 341 | if (this.isObject) { 342 | togglerLink.appendChild(helpers_ts_1.createElement('span', 'toggler')); 343 | } 344 | // if this is child of a parent formatter we need to append the key 345 | if (this.hasKey) { 346 | togglerLink.appendChild(helpers_ts_1.createElement('span', 'key', this.key + ":")); 347 | } 348 | // Value for objects and arrays 349 | if (this.isObject) { 350 | // construct the value holder element 351 | var value = helpers_ts_1.createElement('span', 'value'); 352 | // we need a wrapper span for objects 353 | var objectWrapperSpan = helpers_ts_1.createElement('span'); 354 | // get constructor name and append it to wrapper span 355 | var constructorName = helpers_ts_1.createElement('span', 'constructor-name', this.constructorName); 356 | objectWrapperSpan.appendChild(constructorName); 357 | // if it's an array append the array specific elements like brackets and length 358 | if (this.isArray) { 359 | var arrayWrapperSpan = helpers_ts_1.createElement('span'); 360 | arrayWrapperSpan.appendChild(helpers_ts_1.createElement('span', 'bracket', '[')); 361 | arrayWrapperSpan.appendChild(helpers_ts_1.createElement('span', 'number', (this.json.length))); 362 | arrayWrapperSpan.appendChild(helpers_ts_1.createElement('span', 'bracket', ']')); 363 | objectWrapperSpan.appendChild(arrayWrapperSpan); 364 | } 365 | // append object wrapper span to toggler link 366 | value.appendChild(objectWrapperSpan); 367 | togglerLink.appendChild(value); 368 | } 369 | else { 370 | // make a value holder element 371 | var value = this.isUrl ? helpers_ts_1.createElement('a') : helpers_ts_1.createElement('span'); 372 | // add type and other type related CSS classes 373 | value.classList.add(helpers_ts_1.cssClass(this.type)); 374 | if (this.isDate) { 375 | value.classList.add(helpers_ts_1.cssClass('date')); 376 | } 377 | if (this.isUrl) { 378 | value.classList.add(helpers_ts_1.cssClass('url')); 379 | value.setAttribute('href', this.json); 380 | } 381 | // Append value content to value element 382 | var valuePreview = helpers_ts_1.getValuePreview(this.json, this.json); 383 | value.appendChild(document.createTextNode(valuePreview)); 384 | // append the value element to toggler link 385 | togglerLink.appendChild(value); 386 | } 387 | // if hover preview is enabled, append the inline preview element 388 | if (this.isObject && this.config.hoverPreviewEnabled) { 389 | var preview = helpers_ts_1.createElement('span', 'preview-text'); 390 | preview.appendChild(document.createTextNode(this.getInlinepreview())); 391 | togglerLink.appendChild(preview); 392 | } 393 | // construct a children element 394 | var children = helpers_ts_1.createElement('div', 'children'); 395 | // set CSS classes for children 396 | if (this.isObject) { 397 | children.classList.add(helpers_ts_1.cssClass('object')); 398 | } 399 | if (this.isArray) { 400 | children.classList.add(helpers_ts_1.cssClass('array')); 401 | } 402 | if (this.isEmpty) { 403 | children.classList.add(helpers_ts_1.cssClass('empty')); 404 | } 405 | // set CSS classes for root element 406 | if (this.config && this.config.theme) { 407 | this.element.classList.add(helpers_ts_1.cssClass(this.config.theme)); 408 | } 409 | if (this.isOpen) { 410 | this.element.classList.add(helpers_ts_1.cssClass('open')); 411 | } 412 | // append toggler and children elements to root element 413 | this.element.appendChild(togglerLink); 414 | this.element.appendChild(children); 415 | // if formatter is set to be open call appendChildren 416 | if (this.isObject && this.isOpen) { 417 | this.appendChildren(); 418 | } 419 | // add event listener for toggling 420 | if (this.isObject) { 421 | togglerLink.addEventListener('click', this.toggleOpen.bind(this)); 422 | } 423 | return this.element; 424 | }; 425 | /** 426 | * Appends all the children to children element 427 | * Animated option is used when user triggers this via a click 428 | */ 429 | JSONFormatter.prototype.appendChildren = function (animated) { 430 | var _this = this; 431 | if (animated === void 0) { animated = false; } 432 | var children = this.element.querySelector("div." + helpers_ts_1.cssClass('children')); 433 | if (!children || this.isEmpty) { 434 | return; 435 | } 436 | if (animated) { 437 | var index_1 = 0; 438 | var addAChild_1 = function () { 439 | var key = _this.keys[index_1]; 440 | var formatter = new JSONFormatter(_this.json[key], _this.open - 1, _this.config, key); 441 | children.appendChild(formatter.render()); 442 | index_1 += 1; 443 | if (index_1 < _this.keys.length) { 444 | if (index_1 > MAX_ANIMATED_TOGGLE_ITEMS) { 445 | addAChild_1(); 446 | } 447 | else { 448 | requestAnimationFrame(addAChild_1); 449 | } 450 | } 451 | }; 452 | requestAnimationFrame(addAChild_1); 453 | } 454 | else { 455 | this.keys.forEach(function (key) { 456 | var formatter = new JSONFormatter(_this.json[key], _this.open - 1, _this.config, key); 457 | children.appendChild(formatter.render()); 458 | }); 459 | } 460 | }; 461 | /** 462 | * Removes all the children from children element 463 | * Animated option is used when user triggers this via a click 464 | */ 465 | JSONFormatter.prototype.removeChildren = function (animated) { 466 | if (animated === void 0) { animated = false; } 467 | var childrenElement = this.element.querySelector("div." + helpers_ts_1.cssClass('children')); 468 | if (animated) { 469 | var childrenRemoved_1 = 0; 470 | var removeAChild_1 = function () { 471 | if (childrenElement && childrenElement.children.length) { 472 | childrenElement.removeChild(childrenElement.children[0]); 473 | childrenRemoved_1 += 1; 474 | if (childrenRemoved_1 > MAX_ANIMATED_TOGGLE_ITEMS) { 475 | removeAChild_1(); 476 | } 477 | else { 478 | requestAnimationFrame(removeAChild_1); 479 | } 480 | } 481 | }; 482 | requestAnimationFrame(removeAChild_1); 483 | } 484 | else { 485 | if (childrenElement) { 486 | childrenElement.innerHTML = ''; 487 | } 488 | } 489 | }; 490 | return JSONFormatter; 491 | }()); 492 | 493 | 494 | /***/ }, 495 | /* 2 */ 496 | /***/ function(module, exports, __webpack_require__) { 497 | 498 | // style-loader: Adds some css to the DOM by adding a