├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── extension │ ├── assets │ │ ├── 128icon.png │ │ ├── 16icon.png │ │ └── 48icon.png │ ├── background.js │ ├── content.js │ ├── contentSrc.js │ ├── devtools.html │ ├── devtools.js │ ├── manifest.json │ ├── panel.html │ ├── panel.js │ └── scripts │ │ ├── detector.js │ │ └── parser.js └── panel │ ├── App.vue │ ├── assets │ ├── flare-2.json │ ├── flare.json │ └── img │ │ ├── 48icon.png │ │ ├── icon-circle-pack.svg │ │ ├── logo.png │ │ └── tree-icon.svg │ ├── components │ ├── CirclePack.vue │ ├── HelloWorld.vue │ ├── NavBar.vue │ ├── Tree.vue │ ├── VerticalTree.vue │ └── ZoomableCirclePack.vue │ ├── main.ts │ ├── router │ └── index.ts │ ├── shims-vue.d.ts │ ├── shims-vuex.ts │ ├── store │ └── index.ts │ └── views │ ├── CircleView.vue │ ├── Home.vue │ ├── MetricsView.vue │ └── TreeView.vue ├── tests └── unit │ └── example.spec.ts ├── tsconfig.json ├── vue.config.js └── webpack.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | globals: { 7 | chrome: true 8 | }, 9 | extends: [ 10 | 'plugin:vue/vue3-essential', 11 | '@vue/airbnb', 12 | '@vue/typescript/recommended', 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 2020, 16 | }, 17 | rules: { 18 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 20 | 'no-param-reassign': 0, 21 | 'no-return-assign': 0, 22 | 'no-plusplus': 0, 23 | }, 24 | overrides: [ 25 | { 26 | files: [ 27 | '**/__tests__/*.{j,t}s?(x)', 28 | '**/tests/unit/**/*.spec.{j,t}s?(x)', 29 | ], 30 | env: { 31 | jest: true, 32 | }, 33 | }, 34 | ], 35 | }; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /src/extension/dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ClearVue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClearVue 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your unit tests 19 | ``` 20 | npm run test:unit 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clearvue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve src/panel/main.ts", 7 | "build": "vue-cli-service build src/panel/main.ts", 8 | "build:extension": "webpack", 9 | "test:unit": "vue-cli-service test:unit", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "@popperjs/core": "^2.11.2", 14 | "bootstrap": "^5.1.3", 15 | "core-js": "^3.6.5", 16 | "d3": "^6.0.0", 17 | "d3-flextree": "^2.1.2", 18 | "vue": "^3.0.0", 19 | "vue-router": "^4.0.0-0", 20 | "vuex": "^4.0.0-0", 21 | "web-vitals": "^2.1.4" 22 | }, 23 | "devDependencies": { 24 | "@types/chrome": "^0.0.177", 25 | "@types/d3": "^7.1.0", 26 | "@types/jest": "^24.0.19", 27 | "@typescript-eslint/eslint-plugin": "^4.18.0", 28 | "@typescript-eslint/parser": "^4.18.0", 29 | "@vue/cli-plugin-babel": "~4.5.0", 30 | "@vue/cli-plugin-eslint": "~4.5.0", 31 | "@vue/cli-plugin-router": "~4.5.0", 32 | "@vue/cli-plugin-typescript": "~4.5.0", 33 | "@vue/cli-plugin-unit-jest": "~4.5.0", 34 | "@vue/cli-plugin-vuex": "~4.5.0", 35 | "@vue/cli-service": "~4.5.0", 36 | "@vue/compiler-sfc": "^3.0.0", 37 | "@vue/eslint-config-airbnb": "^5.0.2", 38 | "@vue/eslint-config-typescript": "^7.0.0", 39 | "@vue/test-utils": "^2.0.0-0", 40 | "eslint": "^6.7.2", 41 | "eslint-plugin-import": "^2.20.2", 42 | "eslint-plugin-vue": "^7.0.0", 43 | "typescript": "~4.1.5", 44 | "vue-jest": "^5.0.0-0", 45 | "webpack": "^4.46.0", 46 | "webpack-cli": "^4.9.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClearVue/6ab3d84d7c00a1653699cf82c604d21e5828bb62/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/extension/assets/128icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClearVue/6ab3d84d7c00a1653699cf82c604d21e5828bb62/src/extension/assets/128icon.png -------------------------------------------------------------------------------- /src/extension/assets/16icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClearVue/6ab3d84d7c00a1653699cf82c604d21e5828bb62/src/extension/assets/16icon.png -------------------------------------------------------------------------------- /src/extension/assets/48icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClearVue/6ab3d84d7c00a1653699cf82c604d21e5828bb62/src/extension/assets/48icon.png -------------------------------------------------------------------------------- /src/extension/background.js: -------------------------------------------------------------------------------- 1 | // Background script is running in the background of the chrome browser listening 2 | // and handling events triggerd. Our background script will mostly listen for events 3 | // coming from the devtool extension as well as those from the inspected window. 4 | // console.log('Hello from Background Service Worker'); 5 | 6 | const ports = {}; 7 | 8 | // Listener for receiving message from inspected window 9 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 10 | console.log('Received message from content script: ', message); 11 | console.log('From sender: ', sender.tab); 12 | const { action, payload } = message; 13 | const { tab } = sender; 14 | let targetPort; 15 | 16 | switch (action) { 17 | case 'detectVue': 18 | // if the message we receive from content script asks us to detect vue 19 | console.log('run script for detecting vue'); 20 | chrome.scripting.executeScript({ 21 | target: { tabId: tab.id }, 22 | function: () => { 23 | const script = document.createElement('script'); 24 | script.src = chrome.runtime.getURL('scripts/detector.js'); 25 | if (document.doctype) { 26 | document.documentElement.appendChild(script); 27 | } 28 | }, 29 | }); 30 | break; 31 | case 'updateTree': 32 | targetPort = ports[tab.id]; 33 | console.log('sending tree data to port: ', targetPort); 34 | 35 | targetPort.postMessage({ 36 | action, 37 | payload, 38 | }); 39 | break; 40 | case 'getVitals': 41 | targetPort = ports[tab.id]; 42 | console.log('sending web vitals data to port: ', targetPort); 43 | 44 | targetPort.postMessage({ 45 | action, 46 | payload, 47 | }); 48 | break; 49 | default: 50 | console.log('default case: nonspecified action'); 51 | } 52 | }); 53 | 54 | // Listener for receiving message from devtool extension (devtool and panel) 55 | chrome.runtime.onConnect.addListener((port) => { 56 | console.log('newly connected port: ', port); 57 | // tabId is used stored as port.name, used to uniquely identify each port 58 | const portId = parseInt(port.name, 10); 59 | ports[portId] = port; 60 | 61 | port.onMessage.addListener((message) => { 62 | const { action, payload, tabId } = message; 63 | console.log('Received message from connected port: ', message); 64 | 65 | switch (action) { 66 | case 'parseTab': 67 | console.log('injecting parser script to tab: ', tabId); 68 | 69 | chrome.scripting.executeScript({ 70 | target: { tabId }, 71 | function: () => { 72 | const script = document.createElement('script'); 73 | script.src = chrome.runtime.getURL('scripts/parser.js'); 74 | if (document.doctype) { 75 | document.documentElement.appendChild(script); 76 | } 77 | }, 78 | }); 79 | break; 80 | case 'getVitals': 81 | chrome.tabs.sendMessage(tabId, { tabId, action }); 82 | break; 83 | default: 84 | console.log('default case: nonspecified action'); 85 | } 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/extension/content.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(i,r,function(t){return e[t]}.bind(null,r));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);var i,r,a,o,c=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},u=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},s=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},f=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},d=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},m=-1,l=function(){return"hidden"===document.visibilityState?0:1/0},p=function(){s((function(e){var t=e.timeStamp;m=t}),!0)},v=function(){return m<0&&(m=l(),p(),f((function(){setTimeout((function(){m=l(),p()}),0)}))),{get firstHiddenTime(){return m}}},g=function(e,t){var n,i=v(),r=c("FCP"),a=function(e){"first-contentful-paint"===e.name&&(s&&s.disconnect(),e.startTime=0&&r1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){S(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,T),removeEventListener("pointercancel",i,T)};addEventListener("pointerup",n,T),addEventListener("pointercancel",i,T)}(t,e):S(t,e)}},L=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,w,T)}))},P={};const F={CLS:0,FID:0,LCP:0,TTFB:0,FCP:0},M=({name:e,value:t})=>{F[e]=t};!function(e,t){y||(g((function(e){h=e.value})),y=!0);var n,i=function(t){h>-1&&e(t)},r=c("CLS",0),a=0,o=[],m=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},l=u("layout-shift",m);l&&(n=d(i,r,t),s((function(){l.takeRecords().map(m),n(!0)})),f((function(){a=0,h=-1,r=c("CLS",0),n=d(i,r,t)})))}(M),function(e,t){var n,a=v(),m=c("FID"),l=function(e){e.startTimeperformance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}(M),g(M),console.log(F),window.addEventListener("message",e=>{if("clearVue"===e.data.type){console.log("received clearVue message on content script listener: ",e);const{action:t,payload:n}=e.data;chrome.runtime.sendMessage({action:t,payload:n})}}),chrome.runtime.onMessage.addListener((e,t,n)=>{console.log("Received message from background: ",e);const{tabId:i,action:r}=e;switch(r){case"getVitals":chrome.runtime.sendMessage({tabId:i,action:r,payload:JSON.stringify(F)});break;default:console.log("unsupported action received on content script")}}),chrome.runtime.sendMessage({action:"detectVue"})}]); -------------------------------------------------------------------------------- /src/extension/contentSrc.js: -------------------------------------------------------------------------------- 1 | // Content scripts are files that run in the context of web pages (inspected windows). 2 | // By using the standard Document Object Model (DOM), they are able to read details of 3 | // the web pages the browser visits, make changes to them, and pass information to their 4 | // parent extension. Our content scripts set up message passing wit extension background script. 5 | // console.log('Hello from Content Script'); 6 | 7 | // event listener for window in context of target web page (inspected window) 8 | // event listener waiting for a message to be passed back from 'backend/detector.js' 9 | import { 10 | getLCP, getFID, getCLS, getTTFB, getFCP, 11 | } from 'web-vitals'; 12 | 13 | const metrics = { 14 | CLS: 0, 15 | FID: 0, 16 | LCP: 0, 17 | TTFB: 0, 18 | FCP: 0, 19 | }; 20 | 21 | const gatherMetrics = ({ name, value }) => { 22 | metrics[name] = value; 23 | }; 24 | 25 | getCLS(gatherMetrics); 26 | getFID(gatherMetrics); 27 | getLCP(gatherMetrics); 28 | getTTFB(gatherMetrics); 29 | getFCP(gatherMetrics); 30 | console.log(metrics); 31 | 32 | window.addEventListener('message', (e) => { 33 | if (e.data.type === 'clearVue') { 34 | console.log('received clearVue message on content script listener: ', e); 35 | const { action, payload } = e.data; 36 | 37 | chrome.runtime.sendMessage({ action, payload }); 38 | } 39 | }); 40 | 41 | // event listener for messages from extension background 42 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 43 | console.log('Received message from background: ', message); 44 | const { tabId, action } = message; 45 | 46 | switch (action) { 47 | case 'getVitals': 48 | chrome.runtime.sendMessage({ tabId, action, payload: JSON.stringify(metrics) }); 49 | break; 50 | default: 51 | console.log('unsupported action received on content script'); 52 | } 53 | }); 54 | 55 | chrome.runtime.sendMessage({ action: 'detectVue' }); 56 | -------------------------------------------------------------------------------- /src/extension/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ClearVue 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/extension/devtools.js: -------------------------------------------------------------------------------- 1 | // A DevTools extension adds functionality to the Chrome DevTools. 2 | // DevTool extensions have access to DevTool extension APIs and 3 | // interact with the inspected window. 4 | 5 | // An instance of the extension's DevTools page is created each time a 6 | // DevTools window opens. The DevTools page exists for the lifetime of 7 | // the DevTools window. It has access to the same subset of the extension and 8 | // runtime APIs that a content script has access to. Like a content script, a 9 | // DevTools page can communicate with the background page using Message Passing. 10 | 11 | chrome.devtools.panels.create('ClearVue', null, '/dist/index.html'); 12 | -------------------------------------------------------------------------------- /src/extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ClearVue", 3 | "version": "1.0", 4 | "manifest_version": 3, 5 | "minimum_chrome_version": "10.0", 6 | "devtools_page": "devtools.html", 7 | "permissions": [ 8 | "tabs", 9 | "activeTab", 10 | "scripting", 11 | "storage" 12 | ], 13 | "host_permissions": [ 14 | "" 15 | ], 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "" 20 | ], 21 | "js": [ 22 | "content.js" 23 | ], 24 | "run_at": "document_idle" 25 | } 26 | ], 27 | "background": { 28 | "service_worker": "background.js" 29 | }, 30 | "web_accessible_resources": [{ 31 | "resources": ["scripts/detector.js", "scripts/parser.js"], 32 | "matches": [""] 33 | }], 34 | "icons": { 35 | "16": "assets/16icon.png", 36 | "48": "assets/48icon.png", 37 | "128": "assets/128icon.png" 38 | } 39 | } -------------------------------------------------------------------------------- /src/extension/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Main 7 | 8 | 9 | Hi 👋 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/extension/panel.js: -------------------------------------------------------------------------------- 1 | console.log('devTool API:', chrome.devtools.inspectedWindow); 2 | 3 | // 'runtime.connect' allows for longer conversations between content script to an extension 4 | // than a one-time request, when establishing a connection, each end is given a runtime.Port 5 | // used for sending and recieving msgs through that connection 6 | 7 | const port = chrome.runtime.connect(); 8 | 9 | port.postMessage({ 10 | action: 'initPanel', 11 | message: 'extension dispatch action to init devtool panel', 12 | }); 13 | 14 | port.onMessage.addListener((msg) => { 15 | console.log('port received msg:', msg); 16 | const received = document.createElement('div'); 17 | received.innerHTML = msg.data; 18 | document.getElementsByTagName('BODY')[0].appendChild(received); 19 | }); 20 | -------------------------------------------------------------------------------- /src/extension/scripts/detector.js: -------------------------------------------------------------------------------- 1 | // detector will check for "__VUE__" - a property that exists on Vue 3 web applications 2 | // once detected or not detected, it should pass a message back up to content/background 3 | 4 | // console.log('Hello from Detector Script'); 5 | 6 | function detectVue(win) { 7 | const vueDetected = !!window.__VUE__; 8 | if (vueDetected) { 9 | console.log('Vue3 detected: ', vueDetected); 10 | } else { 11 | console.log('Vue3 not detected'); 12 | } 13 | return vueDetected; 14 | } 15 | 16 | detectVue(window); 17 | -------------------------------------------------------------------------------- /src/extension/scripts/parser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /* eslint-disable no-trailing-spaces */ 3 | /* eslint-disable eol-last */ 4 | /* eslint-disable no-restricted-syntax */ 5 | /* eslint-disable no-underscore-dangle */ 6 | 7 | // parser will parse and process DOM on inpected window and pull all vue 8 | // instances/components and send it to content -> background -> devtool extension 9 | // start from HTML body 10 | const body = document.querySelector('body'); 11 | 12 | // Store DOM tree in a variable 13 | const candidates = body.children; 14 | 15 | // Vue elements storage in roots array 16 | const roots = []; 17 | 18 | // we check if the __vue_app__ property is defined on each DOM node 19 | // If present, we populate roots with any DOM element with the Vue property 20 | for (const candidate of candidates) { 21 | if (candidate.__vue_app__) { 22 | roots.push(candidate); 23 | } 24 | } 25 | 26 | const setName = (fileNamePath) => { 27 | const str = fileNamePath; 28 | const strArr = str.split('/'); 29 | 30 | return (strArr[strArr.length - 1].split('.')[0]); 31 | }; 32 | 33 | // Helper function to parse subTree array of properties (present in root level ._instance & component nodes) 34 | const parseSubTree = (instance) => { 35 | // access the instance's subTree object and iterate through 36 | const subTreeObj = instance.subTree; 37 | // parsedTree would store and return an entire instance's vue application hierarchy 38 | const parsedTree = {}; 39 | // iterate through each property within the instance.subTree object, pulling out "name", "children", and "props" 40 | for (const property in subTreeObj) { 41 | // if children or dynamic children property exists, call parseChildren function to parse array of child nodes 42 | if ((property === 'dynamicChildren' && Array.isArray(subTreeObj[property]) && subTreeObj[property].length !== 0)) parsedTree.children = parseChildren(subTreeObj[property]); 43 | // if component property exists, recursive call parseSubTree on the component property's SUBTREE 44 | else if (property === 'component' && subTreeObj[property]) parsedTree.components = parseSubTree(subTreeObj[property]); 45 | } 46 | // if name is not defined (for children it will be in its parser), regex the type.file 47 | if (!parsedTree.name) { 48 | if (instance.type['__file']) parsedTree.name = setName(instance.type['__file']); 49 | } else parsedTree.name = instance.type['name']; 50 | 51 | // save Vue data if component is called on instance 52 | if (Object.keys(instance.data).length !== 0) parsedTree.data = instance.data; 53 | 54 | // save relevant component parent/children props 55 | if (Object.keys(instance.props).length !== 0) parsedTree.props = instance.props; 56 | return parsedTree; 57 | }; 58 | 59 | // Helper function to parse children & dynamicChildren arrays 60 | const parseChildren = (childrenArray) => { 61 | // parsedChildNode is the storage array of children objects, return this data 62 | const parsedChildNode = []; 63 | // parse through array of childNodes 64 | childrenArray.forEach((childNode) => { 65 | // store each childNodes' "name", "children", and "props", if we hit a "component" that's truthy, call parseSubTree passing in the component's subTree 66 | const singleChildObject = {}; 67 | for (const property in childNode) { 68 | if (property === 'dynamicChildren' && Array.isArray(childNode[property]) && Object.keys(childNode[property].lenght !== 0)) singleChildObject.children = parseChildren(childNode[property]); 69 | else if (property === 'props' && childNode[property]) singleChildObject.props = childNode[property]; 70 | else if (property === 'type' && childNode[property].name) singleChildObject.name = childNode[property].name; 71 | 72 | else if (property === 'component' && childNode[property]) singleChildObject.children = parseSubTree(childNode[property]); 73 | } 74 | 75 | parsedChildNode.push(singleChildObject); 76 | }); 77 | return parsedChildNode; 78 | }; 79 | 80 | // create subTree to begin storing DOM nodes and relevant data 81 | const subTree = []; 82 | 83 | /* iterate through each Vue instance component 84 | and store the subTree (object containing root component hierarchy) */ 85 | roots.forEach((node) => { 86 | const instance = node.__vue_app__._instance; 87 | // iterate through root instance level subTree and pull out necessary properties and store into subTree 88 | subTree.push(parseSubTree(instance)); 89 | }); 90 | 91 | console.log('this is the subTree array: ', subTree); 92 | 93 | window.postMessage({ 94 | type: 'clearVue', 95 | action: 'updateTree', 96 | payload: JSON.stringify(subTree), 97 | }); -------------------------------------------------------------------------------- /src/panel/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 63 | 64 | 91 | -------------------------------------------------------------------------------- /src/panel/assets/flare-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flare", 3 | "children": [ 4 | { 5 | "name": "analytics", 6 | "children": [ 7 | { 8 | "name": "cluster", 9 | "children": [ 10 | {"name": "AgglomerativeCluster", "value": 3938}, 11 | {"name": "CommunityStructure", "value": 3812}, 12 | {"name": "HierarchicalCluster", "value": 6714}, 13 | {"name": "MergeEdge", "value": 743} 14 | ] 15 | }, 16 | { 17 | "name": "graph", 18 | "children": [ 19 | {"name": "BetweennessCentrality", "value": 3534}, 20 | {"name": "LinkDistance", "value": 5731}, 21 | {"name": "MaxFlowMinCut", "value": 7840}, 22 | {"name": "ShortestPaths", "value": 5914}, 23 | {"name": "SpanningTree", "value": 3416} 24 | ] 25 | }, 26 | { 27 | "name": "optimization", 28 | "children": [ 29 | {"name": "AspectRatioBanker", "value": 7074} 30 | ] 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "animate", 36 | "children": [ 37 | {"name": "Easing", "value": 17010}, 38 | {"name": "FunctionSequence", "value": 5842}, 39 | { 40 | "name": "interpolate", 41 | "children": [ 42 | {"name": "ArrayInterpolator", "value": 1983}, 43 | {"name": "ColorInterpolator", "value": 2047}, 44 | {"name": "DateInterpolator", "value": 1375}, 45 | {"name": "Interpolator", "value": 8746}, 46 | {"name": "MatrixInterpolator", "value": 2202}, 47 | {"name": "NumberInterpolator", "value": 1382}, 48 | {"name": "ObjectInterpolator", "value": 1629}, 49 | {"name": "PointInterpolator", "value": 1675}, 50 | {"name": "RectangleInterpolator", "value": 2042} 51 | ] 52 | }, 53 | {"name": "ISchedulable", "value": 1041}, 54 | {"name": "Parallel", "value": 5176}, 55 | {"name": "Pause", "value": 449}, 56 | {"name": "Scheduler", "value": 5593}, 57 | {"name": "Sequence", "value": 5534}, 58 | {"name": "Transition", "value": 9201}, 59 | {"name": "Transitioner", "value": 19975}, 60 | {"name": "TransitionEvent", "value": 1116}, 61 | {"name": "Tween", "value": 6006} 62 | ] 63 | }, 64 | { 65 | "name": "data", 66 | "children": [ 67 | { 68 | "name": "converters", 69 | "children": [ 70 | {"name": "Converters", "value": 721}, 71 | {"name": "DelimitedTextConverter", "value": 4294}, 72 | {"name": "GraphMLConverter", "value": 9800}, 73 | {"name": "IDataConverter", "value": 1314}, 74 | {"name": "JSONConverter", "value": 2220} 75 | ] 76 | }, 77 | {"name": "DataField", "value": 1759}, 78 | {"name": "DataSchema", "value": 2165}, 79 | {"name": "DataSet", "value": 586}, 80 | {"name": "DataSource", "value": 3331}, 81 | {"name": "DataTable", "value": 772}, 82 | {"name": "DataUtil", "value": 3322} 83 | ] 84 | }, 85 | { 86 | "name": "display", 87 | "children": [ 88 | {"name": "DirtySprite", "value": 8833}, 89 | {"name": "LineSprite", "value": 1732}, 90 | {"name": "RectSprite", "value": 3623}, 91 | {"name": "TextSprite", "value": 10066} 92 | ] 93 | }, 94 | { 95 | "name": "flex", 96 | "children": [ 97 | {"name": "FlareVis", "value": 4116} 98 | ] 99 | }, 100 | { 101 | "name": "physics", 102 | "children": [ 103 | {"name": "DragForce", "value": 1082}, 104 | {"name": "GravityForce", "value": 1336}, 105 | {"name": "IForce", "value": 319}, 106 | {"name": "NBodyForce", "value": 10498}, 107 | {"name": "Particle", "value": 2822}, 108 | {"name": "Simulation", "value": 9983}, 109 | {"name": "Spring", "value": 2213}, 110 | {"name": "SpringForce", "value": 1681} 111 | ] 112 | }, 113 | { 114 | "name": "query", 115 | "children": [ 116 | {"name": "AggregateExpression", "value": 1616}, 117 | {"name": "And", "value": 1027}, 118 | {"name": "Arithmetic", "value": 3891}, 119 | {"name": "Average", "value": 891}, 120 | {"name": "BinaryExpression", "value": 2893}, 121 | {"name": "Comparison", "value": 5103}, 122 | {"name": "CompositeExpression", "value": 3677}, 123 | {"name": "Count", "value": 781}, 124 | {"name": "DateUtil", "value": 4141}, 125 | {"name": "Distinct", "value": 933}, 126 | {"name": "Expression", "value": 5130}, 127 | {"name": "ExpressionIterator", "value": 3617}, 128 | {"name": "Fn", "value": 3240}, 129 | {"name": "If", "value": 2732}, 130 | {"name": "IsA", "value": 2039}, 131 | {"name": "Literal", "value": 1214}, 132 | {"name": "Match", "value": 3748}, 133 | {"name": "Maximum", "value": 843}, 134 | { 135 | "name": "methods", 136 | "children": [ 137 | {"name": "add", "value": 593}, 138 | {"name": "and", "value": 330}, 139 | {"name": "average", "value": 287}, 140 | {"name": "count", "value": 277}, 141 | {"name": "distinct", "value": 292}, 142 | {"name": "div", "value": 595}, 143 | {"name": "eq", "value": 594}, 144 | {"name": "fn", "value": 460}, 145 | {"name": "gt", "value": 603}, 146 | {"name": "gte", "value": 625}, 147 | {"name": "iff", "value": 748}, 148 | {"name": "isa", "value": 461}, 149 | {"name": "lt", "value": 597}, 150 | {"name": "lte", "value": 619}, 151 | {"name": "max", "value": 283}, 152 | {"name": "min", "value": 283}, 153 | {"name": "mod", "value": 591}, 154 | {"name": "mul", "value": 603}, 155 | {"name": "neq", "value": 599}, 156 | {"name": "not", "value": 386}, 157 | {"name": "or", "value": 323}, 158 | {"name": "orderby", "value": 307}, 159 | {"name": "range", "value": 772}, 160 | {"name": "select", "value": 296}, 161 | {"name": "stddev", "value": 363}, 162 | {"name": "sub", "value": 600}, 163 | {"name": "sum", "value": 280}, 164 | {"name": "update", "value": 307}, 165 | {"name": "variance", "value": 335}, 166 | {"name": "where", "value": 299}, 167 | {"name": "xor", "value": 354}, 168 | {"name": "_", "value": 264} 169 | ] 170 | }, 171 | {"name": "Minimum", "value": 843}, 172 | {"name": "Not", "value": 1554}, 173 | {"name": "Or", "value": 970}, 174 | {"name": "Query", "value": 13896}, 175 | {"name": "Range", "value": 1594}, 176 | {"name": "StringUtil", "value": 4130}, 177 | {"name": "Sum", "value": 791}, 178 | {"name": "Variable", "value": 1124}, 179 | {"name": "Variance", "value": 1876}, 180 | {"name": "Xor", "value": 1101} 181 | ] 182 | }, 183 | { 184 | "name": "scale", 185 | "children": [ 186 | {"name": "IScaleMap", "value": 2105}, 187 | {"name": "LinearScale", "value": 1316}, 188 | {"name": "LogScale", "value": 3151}, 189 | {"name": "OrdinalScale", "value": 3770}, 190 | {"name": "QuantileScale", "value": 2435}, 191 | {"name": "QuantitativeScale", "value": 4839}, 192 | {"name": "RootScale", "value": 1756}, 193 | {"name": "Scale", "value": 4268}, 194 | {"name": "ScaleType", "value": 1821}, 195 | {"name": "TimeScale", "value": 5833} 196 | ] 197 | }, 198 | { 199 | "name": "util", 200 | "children": [ 201 | {"name": "Arrays", "value": 8258}, 202 | {"name": "Colors", "value": 10001}, 203 | {"name": "Dates", "value": 8217}, 204 | {"name": "Displays", "value": 12555}, 205 | {"name": "Filter", "value": 2324}, 206 | {"name": "Geometry", "value": 10993}, 207 | { 208 | "name": "heap", 209 | "children": [ 210 | {"name": "FibonacciHeap", "value": 9354}, 211 | {"name": "HeapNode", "value": 1233} 212 | ] 213 | }, 214 | {"name": "IEvaluable", "value": 335}, 215 | {"name": "IPredicate", "value": 383}, 216 | {"name": "IValueProxy", "value": 874}, 217 | { 218 | "name": "math", 219 | "children": [ 220 | {"name": "DenseMatrix", "value": 3165}, 221 | {"name": "IMatrix", "value": 2815}, 222 | {"name": "SparseMatrix", "value": 3366} 223 | ] 224 | }, 225 | {"name": "Maths", "value": 17705}, 226 | {"name": "Orientation", "value": 1486}, 227 | { 228 | "name": "palette", 229 | "children": [ 230 | {"name": "ColorPalette", "value": 6367}, 231 | {"name": "Palette", "value": 1229}, 232 | {"name": "ShapePalette", "value": 2059}, 233 | {"name": "SizePalette", "value": 2291} 234 | ] 235 | }, 236 | {"name": "Property", "value": 5559}, 237 | {"name": "Shapes", "value": 19118}, 238 | {"name": "Sort", "value": 6887}, 239 | {"name": "Stats", "value": 6557}, 240 | {"name": "Strings", "value": 22026} 241 | ] 242 | }, 243 | { 244 | "name": "vis", 245 | "children": [ 246 | { 247 | "name": "axis", 248 | "children": [ 249 | {"name": "Axes", "value": 1302}, 250 | {"name": "Axis", "value": 24593}, 251 | {"name": "AxisGridLine", "value": 652}, 252 | {"name": "AxisLabel", "value": 636}, 253 | {"name": "CartesianAxes", "value": 6703} 254 | ] 255 | }, 256 | { 257 | "name": "controls", 258 | "children": [ 259 | {"name": "AnchorControl", "value": 2138}, 260 | {"name": "ClickControl", "value": 3824}, 261 | {"name": "Control", "value": 1353}, 262 | {"name": "ControlList", "value": 4665}, 263 | {"name": "DragControl", "value": 2649}, 264 | {"name": "ExpandControl", "value": 2832}, 265 | {"name": "HoverControl", "value": 4896}, 266 | {"name": "IControl", "value": 763}, 267 | {"name": "PanZoomControl", "value": 5222}, 268 | {"name": "SelectionControl", "value": 7862}, 269 | {"name": "TooltipControl", "value": 8435} 270 | ] 271 | }, 272 | { 273 | "name": "data", 274 | "children": [ 275 | {"name": "Data", "value": 20544}, 276 | {"name": "DataList", "value": 19788}, 277 | {"name": "DataSprite", "value": 10349}, 278 | {"name": "EdgeSprite", "value": 3301}, 279 | {"name": "NodeSprite", "value": 19382}, 280 | { 281 | "name": "render", 282 | "children": [ 283 | {"name": "ArrowType", "value": 698}, 284 | {"name": "EdgeRenderer", "value": 5569}, 285 | {"name": "IRenderer", "value": 353}, 286 | {"name": "ShapeRenderer", "value": 2247} 287 | ] 288 | }, 289 | {"name": "ScaleBinding", "value": 11275}, 290 | {"name": "Tree", "value": 7147}, 291 | {"name": "TreeBuilder", "value": 9930} 292 | ] 293 | }, 294 | { 295 | "name": "events", 296 | "children": [ 297 | {"name": "DataEvent", "value": 2313}, 298 | {"name": "SelectionEvent", "value": 1880}, 299 | {"name": "TooltipEvent", "value": 1701}, 300 | {"name": "VisualizationEvent", "value": 1117} 301 | ] 302 | }, 303 | { 304 | "name": "legend", 305 | "children": [ 306 | {"name": "Legend", "value": 20859}, 307 | {"name": "LegendItem", "value": 4614}, 308 | {"name": "LegendRange", "value": 10530} 309 | ] 310 | }, 311 | { 312 | "name": "operator", 313 | "children": [ 314 | { 315 | "name": "distortion", 316 | "children": [ 317 | {"name": "BifocalDistortion", "value": 4461}, 318 | {"name": "Distortion", "value": 6314}, 319 | {"name": "FisheyeDistortion", "value": 3444} 320 | ] 321 | }, 322 | { 323 | "name": "encoder", 324 | "children": [ 325 | {"name": "ColorEncoder", "value": 3179}, 326 | {"name": "Encoder", "value": 4060}, 327 | {"name": "PropertyEncoder", "value": 4138}, 328 | {"name": "ShapeEncoder", "value": 1690}, 329 | {"name": "SizeEncoder", "value": 1830} 330 | ] 331 | }, 332 | { 333 | "name": "filter", 334 | "children": [ 335 | {"name": "FisheyeTreeFilter", "value": 5219}, 336 | {"name": "GraphDistanceFilter", "value": 3165}, 337 | {"name": "VisibilityFilter", "value": 3509} 338 | ] 339 | }, 340 | {"name": "IOperator", "value": 1286}, 341 | { 342 | "name": "label", 343 | "children": [ 344 | {"name": "Labeler", "value": 9956}, 345 | {"name": "RadialLabeler", "value": 3899}, 346 | {"name": "StackedAreaLabeler", "value": 3202} 347 | ] 348 | }, 349 | { 350 | "name": "layout", 351 | "children": [ 352 | {"name": "AxisLayout", "value": 6725}, 353 | {"name": "BundledEdgeRouter", "value": 3727}, 354 | {"name": "CircleLayout", "value": 9317}, 355 | {"name": "CirclePackingLayout", "value": 12003}, 356 | {"name": "DendrogramLayout", "value": 4853}, 357 | {"name": "ForceDirectedLayout", "value": 8411}, 358 | {"name": "IcicleTreeLayout", "value": 4864}, 359 | {"name": "IndentedTreeLayout", "value": 3174}, 360 | {"name": "Layout", "value": 7881}, 361 | {"name": "NodeLinkTreeLayout", "value": 12870}, 362 | {"name": "PieLayout", "value": 2728}, 363 | {"name": "RadialTreeLayout", "value": 12348}, 364 | {"name": "RandomLayout", "value": 870}, 365 | {"name": "StackedAreaLayout", "value": 9121}, 366 | {"name": "TreeMapLayout", "value": 9191} 367 | ] 368 | }, 369 | {"name": "Operator", "value": 2490}, 370 | {"name": "OperatorList", "value": 5248}, 371 | {"name": "OperatorSequence", "value": 4190}, 372 | {"name": "OperatorSwitch", "value": 2581}, 373 | {"name": "SortOperator", "value": 2023} 374 | ] 375 | }, 376 | {"name": "Visualization", "value": 16540} 377 | ] 378 | } 379 | ] 380 | } 381 | -------------------------------------------------------------------------------- /src/panel/assets/flare.json: -------------------------------------------------------------------------------- 1 | {"name":"flare","children":[{"name":"analytics","children":[{"name":"cluster","children":[{"name":"AgglomerativeCluster","size":3938},{"name":"CommunityStructure","size":3812},{"name":"HierarchicalCluster","size":6714},{"name":"MergeEdge","size":743}]},{"name":"graph","children":[{"name":"BetweennessCentrality","size":3534},{"name":"LinkDistance","size":5731},{"name":"MaxFlowMinCut","size":7840},{"name":"ShortestPaths","size":5914},{"name":"SpanningTree","size":3416}]},{"name":"optimization","children":[{"name":"AspectRatioBanker","size":7074}]}]},{"name":"animate","children":[{"name":"Easing","size":17010},{"name":"FunctionSequence","size":5842},{"name":"interpolate","children":[{"name":"ArrayInterpolator","size":1983},{"name":"ColorInterpolator","size":2047},{"name":"DateInterpolator","size":1375},{"name":"Interpolator","size":8746},{"name":"MatrixInterpolator","size":2202},{"name":"NumberInterpolator","size":1382},{"name":"ObjectInterpolator","size":1629},{"name":"PointInterpolator","size":1675},{"name":"RectangleInterpolator","size":2042}]},{"name":"ISchedulable","size":1041},{"name":"Parallel","size":5176},{"name":"Pause","size":449},{"name":"Scheduler","size":5593},{"name":"Sequence","size":5534},{"name":"Transition","size":9201},{"name":"Transitioner","size":19975},{"name":"TransitionEvent","size":1116},{"name":"Tween","size":6006}]},{"name":"data","children":[{"name":"converters","children":[{"name":"Converters","size":721},{"name":"DelimitedTextConverter","size":4294},{"name":"GraphMLConverter","size":9800},{"name":"IDataConverter","size":1314},{"name":"JSONConverter","size":2220}]},{"name":"DataField","size":1759},{"name":"DataSchema","size":2165},{"name":"DataSet","size":586},{"name":"DataSource","size":3331},{"name":"DataTable","size":772},{"name":"DataUtil","size":3322}]},{"name":"display","children":[{"name":"DirtySprite","size":8833},{"name":"LineSprite","size":1732},{"name":"RectSprite","size":3623},{"name":"TextSprite","size":10066}]},{"name":"flex","children":[{"name":"FlareVis","size":4116}]},{"name":"physics","children":[{"name":"DragForce","size":1082},{"name":"GravityForce","size":1336},{"name":"IForce","size":319},{"name":"NBodyForce","size":10498},{"name":"Particle","size":2822},{"name":"Simulation","size":9983},{"name":"Spring","size":2213},{"name":"SpringForce","size":1681}]},{"name":"query","children":[{"name":"AggregateExpression","size":1616},{"name":"And","size":1027},{"name":"Arithmetic","size":3891},{"name":"Average","size":891},{"name":"BinaryExpression","size":2893},{"name":"Comparison","size":5103},{"name":"CompositeExpression","size":3677},{"name":"Count","size":781},{"name":"DateUtil","size":4141},{"name":"Distinct","size":933},{"name":"Expression","size":5130},{"name":"ExpressionIterator","size":3617},{"name":"Fn","size":3240},{"name":"If","size":2732},{"name":"IsA","size":2039},{"name":"Literal","size":1214},{"name":"Match","size":3748},{"name":"Maximum","size":843},{"name":"methods","children":[{"name":"add","size":593},{"name":"and","size":330},{"name":"average","size":287},{"name":"count","size":277},{"name":"distinct","size":292},{"name":"div","size":595},{"name":"eq","size":594},{"name":"fn","size":460},{"name":"gt","size":603},{"name":"gte","size":625},{"name":"iff","size":748},{"name":"isa","size":461},{"name":"lt","size":597},{"name":"lte","size":619},{"name":"max","size":283},{"name":"min","size":283},{"name":"mod","size":591},{"name":"mul","size":603},{"name":"neq","size":599},{"name":"not","size":386},{"name":"or","size":323},{"name":"orderby","size":307},{"name":"range","size":772},{"name":"select","size":296},{"name":"stddev","size":363},{"name":"sub","size":600},{"name":"sum","size":280},{"name":"update","size":307},{"name":"variance","size":335},{"name":"where","size":299},{"name":"xor","size":354},{"name":"_","size":264}]},{"name":"Minimum","size":843},{"name":"Not","size":1554},{"name":"Or","size":970},{"name":"Query","size":13896},{"name":"Range","size":1594},{"name":"StringUtil","size":4130},{"name":"Sum","size":791},{"name":"Variable","size":1124},{"name":"Variance","size":1876},{"name":"Xor","size":1101}]},{"name":"scale","children":[{"name":"IScaleMap","size":2105},{"name":"LinearScale","size":1316},{"name":"LogScale","size":3151},{"name":"OrdinalScale","size":3770},{"name":"QuantileScale","size":2435},{"name":"QuantitativeScale","size":4839},{"name":"RootScale","size":1756},{"name":"Scale","size":4268},{"name":"ScaleType","size":1821},{"name":"TimeScale","size":5833}]},{"name":"util","children":[{"name":"Arrays","size":8258},{"name":"Colors","size":10001},{"name":"Dates","size":8217},{"name":"Displays","size":12555},{"name":"Filter","size":2324},{"name":"Geometry","size":10993},{"name":"heap","children":[{"name":"FibonacciHeap","size":9354},{"name":"HeapNode","size":1233}]},{"name":"IEvaluable","size":335},{"name":"IPredicate","size":383},{"name":"IValueProxy","size":874},{"name":"math","children":[{"name":"DenseMatrix","size":3165},{"name":"IMatrix","size":2815},{"name":"SparseMatrix","size":3366}]},{"name":"Maths","size":17705},{"name":"Orientation","size":1486},{"name":"palette","children":[{"name":"ColorPalette","size":6367},{"name":"Palette","size":1229},{"name":"ShapePalette","size":2059},{"name":"SizePalette","size":2291}]},{"name":"Property","size":5559},{"name":"Shapes","size":19118},{"name":"Sort","size":6887},{"name":"Stats","size":6557},{"name":"Strings","size":22026}]},{"name":"vis","children":[{"name":"axis","children":[{"name":"Axes","size":1302},{"name":"Axis","size":24593},{"name":"AxisGridLine","size":652},{"name":"AxisLabel","size":636},{"name":"CartesianAxes","size":6703}]},{"name":"controls","children":[{"name":"AnchorControl","size":2138},{"name":"ClickControl","size":3824},{"name":"Control","size":1353},{"name":"ControlList","size":4665},{"name":"DragControl","size":2649},{"name":"ExpandControl","size":2832},{"name":"HoverControl","size":4896},{"name":"IControl","size":763},{"name":"PanZoomControl","size":5222},{"name":"SelectionControl","size":7862},{"name":"TooltipControl","size":8435}]},{"name":"data","children":[{"name":"Data","size":20544},{"name":"DataList","size":19788},{"name":"DataSprite","size":10349},{"name":"EdgeSprite","size":3301},{"name":"NodeSprite","size":19382},{"name":"render","children":[{"name":"ArrowType","size":698},{"name":"EdgeRenderer","size":5569},{"name":"IRenderer","size":353},{"name":"ShapeRenderer","size":2247}]},{"name":"ScaleBinding","size":11275},{"name":"Tree","size":7147},{"name":"TreeBuilder","size":9930}]},{"name":"events","children":[{"name":"DataEvent","size":2313},{"name":"SelectionEvent","size":1880},{"name":"TooltipEvent","size":1701},{"name":"VisualizationEvent","size":1117}]},{"name":"legend","children":[{"name":"Legend","size":20859},{"name":"LegendItem","size":4614},{"name":"LegendRange","size":10530}]},{"name":"operator","children":[{"name":"distortion","children":[{"name":"BifocalDistortion","size":4461},{"name":"Distortion","size":6314},{"name":"FisheyeDistortion","size":3444}]},{"name":"encoder","children":[{"name":"ColorEncoder","size":3179},{"name":"Encoder","size":4060},{"name":"PropertyEncoder","size":4138},{"name":"ShapeEncoder","size":1690},{"name":"SizeEncoder","size":1830}]},{"name":"filter","children":[{"name":"FisheyeTreeFilter","size":5219},{"name":"GraphDistanceFilter","size":3165},{"name":"VisibilityFilter","size":3509}]},{"name":"IOperator","size":1286},{"name":"label","children":[{"name":"Labeler","size":9956},{"name":"RadialLabeler","size":3899},{"name":"StackedAreaLabeler","size":3202}]},{"name":"layout","children":[{"name":"AxisLayout","size":6725},{"name":"BundledEdgeRouter","size":3727},{"name":"CircleLayout","size":9317},{"name":"CirclePackingLayout","size":12003},{"name":"DendrogramLayout","size":4853},{"name":"ForceDirectedLayout","size":8411},{"name":"IcicleTreeLayout","size":4864},{"name":"IndentedTreeLayout","size":3174},{"name":"Layout","size":7881},{"name":"NodeLinkTreeLayout","size":12870},{"name":"PieLayout","size":2728},{"name":"RadialTreeLayout","size":12348},{"name":"RandomLayout","size":870},{"name":"StackedAreaLayout","size":9121},{"name":"TreeMapLayout","size":9191}]},{"name":"Operator","size":2490},{"name":"OperatorList","size":5248},{"name":"OperatorSequence","size":4190},{"name":"OperatorSwitch","size":2581},{"name":"SortOperator","size":2023}]},{"name":"Visualization","size":16540}]}]} -------------------------------------------------------------------------------- /src/panel/assets/img/48icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClearVue/6ab3d84d7c00a1653699cf82c604d21e5828bb62/src/panel/assets/img/48icon.png -------------------------------------------------------------------------------- /src/panel/assets/img/icon-circle-pack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/panel/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ClearVue/6ab3d84d7c00a1653699cf82c604d21e5828bb62/src/panel/assets/img/logo.png -------------------------------------------------------------------------------- /src/panel/assets/img/tree-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/panel/components/CirclePack.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/panel/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 47 | 48 | 49 | 65 | -------------------------------------------------------------------------------- /src/panel/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /src/panel/components/Tree.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 199 | 200 | 201 | 204 | -------------------------------------------------------------------------------- /src/panel/components/VerticalTree.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 300 | 301 | 311 | -------------------------------------------------------------------------------- /src/panel/components/ZoomableCirclePack.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 116 | -------------------------------------------------------------------------------- /src/panel/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | import 'bootstrap/dist/css/bootstrap.min.css'; 6 | import 'bootstrap'; 7 | 8 | createApp(App).use(store).use(router).mount('#app'); 9 | -------------------------------------------------------------------------------- /src/panel/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; 2 | import Home from '../views/Home.vue'; 3 | import TreeView from '../views/TreeView.vue'; 4 | import Metrics from '../views/MetricsView.vue'; 5 | 6 | const routes: Array = [ 7 | { 8 | path: '/', 9 | name: 'TreeView', 10 | component: TreeView, 11 | }, 12 | { 13 | path: '/home', 14 | name: 'Home', 15 | component: Home, 16 | }, 17 | { 18 | path: '/circle-pack-view', 19 | name: 'CircleView', 20 | // route level code-splitting 21 | // this generates a separate chunk (about.[hash].js) for this route 22 | // which is lazy-loaded when the route is visited. 23 | component: () => import(/* webpackChunkName: "about" */ '../views/CircleView.vue'), 24 | }, 25 | { 26 | path: '/metrics', 27 | name: 'Metrics', 28 | component: Metrics, 29 | }, 30 | ]; 31 | 32 | const router = createRouter({ 33 | history: createWebHashHistory(), 34 | routes, 35 | }); 36 | 37 | export default router; 38 | -------------------------------------------------------------------------------- /src/panel/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/panel/shims-vuex.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { ComponentCustomProperties } from 'vue'; 3 | import { Store } from 'vuex'; 4 | 5 | declare module '@vue/runtime-core' { 6 | // Declare your own store states. 7 | interface State { 8 | tabId: number, 9 | activeData: any, 10 | activeProps: any, 11 | webVitals: any, 12 | } 13 | 14 | interface ComponentCustomProperties { 15 | $store: Store 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/panel/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex'; 2 | 3 | export default createStore({ 4 | state: { 5 | tabId: 0, 6 | port: {}, 7 | webVitals: { 8 | CLS: 0, 9 | FID: 0, 10 | LCP: 0, 11 | TTFB: 0, 12 | FCP: 0, 13 | }, 14 | treeData: [], 15 | testData: {}, 16 | activeData: { sample: 'data' }, 17 | activeProps: { sample: 'prop' }, 18 | }, 19 | mutations: { 20 | initTab(state) { 21 | console.log('init tabId'); 22 | const { tabId } = chrome.devtools.inspectedWindow; 23 | state.tabId = tabId; 24 | }, 25 | initPort(state, port) { 26 | state.port = port; 27 | }, 28 | updateVitals(state, newVitals) { 29 | state.webVitals = JSON.parse(newVitals); 30 | }, 31 | updateTree(state, payload) { 32 | state.treeData = JSON.parse(payload); 33 | console.log('vuex treeData updated: ', state.treeData); 34 | }, 35 | updateActiveData(state, newData) { 36 | state.activeData = newData; 37 | }, 38 | updateActiveProps(state, newProps) { 39 | state.activeProps = newProps; 40 | }, 41 | testLog(state, str) { 42 | console.log('mutation invoked: ', str); 43 | }, 44 | }, 45 | getters: { 46 | getChartData(state) { 47 | const deepCopy = (data: any) => JSON.parse(JSON.stringify(data)); 48 | const roughSizeOfObject = (object: any) : number => { 49 | const objectList = []; 50 | const stack = [object]; 51 | let bytes = 0; 52 | 53 | while (stack.length) { 54 | const value = stack.pop(); 55 | 56 | if (typeof value === 'boolean') { 57 | bytes += 4; 58 | } else if (typeof value === 'string') { 59 | bytes += value.length * 2; 60 | } else if (typeof value === 'number') { 61 | bytes += 8; 62 | } else if 63 | ( 64 | typeof value === 'object' 65 | && objectList.indexOf(value) === -1 66 | ) { 67 | objectList.push(value); 68 | stack.push(...Object.values(value)); 69 | } 70 | } 71 | return bytes; 72 | }; 73 | const processTree = (tree: any) => { 74 | if (tree === undefined || tree === null) return {}; 75 | const { 76 | name, props, data, children, components, 77 | } = tree; 78 | const node = { 79 | name, 80 | props, 81 | data, 82 | children: [], 83 | size: [100, 50], 84 | value: 0, 85 | }; 86 | 87 | if (node.name === undefined) node.name = 'Component'; 88 | 89 | // if (typeof props === 'object') { 90 | // node.props = deepCopy(props); 91 | // node.value += roughSizeOfObject(node.props); 92 | // } 93 | // if (typeof data === 'object') { 94 | // node.data = deepCopy(data); 95 | // node.value += roughSizeOfObject(node.data); 96 | // } 97 | 98 | if (components) { 99 | (node.children as any[]).push(processTree(components)); 100 | } else if (children) { 101 | console.log('children found! -->', children); 102 | if (Array.isArray(children)) { 103 | for (let i = 0; i < children.length; i++) { 104 | if (Object.keys(children[i]).length) { 105 | (node.children as any[]).push(processTree(children[i])); 106 | } 107 | } 108 | } else if (typeof (children) === 'object' && Object.keys(children).length) { 109 | (node.children as any[]).push(processTree(children)); 110 | if (children.components) { 111 | (node.children as any[]).push(processTree(children.components)); 112 | } 113 | } 114 | } 115 | 116 | return node; 117 | }; 118 | 119 | return processTree(state.treeData[0]); 120 | }, 121 | isDevMode(state) { 122 | if ((process.env.NODE_ENV === 'production')) { 123 | return false; 124 | } 125 | return true; 126 | }, 127 | }, 128 | actions: {}, 129 | modules: {}, 130 | }); 131 | -------------------------------------------------------------------------------- /src/panel/views/CircleView.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /src/panel/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /src/panel/views/MetricsView.vue: -------------------------------------------------------------------------------- 1 | 182 | 183 | 195 | -------------------------------------------------------------------------------- /src/panel/views/TreeView.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 86 | 87 | 98 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import HelloWorld from '@/panel/components/HelloWorld.vue'; 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message'; 7 | const wrapper = shallowMount(HelloWorld, { 8 | props: { msg }, 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env", 16 | "jest", 17 | "chrome" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | publicPath: './', 5 | outputDir: path.resolve(__dirname, './src/extension/dist'), 6 | }; 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const webpack = require('webpack'); 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: './src/extension/contentSrc.js', 7 | output: { 8 | filename: 'content.js', 9 | path: path.resolve(__dirname, './src/extension'), 10 | }, 11 | }; --------------------------------------------------------------------------------