├── .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 |
12 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/panel/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation .
8 |
9 |
Installed CLI Plugins
10 |
18 |
Essential Links
19 |
26 |
Ecosystem
27 |
34 |
35 |
36 |
37 |
47 |
48 |
49 |
65 |
--------------------------------------------------------------------------------
/src/panel/components/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
11 | {{ title }}
12 |
13 |
14 |
15 |
16 |
17 | Hierarchy
18 |
19 |
20 |
21 | Composition
22 |
23 |
24 |
25 | Metrics
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
--------------------------------------------------------------------------------
/src/panel/components/Tree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
199 |
200 |
201 |
204 |
--------------------------------------------------------------------------------
/src/panel/components/VerticalTree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
300 |
301 |
311 |
--------------------------------------------------------------------------------
/src/panel/components/ZoomableCirclePack.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
7 |
8 |
9 |
18 |
--------------------------------------------------------------------------------
/src/panel/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/src/panel/views/MetricsView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Metrics
7 | Time (ms)
8 | Passable Threshold
9 | Critical Threshold
10 |
11 |
12 |
13 |
14 | CLS
15 | {{ metrics.CLS }}
16 | 100 ms
17 | 250 ms
18 |
19 |
20 | FCP
21 | {{ metrics.FCP }}
22 | 1800 ms
23 | 3000 ms
24 |
25 |
26 | LCP
27 | {{ metrics.LCP }}
28 | 2500 ms
29 | 4000 ms
30 |
31 |
32 | TTFB
33 | {{ metrics.TTFB }}
34 | 100 ms
35 | 500 ms
36 |
37 |
38 | FID
39 | {{ metrics.FID }}
40 | 100 ms
41 | 300 ms
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
61 |
67 |
68 | CLS is a measure of the largest burst of layout shift scores for every unexpected layout
69 | shift that occurs during the entire lifespan of a page. A layout shift occurs any time a
70 | visible element changes its position from one rendered frame to the next.
71 |
72 |
73 |
74 |
75 |
87 |
93 |
94 | The First Contentful Paint (FCP) metric measures the time from when the page starts
95 | loading to when any part of the page's content is rendered on the screen. For this
96 | metric, 'content' refers to text, images (including background images),
97 | svg
elements, or non-white canvas
elements.
98 |
99 |
100 |
101 |
102 |
114 |
120 |
121 | The Largest Contentful Paint (LCP) metric reports the render time of the largest image
122 | or text block visible within the viewport, relative to when the page first started
123 | loading.
124 |
125 |
126 |
127 |
128 |
140 |
146 |
147 | The Time to First Byte (TTFB) measures the time between the request for a resource and
148 | when the first byte of a response begins to arrive. It helps identify when a web server
149 | is running too slow to respond to requests.
150 |
151 |
152 |
153 |
154 |
166 |
172 |
173 | First Input Delay (FID) measures the time when a user first interacts with the page
174 | (when they click or hit a button) to the time when the browser is actually able to first
175 | begin processing event handlers in response to that interaction.
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
195 |
--------------------------------------------------------------------------------
/src/panel/views/TreeView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
26 |
30 |
31 | {{ activeProps }}
32 |
33 |
34 |
35 |
36 |
37 |
48 |
52 |
53 | {{ activeData }}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
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 | };
--------------------------------------------------------------------------------