(isOpen = false)} />
21 |
22 |
23 | - {
26 | ($visibility.component = !$visibility.component)}
27 | }
28 | >
29 | Components
30 |
31 | - ($visibility.element = !$visibility.element)}
34 | >
35 | Elements
36 |
37 | - ($visibility.text = !$visibility.text)}
40 | >
41 | Text
42 |
43 |
44 | {/if}
45 |
46 |
47 |
134 |
--------------------------------------------------------------------------------
/src/utils/changeState.js:
--------------------------------------------------------------------------------
1 | //iterate through data recursively
2 | //the data that we're pulling is from props display view
3 |
4 | const componentSearch = (d, sourceData) => {
5 | let resultData;
6 |
7 | if (sourceData.name === d) {
8 | resultData = { props: sourceData.props, state: sourceData.state };
9 | return resultData;
10 | }
11 |
12 | //if it's not root component that was clicked --> if root component has children, then iterate through children
13 | if (sourceData && sourceData.children) {
14 | for (let i = 0; i < sourceData.children.length; i++) {
15 | //recursively call component search to see if sourceData.children[i] has children
16 | let recursive = componentSearch(d, sourceData.children[i]);
17 | if (recursive) return recursive;
18 | }
19 | }
20 | return null;
21 | };
22 |
23 | function createContainerEl() {
24 | const containerDiv = document.createElement('div');
25 | containerDiv.style.padding = '1rem 0';
26 |
27 | return containerDiv;
28 | }
29 |
30 | function parseObjects(state, parent) {
31 | for (const key in state) {
32 | const child = createDivEl(key, state[key]);
33 | parent.appendChild(child);
34 | }
35 | }
36 |
37 | function parseList(list, heading) {
38 | const container = createContainerEl();
39 | const header = document.createElement('div');
40 |
41 | const parent = document.createElement('ul');
42 | const footer = document.createElement('div');
43 | footer.innerHTML = `]`;
44 | footer.style.fontSize = '1.5rem';
45 |
46 | for (let i = 0; i < list.length; i++) {
47 | if (list[i] !== 0 && !list[i]) continue;
48 |
49 | const child = document.createElement('li');
50 | if (Array.isArray(list[i])) {
51 | child.appendChild(parseList(list[i]));
52 | } else if (typeof list[i] === 'object') {
53 | parseObjects(list[i], child);
54 | } else {
55 | child.innerText = list[i];
56 | }
57 |
58 | child.style.padding = '1rem 1rem 0 1rem';
59 | parent.appendChild(child);
60 | }
61 |
62 | header.innerHTML = `${heading} [`;
63 | header.style.fontSize = '1.5rem';
64 |
65 | container.appendChild(header);
66 | container.appendChild(parent);
67 | container.appendChild(footer);
68 | return container;
69 | }
70 |
71 | function createDivEl(key, value) {
72 | const divParent = document.createElement('div');
73 | const spanChild = document.createElement('span');
74 | const inputValue = document.createElement('input');
75 | const inputKey = document.createElement('div');
76 |
77 | inputKey.innerHTML = `${key}: `;
78 |
79 | spanChild.style.display = 'flex';
80 | spanChild.style.gap = '2%';
81 | spanChild.style.fontSize = '12px';
82 | spanChild.style.fontWeight = 500;
83 |
84 | inputValue.value = value;
85 | inputValue.style.color = '#40b3ff';
86 | inputValue.style.background = 'transparent';
87 | inputValue.style.border = 'none';
88 | inputValue.style.outline = 'none';
89 | inputValue.style.width = '-webkit-fill-available';
90 | inputValue.style.fontSize = '12px';
91 | inputValue.style.fontWeight = 500;
92 | spanChild.appendChild(inputKey);
93 | spanChild.appendChild(inputValue);
94 | divParent.appendChild(spanChild);
95 |
96 | inputValue.addEventListener('keyup', function (event) {
97 | event.preventDefault();
98 | if (event.keyCode === 13) {
99 | this.blur();
100 | }
101 | });
102 |
103 | inputValue.addEventListener('onChange', function (event) {});
104 |
105 | return divParent;
106 | }
107 |
108 | const changeState = (state, nested = false) => {
109 | let result = [];
110 |
111 | const uniContainer = createContainerEl();
112 | const header = document.createElement('div');
113 | const footer = document.createElement('div');
114 | header.style.fontSize = '1.5rem';
115 | footer.style.fontSize = '1.5rem';
116 | if (!nested) {
117 | uniContainer.appendChild(header);
118 | }
119 |
120 | if (state !== 0 && !state) return state;
121 | if (typeof state === 'object') {
122 | for (const key in state) {
123 | if (Array.isArray(state[key])) {
124 | result.push(parseList(state[key], key));
125 | } else if (typeof state[key] !== 'object') {
126 | const div = createDivEl(key, state[key]);
127 | div.style.padding = '0 1rem 0 1rem';
128 | uniContainer.appendChild(div);
129 | } else {
130 | const container = createContainerEl();
131 | const header = document.createElement('div');
132 | header.innerHTML = `${key} {`;
133 | header.style.fontSize = '1.5rem';
134 |
135 | const footer = document.createElement('div');
136 | footer.innerHTML = `}`;
137 | footer.style.fontSize = '1.5rem';
138 |
139 | container.appendChild(header);
140 | container.appendChild(...changeState(state[key], true));
141 |
142 | container.appendChild(footer);
143 |
144 | result.push(container);
145 | }
146 | }
147 |
148 | if (uniContainer.childNodes.length > 1) {
149 | if (!nested) {
150 | uniContainer.appendChild(footer);
151 | }
152 | result.push(uniContainer);
153 | }
154 | }
155 |
156 | return result;
157 | };
158 |
159 | export default changeState;
160 |
--------------------------------------------------------------------------------
/src/utils/createD3DataObject.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Takes in the root component and the dependency definitions object.
4 | * Builds the output json to send to the D3 renderer.
5 | *
6 | **/
7 |
8 | function D3DataObject(root = '
', dependencies = {}, state = {}) {
9 | this.getData = (component = root, props) => {
10 | const componentData = {};
11 | componentData.name = component;
12 | if (props) componentData.props = props;
13 | if (state[component]) componentData.state = state[component];
14 | if (dependencies[component])
15 | componentData.children = dependencies[component].map((dependency) =>
16 | this.getData(dependency.name, dependency.props)
17 | );
18 | return componentData;
19 | };
20 | this.data = this.getData();
21 | }
22 |
23 | export default D3DataObject;
24 |
--------------------------------------------------------------------------------
/src/utils/d3ProfilerRender.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Sveltool/2b2d3615cf81307ac3b1c67457bbec27c9bb5256/src/utils/d3ProfilerRender.js
--------------------------------------------------------------------------------
/src/utils/d3TreeRender.js:
--------------------------------------------------------------------------------
1 | import * as d3 from 'd3';
2 | import { componentProps, componentState, currentComponent } from './store';
3 |
4 | /*jshint esversion: 6 */
5 | (function () {
6 | 'use strict';
7 | })();
8 |
9 | let { tree, hierarchy, select } = d3;
10 |
11 | class MyTree {
12 | constructor(data) {
13 | this.margin = { left: null, right: null, top: null, bottom: null };
14 | this.width = null;
15 | this.height = null;
16 | this.barHeight = null;
17 | this.barWidth = null;
18 | this.i = 0;
19 | this.duration = null;
20 | this.tree = null;
21 | this.root = null;
22 | this.svg = null;
23 | this.colorScheme = null;
24 | this.component = '';
25 | this.data = data;
26 | }
27 |
28 | $onInit(d3El, width, height, colorScheme) {
29 | this.margin = { top: 20, right: 10, bottom: 20, left: 10 };
30 | this.width = width - this.margin.right - this.margin.left;
31 | this.height = height - this.margin.top - this.margin.bottom;
32 | this.barHeight = 20;
33 | this.barWidth = this.width * 0.8;
34 | this.i = 0;
35 | this.duration = 600;
36 | this.tree = tree().size([this.width, this.height]);
37 | // this.tree = tree().nodeSize([0, 30]);
38 | this.component = '';
39 | this.tree = tree().nodeSize([0, 30]);
40 | this.root = this.tree(hierarchy(this.data));
41 |
42 | this.root.each((d) => {
43 | d.name = d.id; //transferring name to a name letiable
44 | d.id = this.i; //Assigning numerical Ids
45 | this.i++;
46 | });
47 | this.root.x0 = this.root.x;
48 | this.root.y0 = this.root.y;
49 |
50 | this.svg = select(d3El)
51 | .append('svg')
52 | .attr('width', this.width + this.margin.right + this.margin.left)
53 | .attr('height', this.height + this.margin.top + this.margin.bottom)
54 | .append('g')
55 | .attr(
56 | 'transform',
57 | 'translate(' + this.margin.left + ',' + this.margin.top + ')'
58 | );
59 |
60 | if (this.root.children) {
61 | this.root.children.forEach(this.collapse);
62 | }
63 | this.update(this.root, colorScheme);
64 | }
65 |
66 | connector = function (d) {
67 | return 'M' + d.parent.y + ',' + d.parent.x + 'V' + d.x + 'H' + d.y;
68 | };
69 |
70 | collapse = (d) => {
71 | if (d.children) {
72 | d._children = d.children;
73 | d._children.forEach(this.collapse);
74 | d.children = null;
75 | }
76 | };
77 |
78 | click = (d) => {
79 | d = d.target.__data__;
80 | let props;
81 | componentProps.update(() => {
82 | props = d.data.props;
83 |
84 | return props;
85 | });
86 | componentState.update((state) => {
87 | state = d.data.state;
88 | return state;
89 | });
90 |
91 | currentComponent.update(() => {
92 | return d.data.name;
93 | });
94 |
95 | if (d.children) {
96 | d._children = d.children;
97 | d.children = null;
98 | } else {
99 | d.children = d._children;
100 | d._children = null;
101 | }
102 |
103 | this.update(d, this.colorScheme);
104 | };
105 |
106 | update = (source, colorScheme) => {
107 | this.width = 800;
108 | this.colorScheme = colorScheme ? colorScheme : this.colorScheme;
109 | // Compute the new tree layout.
110 | let nodes = this.tree(this.root);
111 | let nodesSort = [];
112 | nodes.eachBefore(function (n) {
113 | nodesSort.push(n);
114 | });
115 | this.height = Math.max(
116 | 500,
117 | nodesSort.length * this.barHeight + this.margin.top + this.margin.bottom
118 | );
119 | let links = nodesSort.slice(1);
120 | // Compute the "layout".
121 | nodesSort.forEach((n, i) => {
122 | n.x = i * this.barHeight;
123 | });
124 |
125 | d3.select('svg')
126 | .transition()
127 | .duration(this.duration)
128 | .attr('height', this.height);
129 |
130 | // Update the nodes
131 | let node = this.svg.selectAll('g.node').data(nodesSort, function (d) {
132 | return d.id || (d.id = ++this.i);
133 | });
134 |
135 | // Enter any new nodes at the parent's previous position.
136 | let nodeEnter = node
137 | .enter()
138 | .append('g')
139 | .attr('class', 'node')
140 | .attr('transform', function () {
141 | return 'translate(' + source.y0 + ',' + source.x0 + ')';
142 | })
143 | .on('click', (e) => {
144 | this.click(e);
145 | });
146 |
147 | nodeEnter
148 | .append('polygon')
149 | .attr('points', function (d) {
150 | return d._children ? '0 -5, 0 4, 7 0' : '0 -1, 5 5, 9 -1';
151 | })
152 | .style('cursor', function (d) {
153 | return 'pointer';
154 | })
155 | .attr('fill', '#ff3e00');
156 |
157 | nodeEnter
158 | .append('text')
159 | .attr('x', function (d) {
160 | return d.children || d._children ? 10 : 10;
161 | })
162 | .attr('dy', '.35em')
163 | .attr('text-anchor', function (d) {
164 | return d.children || d._children ? 'start' : 'start';
165 | })
166 | .text(function (d) {
167 | if (d.data.name && d.data.name.length > 20) {
168 | return d.data.name.substring(0, 20) + '...';
169 | } else {
170 | return d.data.name;
171 | }
172 | })
173 | .style('fill-opacity', 1e-6)
174 | .style('cursor', 'pointer');
175 | nodeEnter.append('svg:title').text(function (d) {
176 | return d.data.name;
177 | });
178 |
179 | // Transition nodes to their new position.
180 | let nodeUpdate = node.merge(nodeEnter).transition().duration(this.duration);
181 | nodeUpdate.attr('transform', function (d) {
182 | return 'translate(' + d.y + ',' + d.x + ')';
183 | });
184 |
185 | nodeUpdate
186 | .select('polygon')
187 | .attr('points', function (d) {
188 | return d._children ? '0.9 -5, 0.9 4, 7 0' : '0 -3, 5 2, 9 -3';
189 | })
190 | .attr('fill', '#ff3e00')
191 | .attr('height', '50px')
192 | .style('cursor', function (d) {
193 | return 'pointer';
194 | });
195 |
196 | nodeUpdate.select('text').style('fill-opacity', 1);
197 |
198 | // Transition exiting nodes to the parent's new position (and remove the nodes)
199 | let nodeExit = node.exit().transition().duration(this.duration);
200 |
201 | nodeExit
202 | .attr('transform', function (d) {
203 | return 'translate(' + source.y + ',' + source.x + ')';
204 | })
205 | .remove();
206 |
207 | nodeExit.select('polygon').attr('points', function (d) {
208 | return '0 -5, 0 4, 7 0';
209 | });
210 |
211 | nodeExit.select('text').style('fill-opacity', 1e-6);
212 |
213 | // Update the links…
214 | let link = this.svg.selectAll('path.link').data(links, function (d) {
215 | // return d.target.id;
216 | let id = d.id + '->' + d.parent.id;
217 | return id;
218 | });
219 |
220 | // Enter any new links at the parent's previous position.
221 | let linkEnter = link
222 | .enter()
223 | .insert('path', 'g')
224 | .attr('class', 'link')
225 | .attr('d', (d) => {
226 | let o = {
227 | x: source.x0,
228 | y: source.y0,
229 | parent: { x: source.x0, y: source.y0 },
230 | };
231 | return this.connector(o);
232 | });
233 |
234 | // Transition links to their new position.
235 | link
236 | .merge(linkEnter)
237 | .transition()
238 | .duration(this.duration)
239 | .attr('d', this.connector);
240 |
241 | // // Transition exiting nodes to the parent's new position.
242 | link
243 | .exit()
244 | .transition()
245 | .duration(this.duration)
246 | .attr('d', (d) => {
247 | let o = {
248 | x: source.x,
249 | y: source.y,
250 | parent: { x: source.x, y: source.y },
251 | };
252 | return this.connector(o);
253 | })
254 | .remove();
255 |
256 | // Stash the old positions for transition.
257 | nodesSort.forEach(function (d) {
258 | d.x0 = d.x;
259 | d.y0 = d.y;
260 | });
261 | };
262 | }
263 |
264 | export default MyTree;
265 |
--------------------------------------------------------------------------------
/src/utils/parser.js:
--------------------------------------------------------------------------------
1 | import { parse, walk } from 'svelte/compiler';
2 | import D3DataObject from './createD3DataObject';
3 |
4 | async function parser() {
5 | // Define temporary data structures
6 | const dependencies = {};
7 | const state = {};
8 | const checked = {};
9 | const usedComponents = [];
10 |
11 | // Get all files from inspected window that end in ".svelte"
12 | const arrSvelteFiles = await new Promise((resolve, reject) => {
13 | chrome.devtools.inspectedWindow.getResources((resources) => {
14 | const filteredResources = resources.filter((file) =>
15 | file.url.includes('.svelte')
16 | );
17 | if (filteredResources) resolve(filteredResources);
18 | else reject('No Svelte Resources Found');
19 | });
20 | });
21 |
22 | // Create an array of component names from ".svelte" files
23 | const componentNames = arrSvelteFiles.map(
24 | (svelteFile) =>
25 | `<${svelteFile.url.slice(
26 | svelteFile.url.lastIndexOf('/') + 1,
27 | svelteFile.url.lastIndexOf('.')
28 | )} />`
29 | );
30 |
31 | // Define function to pull file content from filtered file array
32 | async function getContent(arrFiles) {
33 | const content = await arrFiles.map(async (file, index) => {
34 | const currentComponent = componentNames[index];
35 |
36 | // Only check each component once no matter how many copies of each .svelte file there are
37 | if (checked[currentComponent]) return;
38 | checked[currentComponent] = true;
39 | usedComponents.push(currentComponent);
40 |
41 | // get file content for each Svelte file and process it
42 | const output = await new Promise((resolve, reject) => {
43 | file.getContent((source) => {
44 | if (source) resolve(source);
45 | });
46 | });
47 | return output;
48 | });
49 | return Promise.all(content);
50 | }
51 |
52 | // Process file array into content array
53 | let arrSvelteContent = await getContent(arrSvelteFiles);
54 | // Filter components with no content or duplicate components
55 | arrSvelteContent = arrSvelteContent.filter((content) => {
56 | if (content) return content;
57 | });
58 |
59 | // Iterate over each file content object and process it
60 | arrSvelteContent.forEach((content, index) => {
61 | const currentComponent = usedComponents[index];
62 |
63 | // Parse the file contents and build an AST
64 | const ast = parse(content);
65 |
66 | // Walk the AST and output dependencies, props, and state
67 | walk(ast, {
68 | enter(ASTnode, parent, prop, index) {
69 | // find component dependencies
70 | if (ASTnode.type === 'InlineComponent') {
71 | const dependencyValue = {};
72 | dependencyValue.name = `<${ASTnode.name} />`;
73 | // find props
74 | if (ASTnode.attributes[0]) {
75 | const foundProps = {};
76 | ASTnode.attributes.forEach((el) => {
77 | foundProps[el.name] = el.value[0].data || '';
78 | });
79 | dependencyValue.props = foundProps;
80 | }
81 | dependencies[currentComponent]
82 | ? dependencies[currentComponent].push(dependencyValue)
83 | : (dependencies[currentComponent] = [dependencyValue]);
84 | }
85 | },
86 | });
87 | });
88 |
89 | // Build array of all components from dependency array with thier depedencies
90 | const allComponents = [];
91 | for (const key in dependencies) {
92 | allComponents.push([key, dependencies[key]]);
93 | }
94 |
95 | // find the root component
96 | let rootComponent;
97 | // const allComponents = Object.entries(dependencies);
98 | console.log('All Components ==> ', allComponents);
99 | while (!rootComponent) {
100 | const curr = allComponents.shift();
101 | const currName = curr[0];
102 | // console.log('Current element ==> ', curr);
103 | let foundRoot = true;
104 | allComponents.forEach((comp) => {
105 | comp[1].forEach((dep) => {
106 | const { name } = dep;
107 | if (name === currName) foundRoot = false;
108 | });
109 | });
110 | if (foundRoot) rootComponent = currName;
111 | allComponents.push(curr);
112 | }
113 |
114 | // Build output json to send to D3 renderer
115 | // state is not currently being found or passed to D3
116 | const output = new D3DataObject(rootComponent, dependencies, state);
117 | return output.data;
118 | }
119 |
120 | export default parser;
121 |
--------------------------------------------------------------------------------
/src/utils/store.js:
--------------------------------------------------------------------------------
1 | import { writable } from 'svelte/store';
2 |
3 | export const componentProps = writable({});
4 | export const componentState = writable({});
5 | export const viewType = writable('none');
6 |
7 | export const visibility = writable({
8 | component: true,
9 | element: true,
10 | block: true,
11 | iteration: true,
12 | slot: true,
13 | text: true,
14 | anchor: false,
15 | });
16 | export const selectedNode = writable({});
17 | export const hoveredNodeId = writable(null);
18 | export const rootNodes = writable([]);
19 | export const searchValue = writable('');
20 | export const profilerEnabled = writable(false);
21 | export const profileFrame = writable({});
22 |
23 | export const treeData = writable({
24 | edited: false,
25 | initData: { name: '
' },
26 | editData: {},
27 | });
28 |
29 | export const currentComponent = writable('');
30 |
--------------------------------------------------------------------------------