├── .gitignore ├── .prettierrc ├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── debug └── test-app.html ├── dist └── arcgis-js-api-devtools.d.ts ├── docs ├── devtools-settings.png ├── formatted-output.png ├── formatted-simple-fill-symbol.png └── regular-output.png ├── package-lock.json ├── package.json ├── rollup.config.js └── src ├── AccessorFormatter.ts ├── ArcGISJSAPIFormatter.ts ├── CollectionFormatter.ts ├── ColorFormatter.ts ├── JSONMLElement.ts ├── JSONMLFormatter.ts ├── interfaces.d.ts ├── main.ts └── utils.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/*.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "printWidth": 80 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "tsc", 4 | "isShellCommand": true, 5 | "args": ["-w", "-p", "."], 6 | "showOutput": "silent", 7 | "isWatching": true, 8 | "problemMatcher": "$tsc-watch" 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2016 Yann Cabon 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Custom ArcGIS API 4 for JavaScript object formatting for Chrome DevTools. 2 | --- 3 | 4 | Chrome DevTools provides an option to take control over how objects are formatted in DevTools. 5 | More info: https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U/preview?pref=2&pli=1 6 | 7 | These formatters display public properties from objects produced by the [ArcGIS API 4 for JavaScript](https://js.arcgis.com). 8 | 9 | Example of an `esri/Map` default console formatting: 10 | ![esri/Map default console formatting](docs/regular-output.png) 11 | 12 | Example of an `esri/Map` custom console formatting: 13 | ![esri/Map custom console formatting](docs/formatted-output.png) 14 | 15 | Example of an `esri/symbols/SimpleFillSymbol` custom console formatting: 16 | ![esri/symbols/SimpleFillSymbol custom console formatting](docs/formatted-simple-fill-symbol.png) 17 | 18 | 19 | Usage 20 | --- 21 | 22 | #### Enable custom formatters in Chrome Devtools 23 | 24 | Open the Chrome DevTools settings page and check the option _"Enable custom formatters"_ 25 | 26 | ![DevTools settings](docs/devtools-settings.png) 27 | 28 | 29 | #### Import and install the devtools formatters 30 | 31 | Require the appropriate module and invoke `install()` to enable the formatter. 32 | 33 | ```html 34 | 38 | ``` 39 | -------------------------------------------------------------------------------- /debug/test-app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ArcGIS Devtools 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /dist/arcgis-js-api-devtools.d.ts: -------------------------------------------------------------------------------- 1 | export function installArcGISAPIChromeDevtoolsFormatter(): void; 2 | -------------------------------------------------------------------------------- /docs/devtools-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycabon/arcgis-js-api-devtools/e2fced3394c7c30693915cf34006c86aba77f3f9/docs/devtools-settings.png -------------------------------------------------------------------------------- /docs/formatted-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycabon/arcgis-js-api-devtools/e2fced3394c7c30693915cf34006c86aba77f3f9/docs/formatted-output.png -------------------------------------------------------------------------------- /docs/formatted-simple-fill-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycabon/arcgis-js-api-devtools/e2fced3394c7c30693915cf34006c86aba77f3f9/docs/formatted-simple-fill-symbol.png -------------------------------------------------------------------------------- /docs/regular-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycabon/arcgis-js-api-devtools/e2fced3394c7c30693915cf34006c86aba77f3f9/docs/regular-output.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arcgis-js-api-devtools", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.8.3", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", 10 | "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", 11 | "requires": { 12 | "@babel/highlight": "^7.8.3" 13 | } 14 | }, 15 | "@babel/helper-validator-identifier": { 16 | "version": "7.9.0", 17 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", 18 | "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==" 19 | }, 20 | "@babel/highlight": { 21 | "version": "7.9.0", 22 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", 23 | "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", 24 | "requires": { 25 | "@babel/helper-validator-identifier": "^7.9.0", 26 | "chalk": "^2.0.0", 27 | "js-tokens": "^4.0.0" 28 | } 29 | }, 30 | "@rollup/plugin-typescript": { 31 | "version": "4.0.0", 32 | "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-4.0.0.tgz", 33 | "integrity": "sha512-qA3r4WlR8JnTm+VdBzvQSIkfXt802keGxXuE4SAjUjRMKK3nMXTUCvOGSzFkav2qf0QiGv6yijfbjuf+bhwmZQ==", 34 | "dev": true, 35 | "requires": { 36 | "@rollup/pluginutils": "^3.0.1", 37 | "resolve": "^1.14.1" 38 | } 39 | }, 40 | "@rollup/pluginutils": { 41 | "version": "3.0.8", 42 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz", 43 | "integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==", 44 | "dev": true, 45 | "requires": { 46 | "estree-walker": "^1.0.1" 47 | } 48 | }, 49 | "ansi-styles": { 50 | "version": "3.2.1", 51 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 52 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 53 | "requires": { 54 | "color-convert": "^1.9.0" 55 | } 56 | }, 57 | "buffer-from": { 58 | "version": "1.1.1", 59 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 60 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 61 | }, 62 | "chalk": { 63 | "version": "2.4.2", 64 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 65 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 66 | "requires": { 67 | "ansi-styles": "^3.2.1", 68 | "escape-string-regexp": "^1.0.5", 69 | "supports-color": "^5.3.0" 70 | } 71 | }, 72 | "color-convert": { 73 | "version": "1.9.3", 74 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 75 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 76 | "requires": { 77 | "color-name": "1.1.3" 78 | } 79 | }, 80 | "color-name": { 81 | "version": "1.1.3", 82 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 83 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 84 | }, 85 | "commander": { 86 | "version": "2.20.3", 87 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 88 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 89 | }, 90 | "escape-string-regexp": { 91 | "version": "1.0.5", 92 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 93 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 94 | }, 95 | "estree-walker": { 96 | "version": "1.0.1", 97 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", 98 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", 99 | "dev": true 100 | }, 101 | "fsevents": { 102 | "version": "2.1.2", 103 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", 104 | "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", 105 | "dev": true, 106 | "optional": true 107 | }, 108 | "has-flag": { 109 | "version": "3.0.0", 110 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 111 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 112 | }, 113 | "jest-worker": { 114 | "version": "24.9.0", 115 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", 116 | "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", 117 | "requires": { 118 | "merge-stream": "^2.0.0", 119 | "supports-color": "^6.1.0" 120 | }, 121 | "dependencies": { 122 | "supports-color": { 123 | "version": "6.1.0", 124 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", 125 | "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", 126 | "requires": { 127 | "has-flag": "^3.0.0" 128 | } 129 | } 130 | } 131 | }, 132 | "js-tokens": { 133 | "version": "4.0.0", 134 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 135 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 136 | }, 137 | "merge-stream": { 138 | "version": "2.0.0", 139 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 140 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" 141 | }, 142 | "path-parse": { 143 | "version": "1.0.6", 144 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 145 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 146 | "dev": true 147 | }, 148 | "prettier": { 149 | "version": "2.0.2", 150 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", 151 | "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", 152 | "dev": true 153 | }, 154 | "resolve": { 155 | "version": "1.15.1", 156 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", 157 | "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", 158 | "dev": true, 159 | "requires": { 160 | "path-parse": "^1.0.6" 161 | } 162 | }, 163 | "rollup": { 164 | "version": "2.2.0", 165 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", 166 | "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", 167 | "dev": true, 168 | "requires": { 169 | "fsevents": "~2.1.2" 170 | } 171 | }, 172 | "rollup-plugin-terser": { 173 | "version": "5.3.0", 174 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz", 175 | "integrity": "sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==", 176 | "requires": { 177 | "@babel/code-frame": "^7.5.5", 178 | "jest-worker": "^24.9.0", 179 | "rollup-pluginutils": "^2.8.2", 180 | "serialize-javascript": "^2.1.2", 181 | "terser": "^4.6.2" 182 | } 183 | }, 184 | "rollup-pluginutils": { 185 | "version": "2.8.2", 186 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 187 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 188 | "requires": { 189 | "estree-walker": "^0.6.1" 190 | }, 191 | "dependencies": { 192 | "estree-walker": { 193 | "version": "0.6.1", 194 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 195 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" 196 | } 197 | } 198 | }, 199 | "serialize-javascript": { 200 | "version": "2.1.2", 201 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", 202 | "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" 203 | }, 204 | "source-map": { 205 | "version": "0.6.1", 206 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 207 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 208 | }, 209 | "source-map-support": { 210 | "version": "0.5.16", 211 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", 212 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", 213 | "requires": { 214 | "buffer-from": "^1.0.0", 215 | "source-map": "^0.6.0" 216 | } 217 | }, 218 | "supports-color": { 219 | "version": "5.5.0", 220 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 221 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 222 | "requires": { 223 | "has-flag": "^3.0.0" 224 | } 225 | }, 226 | "terser": { 227 | "version": "4.6.7", 228 | "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", 229 | "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", 230 | "requires": { 231 | "commander": "^2.20.0", 232 | "source-map": "~0.6.1", 233 | "source-map-support": "~0.5.12" 234 | } 235 | }, 236 | "tslib": { 237 | "version": "1.11.1", 238 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", 239 | "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", 240 | "dev": true 241 | }, 242 | "typescript": { 243 | "version": "3.8.3", 244 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", 245 | "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", 246 | "dev": true 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arcgis-js-api-devtools", 3 | "version": "2.0.1", 4 | "description": "Chrome Devtools custom formatter for objects from ArcGIS JS API 4 for JavaScript", 5 | "main": "dist/arcgis-js-api-devtools.js", 6 | "module": "dist/arcgis-js-api-devtools.esm.js", 7 | "types": "dist/arcgis-js-api-devtools.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "scripts": { 12 | "build": "rollup --config", 13 | "watch": "rollup --config --watch" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/ycabon/arcgis-js-api-devtools.git" 18 | }, 19 | "keywords": [ 20 | "chrome", 21 | "devtools", 22 | "formatter", 23 | "arcgis", 24 | "arcgis-js-api", 25 | "esrijs" 26 | ], 27 | "author": "Yann Cabon ", 28 | "license": "Apache-2.0", 29 | "bugs": { 30 | "url": "https://github.com/ycabon/arcgis-js-api-devtools/issues" 31 | }, 32 | "homepage": "https://github.com/ycabon/arcgis-js-api-devtools#readme", 33 | "devDependencies": { 34 | "@rollup/plugin-typescript": "^4.0.0", 35 | "rollup-plugin-terser": "^5.3.0", 36 | "prettier": "2.0.2", 37 | "rollup": "^2.2.0", 38 | "tslib": "^1.11.1", 39 | "typescript": "^3.8.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | 4 | const tsconfig = { 5 | target: 'ES2017', 6 | }; 7 | 8 | export default [ 9 | { 10 | input: './src/main.ts', 11 | plugins: [typescript(tsconfig), terser()], 12 | output: { 13 | file: 'dist/arcgis-js-api-devtools.js', 14 | format: 'cjs', 15 | }, 16 | }, 17 | { 18 | input: './src/main.ts', 19 | plugins: [ 20 | typescript(tsconfig), 21 | terser({ 22 | module: true, 23 | }), 24 | ], 25 | output: { 26 | file: 'dist/arcgis-js-api-devtools.esm.js', 27 | format: 'es', 28 | }, 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /src/AccessorFormatter.ts: -------------------------------------------------------------------------------- 1 | import JSONMLElement from './JSONMLElement'; 2 | import { Formatter } from './interfaces'; 3 | import { className, propertyNames } from './utils'; 4 | 5 | export default class AccessorFormatter implements Formatter { 6 | accept(object: any): boolean { 7 | return object && object.__accessor__ != null; 8 | } 9 | 10 | preview(object: any): any { 11 | let element = new JSONMLElement('span'); 12 | element.createChild('span').createTextChild(className(object)); 13 | return element; 14 | } 15 | 16 | hasChildren(object: any): boolean { 17 | return propertyNames(object).length > 0; 18 | } 19 | 20 | children(object: any): { name: string; value: any }[] { 21 | let result: { name: string; value: any }[] = []; 22 | let names: string[] = propertyNames(object); 23 | for (let name of names) { 24 | result.push({ 25 | name: name, 26 | value: object[name], 27 | }); 28 | } 29 | return result; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ArcGISJSAPIFormatter.ts: -------------------------------------------------------------------------------- 1 | import { Formatter } from './interfaces'; 2 | 3 | import CollectionFormatter from './CollectionFormatter'; 4 | import ColorFormatter from './ColorFormatter'; 5 | import AccessorFormatter from './AccessorFormatter'; 6 | 7 | export default class ArcGISJSAPIFormatter { 8 | private _formatters: Formatter[] = [ 9 | new CollectionFormatter(), 10 | new ColorFormatter(), 11 | new AccessorFormatter(), 12 | ]; 13 | 14 | accept(object: any): boolean { 15 | for (let formatter of this._formatters) { 16 | if (formatter.accept(object)) { 17 | return true; 18 | } 19 | } 20 | 21 | return false; 22 | } 23 | 24 | preview(object: any) { 25 | for (let formatter of this._formatters) { 26 | if (formatter.accept(object)) { 27 | return formatter.preview(object); 28 | } 29 | } 30 | 31 | return null; 32 | } 33 | 34 | hasChildren(object: any) { 35 | for (let formatter of this._formatters) { 36 | if (formatter.accept(object)) { 37 | return formatter.hasChildren(object); 38 | } 39 | } 40 | 41 | return false; 42 | } 43 | 44 | children(object: any) { 45 | if (!object) { 46 | return []; 47 | } 48 | 49 | for (let formatter of this._formatters) { 50 | if (formatter.accept(object)) { 51 | return formatter.children(object); 52 | } 53 | } 54 | 55 | return []; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/CollectionFormatter.ts: -------------------------------------------------------------------------------- 1 | import { Formatter } from './interfaces'; 2 | import { className } from './utils'; 3 | import JSONMLElement from './JSONMLElement'; 4 | 5 | export default class CollectionFormatter implements Formatter { 6 | accept(object: any): boolean { 7 | return ( 8 | object && object.__accessor__ != null && Array.isArray(object._items) 9 | ); 10 | } 11 | 12 | preview(object: any): any { 13 | let element = new JSONMLElement('span'); 14 | element.createChild('span').createTextChild(className(object)); 15 | element.createChild('span').createTextChild(`[${object.length}]`); 16 | return element; 17 | } 18 | 19 | hasChildren(object: any): boolean { 20 | return object.length > 0; 21 | } 22 | 23 | children(object: any): { name: string; value: any }[] { 24 | let result: { name: string; value: any }[] = []; 25 | let elements: any[] = object.toArray(); 26 | for (let i = 0; i < elements.length; ++i) { 27 | result.push({ name: i + '', value: elements[i] }); 28 | } 29 | return result; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ColorFormatter.ts: -------------------------------------------------------------------------------- 1 | import JSONMLElement from './JSONMLElement'; 2 | import { Formatter } from './interfaces'; 3 | 4 | export default class AccessorFormatter implements Formatter { 5 | accept(object: any): boolean { 6 | return object && typeof object.toRgba === 'function'; 7 | } 8 | 9 | preview(object: any): any { 10 | let element = new JSONMLElement('span'); 11 | let [r, g, b, a] = object.toRgba(); 12 | element 13 | .createChild('span') 14 | .createTextChild(`[${r}, ${g}, ${b}, ${a}]`) 15 | .setStyle('float: right'); 16 | element 17 | .createChild('div') 18 | .setStyle( 19 | `float: right; width: 10px; height: 10px; margin-top: 3px; margin-right: 4px; border: 1px solid black; background: ${object}` 20 | ); 21 | return element; 22 | } 23 | 24 | hasChildren(object: any): boolean { 25 | return false; 26 | } 27 | 28 | children(object: any): { name: string; value: any }[] { 29 | return []; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/JSONMLElement.ts: -------------------------------------------------------------------------------- 1 | export type tagName = 2 | | 'div' 3 | | 'span' 4 | | 'ol' 5 | | 'li' 6 | | 'table' 7 | | 'tr' 8 | | 'td' 9 | | 'object'; 10 | 11 | export default class JSONMLElement { 12 | constructor(tagName: tagName) { 13 | this._attributes = {}; 14 | this._jsonML = [tagName, this._attributes]; 15 | } 16 | 17 | private _attributes: { [key: string]: any }; 18 | private _jsonML: any[]; 19 | 20 | appendChild(element: JSONMLElement) { 21 | this._jsonML.push(element.toJSONML()); 22 | return element; 23 | } 24 | 25 | createChild(tagName: tagName) { 26 | return this.appendChild(new JSONMLElement(tagName)); 27 | } 28 | 29 | createObjectTag(object: any) { 30 | let tag = this.createChild('object'); 31 | tag.addAttribute('object', object); 32 | return tag; 33 | } 34 | 35 | setStyle(style: string) { 36 | this._attributes['style'] = style; 37 | return this; 38 | } 39 | 40 | addAttribute(key: string, value: any) { 41 | this._attributes[key] = value; 42 | return this; 43 | } 44 | 45 | createTextChild(text: any) { 46 | this._jsonML.push(text + ''); 47 | return this; 48 | } 49 | 50 | toJSONML() { 51 | return this._jsonML; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/JSONMLFormatter.ts: -------------------------------------------------------------------------------- 1 | import JSONMLElement from './JSONMLElement'; 2 | 3 | import { Formatter } from './interfaces'; 4 | 5 | type Config = { preview?: JSONMLElement }; 6 | 7 | export default class JSONMLFormatter { 8 | constructor(formatter: Formatter) { 9 | this._formatter = formatter; 10 | } 11 | 12 | private _formatter: Formatter; 13 | 14 | header(object: any, config: Config): null | string | any[] { 15 | if (!this._formatter.accept(object)) { 16 | return null; 17 | } 18 | 19 | let child = this._formatter.preview(object); 20 | 21 | let preview = config && config.preview; 22 | let header = new JSONMLElement('span'); 23 | if (preview) { 24 | header.appendChild(preview); 25 | } 26 | 27 | if (child && typeof child === 'string') { 28 | header.createTextChild(child); 29 | } else { 30 | header.appendChild(child); 31 | } 32 | 33 | return header.toJSONML(); 34 | } 35 | 36 | hasBody(object: any, config: Config) { 37 | return this._formatter.hasChildren(object); 38 | } 39 | 40 | body(object: any) { 41 | let formatter = this._formatter; 42 | let body = new JSONMLElement('ol'); 43 | body.setStyle( 44 | 'list-style-type: none; padding: 0; margin: 0 0 0 12px; font-style: normal' 45 | ); 46 | 47 | let children = formatter.children(object); 48 | 49 | for (let child of children) { 50 | let li = body.createChild('li'); 51 | 52 | let nameSpan = new JSONMLElement('span'); 53 | nameSpan.createTextChild(`${child.name}: `); 54 | nameSpan.setStyle('color: rgb(136, 19, 145);'); 55 | 56 | if (formatter.accept(child.value)) { 57 | // expandable arrow 58 | let objectTag = li.createObjectTag(child.value); 59 | objectTag.addAttribute('config', { preview: nameSpan }); 60 | 61 | if (!formatter.hasChildren(child.value)) { 62 | li.setStyle('padding-left: 13px;'); 63 | } 64 | } else { 65 | li.appendChild(nameSpan); 66 | li.setStyle('padding-left: 13px;'); 67 | 68 | // special treatement if the value is undefined 69 | if (child.value === undefined) { 70 | let valueSpan = new JSONMLElement('span'); 71 | valueSpan.createTextChild('undefined'); 72 | valueSpan.setStyle('color: #777'); 73 | li.appendChild(valueSpan); 74 | } else { 75 | // else let chrome rendering the value 76 | li.createObjectTag(child.value); 77 | } 78 | } 79 | } 80 | 81 | return body.toJSONML(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | export interface Formatter { 2 | accept(object: any): boolean; 3 | preview(object: any): any; 4 | hasChildren(object: any): boolean; 5 | children(object: any): { name: string; value: any }[]; 6 | } 7 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import ArcGISJSAPIFormatter from './ArcGISJSAPIFormatter'; 2 | import JSONMLFormatter from './JSONMLFormatter'; 3 | 4 | let installed = false; 5 | 6 | export function installArcGISAPIChromeDevtoolsFormatter() { 7 | if (typeof window === 'undefined') { 8 | console.error( 9 | new Error( 10 | 'Can only install arcgis-js-api-devtools in a browser environment.' 11 | ) 12 | ); 13 | } 14 | 15 | // Don't install more than once. 16 | if (installed === true) { 17 | return; 18 | } 19 | 20 | let devtoolsFormatters = ((window as any).devtoolsFormatters = 21 | (window as any).devtoolsFormatters || []); 22 | 23 | devtoolsFormatters.push(new JSONMLFormatter(new ArcGISJSAPIFormatter())); 24 | 25 | installed = true; 26 | } 27 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function className(object: any): string { 2 | let declaredClass = object.declaredClass; 3 | 4 | if (!declaredClass) { 5 | return ''; 6 | } 7 | 8 | let lastIndexOfPoint = declaredClass.lastIndexOf('.'); 9 | let indexOfAngleBracket = declaredClass.indexOf('<'); 10 | if (indexOfAngleBracket > -1) { 11 | lastIndexOfPoint = declaredClass.lastIndexOf('.', indexOfAngleBracket); 12 | } 13 | 14 | return declaredClass.substring(lastIndexOfPoint + 1, declaredClass.length); 15 | } 16 | 17 | export function propertyNames(object: any): string[] { 18 | return object._accessorProps 19 | ? Object.keys(object.constructor._esriMeta.classMetadata.properties).sort() 20 | : Object.keys(object.__accessor__.metadatas).sort(); 21 | } 22 | --------------------------------------------------------------------------------