Skip to content

Commit eebc62f

Browse files
authored
Merge pull request #4 from hienqn/workingwithkevinfinishshowthestate
MVP Recoil app, time jump still needs to be fixed
2 parents 991fea9 + 3bf00a4 commit eebc62f

File tree

16 files changed

+610
-135
lines changed

16 files changed

+610
-135
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ src/extension/build.crx
1010
src/extension/build.pem
1111
bower_components
1212
sandboxes/manual-tests/NextJS/.next
13-
.vscode
13+
.vscode
14+
src/app/components/Map.tsx

AppStructureDiagram.png

440 KB
Loading

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
"react-html-parser": "^2.0.2",
119119
"react-json-tree": "^0.11.2",
120120
"react-router-dom": "^5.2.0",
121-
"react-select": "^3.1.0"
121+
"react-select": "^3.1.0",
122+
"recoil": "0.0.10"
122123
}
123124
}

readme_2.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
This documentation explains the architecture of Reactime v4.
2+
3+
4+
![demo](./AppStructureDiagram.png)
5+
6+
In the src folder, there are three directories: app, backend, and extension.
7+
8+
9+
The app folder is responsible a SPA that you see when you open the chrome dev tools under the Reactime tab.
10+
11+
The backend folder is responsible for generating data and handle time-jump request from the background.js scripts in extension.
12+
13+
The extension folder is where the contentscript.js and background.js located. These two files belongs to Chrome internal to help us handle requests both from the web browser and from the chrome dev tools. Unsure what contentscripts and backgroundscripts are? The details implementation are documented in the files themselves.
14+
15+
> Content scripts are files that run in the context of web pages. By using the standard Document Object Model (DOM), they are able to read details of the web pages the browser visits, make changes to them and pass information to their parent extension. Source: https://developer.chrome.com/extensions/content_scripts
16+
17+
>A background page is loaded when it is needed, and unloaded when it goes idle. Some examples of events include:
18+
>The extension is first installed or updated to a new version.
19+
>The background page was listening for an event, and the event is dispatched.
20+
>A content script or other extension sends a message.
21+
>Another view in the extension, such as a popup, calls runtime.getBackgroundPage.
22+
>Once it has been loaded, a background page will stay running as long as it is performing an action, such as calling a Chrome API or issuing a network request. Additionally, the background page will not unload until all visible views and all message ports are closed. Note that opening a view does not cause the event page to load, but only prevents it from closing once loaded. Source: https://developer.chrome.com/extensions/background_pages
23+
24+
Just to reiterate, contentscript is use to read and modify information that is rendered on the webpage, and a host of other objects. Background is very similar to client/server concept in which background is behaving like a server, listening to request from the contentscipt and **the request from the "front-end" of the chrome dev tools in the reactime tab (not the interface of the browser, this is an important distinction.)** In other words, background script works directly with the React Dev Tools, whereas contentscript works with the interface of the browser.
25+
26+
The general flow of data is described in the following steps:
27+
28+
1. When the background bundle is loaded from the browser, it injects a script into the dom. This script uses a technique called [throttle](https://medium.com/@bitupon.211/debounce-and-throttle-160affa5457b) to get the data of the state of the app to send to the contentscript every specified miliseconds (in our case, it's 70ms).
29+
30+
31+
2. This contentscript always listens to the messages being sent from the interface of the browser. The recieved data will immediately be sent to the background script which then update an object that persist in background script called **tabsObj**. Each time tabsObj is updated, the most recent version will be sent to the interface of reactime dev tools written the app folder.
32+
33+
3. Likewise, when there is an action from Reactime dev tools - a jump request for example, a request will be made to the background script which is proxied to the content script. This content script will talk to the browser interface to request the *state* that the user wants to jump to. One important thing to note here is that the jump action will be excecuted in the backend script because it has direct access to the DOM.

src/app/components/Map.tsx

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/* eslint-disable arrow-body-style */
2+
/* eslint-disable max-len */
3+
/* eslint-disable @typescript-eslint/no-explicit-any */
4+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
5+
/* eslint-disable @typescript-eslint/ban-types */
6+
7+
import React, { useRef, useState, useEffect } from 'react';
8+
import * as d3 from 'd3';
9+
10+
const Map = (props) => {
11+
const { snapshot } = props;
12+
console.log('MAP SNAPSHOT', snapshot);
13+
14+
// set the heights and width of the tree to be passed into treeMap function
15+
const width: number = 600;
16+
const height: number = 1100;
17+
18+
// this state allows the canvas to stay at the zoom level on multiple re-renders
19+
const [{ x, y, k }, setZoomState]: any = useState({ x: 0, y: 0, k: 0 });
20+
21+
useEffect(() => {
22+
setZoomState(d3.zoomTransform(d3.select('#canvas').node()));
23+
}, [snapshot]);
24+
25+
// this only clears the canvas if Visualizer is already rendered on the extension
26+
useEffect(() => {
27+
document.getElementById('canvas').innerHTML = '';
28+
29+
// creating the main svg container for d3 elements
30+
const svgContainer: any = d3
31+
.select('#canvas')
32+
.attr('width', width)
33+
.attr('height', height);
34+
35+
// creating a pseudo-class for reusability
36+
const g: any = svgContainer
37+
.append('g')
38+
.attr('transform', `translate(${x}, ${y}), scale(${k})`); // sets the canvas to the saved zoomState
39+
40+
//RE-WRITE ALGORITHIM For All Possible Snapshot Formats
41+
42+
// appState is the object that is passed into d3.hierarchy
43+
// const childrenArr = [];
44+
// if (snapshot.children[0].state.hooksState) {
45+
// snapshot.children[0].state.hooksState.forEach((el) =>
46+
// childrenArr.push(el)
47+
// );
48+
// }
49+
50+
// console.log('CHILDREN', childrenArr);
51+
52+
const appState: any = {
53+
name: ' Root',
54+
// pass in parsed data here
55+
// call the helper function passing in the most recent snapshot
56+
children: snapshot.children,
57+
};
58+
59+
console.log('STATE', appState);
60+
// creating the tree map
61+
const treeMap: any = d3.tree().nodeSize([width, height]);
62+
63+
// creating the nodes of the tree
64+
// pass
65+
const hierarchyNodes: any = d3.hierarchy(appState);
66+
67+
console.log('Hierarchy NODES', hierarchyNodes);
68+
69+
// calling the tree function with nodes created from data
70+
const finalMap: any = treeMap(hierarchyNodes);
71+
72+
console.log('FINAL MAP', finalMap);
73+
74+
// renders a flat array of objects containing all parent-child links
75+
// renders the paths onto the component
76+
let paths: any = finalMap.links();
77+
console.log('PATHS', paths);
78+
79+
// this creates the paths to each atom and its contents in the tree
80+
g.append('g')
81+
.attr('fill', 'none')
82+
.attr('stroke', '#646464')
83+
.attr('stroke-width', 5)
84+
.selectAll('path')
85+
.data(paths)
86+
.enter()
87+
.append('path')
88+
.attr(
89+
'd',
90+
d3
91+
.linkHorizontal()
92+
.x((d: any) => d.y)
93+
.y((d: any) => d.x)
94+
);
95+
96+
// returns a flat array of objects containing all the nodes and their information
97+
// renders nodes onto the canvas
98+
let nodes: any = hierarchyNodes.descendants();
99+
100+
// const node is used to create all the nodes
101+
// this segment places all the nodes on the canvas
102+
const node: any = g
103+
.append('g')
104+
.attr('stroke-linejoin', 'round') // no clue what this does
105+
.attr('stroke-width', 1)
106+
.selectAll('g')
107+
.data(nodes)
108+
.enter()
109+
.append('g')
110+
.attr('transform', (d: any) => `translate(${d.y}, ${d.x})`)
111+
.attr('class', 'atomNodes');
112+
113+
// for each node that got created, append a circle element
114+
node.append('circle').attr('fill', '#c300ff').attr('r', 50);
115+
116+
// for each node that got created, append a text element that displays the name of the node
117+
node
118+
.append('text')
119+
.attr('dy', '.31em')
120+
.attr('x', (d: any) => (d.children ? -75 : 75))
121+
.attr('text-anchor', (d: any) => (d.children ? 'end' : 'start'))
122+
.text((d: any) => d.data.name)
123+
.style('font-size', `2rem`)
124+
.style('fill', 'white')
125+
.clone(true)
126+
.lower()
127+
.attr('stroke', '#646464')
128+
.attr('stroke-width', 2);
129+
130+
// adding a mouseOver event handler to each node
131+
// only add popup text on nodes with no children
132+
// display the data in the node on hover
133+
node.on('mouseover', function (d: any, i: number): any {
134+
if (!d.children) {
135+
d3.select(this)
136+
.append('text')
137+
.text(JSON.stringify(d.data, undefined, 2))
138+
.style('fill', 'white')
139+
.attr('x', 75)
140+
.attr('y', 60)
141+
.style('font-size', '3rem')
142+
.attr('stroke', '#646464')
143+
.attr('id', `popup${i}`);
144+
}
145+
});
146+
147+
// add mouseOut event handler that removes the popup text
148+
node.on('mouseout', function (d: any, i: number): any {
149+
d3.select(`#popup${i}`).remove();
150+
});
151+
152+
// allows the canvas to be draggable
153+
node.call(
154+
d3
155+
.drag()
156+
.on('start', dragStarted)
157+
.on('drag', dragged)
158+
.on('end', dragEnded)
159+
);
160+
161+
// allows the canvas to be zoom-able
162+
svgContainer.call(
163+
d3
164+
.zoom()
165+
.extent([
166+
[0, 0],
167+
[width, height],
168+
])
169+
.scaleExtent([0, 8])
170+
.on('zoom', zoomed)
171+
);
172+
173+
// helper functions that help with dragging functionality
174+
function dragStarted(): any {
175+
d3.select(this).raise();
176+
g.attr('cursor', 'grabbing');
177+
}
178+
179+
function dragged(d: any): any {
180+
d3.select(this)
181+
.attr('dx', (d.x = d3.event.x))
182+
.attr('dy', (d.y = d3.event.y));
183+
}
184+
185+
function dragEnded(): any {
186+
g.attr('cursor', 'grab');
187+
}
188+
189+
// helper function that allows for zooming
190+
function zoomed(): any {
191+
g.attr('transform', d3.event.transform);
192+
}
193+
});
194+
195+
console.log('208');
196+
return (
197+
<div data-testid="canvas">
198+
<div className="Visualizer">
199+
<svg id="canvas"></svg>
200+
</div>
201+
</div>
202+
);
203+
};
204+
205+
export default Map;

0 commit comments

Comments
 (0)