Showing preview only (334K chars total). Download the full file or copy to clipboard to get everything.
Repository: oslabs-beta/Recoilize
Branch: staging
Commit: 1af38cbd8131
Files: 94
Total size: 303.2 KB
Directory structure:
gitextract_l6qwon48/
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── README_KO.md
├── babel.config.js
├── docs/
│ └── pull_request_template.md
├── mock/
│ ├── snapshot.js
│ └── state-snapshot.js
├── package/
│ ├── .npmignore
│ ├── README.md
│ ├── formatFiberNodes.js
│ ├── formatFiberNodes.ts
│ ├── index.js
│ ├── index.ts
│ └── package.json
├── package.json
├── src/
│ ├── README.md
│ ├── app/
│ │ ├── Containers/
│ │ │ ├── ButtonsContainer.tsx
│ │ │ ├── MainContainer.tsx
│ │ │ ├── SnapshotContainer.tsx
│ │ │ ├── TravelContainer.tsx
│ │ │ ├── VisualContainer.tsx
│ │ │ └── __tests__/
│ │ │ ├── MainContainer.unit.test.js
│ │ │ ├── SnapshotContainer.unit.test.js
│ │ │ └── VisualContainer.unit.test.js
│ │ ├── components/
│ │ │ ├── App.tsx
│ │ │ ├── AtomNetwork/
│ │ │ │ ├── AtomNetwork.tsx
│ │ │ │ ├── AtomNetworkLegend.tsx
│ │ │ │ ├── AtomNetworkVisual.tsx
│ │ │ │ └── __tests__/
│ │ │ │ ├── Network.unit.test.js
│ │ │ │ └── __snapshots__/
│ │ │ │ └── Network.unit.test.js.snap
│ │ │ ├── ComponentGraph/
│ │ │ │ ├── AtomComponentContainer.tsx
│ │ │ │ ├── AtomComponentVisual.tsx
│ │ │ │ └── __tests__/
│ │ │ │ ├── AtomComponentContainer.unit.test.js
│ │ │ │ ├── AtomComponentVisual.unit.test.js
│ │ │ │ └── AtomSelectorLegend.unit.test.js
│ │ │ ├── Metrics/
│ │ │ │ ├── ComparisonGraph.tsx
│ │ │ │ ├── FlameGraph.js
│ │ │ │ ├── MetricsContainer.tsx
│ │ │ │ ├── RankedGraph.tsx
│ │ │ │ └── __tests__/
│ │ │ │ ├── IcicleVerticle.unit.test.js
│ │ │ │ ├── Metrics.unit.test.js
│ │ │ │ └── Visualizer.unit.test.js
│ │ │ ├── NavBar/
│ │ │ │ ├── NavBar.tsx
│ │ │ │ └── __test__/
│ │ │ │ └── Navbar.unit.test.js
│ │ │ ├── Settings/
│ │ │ │ ├── AtomSettings.tsx
│ │ │ │ ├── SettingsContainer.tsx
│ │ │ │ ├── ThrottleSettings.tsx
│ │ │ │ └── __tests__/
│ │ │ │ ├── AtomSettings.unit.test.js
│ │ │ │ ├── StateSettings.unit.test.js
│ │ │ │ ├── ThrottleSettings.unit.test.js
│ │ │ │ └── __snapshots__/
│ │ │ │ ├── StateSettings.unit.test.js.snap
│ │ │ │ └── ThrottleSettings.unit.test.js.snap
│ │ │ ├── Slider/
│ │ │ │ └── MainSlider.tsx
│ │ │ ├── SnapshotList/
│ │ │ │ └── __tests__/
│ │ │ │ └── SnapshotList.unit.test.js
│ │ │ ├── StateDiff/
│ │ │ │ ├── Diff.tsx
│ │ │ │ └── __tests__/
│ │ │ │ ├── StateDiff.unit.test.js
│ │ │ │ └── __snapshots__/
│ │ │ │ └── StateDiff.unit.test.js.snap
│ │ │ ├── StateTree/
│ │ │ │ ├── Tree.tsx
│ │ │ │ └── __tests__/
│ │ │ │ └── Tree.unit.test.js
│ │ │ └── Testing/
│ │ │ ├── CodeResults.js
│ │ │ ├── Editor.js
│ │ │ ├── SelectorsButton.tsx
│ │ │ ├── TestingContainer.tsx
│ │ │ ├── displayTests.tsx
│ │ │ ├── dummySelector.js
│ │ │ └── testing.css
│ │ ├── index.tsx
│ │ ├── state-management/
│ │ │ ├── __tests__/
│ │ │ │ └── slices.test.tsx
│ │ │ ├── hooks.tsx
│ │ │ ├── index.tsx
│ │ │ └── slices/
│ │ │ ├── AtomNetworkSlice.tsx
│ │ │ ├── AtomsAndSelectorsSlice.tsx
│ │ │ ├── FilterSlice.tsx
│ │ │ ├── SelectedSlice.tsx
│ │ │ ├── SnapshotSlice.tsx
│ │ │ ├── ThrottleSlice.tsx
│ │ │ └── ZoomSlice.tsx
│ │ └── utils/
│ │ ├── cleanComponentAtomTree.ts
│ │ ├── makeRelationshipLinks.ts
│ │ └── makeTreeConversion.ts
│ ├── extension/
│ │ ├── background.ts
│ │ ├── build/
│ │ │ ├── devtools.html
│ │ │ ├── devtools.js
│ │ │ ├── diff.css
│ │ │ ├── manifest.json
│ │ │ ├── panel.html
│ │ │ └── stylesheet.css
│ │ └── contentScript.ts
│ └── types/
│ └── index.d.ts
├── tsconfig.json
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
module.exports = {
env: {
browser: true,
es2020: true,
},
extends: [
'plugin:react/recommended',
'prettier',
'prettier/flowtype', // if you are using flow
'prettier/react',
],
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 11,
sourceType: 'module',
},
plugins: ['flowtype', 'react', 'jsx-a11y', 'prettier'],
rules: {
'prettier/prettier': ['error'],
'react/prop-types': 'off',
},
settings: {
react: {
version: 'detect',
},
},
};
================================================
FILE: .gitignore
================================================
node_modules
package-lock.json
.vscode
src/extension/build/bundles
.DS_Store
package/node_modules/
================================================
FILE: .prettierrc
================================================
{
"arrowParens": "avoid",
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": false,
"jsxBracketSameLine": false,
"endOfLine": "auto",
"overrides": [
{
"files": ["*.js", "*.jsx"],
"options": {
"parser": "flow"
}
}
]
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 OSLabs Beta
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<meta name='keywords' content='Recoil, Recoil.js, Recoil Dev Tool, Recoilize, Chrome Dev Tool, Recoil Chrome'>
<p align='center'>
<img src='./src/extension/build/assets/cover-photo-logo-recoilize.jpg' width=100%>
</p>
<h1>Debugger for Recoil Applications</h1>
# [](https://github.com/oslabs-beta/Recoilize/blob/staging/LICENSE) [](https://www.npmjs.com/package/recoilize) 
[Korean README 한국어](README_KO.md)
<h1> About</h1>
<p>
Recoilize is a Chrome Dev Tool meant for debugging applications built with the experimental Recoil.js state management library.
The tool records Recoil state and allows users to easily debug their applications with features such as: time travel to previous states, visualization of the component graph and display of the atom selector network.
</p>
<p>
Download Recoilize from the <a href='https://chrome.google.com/webstore/detail/recoilize/jhfmmdhbinleghabnblahfjfalfgidik'>Chrome Store</a>
</p>
<p>Visit the Recoilize <a href='https://www.recoilize.io/'>landing page</a> to demo</p>
<h2>
** STILL IN BETA **
</h2>
<p>Please note that Recoilize is in BETA. We will continue to make improvements and implement fixes but if you find any issues, please dont hesitate to report them in the issues tab or submit a PR and we'll happily take a look.</p>
<h1>
Installation
</h1>
#### Install Recoilize Module
```js
npm install recoilize
```
### ** IMPORTANT **
#### Import RecoilizeDebugger from the Recoilize module
```js
import RecoilizeDebugger from 'recoilize';
```
#### Integrate RecoilizeDebugger as a React component within the recoil root:
```js
import RecoilizeDebugger from 'recoilize';
import RecoilRoot from 'recoil';
ReactDOM.render(
<RecoilRoot>
<RecoilizeDebugger />
<App />
</RecoilRoot>,
document.getElementById('root'),
);
```
#### Please note, Recoilize assumes that the HTML element used to inject your React application has an ID of 'root'. If it does not the HTML element must be passed in as an attribute called 'root' to the RecoilizeDebugger component
#### Example:
```js
import RecoilizeDebugger from 'recoilize';
import RecoilRoot from 'recoil';
//If your app injects on an element with ID of 'app'
const app = document.getElementById('app');
ReactDOM.render(
<RecoilRoot>
<RecoilizeDebugger root={app} />
<App />
</RecoilRoot>,
app,
);
```
### In order to integrate Next.js applications with RecoilizeDebugger, follow the example below.
```js
//If your application uses Next.js modify the _app.js as follows
import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
import { RecoilRoot } from 'recoil';
function MyApp({ Component, pageProps }) {
const [root, setRoot] = useState(null)
const RecoilizeDebugger = dynamic(
() => {
return import('recoilize');
},
{ ssr: false}
);
useEffect(() => {
if (typeof window.document !== 'undefined') {
setRoot(document.getElementById('__next'));
}
}, [root]);
return (
<>
<RecoilRoot>
<RecoilizeDebugger root = {root}/>
<Component {...pageProps} />
</RecoilRoot>
</>
);
}
export default MyApp;
```
#### Open your application on the Chrome Browser and start debugging with Recoilize!
##### (Only supported with React applications using Recoil as state management)
<h1>New Features for Version 3.0.0</h1>
<h3>Support for Recoil 0.1.3</h3>
<p>Recoilize now supports the most recent update to the Recoil library and is backwards compatible with older versions of Recoil.</p>
<h3>Time Travel with ease</h3>
<p>If you had used Recoilize before, you would have noticed an annoying bug that sometimes breaks the app and won’t allow you to be productive. With the new version of Recoilize, that issue is forever gone. Users can now use the tool with confidence and worry-free.</p>
<p>The main mission of Recoilize 3.0 is to make it more user-friendly, so you will enjoy our brand new time travel feature — the time travel slider! Why click and scroll through snapshots when you can do it with a slider and some buttons, right?</p>
<p align='center'>
<img src='./src/extension/build/assets/timeslider-gif.gif' width=600 height=300/>
</p>
<h3>Customizable Component Graph</h3>
<p>This is one of the coolest updates of Recoilize 3.0. We understand that different users have different ways of thinking and visualizing, and for that reason, the component tree now is fully customizable. You can expand or collapse the components, choose vertical or horizontal displays or adjust the spacing between elements.</p>
<p align='center'>
<img src='./src/extension/build/assets/componentTree-gif.gif' width=600 height=300/>
</p>
<h3>Better User Experience with Atom Network</h3>
<p>The atom network is one of the key features that differentiate Recoil.js from other alternative state management libraries. However, the atom network will grow bigger together with the app. At some points, it will be unmanageable and hard to keep track of all of the atoms. To make this easier and pain-free, the new Atom Network will allow you to freely move and arrange them anywhere you want.</p>
<p align='center'>
<img src='./src/extension/build/assets/atomNetwork-gif.gif' width=600 height=300/>
</p>
<h3>Snapshot Comparison</h3>
<p>We understand that developers always develop an app with an optimization philosophy in mind. Component rendering time can be difficult to measure for two reasons. First, your computer and your web browser are not the same as mine, so the run-time can be vastly different. Second, it’s really hard to keep track of a long series of snapshots. You definitely don’t want to waste all of your time calculating the rendering time by hand.</p>
<p>With the new Recoilize, users can now save a series of state snapshots and use it later to analyze/compare with the current series.</p>
<p align='center'>
<img src='./src/extension/build/assets/snapshotcomparison-gif.gif' width=600 height=300/>
</p>
<h1>Features</h1>
<h3>Support for Concurrent Mode</h3>
<p>If a Suspense component was used as a placeholder during component renderings, those suspense components will display with a red border in the expanded component graph. This indicates that a component was suspended during the render of the selected snapshot.</p>
<p align='center'>
<img src='./src/extension/build/assets/suspenseMode.gif' width=600 height=300/>
</p>
<h3>Performance Metrics</h3>
<p>In 'Metrics' tab, two graphs display component render times.
The flame graph displays the time a component took to render itself, and all of its child components. The bar graph displays the individual render times of each component.<p>
<!-- <p align='center'>
<img src='./src/extension/build/assets/metrics.gif' width=600 height=300/>
</p> -->
<h3>Throttle</h3>
<p>In the settings tab, users are able to set throttle (in milliseconds) for large scale applications or any applications that changes state rapidly. The default is set at 70ms.<p>
<h3>State Persistence</h3>
<p>Recoilize allows the users to persist their application's state through a refresh or reload. At this time, the user is able to view the previous states in the dev tool, but cannot time travel to the states before refresh.</p>
<h3>Additional Features</h3>
<ul><li>legend to see relationship between component graph and state</li></ul>
<ul><li>toggle to view raw component graph</li></ul>
<ul><li>filter atom/selector network relationship</li></ul>
<ul><li>filter snapshots by atom/selector keys</li></ul>
<h2> We will continue updating Recoilize alongside Recoil's updates!</h2>
<h1>
Contributors
</h1>
<h4>Bren Yamaguchi <a href='https://github.com/brenyama' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/brenyamaguchi/' target=“_blank”>@linkedin</a></h4>
<h4>Saejin Kang <a href='https://github.com/skang1004' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/saejinkang1004/' target=“_blank”>@linkedin</a></h4>
<h4>Jonathan Escamila <a href='https://github.com/jonescamilla' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/jon-escamilla/' target=“_blank”>@linkedin</a> </h4>
<h4>Sean Smith <a href='https://github.com/SmithSean17' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/sean-smith17/' target=“_blank”>@linkedin</a> </h4>
<h4>Justin Choo <a href='https://github.com/justinchoo93' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/justinchoo93/' target=“_blank”>@linkedin</a></h4>
<h4>Anthony Lin <a href='https://github.com/anthonylin198' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/anthony-lin/' target=“_blank”>@linkedin</a></h4>
<h4>Spenser Schwartz <a href='https://github.com/spenserschwartz' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/spenser-schwartz/' target=“_blank”>@linkedin</a> </h4>
<h4>Steven Nguyen <a href='https://github.com/Steven-Nguyen-T' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/steven-nguyen-t/' target=“_blank”>@linkedin</a> </h4>
<h4>Henry Taing <a href='https://github.com/henrytaing' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/henrytaing/' target=“_blank”>@linkedin</a> </h4>
<h4>Seungho Baek <a href='https://github.com/hobaek' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/s2unghobaek/' target=“_blank”>@linkedin</a> </h4>
<h4>Aaron Yang <a href='https://github.com/aaronyang24' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/aaronyang24/' target=“_blank”>@linkedin</a> </h4>
<h4>Jesus Vargas <a href='https://github.com/jmodestov' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/jesus-modesto-vargas/' target=“_blank”>@linkedin</a> </h4>
<h4>Davide Molino <a href='https://github.com/davidemmolino' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/davide-molino/' target=“_blank”>@linkedin</a> </h4>
<h4>Taven Shumaker <a href='https://github.com/TavenShumaker' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/Taven-Shumaker/' target=“_blank”>@linkedin</a> </h4>
<h4>Janis Hernandez <a href='https://github.com/Janis-H' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/janis-h/' target=“_blank”>@linkedin</a> </h4>
<h4>Jaime Baik <a href='https://github.com/jaimebaik' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/jaime-baik/' target=“_blank”>@linkedin</a> </h4>
<h4>Anthony Magallanes <a href='https://github.com/amagalla' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/anthony-magallanes/' target=“_blank”>@linkedin</a> </h4>
<h4>Edward Shei <a href='https://github.com/calibeach' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/edwardshei/' target=“_blank”>@linkedin</a> </h4>
<h4>Nathan Bargers <a href='https://github.com/nbargers' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/nathan-bargers/' target=“_blank”>@linkedin</a> </h4>
<h4>Scott Campbell <a href='https://github.com/thisisscottcampbell' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/thisisscottcampbell/' target=“_blank”>@linkedin</a> </h4>
<h4>Steve Hong <a href='https://github.com/stevehong423' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/stevehongpa/' target=“_blank”>@linkedin</a> </h4>
<h4>Jason Lee <a href='https://github.com/j4s0n1020' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/jasonjml/' target=“_blank”>@linkedin</a> </h4>
<h4>Razana Nisathar <a href='https://github.com/razananisathar' target=“_blank”>@github </a><a href='http://www.linkedin.com/in/razananisathar' target=“_blank”>@linkedin</a> </h4>
<h4>Harvey Nguyen <a href='https://github.com/harveynwynn' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/harveynwynn' target=“_blank”>@linkedin</a> </h4>
<h4>Joey Ma <a href='https://github.com/yoyoyojoe' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/joeyma' target=“_blank”>@linkedin</a> </h4>
<h4>Leonard Lew <a href='https://github.com/leolew97' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/leonardlew' target=“_blank”>@linkedin</a> </h4>
<h4>Victor Wang <a href='https://github.com/wangvwr' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/wangvwr' target=“_blank”>@linkedin</a> </h4>
================================================
FILE: README_KO.md
================================================
<meta name='keywords' content='Recoil, Recoil.js, Recoil Dev Tool, Recoilize, Chrome Dev Tool, Recoil Chrome'>
<p align='center'>
<img src='./src/extension/build/assets/cover-photo-logo-recoilize.jpg' width=100%>
</p>
<h1>Recoil 애플리케이션을 위한 디버깅 개발도구</h1>
# [](https://github.com/oslabs-beta/Recoilize/blob/staging/LICENSE) [](https://www.npmjs.com/package/recoilize) 
[영어 README](README.md)
<h1>Recoilize에 대해서</h1>
<p>
Recoilize는 Recoil 상태관리 라이브러리를 사용하여 만들어진 애플리케이션을 디버깅 할수있는 Chrome Dev Tool입니다.
Recoil 상태를 기록하여 유저들이 애플리케이션을 편하게 디버깅 할수 있도록 도와주는 기능들을 가지고 있습니다. 리액트 컴포넌트를 시각화 하여 그래프로 보여줌과 동시에 스냅샷을 이요하여 이전 상태로 시간이동을 가능하게 만들어줄수있는 도구입니다.
</p>
<p>
<a href='https://chrome.google.com/webstore/detail/recoilize/jhfmmdhbinleghabnblahfjfalfgidik'>크롬 스토어</a> 에서 다운로드 받으실수 있습니다.
</p>
<!-- <p>데모 <a href='https://github.com/justinchoo93/recoil-paint'>페인트 애플리케이션</a></p> -->
<p>데모를 위해서는 <a href='https://www.recoilize.io/'>Recoilize</a> 웹사이트를 방문하십시오.</p>
<h2>
** 현재는 베타 버젼입니다 **
</h2>
<p>Recoilize는 현재 베타버젼 입니다. 툴을 계속 개선하고 새로운 이슈들을 수정해 나갈것이고, 혹시 다른 버그들이나 이슈들이 나타난다면 언제든지 이슈 탭에 글을 작성하시거나 PR을 해주시면 감사하겠습니다.</p>
<h1>
설치 방법
</h1>
#### Recoilize 모듈 설치
```js
npm install recoilize
```
### ** 중요 **
#### Recoilize모듈에서 RecoilizeDebugger를 import해줘야 합니다
```js
import RecoilizeDebugger from 'recoilize';
```
#### RecoilizeDebugger를 recoil root 안에 리액트 컴포넌트로 넣어야 합니다
```js
import RecoilizeDebugger from 'recoilize';
import RecoilRoot from 'recoil';
ReactDOM.render(
<RecoilRoot>
<RecoilizeDebugger />
<App />
</RecoilRoot>,
document.getElementById('root'),
);
```
#### Recoilize는 리액트 애플리케이션을 주입시키기 위해 쓴 HTML 엘리먼트의 아이디를 'root'으로 가정합니다.아닐경우 RecoilizeDebugger에 'root'속성을 만들고 HTML 엘리먼트를 패스하십시오.
```js
import RecoilizeDebugger from 'recoilize';
import RecoilRoot from 'recoil';
//If your app injects on an element with ID of 'app'
const app = document.getElementById('app');
ReactDOM.render(
<RecoilRoot>
<RecoilizeDebugger root={app} />
<App />
</RecoilRoot>,
app,
);
```
#### 애플리케이션을 크롬 브라우저에서 열고 Recoilize 디버깅툴을 실행하시면 됩니다.
##### (현재 Recoil을 상태관리 라이브러리로 사용하는 리액트 애플리케이션만 지원합니다.)
<h1>새로운 기능</h1>
<h3>Recoil 0.1.2를 지원합니다</h3>
<p>Recoilize는 최신 버전과 구버전의 Recoil과 호환이 됩니다</p>
<h3>스냅샷 클리어</h3>
<p>Previous와 Forward 버튼을 넣어 선택된 스냅샵의 전이나 후에 있는 스냅샷을 지울 수 있게 했습니다</p>
<p align='center'>
<img src='./src/extension/build/assets/clearButtons.gif' width=600 height=300/>
</p>
<h3>컴포넌트 그래프</h3>
<h4>호버</h4>
<p>그래프의 노드를 호버했을 때 안의 텍스트가 보이는 형태를 개선하였습니다</p>
<h4>atom 범례</h4>
<p>범례의 텍스트가 클릭되면 드롭다운 형태의 atom이나 selector 리스트가 보이게 하였습니다</p>
<p>드롭다운 리스트에 있는 각각의 atom이나 selector를 누를 경우 해당 atom이나 selector를 쓰는 컴포넌트가 하이라이트되도록 바꾸었습니다</p>
<p align='center'>
<img src='./src/extension/build/assets/componentGraph.gif' width=600 height=300/>
</p>
<h3>atom 네트워크</h3>
<h4>atom 범례</h4>
<p>범례의 텍스트가 클릭되면 드롭다운 형태의 atom이나 selector 리스트가 보이게 하였습니다</p>
<p>드롭다운 리스트에 있는 각각의 atom이나 selector를 누를 경우 관련 atom이나 selector 노드가 보이도록 했습니다</p>
<h4>그래프</h4>
<p>여러개의 그래프가 겹치지 않도록 조정하였습니다</p>
<h4>검색 창</h4>
<p>검색 창이 탐색 버튼과 겹치지 않도록 변경하였습니다</p>
<p align='center'>
<img src='./src/extension/build/assets/atomNetwork.gif' width=600 height=300/>
</p>
<h3>Ranked 그래프</h3>
<p>애니메이션을 없애서 전과 후 상태비교가 쉽게 보이도록 바꾸었습니다</p>
<p align='center'>
<img src='./src/extension/build/assets/rankedGraph.gif' width=600 height=300/>
</p>
<h1>기능</h1>
<h3>Concurrent 모드 지원</h3>
<p>만약 컴포넌트를 보류시키기 위해 Suspense 컴포넌트가 사용됐을 경우, 해당 컴포넌트의 노드 주위에 빨간 테두리로 표시하여 컴포넌트가 나타나기까지 지연되었음을 알려줄 것입니다</p>
<p align='center'>
<img src='./src/extension/build/assets/suspenseMode.gif' width=600 height=300/>
</p>
<h3>퍼포먼스 측정 그래프</h3>
<p>'Metrics' 탭에 있는 두가지 그래프는 렌더링 시간을 보여줍니다</p>
<p>Flame 그래프는 각각의 컴포넌트와 자식 컴포넌트가 나타나기까지 걸린 합산된 시간을 보여주고 Ranked 그래프는 각각의 컴포넌트가 나오기까지 걸린 시간을 보여줍니다</p>
<h3>시간 이동</h3>
<p>Recoilize의 주요 기능 중 하나로, 이 도구는 사용자가 이전의 모든 스냅샷으로 이동할 수 있게 해줍니다. 각 스냅샷 옆에 있는 점프 버튼을 누르면 해당 스냅샷으로 상태를 설정하여 DOM이 변경됩니다.<p>
<p align='center'>
<img src='./src/extension/build/assets/timeTravel.gif' width=600 height=300/>
</p>
<h3>시각화</h3>
<p>사용자는 개별 스냅샷을 클릭하여 애플리케이션 상태에 대한 시각화된 그래프를 볼수있고, 컴포넌트 트리와 다른 그래프 뿐만 아니라 State tree를 JSON 형식으로 지원합니다<p>
<h3>쓰로틀링</h3>
<p>대규모 애플리케이션 또는 상태를 빠르게 변경하는 모든 애플리케이션에 대해 쓰로틀링(ms)을 설정할 수 있습니다. 기본값은 70ms로 설정되어 있습니다.<p>
<h3>상태 유지 (베타)</h3>
<p>Recoilize는 사용자가 새로 고침을 했을 경우에도 응용 프로그램의 상태를 유지할 수 있도록 해줍니다. 이때 사용자는 개발 도구에서는 이전 상태를 볼 수 있지만, 새로고침 전에 상태로의 시간 이동은 할 수 없습니다. 우리 팀은 여전히 이 기능을 완성하기 위해 노력하고 있습니다.</p>
<h3>부가 기능</h3>
<ul><li>컴포넌트 그래프에 마우스를 올렸을때 관련있는 atom과 selector들이 나타납니다</li></ul>
<ul><li>컴포넌트 그래프 안에 오른쪽 작은 창에서 관련된 상태들을 선택하여 볼 있습니다</li></ul>
<ul><li>컴포넌트 그래프 안에 Expand 버튼을 누르면 확장된 컴포넌트 그래프를 볼 수 있습니다</li></ul>
<ul><li>네트워크 그래프 안에 atom과 selector들을 볼수있고 필터링도 가능합니다.</li></ul>
<ul><li>설정탭에서 atom과 selector key를 사용하여 관련된 스냅샷들을 필터링 할 수 있습니다</li></ul>
<h2>우리는 Recoil의 업데이트와 함께 Recoilize를 계속 업데이트 할 것 입니다</h2>
<h1>
기여
</h1>
<h4>Bren Yamaguchi <a href='https://github.com/brenyama' target="_blank">@github </a><a href='https://www.linkedin.com/in/brenyamaguchi/' target="_blank">@linkedin</a></h4>
<h4>Saejin Kang <a href='https://github.com/skang1004' target="_blank">@github </a><a href='https://www.linkedin.com/in/saejinkang1004/' target="_blank">@linkedin</a></h4>
<h4>Jonathan Escamila <a href='https://github.com/jonescamilla' target="_blank">@github </a><a href='https://www.linkedin.com/in/jon-escamilla/' target="_blank">@linkedin</a> </h4>
<h4>Sean Smith <a href='https://github.com/SmithSean17' target="_blank">@github </a><a href='https://www.linkedin.com/in/sean-smith17/' target="_blank">@linkedin</a> </h4>
<h4>Justin Choo <a href='https://github.com/justinchoo93' target="_blank">@github </a><a href='https://www.linkedin.com/in/justinchoo93/' target="_blank">@linkedin</a></h4>
<h4>Anthony Lin <a href='https://github.com/anthonylin198' target="_blank">@github </a><a href='https://www.linkedin.com/in/anthony-lin/' target="_blank">@linkedin</a></h4>
<h4>Spenser Schwartz <a href='https://github.com/spenserschwartz' target="_blank">@github </a><a href='https://www.linkedin.com/in/spenser-schwartz/' target="_blank">@linkedin</a> </h4>
<h4>Steven Nguyen <a href='https://github.com/Steven-Nguyen-T' target="_blank">@github </a><a href='https://www.linkedin.com/in/steven-nguyen-t/' target="_blank">@linkedin</a> </h4>
<h4>Henry Taing <a href='https://github.com/henrytaing' target="_blank">@github </a><a href='https://www.linkedin.com/in/henrytaing/' target="_blank">@linkedin</a> </h4>
<h4>Seungho Baek <a href='https://github.com/hobaek' target="_blank">@github </a><a href='https://www.linkedin.com/in/s2unghobaek/' target="_blank">@linkedin</a> </h4>
<h4>Aaron Yang <a href='https://github.com/aaronyang24' target="_blank">@github </a><a href='https://www.linkedin.com/in/aaronyang24/' target="_blank">@linkedin</a> </h4>
<h4>Jesus Vargas <a href='https://github.com/jmodestov' target="_blank">@github </a><a href='https://www.linkedin.com/in/jesus-modesto-vargas/' target="_blank">@linkedin</a> </h4>
<h4>Davide Molino <a href='https://github.com/davidemmolino' target="_blank">@github </a><a href='https://www.linkedin.com/in/davide-molino/' target="_blank">@linkedin</a> </h4>
<h4>Taven Shumaker <a href='https://github.com/TavenShumaker' target="_blank">@github </a><a href='https://www.linkedin.com/in/Taven-Shumaker/' target="_blank">@linkedin</a> </h4>
<h4>Janis Hernandez <a href='https://github.com/Janis-H' target="_blank">@github </a><a href='https://www.linkedin.com/in/janis-hernandez-aguilar/' target="_blank">@linkedin</a> </h4>
<h4>Jaime Baik <a href='https://github.com/jaimebaik' target="_blank">@github </a><a href='https://www.linkedin.com/in/jaime-baik/' target="_blank">@linkedin</a> </h4>
<h4>Anthony Magallanes <a href='https://github.com/amagalla' target="_blank">@github </a><a href='https://www.linkedin.com/in/anthony-magallanes/' target="_blank">@linkedin</a> </h4>
<h4>Edward Shei <a href='https://github.com/calibeach' target="_blank">@github </a><a href='https://www.linkedin.com/in/edwardshei/' target="_blank">@linkedin</a> </h4>
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: ['@babel/plugin-proposal-class-properties'],
};
================================================
FILE: docs/pull_request_template.md
================================================
## Types of changes
<!--- What types of changes does your code introduce to Scratch Project? Put an `x` in the boxes that apply. -->
- [ ] Bugfix (change which fixes an issue)
- [ ] New feature (change which adds functionality)
- [ ] Refactor (change which changes the codebase without affecting its external behavior)
- [ ] Non-breaking change (fix or feature that would causes existing functionality to work as expected)
- [ ] Breaking change (fix or feature that would cause existing functionality to __not__ work as expected)
## Purpose
<!--- Describe the problem or feature. Link to the issue(s) fixed by this pull request if applicable. -->
## Approach
<!--- How does your change address the problem? -->
## Resources
<!--- Describe the research stage. Link to any blog posts, video, patterns, libraries, addons, or other resources that helped you to solve this problem. -->
## Screenshot(s)
<!--- (if applicable--you can delete otherwise) -->
<!--- Include a screenshot here if the change you made changes the look of the site in any way! -->
================================================
FILE: mock/snapshot.js
================================================
export const filteredCurSnapMock = {
dummyAtom1: {
contents: {hello: [], hi: []},
nodeDeps: [],
nodeToNodeSubscriptions: [],
type: 'RecoilState',
},
listState: {
contents: [{text: 'list item'}, {text: 'list item'}, {text: 'list item'}],
nodeDeps: [],
nodeToNodeSubscriptions: ['selectorTest', 'stateLengths'],
type: 'RecoilState',
},
listState2: {
contents: [{text: 'list item'}, {text: 'list item'}, {text: 'list item'}],
nodeDeps: [],
nodeToNodeSubscriptions: ['stateLengths'],
type: 'RecoilState',
},
selectorTest: {
contents: 'test',
nodeDeps: ['listState'],
nodeToNodeSubscriptions: [],
type: 'RecoilValueReadOnly',
},
stateLengths: {
contents: 6,
nodeDeps: ['listState', 'listState2'],
nodeToNodeSubscriptions: [],
type: 'RecoilValueReadOnly',
},
};
export const filteredPrevSnapMock = {
dummyAtom1: {
contents: {hello: [], hi: []},
nodeDeps: [],
nodeToNodeSubscriptions: [],
type: 'RecoilState',
},
listState: {
contents: [{text: 'list item'}, {text: 'list item'}, {text: 'list item'}],
nodeDeps: [],
nodeToNodeSubscriptions: ['selectorTest', 'stateLengths'],
type: 'RecoilState',
},
listState2: {
contents: [
{text: 'list item'},
{text: 'list item'},
{text: 'list item'},
{text: 'list item'},
],
nodeDeps: [],
nodeToNodeSubscriptions: ['stateLengths'],
type: 'RecoilState',
},
selectorTest: {
contents: 'test',
nodeDeps: ['listState'],
nodeToNodeSubscriptions: [],
type: 'RecoilValueReadOnly',
},
stateLengths: {
contents: 6,
nodeDeps: ['listState', 'listState2'],
nodeToNodeSubscriptions: [],
type: 'RecoilValueReadOnly',
},
};
export const componentAtomTreeMock = {
children: [
{
children: [],
name: '',
tag: 0,
},
{
children: [
{
children: [],
name: '',
tag: 0,
},
{
children: [
{
children: [],
name: '',
tag: 0,
},
{
children: [
{
children: [],
name: '',
tag: 0,
},
],
name: '',
tag: 0,
},
],
name: '',
tag: 0,
},
],
name: '',
tag: 0,
},
{
children: [
{
children: [],
name: '',
tag: 0,
},
{
children: [],
name: '',
tag: 0,
},
],
name: '',
tag: 0,
},
],
name: '',
tag: 0,
};
================================================
FILE: mock/state-snapshot.js
================================================
export const snapshotHistoryMock = {
snapshotHistory: [
{
componentAtomTree: {
actualDuration: 1.6750000004321919,
children: [
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 0,
children: [],
name: 'Batcher',
recoilNodes: [],
tag: 0,
treeBaseDuration: 0.15499999972234946,
wasSuspended: false,
},
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 0,
children: [],
name: 'RecoilizeDebugger',
recoilNodes: [],
tag: 0,
treeBaseDuration: 1.4600000004065805,
wasSuspended: false,
},
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 0.8450000004813774,
children: [
{
actualDuration: 0.6400000002031447,
children: [
{
actualDuration: 0.24500000017724233,
children: [],
name: 'p',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.03500000002532033,
wasSuspended: false,
},
{
actualDuration: 0.009999999747378752,
children: [],
name: 'p',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
{
actualDuration: 0.12000000060652383,
children: [
{
actualDuration: 0.054999999520077836,
children: [],
name: 'button',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.030000000151630957,
wasSuspended: false,
},
{
actualDuration: 0.03500000002532033,
children: [],
name: 'button',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
name: 'div',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.05500000042957254,
wasSuspended: false,
},
],
name: 'div',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.3500000002532033,
wasSuspended: false,
},
],
name: 'PlaygroundStart',
recoilNodes: [],
tag: 0,
treeBaseDuration: 0.5550000005314359,
wasSuspended: false,
},
],
name: 'PlaygroundRender',
recoilNodes: ['playStart'],
tag: 0,
treeBaseDuration: 1.3750000007348717,
wasSuspended: false,
},
],
name: 'Fragment',
recoilNodes: [],
tag: 7,
treeBaseDuration: 2.8550000006362097,
wasSuspended: false,
},
],
name: 'react.provider',
recoilNodes: [],
tag: 10,
treeBaseDuration: 3.050000000257569,
wasSuspended: false,
},
],
name: 'react.provider',
recoilNodes: [],
tag: 10,
treeBaseDuration: 3.0849999993733945,
wasSuspended: false,
},
],
name: 'RecoilRoot',
recoilNodes: [],
tag: 0,
treeBaseDuration: 3.2199999996009865,
wasSuspended: false,
},
],
name: 'HR',
recoilNodes: [],
tag: 3,
treeBaseDuration: 3.3249999996769475,
wasSuspended: false,
},
filteredSnapshot: {
currentPlayerState: {
contents: 'X',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
gameEndSelector: {
contents: false,
nodeDeps: [
'square-0',
'square-1',
'square-2',
'square-3',
'square-4',
'square-5',
'square-6',
'square-7',
'square-8',
'currentPlayerState',
],
nodeToNodeSubscriptions: [],
type: 'RecoilValueReadOnly',
},
playStart: {
contents: false,
nodeDeps: [],
nodeToNodeSubscriptions: [],
type: 'RecoilState',
},
'square-0': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-1': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-2': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-3': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-4': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-5': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-6': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-7': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-8': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
},
indexDiff: 0,
},
{
componentAtomTree: {
actualDuration: 1.6750000004321919,
children: [
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 0,
children: [],
name: 'Batcher',
recoilNodes: [],
tag: 0,
treeBaseDuration: 0.15499999972234946,
wasSuspended: false,
},
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 0,
children: [],
name: 'RecoilizeDebugger',
recoilNodes: [],
tag: 0,
treeBaseDuration: 1.4600000004065805,
wasSuspended: false,
},
{
actualDuration: 1.6650000006848131,
children: [
{
actualDuration: 0.8450000004813774,
children: [
{
actualDuration: 0.6400000002031447,
children: [
{
actualDuration: 0.24500000017724233,
children: [],
name: 'p',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.03500000002532033,
wasSuspended: false,
},
{
actualDuration: 0.009999999747378752,
children: [],
name: 'p',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
{
actualDuration: 0.12000000060652383,
children: [
{
actualDuration: 0.054999999520077836,
children: [],
name: 'button',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.030000000151630957,
wasSuspended: false,
},
{
actualDuration: 0.03500000002532033,
children: [],
name: 'button',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
name: 'div',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.05500000042957254,
wasSuspended: false,
},
],
name: 'div',
recoilNodes: [],
tag: 5,
treeBaseDuration: 0.3500000002532033,
wasSuspended: false,
},
],
name: 'PlaygroundStart',
recoilNodes: [],
tag: 0,
treeBaseDuration: 0.5550000005314359,
wasSuspended: false,
},
],
name: 'PlaygroundRender',
recoilNodes: ['playStart'],
tag: 0,
treeBaseDuration: 1.3750000007348717,
wasSuspended: false,
},
],
name: 'Fragment',
recoilNodes: [],
tag: 7,
treeBaseDuration: 2.8550000006362097,
wasSuspended: false,
},
],
name: 'react.provider',
recoilNodes: [],
tag: 10,
treeBaseDuration: 3.050000000257569,
wasSuspended: false,
},
],
name: 'react.provider',
recoilNodes: [],
tag: 10,
treeBaseDuration: 3.0849999993733945,
wasSuspended: false,
},
],
name: 'RecoilRoot',
recoilNodes: [],
tag: 0,
treeBaseDuration: 3.2199999996009865,
wasSuspended: false,
},
],
name: 'HR',
recoilNodes: [],
tag: 3,
treeBaseDuration: 3.3249999996769475,
wasSuspended: false,
},
filteredSnapshot: {
currentPlayerState: {
contents: 'X',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
gameEndSelector: {
contents: false,
nodeDeps: [
'square-0',
'square-1',
'square-2',
'square-3',
'square-4',
'square-5',
'square-6',
'square-7',
'square-8',
'currentPlayerState',
],
nodeToNodeSubscriptions: [],
type: 'RecoilValueReadOnly',
},
playStart: {
contents: false,
nodeDeps: [],
nodeToNodeSubscriptions: [],
type: 'RecoilState',
},
'square-0': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-1': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-2': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-3': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-4': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-5': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-6': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-7': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
'square-8': {
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
type: 'RecoilState',
},
},
indexDiff: 0,
},
{
componentAtomTree: {
name: 'HR',
tag: 3,
children: [
{
name: 'RecoilRoot',
tag: 0,
children: [
{
name: 'react.provider',
tag: 10,
children: [
{
name: 'react.provider',
tag: 10,
children: [
{
name: 'Batcher',
tag: 0,
children: [],
recoilNodes: [],
actualDuration: 0.004999999873689376,
treeBaseDuration: 0.03500000002532033,
wasSuspended: false,
},
{
name: 'Fragment',
tag: 7,
children: [
{
name: 'RecoilizeDebugger',
tag: 0,
children: [],
recoilNodes: [],
actualDuration: 0.5049999999755528,
treeBaseDuration: 0.5049999999755528,
wasSuspended: false,
},
{
name: 'PlaygroundRender',
tag: 0,
children: [
{
name: 'App',
tag: 1,
children: [
{
name: 'div',
tag: 5,
children: [
{
name: 'h1',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.05999999939376721,
treeBaseDuration: 0,
wasSuspended: false,
},
{
name: 'Board',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [
{
name: 'Fragment',
tag: 7,
children: [
{
name: 'Row',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.06000000030326191,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
recoilNodes: [
'square-0',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.17500000012660166,
treeBaseDuration: 0.11999999969702912,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.02499999936844688,
treeBaseDuration: 0,
wasSuspended: false,
},
],
recoilNodes: [
'square-1',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.07999999888852471,
treeBaseDuration: 0.054999999520077836,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.02500000027794158,
treeBaseDuration: 0.005000000783184078,
wasSuspended: false,
},
],
recoilNodes: [
'square-2',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.06500000017695129,
treeBaseDuration: 0.045000000682193786,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.3499999993437086,
treeBaseDuration: 0.2299999996466795,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.40499999977328116,
treeBaseDuration: 0.28500000007625204,
wasSuspended: false,
},
{
name: 'Row',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.014999999621068127,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
recoilNodes: [
'square-3',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.05999999939376721,
treeBaseDuration: 0.04999999964638846,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.020000000404252205,
treeBaseDuration: 0.005000000783184078,
wasSuspended: false,
},
],
recoilNodes: [
'square-4',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.06000000030326191,
treeBaseDuration: 0.045000000682193786,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.020000000404252205,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
recoilNodes: [
'square-5',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.05500000042957254,
treeBaseDuration: 0.03999999989900971,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.19500000053085387,
treeBaseDuration: 0.1450000008844654,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.210000000151922,
treeBaseDuration: 0.16000000050553354,
wasSuspended: false,
},
{
name: 'Row',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.020000000404252205,
treeBaseDuration: 0.005000000783184078,
wasSuspended: false,
},
],
recoilNodes: [
'square-6',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.054999999520077836,
treeBaseDuration: 0.03999999989900971,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.01500000053056283,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
recoilNodes: [
'square-7',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.05500000042957254,
treeBaseDuration: 0.044999999772699084,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [
{
name: 'div',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.009999999747378752,
treeBaseDuration: 0,
wasSuspended: false,
},
],
recoilNodes: [
'square-8',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.03999999898951501,
treeBaseDuration: 0.02499999936844688,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.15999999959603883,
treeBaseDuration: 0.11499999982333975,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.17499999921710696,
treeBaseDuration: 0.12999999944440788,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.7999999988896889,
treeBaseDuration: 0.5849999997735722,
wasSuspended: false,
},
{
name: 'h2',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.01500000053056283,
treeBaseDuration: 0.005000000783184078,
wasSuspended: false,
},
{
name: 'button',
tag: 5,
children: [],
recoilNodes: [],
actualDuration: 0.03999999989900971,
treeBaseDuration: 0.004999999873689376,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.879999999597203,
treeBaseDuration: 0.6050000010873191,
wasSuspended: false,
},
],
recoilNodes: ['gameEndSelector'],
actualDuration: 1.059999999597494,
treeBaseDuration: 0.7850000010876101,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.1449999992692028,
treeBaseDuration: 0.8000000007086783,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.2199999991935329,
treeBaseDuration: 0.8750000006330083,
wasSuspended: false,
},
],
recoilNodes: ['playStart'],
actualDuration: 1.294999999117863,
treeBaseDuration: 0.9500000005573384,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.7999999990934157,
treeBaseDuration: 1.4750000000276486,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.804999998967105,
treeBaseDuration: 1.5499999999519787,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.804999998967105,
treeBaseDuration: 1.5849999990678043,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.804999998967105,
treeBaseDuration: 1.7199999992953963,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 1.804999998967105,
treeBaseDuration: 1.8249999993713573,
wasSuspended: false,
},
filteredSnapshot: {
playStart: {
type: 'RecoilState',
contents: true,
nodeDeps: [],
nodeToNodeSubscriptions: [],
},
'square-0': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-1': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-2': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-3': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-4': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-5': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-6': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-7': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
'square-8': {
type: 'RecoilState',
contents: '-',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
currentPlayerState: {
type: 'RecoilState',
contents: 'X',
nodeDeps: [],
nodeToNodeSubscriptions: ['gameEndSelector'],
},
gameEndSelector: {
type: 'RecoilValueReadOnly',
contents: false,
nodeDeps: [
'square-0',
'square-1',
'square-2',
'square-3',
'square-4',
'square-5',
'square-6',
'square-7',
'square-8',
'currentPlayerState',
],
nodeToNodeSubscriptions: [],
},
},
indexDiff: 0,
},
],
renderIndex: 2,
cleanComponentAtomTree: {
children: [
{
name: 'Board',
tag: 0,
children: [
{
name: 'Row',
tag: 0,
children: [
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-0',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.17500000012660166,
treeBaseDuration: 0.11999999969702912,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-1',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.07999999888852471,
treeBaseDuration: 0.054999999520077836,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-2',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.06500000017695129,
treeBaseDuration: 0.045000000682193786,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.40499999977328116,
treeBaseDuration: 0.28500000007625204,
wasSuspended: false,
},
{
name: 'Row',
tag: 0,
children: [
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-3',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.05999999939376721,
treeBaseDuration: 0.04999999964638846,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-4',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.06000000030326191,
treeBaseDuration: 0.045000000682193786,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-5',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.05500000042957254,
treeBaseDuration: 0.03999999989900971,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.210000000151922,
treeBaseDuration: 0.16000000050553354,
wasSuspended: false,
},
{
name: 'Row',
tag: 0,
children: [
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-6',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.054999999520077836,
treeBaseDuration: 0.03999999989900971,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-7',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.05500000042957254,
treeBaseDuration: 0.044999999772699084,
wasSuspended: false,
},
{
name: 'Box',
tag: 0,
children: [],
recoilNodes: [
'square-8',
'currentPlayerState',
'gameEndSelector',
],
actualDuration: 0.03999999898951501,
treeBaseDuration: 0.02499999936844688,
wasSuspended: false,
},
],
recoilNodes: [],
actualDuration: 0.17499999921710696,
treeBaseDuration: 0.12999999944440788,
wasSuspended: false,
},
],
recoilNodes: ['gameEndSelector'],
actualDuration: 1.059999999597494,
treeBaseDuration: 0.7850000010876101,
wasSuspended: false,
},
],
name: 'PlaygroundRender',
recoilNodes: ['playStart'],
tag: 0,
actualDuration: 1.804999998967105,
},
};
================================================
FILE: package/.npmignore
================================================
__tests__
./*.ts
formatFiberNodes.ts
index.ts
node_modules/
================================================
FILE: package/README.md
================================================
<meta name='keywords' content='Recoil, Recoil.js, Recoil Dev Tool, Recoilize, Chrome Dev Tool, Recoil Chrome'>
<h1>Debugger for Recoil Applications</h1>
# [](https://github.com/oslabs-beta/Recoilize/blob/staging/LICENSE) [](https://www.npmjs.com/package/recoilize) 
# [](https://github.com/oslabs-beta/Recoilize/blob/staging/LICENSE) [](https://www.npmjs.com/package/recoilize) 
[Korean README 한국어](README_KO.md)
<h1> About</h1>
<p>
Recoilize is a Chrome Dev Tool meant for debugging applications built with the experimental Recoil.js state management library.
The tool records Recoil state and allows users to easily debug their applications with features such as time travel to previous states, visualization of the component graph and display of the atom selector network.
</p>
<p>
Download Recoilize from the <a href='https://chrome.google.com/webstore/detail/recoilize/jhfmmdhbinleghabnblahfjfalfgidik'>Chrome Store</a>
</p>
<p>Visit the Recoilize <a href='https://www.recoilize.io/'>landing page</a> to demo</p>
<h2>
** STILL IN BETA **
</h2>
<p>Please note that Recoilize is in BETA. We will continue to make improvements and implement fixes but if you find any issues, please dont hesitate to report them in the issues tab or submit a PR and we'll happily take a look.</p>
<h1>
Installation
</h1>
#### Install Recoilize Module
```js
npm install recoilize
```
### ** IMPORTANT **
#### Import RecoilizeDebugger from the Recoilize module
```js
import RecoilizeDebugger from 'recoilize';
```
#### Integrate RecoilizeDebugger as a React component within the recoil root:
```js
import RecoilizeDebugger from 'recoilize';
import RecoilRoot from 'recoil';
ReactDOM.render(
<RecoilRoot>
<RecoilizeDebugger />
<App />
</RecoilRoot>,
document.getElementById('root'),
);
```
#### Please note, Recoilize assumes that the HTML element used to inject your React application has an ID of 'root'. If it does not the HTML element must be passed in as an attribute called 'root' to the RecoilizeDebugger component
#### Example:
```js
import RecoilizeDebugger from 'recoilize';
import RecoilRoot from 'recoil';
//If your app injects on an element with ID of 'app'
const app = document.getElementById('app');
ReactDOM.render(
<RecoilRoot>
<RecoilizeDebugger root={app} />
<App />
</RecoilRoot>,
app,
);
```
### In order to integrate Next.js applications with RecoilizeDebugger, follow the example below.
```js
//If your application uses Next.js modify the _app.js as follows
import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
import { RecoilRoot } from 'recoil';
function MyApp({ Component, pageProps }) {
const [root, setRoot] = useState(null)
const RecoilizeDebugger = dynamic(
() => {
return import('recoilize');
},
{ ssr: false}
);
useEffect(() => {
if (typeof window.document !== 'undefined') {
setRoot(document.getElementById('__next'));
}
}, [root]);
return (
<>
<RecoilRoot>
<RecoilizeDebugger root = {root}/>
<Component {...pageProps} />
</RecoilRoot>
</>
);
}
export default MyApp;
```
#### Open your application on the Chrome Browser and start debugging with Recoilize!
##### (Only supported with React applications using Recoil as state management)
<h1>New Features for Version 2.0.1</h1>
<h3>Support for Recoil 0.1.3</h3>
<p>Recoilize now supports the most recent update to the Recoil library.</p>
<h3>Ease of Use</h3>
<p>Recoilize nolonger requires atoms and selectors or the root HTML element to be passed into the RecoilizeDebugger React component. Simply import RecoilizeDubugger and integrate it within your app's RecoilRoot component.</p>
<h3>Support for Concurrent Mode</h3>
<p>Additonal functionality has been added for apps that utilize React's Suspense component. If a Suspense component was used to suspend component renderings those components will display with a red border in the component graph. This indicates that a component was suspended during the render of the selected snapshot.</p>
<h3>Performance Metrics</h3>
<p>A new tab, 'Metrics', has been incorperated into the dev tool. In this tab the user will find two graphs which display component render times.
The flame graph displays the time a component took to render itself, and all of its child components. The bar graph displays the individual render times of each component.<p>
<h1>Features</h1>
<h3>Time Travel</h3>
<p>As one of the key features of Recoilize, the tool enables users to jump to any previous snapshots. Pressing the jump button next to each of the snapshots will change the DOM by setting the state to that snapshot.<p>
<h3>Visualizations</h3>
<p>Users are able to view visualizations for their application's state by clicking individual snapshots. Recoilize provides component trees and graphs, as well as the state trees in JSON format.<p>
<h3>Throttle</h3>
<p>In the settings tab, users are able to set throttle (in milliseconds) for large scale applications or any applications that changes state rapidly. The default is set at 70ms.<p>
<h3>State Persistence</h3>
<p>Recoilize allows the users to persist their application's state through a refresh or reload. At this time, the user is able to view the previous states in the dev tool, but cannot time travel to the states before refresh.</p>
<h3>Additional Features</h3>
<ul><li>component graph hover to view atoms and selectors</li></ul>
<ul><li>legend to see relationship between component graph and state</li></ul>
<ul><li>Toggle to view raw component graph</li></ul>
<ul><li>filter atom/selector network relationship</li></ul>
<ul><li>filter snapshots by atom/selector keys</li></ul>
<h2> We will continue updating Recoilize alongside Recoil's updates!</h2>
<h1>
Contributors
</h1>
<h4>Bren Yamaguchi <a href='https://github.com/brenyama' target="_blank">@github </a><a href='https://www.linkedin.com/in/brenyamaguchi/' target="_blank">@linkedin</a></h4>
<h4>Saejin Kang <a href='https://github.com/skang1004' target="_blank">@github </a><a href='https://www.linkedin.com/in/saejinkang1004/' target="_blank">@linkedin</a></h4>
<h4>Jonathan Escamila <a href='https://github.com/jonescamilla' target="_blank">@github </a><a href='https://www.linkedin.com/in/jon-escamilla/' target="_blank">@linkedin</a> </h4>
<h4>Sean Smith <a href='https://github.com/SmithSean17' target="_blank">@github </a><a href='https://www.linkedin.com/in/sean-smith17/' target="_blank">@linkedin</a> </h4>
<h4>Justin Choo <a href='https://github.com/justinchoo93' target="_blank">@github </a><a href='https://www.linkedin.com/in/justinchoo93/' target="_blank">@linkedin</a></h4>
<h4>Anthony Lin <a href='https://github.com/anthonylin198' target="_blank">@github </a><a href='https://www.linkedin.com/in/anthony-lin/' target="_blank">@linkedin</a></h4>
<h4>Spenser Schwartz <a href='https://github.com/spenserschwartz' target="_blank">@github </a><a href='https://www.linkedin.com/in/spenser-schwartz/' target="_blank">@linkedin</a> </h4>
<h4>Steven Nguyen <a href='https://github.com/Steven-Nguyen-T' target="_blank">@github </a><a href='https://www.linkedin.com/in/steven-nguyen-t/' target="_blank">@linkedin</a> </h4>
<h4>Henry Taing <a href='https://github.com/henrytaing' target="_blank">@github </a><a href='https://www.linkedin.com/in/henrytaing/' target="_blank">@linkedin</a> </h4>
<h4>Seungho Baek <a href='https://github.com/hobaek' target="_blank">@github </a><a href='https://www.linkedin.com/in/s2unghobaek/' target="_blank">@linkedin</a> </h4>
<h4>Aaron Yang <a href='https://github.com/aaronyang24' target="_blank">@github </a><a href='https://www.linkedin.com/in/aaronyang24/' target="_blank">@linkedin</a> </h4>
<h4>Jesus Vargas <a href='https://github.com/jmodestov' target="_blank">@github </a><a href='https://www.linkedin.com/in/jesus-modesto-vargas/' target="_blank">@linkedin</a> </h4>
<h4>Davide Molino <a href='https://github.com/davidemmolino' target="_blank">@github </a><a href='https://www.linkedin.com/in/davide-molino/' target="_blank">@linkedin</a> </h4>
<h4>Taven Shumaker <a href='https://github.com/TavenShumaker' target="_blank">@github </a><a href='https://www.linkedin.com/in/Taven-Shumaker/' target="_blank">@linkedin</a> </h4>
<h4>Janis Hernandez <a href='https://github.com/Janis-H' target="_blank">@github </a><a href='https://www.linkedin.com/in/janis-h/' target="_blank">@linkedin</a> </h4>
<h4>Jaime Baik <a href='https://github.com/jaimebaik' target="_blank">@github </a><a href='https://www.linkedin.com/in/jaime-baik/' target="_blank">@linkedin</a> </h4>
<h4>Anthony Magallanes <a href='https://github.com/amagalla' target="_blank">@github </a><a href='https://www.linkedin.com/in/anthony-magallanes/' target="_blank">@linkedin</a> </h4>
<h4>Edward Shei <a href='https://github.com/calibeach' target="_blank">@github </a><a href='https://www.linkedin.com/in/edwardshei/' target="_blank">@linkedin</a> </h4>
<h4>Nathan Bargers <a href='https://github.com/nbargers' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/nathan-bargers/' target=“_blank”>@linkedin</a> </h4>
<h4>Scott Campbell <a href='https://github.com/thisisscottcampbell' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/thisisscottcampbell/' target=“_blank”>@linkedin</a> </h4>
<h4>Steve Hong <a href='https://github.com/stevehong423' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/stevehongpa/' target=“_blank”>@linkedin</a> </h4>
<h4>Jason Lee <a href='https://github.com/j4s0n1020' target=“_blank”>@github </a><a href='https://www.linkedin.com/in/jasonjml/' target=“_blank”>@linkedin</a> </h4>
<h4>Razana Nisathar <a href='https://github.com/razananisathar' target=“_blank”>@github </a><a href='http://www.linkedin.com/in/razananisathar' target=“_blank”>@linkedin</a> </h4>
================================================
FILE: package/formatFiberNodes.js
================================================
// node parameter should be root of the fiber node tree, can be grapped with startNode from below
// const startNode = document.getElementById('root')._reactRootContainer._internalRoot.current;
const formatFiberNodes = node => {
const formattedNode = {
// this function grabs a 'name' based on the tag of the node
name: assignName(node),
tag: node.tag,
children: [],
recoilNodes: createAtomsSelectorArray(node),
actualDuration: node.actualDuration,
treeBaseDuration: node.treeBaseDuration,
wasSuspended: node.return && node.return.tag === 13 ? true : false,
};
// loop through and recursively call all nodes to format their 'sibling' and 'child' properties to our desired tree shape
let currentNode = node.child;
while (currentNode) {
formattedNode.children.push(formatFiberNodes(currentNode));
currentNode = currentNode.sibling;
}
return formattedNode;
};
const createAtomsSelectorArray = node => {
// initialize empty array for all atoms and selectors. Elements will be all atom and selector names, as strings
const recoilNodes = [];
//start the pointer at node.memoizedState. All nodes should have this key.
let currentNode = node.memoizedState;
// Traverse through the memoizedStates and look for the deps key which holds selectors or state.
while (currentNode) {
// if the memoizedState has a deps key, and that deps key is an array
// then the first value of that array will be an atom or selector
if (
typeof(currentNode) === 'object' &&
currentNode.hasOwnProperty('memoizedState') &&
typeof currentNode.memoizedState === 'object' &&
currentNode.memoizedState !== null &&
!Array.isArray(currentNode.memoizedState) &&
currentNode.memoizedState.hasOwnProperty('deps')
) {
if (
Array.isArray(currentNode.memoizedState.deps) &&
typeof currentNode.memoizedState.deps[0] === 'object' &&
currentNode.memoizedState.deps[0] !== null
) {
// if recoilNodes (arr) includes the current atom or selector
if (!recoilNodes.includes(currentNode.memoizedState.deps[0].key)) {
// otherwise push atom/selector to recoilNodes
recoilNodes.push(currentNode.memoizedState.deps[0].key);
}
}
}
// move onto next node
currentNode = currentNode.next;
}
// return atom and selectors array
return recoilNodes;
};
// keep an eye on this section as we test bigger and bigger applications SEAN
const assignName = node => {
// Returns symbol key if $$typeof is defined. Some components, such as context providers, will have this value.
if (node.type && node.type.$$typeof) return Symbol.keyFor(node.type.$$typeof);
// Return suspense if tag is equal to 13, which is associated with Suspense components.
if (node.tag === 13) return 'Suspense';
// Find name of a class component
if (node.type && node.type.name) return node.type.name;
// Tag 5 === HostComponent
if (node.tag === 5) return `${node.type}`;
// Tag 3 === HostRoot
if (node.tag === 3) return 'HR';
// Tag 6 === HostText
if (node.tag === 6) return node.memoizedProps;
// Tag 7 === Fragment
if (node.tag === 7) return 'Fragment';
};
module.exports = { formatFiberNodes };
// if testing this function on the browser, use line below to log the formatted tree in the console
//let formattedFiberNodes = formatFiberNodes(document.getElementById('root')._reactRootContainer._internalRoot.current)
================================================
FILE: package/formatFiberNodes.ts
================================================
// node parameter should be root of the fiber node tree, can be grapped with startNode from below
// const startNode = document.getElementById('root')._reactRootContainer._internalRoot.current;
type node = {
tag: number;
key: any;
elementType: string;
child: any;
sibling: any;
actualDuration: number;
treeBaseDuration: number;
return: any;
};
type formattedNode = {
name: string;
tag: number;
children: any[];
recoilNodes: any[];
// adding in render time for fiber node
actualDuration: number;
treeBaseDuration: number;
wasSuspended: boolean;
};
const formatFiberNodes = (node: node) => {
const formattedNode: formattedNode = {
actualDuration: node.actualDuration,
treeBaseDuration: node.treeBaseDuration,
// this function grabs a 'name' based on the tag of the node
name: assignName(node),
tag: node.tag,
children: [],
recoilNodes: createAtomsSelectorArray(node),
wasSuspended: node.return && node.return.tag === 13 ? true : false,
};
// loop through and recursively call all nodes to format their 'sibling' and 'child' properties to our desired tree shape
let currentNode = node.child;
while (currentNode) {
formattedNode.children.push(formatFiberNodes(currentNode));
currentNode = currentNode.sibling;
}
return formattedNode;
};
const createAtomsSelectorArray = (node: any) => {
// initialize empty array for all atoms and selectors. Elements will be all atom and selector names, as strings
const recoilNodes = [];
//start the pointer at node.memoizedState. All nodes should have this key.
let currentNode = node.memoizedState;
// Traverse through the memoizedStates and look for the deps key which holds selectors or state.
while (currentNode) {
// if the memoizedState has a deps key, and that deps key is an array of length 2 then the first value of that array will be an atom or selector
if (
currentNode.deps &&
Array.isArray(currentNode.deps) &&
currentNode.deps.length === 2
) {
// if the atom/selector already exist in the recoilNodes array then break from this while loop. At this point you are traversing through previous atom/selector deps.
if (recoilNodes.includes(currentNode.deps[0].key)) break;
recoilNodes.push(currentNode.deps[0].key);
// if an atom/selector was successfully pushed into the recoilNodes array then the pointer should now point to the next key, which will have its own deps key if there is another atom/selector
currentNode = currentNode.next;
} else {
// This is the case where there is no atom/selector in the memoizedState. Look into the memoized state of the next key. If that doesn't exist then break from the while loop because there are no atoms/selectors at this point.
if (!currentNode.next) break;
if (!currentNode.next.memoizedState) break;
currentNode = currentNode.next.memoizedState;
}
}
return recoilNodes;
};
// keep an eye on this section as we test bigger and bigger applications
const assignName = (node: any) => {
// Returns symbol key if $$typeof is defined. Some components, such as context providers, will have this value.
if (node.type && node.type.$$typeof) return Symbol.keyFor(node.type.$$typeof);
// Return suspense if tag is equal to 13, which is associated with Suspense components.
if (node.tag === 13) return 'Suspense';
// Find name of a class component
if (node.type && node.type.name) return node.type.name;
// Tag 5 === HostComponent
if (node.tag === 5) return `${node.type}`;
// Tag 3 === HostRoot
if (node.tag === 3) return 'HR';
// Tag 3 === HostText
if (node.tag === 6) {
return node.memoizedProps;
}
if (node.tag === 7) return 'Fragment';
};
export default formatFiberNodes;
// if testing this function on the browser, use line below to log the formatted tree in the console
//let formattedFiberNodes = formatFiberNodes(document.getElementById('root')._reactRootContainer._internalRoot.current)
================================================
FILE: package/index.js
================================================
import React, {useState, useEffect} from 'react';
import {
useRecoilTransactionObserver_UNSTABLE,
useRecoilSnapshot,
useGotoRecoilSnapshot,
useRecoilState,
useGetRecoilValueInfo_UNSTABLE
} from 'recoil';
import {formatFiberNodes} from './formatFiberNodes';
// grabs isPersistedState from sessionStorage
let isPersistedState = sessionStorage.getItem('isPersistedState');
// isRestored state disables snapshots from being recorded
// when we jump backwards
let isRestoredState = false;
// set default throttle to 70, throttle timer changes with every snapshot
let throttleTimer = 0;
let throttleLimit = 70;
// assign the value of selectorsObject in formatRecoilizeSelectors function
// will contain the selectors from a user application
let selectorsObject;
export default function RecoilizeDebugger(props) {
// We should ask for Array of atoms and selectors.
// Captures all atoms that were defined to get the initial state
// Define a recoilizeRoot variable which will be assigned based on whether a root is passed in as a prop
let recoilizeRoot;
// Check if a root was passed to props.
if (props.root) {
const {root} = props;
recoilizeRoot = root;
} else {
recoilizeRoot = document.getElementById('root');
}
const snapshot = useRecoilSnapshot();
// getNodes_UNSTABLE will return an iterable that contains atom and selector objects.
const nodes = [...snapshot.getNodes_UNSTABLE()];
// Local state of all previous snapshots to use for time traveling when requested by dev tools.
const [snapshots, setSnapshots] = useState([snapshot]);
// const [isRestoredState, setRestoredState] = useState(false);
const gotoSnapshot = useGotoRecoilSnapshot();
const filteredSnapshot = {};
/*
A nodeDeps object is constructed using getDeps_UNSTABLE.
This object will then be used to construct a nodeSubscriptions object.
After continuous testing, getSubscriptions_UNSTABLE was deemed too unreliable.
*/
const nodeDeps = {};
const nodeSubscriptions = {};
nodes.forEach(node => {
const getDeps = [...snapshot.getInfo_UNSTABLE(node).deps];
nodeDeps[node.key] = getDeps.map(dep => dep.key);
});
for (let key in nodeDeps) {
nodeDeps[key].forEach(node => {
if (nodeSubscriptions[node]) {
nodeSubscriptions[node].push(key);
} else {
nodeSubscriptions[node] = [key];
}
});
}
// Traverse all atoms and selector state nodes and get value
nodes.forEach((node, index) => {
const type = node.__proto__.constructor.name;
const contents = snapshot.getLoadable(node).contents;
// Construct node data structure for dev tool to consume
filteredSnapshot[node.key] = {
type,
contents,
nodeDeps: nodeDeps[node.key],
nodeToNodeSubscriptions: nodeSubscriptions[node.key]
? nodeSubscriptions[node.key]
: [],
};
});
// React lifecycle hook on re-render
useEffect(() => {
// Window listener for messages from dev tool UI & background.js
window.addEventListener('message', onMessageReceived);
if (!isRestoredState) {
const devToolData = createDevToolDataObject(filteredSnapshot);
// Post message to content script on every re-render of the developers application only if content script has started
sendWindowMessage('recordSnapshot', devToolData);
} else {
isRestoredState = false;
}
// Clears the window event listener.
return () => window.removeEventListener('message', onMessageReceived);
});
// Listener callback for messages sent to windowf
const onMessageReceived = msg => {
// Add other actions from dev tool here
switch (msg.data.action) {
// Checks to see if content script has started before sending initial snapshot
case 'contentScriptStarted':
if (isPersistedState === 'false' || isPersistedState === null) {
const initialFilteredSnapshot = formatAtomSelectorRelationship(
filteredSnapshot,
);
// once application renders, grab the array of atoms and array of selectors
const appsKnownAtomsArray = [...snapshot._store.getState().knownAtoms]
// console.log('Store State.getState: Atoms', appsKnownAtomsArray);
const appsKnownSelectorsArray = [...snapshot._store.getState().knownSelectors]
// console.log('Store State.getState: Selectors', appsKnownSelectorsArray);
const atomsAndSelectorsMsg = {
atoms: appsKnownAtomsArray,
selectors: appsKnownSelectorsArray,
$selectors: selectorsObject // the selectors object that contain key and set / get methods as strings
}
//creating a indexDiff variable
//only created on initial creation of devToolData
//determines difference in length of backend snapshots array and frontend snapshotHistoryLength to avoid off by one error
const indexDiff = snapshots.length - 1;
const devToolData = createDevToolDataObject(
initialFilteredSnapshot,
indexDiff,
atomsAndSelectorsMsg,
);
sendWindowMessage('moduleInitialized', devToolData);
} else {
setProperIndexForPersistedState();
sendWindowMessage('persistSnapshots', null);
}
break;
// Listens for a request from dev tool to time travel to previous state of the app.
case 'snapshotTimeTravel':
timeTravelToSnapshot(msg);
break;
case 'persistState':
switchPersistMode();
break;
// Implementing the throttle change
case 'throttleEdit':
throttleLimit = parseInt(msg.data.payload.value);
break;
default:
break;
}
};
// assigns or switches isPersistedState in sessionStorage
const switchPersistMode = () => {
if (isPersistedState === 'false' || isPersistedState === null) {
// switch isPersistedState in sessionStorage to true
sessionStorage.setItem('isPersistedState', true);
// stores the length of current list of snapshots in sessionStorage
sessionStorage.setItem('persistedSnapshots', snapshots.length);
} else {
// switch isPersistedState in sessionStorage to false
sessionStorage.setItem('isPersistedState', false);
}
};
// function retreives length and fills snapshot array
const setProperIndexForPersistedState = () => {
const retreived = sessionStorage.getItem('persistedSnapshots');
const snapshotsArray = new Array(Number(retreived) + 1).fill({});
setSnapshots(snapshotsArray);
};
// Sends window an action and payload message.
const sendWindowMessage = (action, payload) => {
window.postMessage(
JSON.parse(JSON.stringify({
action,
payload,
})),
'*',
);
};
const createDevToolDataObject = (filteredSnapshot, diff, atomsAndSelectors) => {
if (diff === undefined) {
return {
filteredSnapshot: filteredSnapshot,
componentAtomTree: formatFiberNodes(
recoilizeRoot._reactRootContainer._internalRoot.current,
),
atomsAndSelectors,
};
} else {
return {
filteredSnapshot: filteredSnapshot,
componentAtomTree: formatFiberNodes(
recoilizeRoot._reactRootContainer._internalRoot.current,
),
indexDiff: diff,
atomsAndSelectors,
};
}
};
const formatAtomSelectorRelationship = filteredSnapshot => {
if (
window.$recoilDebugStates &&
Array.isArray(window.$recoilDebugStates) &&
window.$recoilDebugStates.length
) {
let snapObj =
window.$recoilDebugStates[window.$recoilDebugStates.length - 1];
if (snapObj.hasOwnProperty('nodeDeps')) {
for (let [key, value] of snapObj.nodeDeps) {
filteredSnapshot[key].nodeDeps = Array.from(value);
}
}
if (snapObj.hasOwnProperty('nodeToNodeSubscriptions')) {
for (let [key, value] of snapObj.nodeToNodeSubscriptions) {
filteredSnapshot[key].nodeToNodeSubscriptions = Array.from(value);
}
}
}
return filteredSnapshot;
};
// Will add hover effect over highlighted component
// Takes an argument of msg.data which contains name and payload
const activateHover = payload => {
let name = payload.name;
};
// FOR TIME TRAVEL: time travels to a given snapshot, re renders application.
const timeTravelToSnapshot = async msg => {
isRestoredState = true;
await gotoSnapshot(snapshots[msg.data.payload.snapshotIndex]);
};
// FOR TIME TRAVEL: Recoil hook to fire a callback on every atom/selector change -- research Throttle
useRecoilTransactionObserver_UNSTABLE(({snapshot}) => {
const now = new Date().getTime();
if (now - throttleTimer < throttleLimit) {
isRestoredState = true;
} else {
throttleTimer = now;
}
if (!isRestoredState) {
setSnapshots([...snapshots, snapshot]);
}
});
return null;
}
// function that receives objects to be passed into selector constructor function to post a message to the window
// cannot send an object with a property that contains a function to the window - need to stringify the set and get methods
export function formatRecoilizeSelectors(...selectors){
// create object to be sent via window message from target recoil application
selectorsObject = {};
// iterate through our array of objects
selectors.forEach(selector => {
// check if the current selector object contains a set method, if so, reassign it to a stringified version
if (selector.hasOwnProperty('set')){
selector.set = selector.set.toString();
}
// check if the current selector object contains a get method, if so, reassign it to a stringified version
if (selector.hasOwnProperty('get')){
selector.get = selector.get.toString();
}
// store the selector in the payload object - providing its property name as the 'key' property of the current selector object
// providing the object the property name of selector key will give easy searchability in GUI application for selector dropdown
selectorsObject[selector.key] = selector;
});
}
================================================
FILE: package/index.ts
================================================
import {useState, useEffect} from 'react';
import {
useRecoilTransactionObserver_UNSTABLE,
useRecoilSnapshot,
useGotoRecoilSnapshot,
} from 'recoil';
import formatFiberNodes from './formatFiberNodes';
// isRestored state disables snapshots from being recorded
let isRestoredState = false;
export default function RecoilizeDebugger(props: any) {
// We should ask for Array of atoms and selectors.
// Captures all atoms that were defined to get the initial state
let nodes = null;
if (typeof props.nodes === 'object' && !Array.isArray(props.nodes)) {
nodes = Object.values(props.nodes);
} else if (Array.isArray(props.nodes)) {
nodes = props.nodes;
}
const {root} = props;
const snapshot: any = useRecoilSnapshot();
// Local state of all previous snapshots to use for time traveling when requested by dev tools.
const [snapshots, setSnapshots] = useState([snapshot]);
// const [isRestoredState, setRestoredState] = useState(false);
const gotoSnapshot = useGotoRecoilSnapshot();
const filteredSnapshot: any = {};
const currentTree = snapshot._store.getState().currentTree;
// Traverse all atoms and selector state nodes and get value
nodes.forEach((node: any) => {
const type = node.__proto__.constructor.name;
const contents = snapshot.getLoadable(node).contents;
const nodeDeps = currentTree.nodeDeps.get(node.key);
const nodeToNodeSubscriptions = currentTree.nodeToNodeSubscriptions.get(
node.key,
);
// Construct node data structure for dev tool to consume
filteredSnapshot[node.key] = {
type,
contents,
nodeDeps: nodeDeps ? Array.from(nodeDeps) : [],
nodeToNodeSubscriptions: nodeToNodeSubscriptions
? Array.from(nodeToNodeSubscriptions)
: [],
};
});
// React lifecycle hook on re-render
useEffect(() => {
if (!isRestoredState) {
setTimeout(() => {
const devToolData = createDevToolDataObject(filteredSnapshot);
// Post message to content script on every re-render of the developers application only if content script has started
sendWindowMessage('recordSnapshot', devToolData);
}, 0);
} else {
isRestoredState = false;
}
// Window listener for messages from dev tool UI & background.js
window.addEventListener('message', onMessageReceived);
// Clears the window event listener.
return () => window.removeEventListener('message', onMessageReceived);
});
// Listener callback for messages sent to window
const onMessageReceived = (msg: any) => {
// Add other actions from dev tool here
switch (msg.data.action) {
// Checks to see if content script has started before sending initial snapshot
case 'contentScriptStarted':
const initialFilteredSnapshot = formatAtomSelectorRelationship(
filteredSnapshot,
);
const devToolData = createDevToolDataObject(initialFilteredSnapshot);
sendWindowMessage('moduleInitialized', devToolData);
break;
// Listens for a request from dev tool to time travel to previous state of the app.
case 'snapshotTimeTravel':
timeTravelToSnapshot(msg);
break;
default:
break;
}
};
// Sends window an action and payload message.
const sendWindowMessage = (action: any, payload: any) => {
window.postMessage(
{
action,
payload,
},
'*',
);
};
const createDevToolDataObject = (filteredSnapshot: any) => {
return {
filteredSnapshot: filteredSnapshot,
componentAtomTree: formatFiberNodes(
root._reactRootContainer._internalRoot.current,
),
};
};
const formatAtomSelectorRelationship = (filteredSnapshot: any) => {
const windowAny: any = window;
if (
windowAny.$recoilDebugStates &&
Array.isArray(windowAny.$recoilDebugStates) &&
windowAny.$recoilDebugStates.length
) {
let snapObj =
windowAny.$recoilDebugStates[windowAny.$recoilDebugStates.length - 1];
if (snapObj.hasOwnProperty('nodeDeps')) {
for (let [key, value] of snapObj.nodeDeps) {
filteredSnapshot[key].nodeDeps = Array.from(value);
}
}
if (snapObj.hasOwnProperty('nodeToNodeSubscriptions')) {
for (let [key, value] of snapObj.nodeToNodeSubscriptions) {
filteredSnapshot[key].nodeToNodeSubscriptions = Array.from(value);
}
}
}
return filteredSnapshot;
};
// FOR TIME TRAVEL: time travels to a given snapshot, re renders application.
const timeTravelToSnapshot = async (msg: any) => {
isRestoredState = true;
await gotoSnapshot(snapshots[msg.data.payload.snapshotIndex]);
};
// FOR TIME TRAVEL: Recoil hook to fire a callback on every snapshot change
useRecoilTransactionObserver_UNSTABLE(({snapshot}) => {
if (!isRestoredState) {
setSnapshots([...snapshots, snapshot]);
}
});
return null;
}
================================================
FILE: package/package.json
================================================
{
"name": "recoilize",
"version": "1.0.0",
"description": "Recoil Dev Tool",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/open-source-labs/Recoilize"
},
"peerDependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1",
"recoil": "^0.1.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Bren Yamaguchi, Saejin Kang, Jonathan Escamilla, Sean Smith, Justin Choo, Anthony Lin, Spenser Schwartz, Steven Nguyen, Henry Taing, Seungho Baek, Taven Shumaker, Aaron Yang, Jesus Vargas, Davide Molino, Janis Hernandez, Jaime Baik, Anthony Magallanes, Edward Shei, Leonard Lew, Joey Ma, Harvey Nguyen, Victor Wang",
"license": "MIT",
"devDependencies": {
"typescript": "^3.9.6"
}
}
================================================
FILE: package.json
================================================
{
"name": "recoilize",
"jest": {
"setupFiles": [
"jest-webextension-mock"
]
},
"version": "3.0.0",
"description": "A Chrome extension that helps debug Recoil applications by memorizing the state of components with every render.",
"main": "index.js",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch",
"test": "jest --verbose",
"lint": "eslint '**/*.tsx' '**/*.ts' '**/*.js' --ignore-path .gitignore"
},
"author": "Bren Yamaguchi, Saejin Kang, Jonathan Escamilla, Sean Smith, Justin Choo, Anthony Lin, Spenser Schwartz, Steven Nguyen, Henry Taing, Seungho Baek, Taven Shumaker, Aaron Yang, Jesus Vargas, Davide Molino, Janis Hernandez, Jaime Baik, Anthony Magallanes, Edward Shei, Leonard Lew, Joey Ma, Harvey Nguyen, Victor Wang",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.10.3",
"@babel/polyfill": "^7.10.4",
"@babel/preset-env": "^7.10.3",
"@babel/preset-react": "^7.10.1",
"@babel/preset-typescript": "^7.10.4",
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/react": "^10.4.6",
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"css-loader": "^3.6.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^7.4.0",
"eslint-config-fbjs": "^3.1.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.3",
"jest": "^26.1.0",
"jest-webextension-mock": "^3.6.1",
"prettier": "^2.0.5",
"recoil": "^0.7.2",
"style-loader": "^2.0.0",
"ts-loader": "^7.0.5",
"typescript": "^3.9.6",
"webpack": "^4.43.0",
"webpack-chrome-extension-reloader": "^1.3.0",
"webpack-cli": "^3.3.12"
},
"dependencies": {
"@angular/cli": "^10.0.5",
"@angular/core": "^10.0.8",
"@popperjs/core": "^2.4.4",
"@reduxjs/toolkit": "^1.5.0",
"@testing-library/react-hooks": "^3.4.1",
"@types/chrome": "0.0.117",
"@types/react-html-parser": "^2.0.1",
"@types/react-json-tree": "^0.6.11",
"@types/react-redux": "^7.1.16",
"@vx/group": "0.0.196",
"@vx/hierarchy": "0.0.196",
"@vx/responsive": "0.0.196",
"babel-eslint": "^10.1.0",
"codemirror": "^5.65.2",
"d3": "^5.16.0",
"d3-hierarchy": "1.1.9",
"d3-interpolate": "1.4.0",
"d3-scale": "3.2.1",
"d3-scale-chromatic": "1.5.0",
"d3-shape": "1.3.7",
"jest-webextension-mock": "^3.6.1",
"jsondiffpatch": "^0.4.1",
"multiselect-react-dropdown": "^1.5.7",
"prop-types": "^15.7.2",
"rc-slider": "^9.7.5",
"rc-tooltip": "^5.1.1",
"react": "^16.13.1",
"react-codemirror2": "^7.2.1",
"react-dom": "^16.13.1",
"react-html-parser": "^2.0.2",
"react-json-tree": "^0.11.2",
"react-multi-select-component": "^3.0.1",
"react-redux": "^7.2.3",
"react-router-dom": "^5.2.0",
"react-spring": "^8.0.27",
"react-testing-library": "^8.0.1",
"redux-persist": "^6.0.0",
"rxjs": "^6.6.2",
"semantic-ui-react": "^1.1.1",
"style-loader": "^2.0.0"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
}
}
================================================
FILE: src/README.md
================================================
# Developer README
## Brief
Though Recoil.js is still in the experimental state, it has proved its ability and catched a tremendous amount of attentions from experience developers for the past three years. We created Recoilize with one great mission in mind - providing a helpful tool so the developers can make an easy transition to Recoil.js and making the debugging process more effective. Recoilize is an open source product that is maintained and iterated constantly, and we're always welcome developers who are interested in it. Getting on board and understanding the codebase are never easy, so here are some useful tips and information that will help you get started quickly.
## File Structure
The scr folder contains Recoilize's source code - frontend and chrome extension.
```
src/
├── app/ # Frontend code
│ ├── components/ # React components
│ ├── Containers/ # More React components
│ ├── state-management/ # Redux Toolkit Slices
│ ├── utils/ # Helper Functions
│ └── index.tsx # Starting point for root App component
│
├── types/
│ └── index.d.ts #
│
├── extension/ # Chrome Extension code
│ ├── build/ # Destination for bundles and manifest.json (Chrome config file)
│ │ #
│ ├── background.js # Chrome Background Script
│ └── contentScript.ts # Chrome Content Script
└──
```
Here is an in-depth view of the app's components:

## Diagramming
If there's an update in the file structure, we suggest using [excalidraw](https://excalidraw.com/)
## Future Features and Possible Improvements
- Optimizing Time Travel Algorithm. The Time Travel Algorithm is working perfectly. However, we believe that it can be better by implementing a better algorithm (or different approaches). Please note that there's no right or wrong approaches, and everyone is welcome to try new ideas.
- UI/UX improvement. The UI/UX aspect definitely has room for improvement. Please feel free to play around with the app to get some ideas. For example, look at the component graph, atom network, graphs (flame, ranked, comparison).
- Testing is an important aspect, and we believe in TDD (Test Driven Development). However, due to time constraint and the limitation of resources, Recoilize is missing the testing part. Since testing has so much potential to improve, we strongly recommend developers who love testing get started as soon as you get onboard.
- Documentations - Documentations often get neglected since developers usually focus on writing code to improve features. However, at Recoilize, we believe that documenting is one of the most important tasks. Why? Because it's helpful not only for you, your teammates but it can also be valuable for future developers who interested in Recoilize. So if you love Recoilize, take good care of it by writing good documentations.
- Containerization - Have you ever wondered why the app that you made ran perfectly on your computer but it couldn't run on other's computers. That is a common problem that software engineers often encounter. We can solve this by containerize our app, and `Docker` is a good candidate for this task.
================================================
FILE: src/app/Containers/ButtonsContainer.tsx
================================================
import React from 'react';
const ButtonsContainer = () => {
const howToUseHandler = () => {
window.open('https://github.com/open-source-labs/Recoilize', '_blank');
};
return (
<div className="buttons_container">
<button
id="docs_button"
type="button"
onClick={() => {
howToUseHandler();
}}>
How To Use
</button>
</div>
);
};
export default ButtonsContainer;
================================================
FILE: src/app/Containers/MainContainer.tsx
================================================
import React from 'react';
import SnapshotsContainer from './SnapshotContainer';
import VisualContainer from './VisualContainer';
import TravelContainer from './TravelContainer';
const MainContainer: React.FC = () => {
return (
<div className="MainContainer">
<SnapshotsContainer />
<VisualContainer />
<TravelContainer />
</div>
);
};
export default MainContainer;
================================================
FILE: src/app/Containers/SnapshotContainer.tsx
================================================
import React, {useEffect, useState, useRef} from 'react';
import {selectFilterState} from '../state-management/slices/FilterSlice';
import {useAppSelector, useAppDispatch} from '../state-management/hooks';
import {setRenderIndex} from '../state-management/slices/SnapshotSlice';
const SnapshotsContainer: React.FC = () => {
const dispatch = useAppDispatch();
const snapshotHistory = useAppSelector(
state => state.snapshot.snapshotHistory,
);
const selected = useAppSelector(state => state.selected.selectedData);
const renderIndex = useAppSelector(state => state.snapshot.renderIndex);
const filterData = useAppSelector(selectFilterState);
const snapshotEndRef = useRef<HTMLDivElement>(null);
let snapshotHistoryLength = snapshotHistory.length;
// useEffect to scroll bottom whenever snapshot history changes
useEffect(() => {
scrollToBottom();
}, [snapshotHistoryLength]);
// using scrollInToView makes a smoother scroll
const scrollToBottom = (): void => {
snapshotEndRef.current.scrollIntoView({behavior: 'smooth'});
};
const snapshotDivs: JSX.Element[] = [];
// iterate the same length of our snapshotHistory
for (let i = 0; i < snapshotHistoryLength; i++) {
// filterFunc will return false if there is no change to state
const filterFunc = (): boolean => {
// don't use the counter for this, not reliable
if (i === 0) {
return true;
}
// checks if the filteredSnapshot object at index i has a key (atom or selector) found in the selected array. This would indicate that there was a change to that state/selector because filter is an array of objects containing differences between snapshots.
if (filterData[i]) {
for (let key in filterData[i].filteredSnapshot) {
for (let j = 0; j < selected.length; j++) {
if (key === selected[j].name) {
return true;
}
}
}
}
return false;
};
const x: boolean = filterFunc();
if (x === false) {
continue;
}
// renderTime is set equal to the actualDuration. If i is zero then we are obtaining actualDuration from the very first snapshot in snapshotHistory. This is to avoid having undefined filter elements since there will be no difference between snapshot at the first instance.
let renderTime: number =
snapshotHistory[i].componentAtomTree.treeBaseDuration;
// if (i === 0) {
// renderTime = snapshotHistory[0].componentAtomTree.treeBaseDuration;
// }
// //Checks to see if the actualDuration within filter is an array. If it is an array then the 2nd value in the array is the new actualDuration.
// else if (Array.isArray(filterData[i].componentAtomTree.actualDuration)) {
// renderTime = filterData[i].componentAtomTree.treeBaseDuration[1];
// } else {
// renderTime = filterData[i].componentAtomTree.treeBaseDuration;
// }
// Push a div container to snapshotDivs array only if there was a change to state.
// The div container will contain renderTimes evaluated above.
snapshotDivs.push(
<div
id={`snapshot${i}`}
className="individualSnapshot"
key={i}
style={
renderIndex === i
? {color: '#E6E6E6', backgroundColor: '#212121'}
: {color: '#989898'}
}
onClick={() => {
dispatch(setRenderIndex(i));
}}>
{/* <li>{i}</li> */}
<li>{`${Math.round(renderTime * 100) / 100}ms`}</li>
<button
className="timeTravelButton"
style={
renderIndex === i
? {color: '#E6E6E6', backgroundColor: '#212121'}
: {}
}
onClick={() => {
timeTravelFunc(i);
}}>
Jump
</button>
</div>,
);
}
//indexDiff is used to ensure the index of filter matches the index of the snapshots array in the backend
let indexDiff: number = 0;
if (filterData[0] && filterData[0].indexDiff) {
indexDiff = filterData[0].indexDiff;
}
// functionality to postMessage the selected snapshot index to background.js
const timeTravelFunc = (index: number) => {
// variable to store/reference connection
const backgroundConnection = chrome.runtime.connect();
//const test = chrome.extension.getBackgroundPage();
// post the message with index in payload to the connection
backgroundConnection.postMessage({
action: 'snapshotTimeTravel',
tabId: chrome.devtools.inspectedWindow.tabId,
payload: {
snapshotIndex: index + indexDiff,
},
});
};
function prevClr() {
const snapshotListArr = document.querySelectorAll('.individualSnapshot');
for (let i = 0; i < snapshotListArr.length; i++) {
let index = parseInt(snapshotListArr[i].id.match(/\d+/g)[0]);
if (index < renderIndex) {
snapshotListArr[i].parentNode.removeChild(snapshotListArr[i]);
} else break;
}
}
function fwrdClr() {
const snapshotListArr = document.querySelectorAll('.individualSnapshot');
for (let i = snapshotListArr.length - 1; i >= 0; i--) {
let index = parseInt(snapshotListArr[i].id.match(/\d+/g)[0]);
if (index > renderIndex) {
snapshotListArr[i].parentNode.removeChild(snapshotListArr[i]);
} else break;
}
}
// create a function to store current data to local storage
const toLocalStorage = (data: any) => {
for (let i = 0; i < data.length; i++) {
console.log('trigger toLocalStorage');
const jsonData = JSON.stringify(data[i]);
localStorage.setItem(`${i}`, jsonData);
}
};
return (
<div className="SnapshotsContainer">
<div id="clear-snapshots-title">Clear Snapshots</div>
<div className="clear-buttons">
<button onClick={prevClr} id="prevClr">
Previous
</button>
<button onClick={fwrdClr} id="fwrdClr">
Forward
</button>
</div>
<span
style={{
fontSize: '14px',
fontWeight: 'bold',
marginTop: '10px',
marginBottom: '10px',
}}>
Snapshots
</span>
<button
className="save-series-button"
onClick={e => {
toLocalStorage(snapshotHistory);
}}>
Save Series
</button>
<div className="SnapshotsList">
<div>{snapshotDivs}</div>
<div ref={snapshotEndRef} />
</div>
</div>
);
};
export default SnapshotsContainer;
================================================
FILE: src/app/Containers/TravelContainer.tsx
================================================
import React from 'react';
import MainSlider from '../components/Slider/MainSlider';
import ButtonsContainer from './ButtonsContainer';
const TravelContainer: React.FC = () => {
return (
<div className="travel-container">
<MainSlider />
<ButtonsContainer />
</div>
);
};
export default TravelContainer;
================================================
FILE: src/app/Containers/VisualContainer.tsx
================================================
import React, {useState} from 'react';
import {RecoilRoot} from 'recoil';
import Diff from '../components/StateDiff/Diff';
import NavBar from '../components/NavBar/NavBar';
import Metrics from '../components/Metrics/MetricsContainer';
import Tree from '../components/StateTree/Tree';
import Network from '../components/AtomNetwork/AtomNetwork';
import AtomComponentVisualContainer from '../components/ComponentGraph/AtomComponentContainer';
import Settings from '../components/Settings/SettingsContainer';
import Testing from '../components/Testing/TestingContainer';
type navTypes = {
[tabName: string]: JSX.Element;
};
// Renders Navbar and conditionally renders Diff, Visualizer, and Tree
const VisualContainer: React.FC = () => {
// object containing all conditional renders based on navBar
const nav: navTypes = {
// compare the diff of filteredPrevSnap and filteredCurSnap
'State Diff': <Diff />,
// render JSON tree of snapshot
'State Tree': <Tree />,
// tree visualizer of components showing atom/selector relationships
'Component Graph': <AtomComponentVisualContainer />,
// atom and selector subscription relationship
'Atom Network': <Network />,
// quotes not needed where name = component variable
// individual snapshot visualizer
Metrics: <Metrics />,
// settings tab
Settings: <Settings />,
// add a testing tab
Testing: (
<RecoilRoot>
<Testing />
</RecoilRoot>
),
};
// array of all nav obj keys
const tabsList: string[] = Object.keys(nav);
// useState hook to update which component to render in the VisualContainer
const [tab, setTab] = useState<string>('State Diff');
// conditionally render based on value of nav[tab]
return (
<div className="VisualContainer">
<NavBar setTab={setTab} tabsList={tabsList} tab={tab} />
{nav[tab]}
</div>
);
};
export default VisualContainer;
================================================
FILE: src/app/Containers/__tests__/MainContainer.unit.test.js
================================================
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
import {getQueriesForElement, getByText} from '@testing-library/dom';
import {render, fireEvent} from '@testing-library/react';
import MainContainer from '../MainContainer';
it('Main Container Renders', () => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const {getByPlaceholderText, debug} = render(
<MainContainer snapshotHistory={[]} />,
);
});
================================================
FILE: src/app/Containers/__tests__/SnapshotContainer.unit.test.js
================================================
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
import {getQueriesForElement, getByText} from '@testing-library/dom';
import {render, fireEvent} from '@testing-library/react';
import SnapshotContainer from '../SnapshotContainer';
it('Snapshot Container Renders', () => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const {getByPlaceholderText, debug} = render(
<SnapshotContainer snapshotHistory={[]} />,
);
});
================================================
FILE: src/app/Containers/__tests__/VisualContainer.unit.test.js
================================================
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
import {getQueriesForElement, getByText} from '@testing-library/dom';
import {render, fireEvent} from '@testing-library/react';
import VisualContainer from '../VisualContainer';
it('Visual Container Renders', () => {
window.HTMLElement.prototype.scrollIntoView = jest.fn();
const {getByPlaceholderText, debug} = render(
<VisualContainer snapshotHistory={[]} />,
);
});
================================================
FILE: src/app/components/App.tsx
================================================
import React, {useEffect} from 'react';
import MainContainer from '../Containers/MainContainer';
import {selectedTypes} from '../../types';
// importing the diff to find difference
import {diff} from 'jsondiffpatch';
import {useAppSelector, useAppDispatch} from '../state-management/hooks';
import {
setSnapshotHistory,
setRenderIndex,
setCleanComponentAtomTree,
} from '../state-management/slices/SnapshotSlice';
import {
addSelected,
setSelected,
} from '../state-management/slices/SelectedSlice';
import {
updateFilter,
selectFilterState,
} from '../state-management/slices/FilterSlice';
import {setAtomsAndSelectors} from '../state-management/slices/AtomsAndSelectorsSlice';
const LOGO_URL = './assets/Recoilize-v2.png';
const App: React.FC = () => {
const dispatch = useAppDispatch();
// useState hook to update the snapshotHistory array
// array of snapshots
const snapshotHistory = useAppSelector(
state => state.snapshot.snapshotHistory,
);
const renderIndex = useAppSelector(state => state.snapshot.renderIndex);
const selected = useAppSelector(state => state.selected.selectedData);
// selected will be an array with objects containing filteredSnapshot key names (the atoms and selectors)
// ex: [{name: 'Atom1'}, {name: 'Atom2'}, {name: 'Selector1'}, ...]
// const [selected, setSelected] = useState<selectedTypes[]>([]);
// todo: Create algo that will clean up the big setSnapshothistory object, now and before
// ! Setting up the selected
const filterData = useAppSelector(selectFilterState);
// Whenever snapshotHistory changes, useEffect will run, and selected will be updated
useEffect(() => {
// whenever snapshotHistory changes, update renderIndex
dispatch(setRenderIndex(snapshotHistory.length - 1));
let last;
if (snapshotHistory[renderIndex]) {
last = snapshotHistory[renderIndex].filteredSnapshot;
}
// we must compare with the original
for (let key in last) {
if (!snapshotHistory[0].filteredSnapshot[key]) {
// only push if the name doesn't already exist
const check = () => {
for (let i = 0; i < selected.length; i++) {
// break if it exists
if (selected[i].name === key) {
return true;
}
}
// does not exist
return false;
};
if (!check()) {
// console.log('after Check');
dispatch(addSelected({name: key}));
}
}
}
}, [snapshotHistory]); // Only re-run the effect if snapshot history changes -- react hooks
//Update cleanComponentAtomTree as Render Index changes
useEffect(() => {
if (snapshotHistory.length === 0) return;
dispatch(
setCleanComponentAtomTree(snapshotHistory[renderIndex].componentAtomTree),
);
}, [renderIndex]);
// useEffect for snapshotHistory
useEffect(() => {
// SETUP connection to bg script
const backgroundConnection = chrome.runtime.connect();
// INITIALIZE connection to bg script
backgroundConnection.postMessage({
action: 'devToolInitialized',
tabId: chrome.devtools.inspectedWindow.tabId,
});
// console.log(
// 'here is the background connection post message IN APP',
// backgroundConnection,
// );
// LISTEN for messages FROM bg script
backgroundConnection.onMessage.addListener(msg => {
if (msg.action === 'recordSnapshot') {
// ! sets the initial selected
//console.log('should have our atoms and selectors: ', msg.payload);
if (!msg.payload[1]) {
// ensures we only set initially
const arr: selectedTypes[] = [];
for (let key in msg.payload[0].filteredSnapshot) {
arr.push({name: key});
}
// setSelected(arr);
//console.log('arr in App.tsx send to setSelected', arr)
dispatch(setSelected(arr));
}
// console.log(
// 'this is snapshotHistory',
// msg.payload[msg.payload.length - 1],
// );
dispatch(setSnapshotHistory(msg.payload[msg.payload.length - 1]));
// update state with the atoms and selectors!!!
dispatch(setAtomsAndSelectors(msg.payload[0].atomsAndSelectors));
//console.log('Payload: IS IT HERE??, ', msg.payload);
// ! Setting the FILTER Array
if (!msg.payload[1] && filterData.length === 0) {
// todo: currently the filter does not work if recoilize is not open, we must change msg.payload to incorporate delta function in the backend
dispatch(updateFilter(msg.payload));
} else {
// push the difference between the objects
const delta = diff(
msg.payload[msg.payload.length - 2],
msg.payload[msg.payload.length - 1],
);
// only push if the snapshot length is chill
if (filterData.length < msg.payload.length) {
dispatch(updateFilter([delta]));
}
}
}
});
}, []);
// Render main container if we have detected a recoil app with the recoilize module passing data
const renderMainContainer: JSX.Element = <MainContainer />;
// Render module not found message if snapHistory is null, this means we have not detected a recoil app with recoilize module installed properly
const renderModuleNotFoundContainer: JSX.Element = (
<div className="notFoundContainer">
<img className="logo" src={LOGO_URL} />
<p>
Supported only with Recoil apps with the Recoilize NPM module.
<br />
Please follow the installation instructions at
<a
target="_blank"
href="https://github.com/open-source-labs/Recoilize"
rel="noreferrer">
Recoilize
</a>
</p>
</div>
);
return (
<div className="App" key="App">
{snapshotHistory.length
? renderMainContainer
: renderModuleNotFoundContainer}
</div>
);
};
export default App;
================================================
FILE: src/app/components/AtomNetwork/AtomNetwork.tsx
================================================
import React from 'react';
import AtomNetworkLegend from './AtomNetworkLegend';
import AtomNetworkVisual from './AtomNetworkVisual';
//Create the container passing in the JSX props for each individual component
const AtomNetwork: React.FC = () => {
return (
<div className="networkContainer">
<AtomNetworkVisual />
<AtomNetworkLegend />
</div>
);
};
export default AtomNetwork;
================================================
FILE: src/app/components/AtomNetwork/AtomNetworkLegend.tsx
================================================
import React, {useState} from 'react';
import {useAppSelector, useAppDispatch} from '../../state-management/hooks';
import { setSearchValue } from '../../state-management/slices/AtomNetworkSlice';
const AtomNetworkLegend: React.FC = () => {
const dispatch = useAppDispatch();
//Retrieve snapshotHistory State from Redux Store
const snapshotHistory = useAppSelector(
state => state.snapshot.snapshotHistory,
);
const renderIndex = useAppSelector(state => state.snapshot.renderIndex);
const filteredCurSnap =
snapshotHistory[renderIndex].filteredSnapshot;
// an array of atoms and selector sub
const atomAndSelectorArr = Object.entries(filteredCurSnap);
// array of atom names (what the drop down showAtomMenu is going to display)
const [atomList] = useState(atomAndSelectorArr.filter(([isAtom, obj]: [string, any])=> !obj.nodeDeps.length ? isAtom : null));
// array of selectors (what the drop down showSelectorMenu is going to display)
const [selectorList] = useState(atomAndSelectorArr.filter(([isSelector, obj]:[string, any]) => obj.nodeDeps.length ? isSelector : null));
//state hook for showing list of atoms
const [showAtomMenu, setShowAtomMenu] = useState(false);
//state hook for showing list of selectors
const [showSelectorMenu, setShowSelectorMenu] = useState(false);
const [atomButtonClicked, setAtomButtonClicked] = useState<boolean>(false);
const [selectorButtonClicked, setSelectorButtonClicked] = useState<boolean>(false);
// function to handle change in search bar. Sets searchValue state
const handleChange = (e: any) => {
dispatch(setSearchValue(e.target.value));
};
// handles clicking on Selector and Atom buttom to bring down
// list of atoms or selects
function openDropdown(e: React.MouseEvent) {
// if user clicks on atom list button
const target = e.target as Element;
if(target.id === 'AtomP') {
// check if selector list was previously open, if it is, close it
if(showSelectorMenu) setShowSelectorMenu(false);
// open atom list
setShowAtomMenu(!showAtomMenu);
// empty search box
dispatch(setSearchValue(''));
setAtomButtonClicked(true);
setSelectorButtonClicked(false);
}
// if user clicks on selector list button
else if(target.id === 'SelectorP') {
// check if atom list was previously open, if it is, close it
if(showAtomMenu) setShowAtomMenu(false);
// show Selector list
setShowSelectorMenu(!showSelectorMenu);
// empty search box
dispatch(setSearchValue(''));
setSelectorButtonClicked(true);
setAtomButtonClicked(false);
}
}
return (
<div className="LegendContainer">
<div className= "RecoilSearch">
<input
id="networkSearch"
type="text"
placeholder="search for state"
//check the input value to render corresponding and related nodes
onChange={handleChange}
/>
</div>
<div className="AtomNetworkLegendWithSearch">
<div className="AtomLegend"> </div>
{/* //change the visibility of the div depending the value of the state */}
<button
onClick={openDropdown}
id="AtomP"
className={
atomButtonClicked ? "AtomP atomSelected" : "AtomP atomLegendDefault"
}>
ATOM
</button>
<div className="SelectorLegend"></div>
<button
onClick={openDropdown}
id="SelectorP"
className={
selectorButtonClicked ? "SelctorP selectorSelected" : "SelectorP selectorLegendDefault"
}>
SELECTOR
</button>
{/* conditional rendering of dropdowns depending on the value of the state */}
{showAtomMenu &&
<div className="AtomDropdown">{atomList.map(([atom, atomObj], i)=> {
return (
<div className= "dropDownButtonDiv">
<button key={i} id={`atom-drop${i}`} className='atom-class atomDropDown' onClick={(event: React.MouseEvent) => {
if (
!(event.target as HTMLInputElement).classList.contains(
'atomSelected',
) &&
(event.target as HTMLInputElement).classList.contains(
'atomNotSelected',
)
) {
(event.target as HTMLInputElement).classList.replace(
'atomNotSelected',
'atomSelected',
);
} else if (
!(event.target as HTMLInputElement).classList.contains(
'atomSelected',
) &&
!(event.target as HTMLInputElement).classList.contains(
'atomNotSelected',
)
) {
(event.target as HTMLInputElement).classList.add(
'atomSelected',
);
}
document.querySelectorAll('.atom-class').forEach(item => {
if (
item.id !== `atom-drop${i}` &&
item.classList.contains('atomSelected')
) {
item.classList.replace(
'atomSelected',
'atomNotSelected',
);
} else if (
item.id !== `atom-drop${i}` &&
!item.classList.contains('atomNotSelected')
) {
item.classList.add('atomNotSelected');
}
});
//set the search value to the name of the paragraph element to render only corresponding and related nodes
dispatch(setSearchValue((event.target as Element).innerHTML));
}}>{atom}</button></div>)
})}</div>}
{showSelectorMenu &&
<div className="SelectorDropdown">{selectorList.map(([selector, selectorObj], i) => {
return (
<div className="dropDownButtonDiv">
<button key={i} id={`selector-drop${i}`} className='selector-class selectorDropDown' onClick={(event: React.MouseEvent) => {
if (
!(event.target as HTMLInputElement).classList.contains(
'selectorSelected',
) &&
(event.target as HTMLInputElement).classList.contains(
'selectorNotSelected',
)
) {
(event.target as HTMLInputElement).classList.replace(
'selectorNotSelected',
'selectorSelected',
);
} else if (
!(event.target as HTMLInputElement).classList.contains(
'selectorSelected',
) &&
!(event.target as HTMLInputElement).classList.contains(
'selectorNotSelected',
)
) {
(event.target as HTMLInputElement).classList.add(
'selectorSelected',
);
}
document
.querySelectorAll('.selector-class')
.forEach(item => {
if (
item.id !== `selector-drop${i}` &&
item.classList.contains('selectorSelected')
) {
item.classList.replace(
'selectorSelected',
'selectorNotSelected',
);
} else if (
item.id !== `selector-drop${i}` &&
!item.classList.contains('selectorNotSelected')
) {
item.classList.add('selectorNotSelected');
}
});
//set the search value to the name of the paragraph element to render only corresponding and related nodes
dispatch(setSearchValue((event.target as Element).innerHTML));
}}>{selector}</button></div>)
})}</div>}
</div>
</div>
)
}
export default AtomNetworkLegend;
================================================
FILE: src/app/components/AtomNetwork/AtomNetworkVisual.tsx
================================================
import React, {useState, useEffect} from 'react';
import * as d3 from 'd3';
import makeRelationshipLinks from '../../utils/makeRelationshipLinks';
import {useAppSelector} from '../../state-management/hooks';
import {filteredSnapshot} from '../../../types';
const AtomNetworkVisual: React.FC = () => {
//Retrieve snapshotHistory State from Redux Store
const snapshotHistory = useAppSelector(
state => state.snapshot.snapshotHistory,
);
const renderIndex = useAppSelector(state => state.snapshot.renderIndex);
const filteredCurSnap = snapshotHistory[renderIndex].filteredSnapshot;
//Retrieve atomNetworkSearch State from Redux Store
const searchValue = useAppSelector(state => state.atomNetwork.searchValue);
useEffect(() => {
// new filtered snap object to be constructed with search value
const newFilteredCurSnap: any = {};
// filters filteredCurSnap object with atoms and selectors that includes are search value
const filter = (filteredCurSnap: filteredSnapshot) => {
for (let key in filteredCurSnap) {
if (key.toLowerCase().includes(searchValue.toLowerCase())) {
newFilteredCurSnap[key] = filteredCurSnap[key];
grabNodeToNodeSubscriptions(newFilteredCurSnap[key]);
grabNodeDeps(newFilteredCurSnap[key]);
}
}
};
// helper functions to recursively include searched atoms/selectors' subscriptions
const grabNodeToNodeSubscriptions = (node: any) => {
let nodeSubscriptionLength = node.nodeToNodeSubscriptions.length;
if (nodeSubscriptionLength > 0) {
for (let i = 0; i < nodeSubscriptionLength; i += 1) {
let currSN = node.nodeToNodeSubscriptions[i];
newFilteredCurSnap[currSN] = filteredCurSnap[currSN];
grabNodeToNodeSubscriptions(filteredCurSnap[currSN]);
}
}
};
const grabNodeDeps = (node: any) => {
let nodeDepsLength = node.nodeDeps.length;
if (nodeDepsLength > 0) {
for (let i = 0; i < nodeDepsLength; i += 1) {
let currDepNode = node.nodeDeps[i];
newFilteredCurSnap[currDepNode] = filteredCurSnap[currDepNode];
grabNodeDeps(filteredCurSnap[currDepNode]);
}
}
};
// invoke filter to populate newFilteredCurSnap
filter(filteredCurSnap);
document.getElementById('networkCanvas').innerHTML = '';
let edgepaths: any;
let edgelabels: any;
const networkContainer = document.querySelector('.networkContainer');
// sets starting position of the atomNetwork graph.
// const width = networkContainer.clientWidth;
// const height = networkContainer.clientHeight;
const width = 300;
const height = 300;
// snap will be newFilteredCurSnap if searchValue exists, if not original
let snap: any = searchValue ? newFilteredCurSnap : filteredCurSnap;
// TRANSFORM DATA INTO D3 SUPPORTED FORMAT FOR NETWORK GRAPH
const networkData: any = makeRelationshipLinks(snap);
//Create Disjoint Force-Directed Graph
const chart = (data: any) => {
const links = data.links.map((d: any) => Object.create(d));
const nodes = data.nodes.map((d: any) => {
return Object.create(d);
});
const simulation = d3
.forceSimulation(nodes)
.force(
'link',
d3.forceLink(links).id((d: any) => d.id),
)
.force('charge', d3.forceManyBody())
.force('x', d3.forceX())
.force('y', d3.forceY());
const svg = d3
// .create('svg')
.select('#networkCanvas')
.attr('viewBox', [-width / 2, -height / 2, width, height]);
const link = svg
.append('g')
.attr('stroke', '#999')
.attr('stroke-opacity', 0.6)
.selectAll('line')
.data(links)
.join('line')
.attr('stroke-width', (d: any) => Math.sqrt(d.value));
const node = svg
.append('g')
.attr('stroke', '#fff')
.attr('stroke-width', 1.5)
.selectAll('circle')
.data(nodes)
.join('circle')
.attr('r', 5)
.style('fill', function (d: any, i: any) {
return d.name === 'Atom' ? '#9580FF' : '#FF80BF';
});
node
.append('title')
.attr('dx', 12)
// .attr('text-anchor', 'middle')
.attr('dy', '.35em')
.text((d: any) => d.label)
.attr('stroke', 'white')
.attr('stroke-width', 3);
node
.append('text')
.attr('dx', 12)
.attr('dy', '.35em')
.text(function (d: any) {
return d.name;
});
simulation.on('tick', () => {
link
.attr('x1', (d: any) => d.source.x)
.attr('y1', (d: any) => d.source.y)
.attr('x2', (d: any) => d.target.x)
.attr('y2', (d: any) => d.target.y);
node.attr('cx', (d: any) => d.x).attr('cy', (d: any) => d.y);
});
//Zoom functions
function zoomActions() {
svg.attr('transform', d3.event.transform);
}
var zoomHandler = d3.zoom().on('zoom', zoomActions);
zoomHandler(svg);
// allows the nodes to be draggable
const dragDrop = d3
.drag()
.on('start', (node: {fx: any; x: any; fy: any; y: any}) => {
node.fx = node.x;
node.fy = node.y;
})
.on('drag', (node: {fx: any; fy: any}) => {
simulation.alphaTarget(1).restart();
node.fx = d3.event.x;
node.fy = d3.event.y;
})
.on('end', (node: {fx: any; fy: any}) => {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
node.fx = null;
node.fy = null;
});
node.call(dragDrop);
simulation.alpha(1).restart();
return svg.node();
};
chart(networkData);
}); //end of useEffect
return (
<div className="Network">
<svg data-testid="networkCanvas" id="networkCanvas"></svg>
</div>
);
};
export default AtomNetworkVisual;
================================================
FILE: src/app/components/AtomNetwork/__tests__/Network.unit.test.js
================================================
import React from 'react';
import Network from '../AtomNetwork';
import {render, cleanup} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import '@babel/polyfill';
import {filteredCurSnapMock} from '../../../../../mock/snapshot.js';
afterEach(cleanup);
it('renders & matches snapshot', () => {
const {asFragment} = render(<Network />);
expect(asFragment()).toMatchSnapshot();
});
it('loads and displays Network component', () => {
const {getByTestId} = render(<Network />);
expect(getByTestId('networkCanvas')).toBeTruthy();
});
it('if empty object props is passed into Network', () => {
const {asFragment} = render(<Network filteredCurSnap={{}} />);
expect(asFragment()).toMatchSnapshot();
});
it('if mock data object prop is passed into Network', () => {
const {asFragment} = render(
<Network filteredCurSnap={filteredCurSnapMock} />,
);
expect(asFragment()).toMatchSnapshot();
});
================================================
FILE: src/app/components/AtomNetwork/__tests__/__snapshots__/Network.unit.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`if empty object props is passed into Network 1`] = `
<DocumentFragment>
<div
class="networkContainer"
>
<div
class="Network"
>
<svg
data-testid="networkCanvas"
id="networkCanvas"
>
<g
transform="translate(0, 0), scale(1)"
>
<defs>
<marker
id="arrowhead"
markerHeight="7"
markerWidth="7"
orient="auto"
refX="13"
refY="0"
viewBox="-0 -5 10 10"
xoverflow="visible"
>
<path
d="M 0,-5 L 10 ,0 L 0,5"
fill="#474747"
style="stroke: none;"
/>
</marker>
</defs>
</g>
</svg>
</div>
<input
id="networkSearch"
placeholder="search for atoms..."
type="text"
value=""
/>
<div
class="AtomNetworkLegend"
>
<div
class="AtomLegend"
/>
<p>
ATOM
</p>
<div
class="SelectorLegend"
/>
<p>
SELECTOR
</p>
</div>
</div>
</DocumentFragment>
`;
exports[`if mock data object prop is passed into Network 1`] = `
<DocumentFragment>
<div
class="networkContainer"
>
<div
class="Network"
>
<svg
data-testid="networkCanvas"
id="networkCanvas"
>
<g
transform="translate(0, 0), scale(1)"
>
<defs>
<marker
id="arrowhead"
markerHeight="7"
markerWidth="7"
orient="auto"
refX="13"
refY="0"
viewBox="-0 -5 10 10"
xoverflow="visible"
>
<path
d="M 0,-5 L 10 ,0 L 0,5"
fill="#474747"
style="stroke: none;"
/>
</marker>
</defs>
<line
class="link"
marker-end="url(#arrowhead)"
>
<title />
</line>
<line
class="link"
marker-end="url(#arrowhead)"
>
<title />
</line>
<line
class="link"
marker-end="url(#arrowhead)"
>
<title />
</line>
<path
class="edgepath"
fill-opacity="1.0"
id="edgepath0"
stroke-opacity="1.0"
style="pointer-events: none; fill: #474747; stroke: #474747; stroke-width: 1px;"
/>
<path
class="edgepath"
fill-opacity="1.0"
id="edgepath1"
stroke-opacity="1.0"
style="pointer-events: none; fill: #474747; stroke: #474747; stroke-width: 1px;"
/>
<path
class="edgepath"
fill-opacity="1.0"
id="edgepath2"
stroke-opacity="1.0"
style="pointer-events: none; fill: #474747; stroke: #474747; stroke-width: 1px;"
/>
<text
class="edgelabel"
font-size="10"
id="edgelabel0"
style="pointer-events: none;"
>
<textpath
href="#edgepath0"
startOffset="50%"
style="text-anchor: middle; pointer-events: none;"
/>
</text>
<text
class="edgelabel"
font-size="10"
id="edgelabel1"
style="pointer-events: none;"
>
<textpath
href="#edgepath1"
startOffset="50%"
style="text-anchor: middle; pointer-events: none;"
/>
</text>
<text
class="edgelabel"
font-size="10"
id="edgelabel2"
style="pointer-events: none;"
>
<textpath
href="#edgepath2"
startOffset="50%"
style="text-anchor: middle; pointer-events: none;"
/>
</text>
<g
class="node"
>
<circle
r="5"
style="fill: #9580FF;"
/>
<title>
0
</title>
<text
dy="-3"
fill="#646464"
stroke="none"
>
dummyAtom1
</text>
</g>
<g
class="node"
>
<circle
r="5"
style="fill: #9580FF;"
/>
<title>
1
</title>
<text
dy="-3"
fill="#646464"
stroke="none"
>
listState
</text>
</g>
<g
class="node"
>
<circle
r="5"
style="fill: #9580FF;"
/>
<title>
2
</title>
<text
dy="-3"
fill="#646464"
stroke="none"
>
listState2
</text>
</g>
<g
class="node"
>
<circle
r="5"
style="fill: #FF80BF;"
/>
<title>
3
</title>
<text
dy="-3"
fill="#646464"
stroke="none"
>
selectorTest
</text>
</g>
<g
class="node"
>
<circle
r="5"
style="fill: #FF80BF;"
/>
<title>
4
</title>
<text
dy="-3"
fill="#646464"
stroke="none"
>
stateLengths
</text>
</g>
</g>
</svg>
</div>
<input
id="networkSearch"
placeholder="search for atoms..."
type="text"
value=""
/>
<div
class="AtomNetworkLegend"
>
<div
class="AtomLegend"
/>
<p>
ATOM
</p>
<div
class="SelectorLegend"
/>
<p>
SELECTOR
</p>
</div>
</div>
</DocumentFragment>
`;
exports[`renders & matches snapshot 1`] = `
<DocumentFragment>
<div
class="networkContainer"
>
<div
class="Network"
>
<svg
data-testid="networkCanvas"
id="networkCanvas"
>
<g
transform="translate(0, 0), scale(1)"
>
<defs>
<marker
id="arrowhead"
markerHeight="7"
markerWidth="7"
orient="auto"
refX="13"
refY="0"
viewBox="-0 -5 10 10"
xoverflow="visible"
>
<path
d="M 0,-5 L 10 ,0 L 0,5"
fill="#474747"
style="stroke: none;"
/>
</marker>
</defs>
</g>
</svg>
</div>
<input
id="networkSearch"
placeholder="search for atoms..."
type="text"
value=""
/>
<div
class="AtomNetworkLegend"
>
<div
class="AtomLegend"
/>
<p>
ATOM
</p>
<div
class="SelectorLegend"
/>
<p>
SELECTOR
</p>
</div>
</div>
</DocumentFragment>
`;
================================================
FILE: src/app/components/ComponentGraph/AtomComponentContainer.tsx
================================================
import React, {useState} from 'react';
import AtomComponentVisual from './AtomComponentVisual';
import {filteredSnapshot, atom, selector} from '../../../types';
import {useAppSelector} from '../../state-management/hooks';
const AtomComponentVisualContainer: React.FC = () => {
//Retrieve State from store
const snapshotHistory = useAppSelector(
state => state.snapshot.snapshotHistory,
);
const renderIndex = useAppSelector(state => state.snapshot.renderIndex);
const cleanedComponentAtomTree = useAppSelector(
state => state.snapshot.cleanComponentAtomTree,
);
const filteredCurSnap: filteredSnapshot =
snapshotHistory[renderIndex].filteredSnapshot;
const componentAtomTree = snapshotHistory[renderIndex].componentAtomTree;
// this will be the atom or selector from the AtomSelectorLegend that the user clicked on. an array with the ele at index 0 as the name of the atom/selector, and ele at index 1 will be 'atom' or 'selector'
// Why was selectedRecoilValue formatted as an array? why not an object?
const [selectedRecoilValue, setSelectedRecoilValue] = useState<string[]>([]);
const [str, setStr] = useState<string[]>([]);
// each property in the atoms or selectors object will be a property whose key is the atom or selector name,
// and whose value is the value of that atom or selector
const atoms: atom = {};
const selectors: selector = {};
if (filteredCurSnap) {
for (let [recoilValueName, object] of Object.entries(filteredCurSnap)) {
if (!object.nodeDeps.length) {
atoms[recoilValueName] = object.contents;
} else {
selectors[recoilValueName] = object.contents;
}
}
}
return (
<div className="Component">
<AtomComponentVisual
componentAtomTree={componentAtomTree}
cleanedComponentAtomTree={cleanedComponentAtomTree}
selectedRecoilValue={selectedRecoilValue}
setSelectedRecoilValue={setSelectedRecoilValue}
atoms={atoms}
selectors={selectors}
setStr={setStr}
/>
</div>
);
};
export default AtomComponentVisualContainer;
================================================
FILE: src/app/components/ComponentGraph/AtomComponentVisual.tsx
================================================
import React, {useState, useEffect} from 'react';
import * as d3 from 'd3';
import {componentAtomTree, atom, selector} from '../../../types';
import {useAppSelector, useAppDispatch} from '../../state-management/hooks';
import {
updateZoomState,
selectZoomState,
setDefaultZoom,
} from '../../state-management/slices/ZoomSlice';
interface AtomComponentVisualProps {
componentAtomTree: componentAtomTree;
cleanedComponentAtomTree: componentAtomTree;
selectedRecoilValue: string[];
atoms: atom;
selectors: selector;
setStr: React.Dispatch<React.SetStateAction<string[]>>;
setSelectedRecoilValue: React.Dispatch<React.SetStateAction<string[]>>;
}
const AtomComponentVisual: React.FC<AtomComponentVisualProps> = ({
componentAtomTree,
cleanedComponentAtomTree,
selectedRecoilValue,
atoms,
selectors,
setStr,
setSelectedRecoilValue,
}) => {
const zoomSelector = useAppSelector(selectZoomState);
const {x, y, k} = zoomSelector; // initial scaling
const dispatch = useAppDispatch();
// set the heights and width of the tree to be passed into treeMap function
let width: number = 0;
let height: number = 0;
// useState hook to update the toggle of displaying entire tree or cleaned tree
const [rawToggle, setRawToggle] = useState<boolean>(false);
// useState hook to update whether a suspense component will be shown on the component graph
const [hasSuspense, setHasSuspense] = useState<boolean>(false);
//declare hooks to render lists of atoms or selectors
const [atomList, setAtomList] = useState(Object.keys(atoms)); // returns an array of atom's properties
const [selectorList, setSelectorList] = useState(Object.keys(selectors));
// need to create a hook for toggling
const [showAtomMenu, setShowAtomMenu] = useState<boolean>(false);
const [showSelectorMenu, setShowSelectorMenu] = useState<boolean>(false);
// hook for selected button styles on the legend
const [atomButtonClicked, setAtomButtonClicked] = useState<boolean>(false);
const [selectorButtonClicked, setSelectorButtonClicked] = useState<boolean>(
false,
);
const [bothButtonClicked, setBothButtonClicked] = useState<boolean>(false);
const [isDropDownItem, setIsDropDownItem] = useState<boolean>(false);
// hooks for sibling level node spacing adjustment
const [siblingSpacingFactor, setSiblingSpacingFactor] = useState<number>(1);
const [siblingSlider, setSiblingSlider] = useState<number>(10);
// hooks for parent-child node spacing adjustment
const [parentSpacingFactor, setParentSpacingFactor] = useState<number>(1);
const [parentChildSlider, setParentChildSlider] = useState<number>(10);
// hook for component graph orientation
const [vertOrient, setVertOrient] = useState<boolean>(false);
useEffect(() => {
height = document.querySelector('.Component').clientHeight;
width = document.querySelector('.Component').clientWidth;
document.getElementById('canvas').innerHTML = '';
// reset hasSuspense to false. This will get updated to true if the red borders are rendered on the component graph.
setHasSuspense(false);
// creating the main svg container for d3 elements
const svgContainer = d3.select('#canvas');
// creating a pseudo-class for reusability
const g = svgContainer
.append('g')
.attr('transform', `translate(${x}, ${y}), scale(${k})`)
.attr('id', 'componentGraph');
let i = 0;
let duration: number = 750;
let root: any;
let path: string;
// creating the tree map
const treeMap = d3.tree().nodeSize(
[
height * siblingSpacingFactor,
width * parentSpacingFactor
]
);
if (!rawToggle) { // expanded tree
root = d3.hierarchy(
cleanedComponentAtomTree,
function (d: componentAtomTree) {
return d.children;
},
);
} else { // collapsed tree
root = d3.hierarchy(componentAtomTree, function (d: componentAtomTree) {
return d.children;
});
}
// Node distance from each other
root.x0 = 10;
root.y0 = width / 2;
update(root);
// d3 zoom functionality
let zoom = d3.zoom().on('zoom', zoomed);
// https://github.com/d3/d3-zoom/blob/v3.0.0/README.md#zoom_transform
svgContainer.call(
zoom.transform,
// Changes the initial view, (left, top)
d3.zoomIdentity.translate(x, y).scale(k),
);
// allows the canvas to be zoom-able
svgContainer.call(
d3
.zoom()
.scaleExtent([0.05, 0.9]) // [max zoomed out view, max zoomed in view]
.on('zoom', zoomed),
);
// helper function that allows for zooming
function zoomed() {
g.attr('transform', d3.event.transform).on(
'mouseup',
dispatch(
updateZoomState(d3.zoomTransform(d3.select('#canvas').node())),
),
);
}
// Update function
/***
* Function: Update()
* Parameters: source
* Output:
*
*/
function update(source: any) {
treeMap(root);
let nodes = root.descendants(),
links = root.descendants().slice(1);
let node = g
.selectAll('g.node')
.attr('stroke-width', 5)
.data(nodes, function (d: any): number {
return d.id || (d.id = ++i);
});
/* getting group element with id = "componentGraph"
ex 1: <path class="link" stroke="#646464" stroke-width="5"
d="M 582 0
C 291 0,
291 0,
0 0">
</path>
ex 2: <g class="node" transform="translate(0, 0)" opacity="1">
<circle class="node" r="100" fill="#9580ff" cursor="pointer" style="stroke: none; stroke-width: 15;">
</circle>
<text dy=".31em" y="138" text-anchor="middle" style="font-size: 7.5rem; fill: white;">PlaygroundRender
</text>
</g>
https://devdocs.io/d3~5/d3-selection#selectAll */
/* this tells node where to be placed and go to
* adding a mouseOver event handler to each node
* display the data in the node on hover
* add mouseOut event handler that removes the popup text
*/
//add div that will hold info regarding atoms and/or selectors for each node
const tooltip = d3
.select('.tooltipContainer')
.append('div')
.attr('class', 'hoverInfo')
.style('opacity', 0);
let nodeEnter = node
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', function (): string {
return `translate(${source.y0}, ${source.x0})`;
// getting <g class="node"> && setting its "transform" attr to "translate(x, y) --> css manipulation"
})
.on('click', click)
.on('mouseover', function (d: any, i: number): void {
// atsel is an array of all the atoms and selectors
const atsel: any = [];
if (d.data.recoilNodes) {
for (let x = 0; x < d.data.recoilNodes.length; x++) {
// pushing all the atoms and selectors for the node into 'atsel'
atsel.push(d.data.recoilNodes[x]);
}
// change the opacity of the node when the mouse is over
d3.select(this).transition().duration('50').attr('opacity', '.85');
// created a str for hover div to have corrensponding info
// let newStr = formatAtomSelectorText(atsel).join('<br>');
// newStr = newStr.replace(/,/g, '<br>');
// newStr = newStr.replace(/{/g, '<br>{');
const nodeData = formatAtomSelectorText(atsel)[0];
const genHTML = (obj: any): string => {
let str = '';
let htmlStr = '';
for (let key in obj) {
const curr = obj[key];
if (key === 'type') str += `${curr}: `;
if (key === 'name') str += curr;
if (key === 'info') {
htmlStr += `<h3>${str}</h3>`;
htmlStr += `<h5>Atomic Values</h5>`;
if (typeof curr === 'string')
htmlStr += `<p>title: ${curr}</p>`;
else
for (let prop in curr) {
const title = prop;
const data = curr[prop];
htmlStr += `<p>${title}: ${data}</p>`;
}
}
}
return `<div>${htmlStr}</div>`;
};
// tooltip appear near your mouse when hover over a node
tooltip
.style('opacity', 1)
.html(genHTML(nodeData))
.style('left', d3.event.pageX + 15 + 'px') // mouse position
.style('top', d3.event.pageY - 20 + 'px');
}
})
.on('mouseout', function (d: any, i: number): void {
d3.select(this).transition().duration('50').attr('opacity', '1');
tooltip.style('opacity', 0);
});
// determines shape/color/size of node // adding styling attributes
nodeEnter
.append('circle')
.attr('class', 'node')
.attr('r', determineSize)
.attr('fill', colorComponents)
.style('stroke', borderColor)
.style('stroke-width', 15);
// TO DO: Add attribute for border if it is a suspense component // what is a suspense component and what color to add?
// for each node that got created, append a text element that displays the name of the node
nodeEnter
.append('text')
.attr('dy', '.31em')
.attr('y', (d: any): number => (d.data.recoilNodes ? 138 : -75))
.attr('text-anchor', function (d: any): string {
return d.children || d._children ? 'middle' : 'middle';
})
.text((d: any): string => d.data.name)
.style('font-size', `7.5rem`)
.style('fill', 'white');
let nodeUpdate = nodeEnter.merge(node);
// transition that makes it slide down to next spot
nodeUpdate
// .transition()
// .duration(duration)
.attr('transform', function (d: any): string {
return vertOrient ? `translate(${d.x}, ${d.y})` : `translate(${d.y}, ${d.x})`;
});
// allows user to see hand pop out when clicking is available and maintains color/size
nodeUpdate
.select('circle.node')
.attr('r', determineSize)
.attr('fill', colorComponents)
.attr('cursor', 'pointer')
.style('stroke', borderColor)
.style('stroke-width', 15);
let nodeExit = node
.exit()
.transition()
.duration(duration)
.attr('transform', function (d: any): string {
return `translate(${source.y}, ${source.x})`;
})
.remove();
let link = g
.attr('fill', 'none')
.attr('stroke-width', 5)
.selectAll('path.link')
.data(links, function (d: any): number {
return d.id;
});
let linkEnter = link
.enter()
.insert('path', 'g')
.attr('class', 'link')
.attr('stroke', '#646464')
.attr('stroke-width', 5)
.attr('d', function (d: any): string {
let o = {x: source.x0, y: source.y0};
return diagonal(o, o);
});
let linkUpdate = linkEnter.merge(link);
linkUpdate
.transition()
.duration(duration)
.attr('stroke', '#646464')
.attr('stroke-width', 5)
.attr('d', function (d: any): string {
return diagonal(d, d.parent);
});
let linkExit = link
.exit()
.transition()
.duration(duration)
.attr('stroke', '#646464')
.attr('stroke-width', 5)
.attr('d', function (d: any): string {
let o = {y: source.y, x: source.x};
return diagonal(o, o);
})
.remove();
nodes.forEach(function (d: any): void {
d.x0 = d.x;
d.y0 = d.y;
});
function diagonal(s: any, d: any): string {
if (vertOrient) {
path = `M ${s.x} ${s.y}
C ${s.x} ${(s.y + d.y) / 2},
${d.x} ${(s.y + d.y) / 2},
${d.x} ${d.y}`;
} else {
path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`;
}
return path;
}
function click(d: any): void {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
const atsel = [];
if (d.data.recoilNodes) {
for (let x = 0; x < d.data.recoilNodes.length; x++) {
atsel.push(d.data.recoilNodes[x]);
}
setStr(formatAtomSelectorText(atsel));
}
}
// allows the canvas to be draggable
node.call(d3.drag());
// https://github.com/d3/d3-selection/blob/v3.0.0/README.md#selection_call
function formatMouseoverXValue(recoilValue: string): number {
if (atoms.hasOwnProperty(recoilValue)) {
return -30;
}
return -150;
}
function formatAtomSelectorText(atomOrSelector: string[]): string[] {
const recoilData: any = [];
for (let i = 0; i < atomOrSelector.length; i++) {
const data: any = {};
const curr = atomOrSelector[i];
data.type = atoms.hasOwnProperty(curr) ? 'atom' : 'selector';
data.name = curr;
if (data.type === 'atom') {
data.info = atoms[curr];
} else {
data.info = selectors[curr];
}
recoilData.push(data);
}
return recoilData;
}
function determineSize(d: any): number {
if (d.data.recoilNodes && d.data.recoilNodes.length) {
if (d.data.recoilNodes.includes(selectedRecoilValue[0])) {
// Size when the atom/selector is clicked on from legend
return 150;
}
// Size of atoms and selectors
return 100;
}
// Size of regular nodes
return 50;
}
function borderColor(d: any): string {
if (d.data.wasSuspended) setHasSuspense(true);
return d.data.wasSuspended ? '#FF0000' : 'none';
}
function colorComponents(d: any): string {
// if component node contains recoil atoms or selectors, make it orange red or yellow, otherwise keep node gray
if (d.data.recoilNodes && d.data.recoilNodes.length) {
if (d.data.recoilNodes.includes(selectedRecoilValue[0])) {
// Color of atom or selector when clicked on in legend
return 'yellow';
}
let hasAtom = false;
let hasSelector = false;
for (let i = 0; i < d.data.recoilNodes.length; i++) {
if (atoms.hasOwnProperty(d.data.recoilNodes[i])) {
hasAtom = true;
}
if (selectors.hasOwnProperty(d.data.recoilNodes[i])) {
hasSelector = true;
}
}
if (hasAtom && hasSelector) {
return 'springgreen';
}
if (hasAtom) {
return '#9580ff';
} else {
return '#ff80bf';
}
}
return 'gray';
}
}
}, [componentAtomTree, rawToggle, siblingSpacingFactor, parentSpacingFactor, vertOrient, selectedRecoilValue]);
// setting the component's user interface state by checking if the dropdown menu is open or not
function openDropdown(e: React.MouseEvent) {
const target = e.target as Element;
// the button element with id 'AtomP'
if (target.id === 'AtomP') {
setAtomButtonClicked(true);
setSelectorButtonClicked(false);
setShowAtomMenu(!showAtomMenu);
setShowSelectorMenu(false);
} else {
setAtomButtonClicked(false);
setSelectorButtonClicked(true);
setShowSelectorMenu(!showSelectorMenu);
setShowAtomMenu(false);
}
}
// resetting the component's user interface state when toggling between atoms & selectors
const resetNodes = () => {
setIsDropDownItem(false);
setSelectedRecoilValue([]);
setShowSelectorMenu(false);
setShowAtomMenu(false);
setAtomButtonClicked(false);
setSelectorButtonClicked(false);
};
return (
<div className="AtomComponentVisual">
<svg id="canvas"></svg>
<div id='spacingSliders'>
<div className='sliderContainer'>
<label
className='sliderLabel'
>{vertOrient ? 'H' : 'V'}
</label>
<input
className='siblingSlider'
type='range'
min={1}
max={20}
value={siblingSlider}
onChange={(e) => {
const val = parseInt(e.target.value);
setSiblingSlider(val);
setSiblingSpacingFactor(val / 10);
}}
/>
</div>
<div className='sliderContainer'>
<label
className='sliderLabel'
>{vertOrient ? 'V' : 'H'}
</label>
<input
className='parentChildSlider'
type='range'
min={1}
max={50}
value={parentChildSlider}
onChange={(e) => {
const val = parseInt(e.target.value);
setParentChildSlider(val);
setParentSpacingFactor(val / 10);
}}
/>
</div>
</div>
<div className='graphBtnContainer'>
<button
id='fixedButton'
className='graphBtn'
style={{
color: rawToggle ? '#E6E6E6' : '#989898',
}}
onClick={() => {
setRawToggle(!rawToggle);
dispatch(setDefaultZoom());
}}>
<span>
{rawToggle ? 'Expand' : 'Collapse'}
</span>
</button>
<button
id='orientationBtn'
className='graphBtn'
style={{
color: vertOrient ? '#E6E6E6' : '#989898',
}}
onClick={() => {
setVertOrient(!vertOrient);
}}>
<span>
{vertOrient ? 'Horizontal' : 'Vertical'}
</span>
</button>
</div>
<div className="AtomNetworkLegend">
<div className="AtomLegend" />
<button
onClick={isDropDownItem ? resetNodes : openDropdown}
id="AtomP"
className={
atomButtonClicked ? 'AtomP atomSelected' : 'AtomP atomLegendDefault'
}>
ATOM
</button>
{showAtomMenu && (
<div id="atomDrop" className="AtomDropDown">
{atomList.map((atom, i) => (
<div className="dropDownButtonDiv">
<button
id={`atom-drop${i}`}
className="atom-class atomDropDown"
key={i}
onClick={event => {
if (
!(event.target as HTMLInputElement).classList.contains(
'atomSelected',
) &&
(event.target as HTMLInputElement).classList.contains(
'atomNotSelected',
)
) {
(event.target as HTMLInputElement).classList.replace(
'atomNotSelected',
'atomSelected',
);
} else if (
!(event.target as HTMLInputElement).classList.contains(
'atomSelected',
) &&
!(event.target as HTMLInputElement).classList.contains(
'atomNotSelected',
)
) {
(event.target as HTMLInputElement).classList.add(
'atomSelected',
);
}
document.querySelectorAll('.atom-class').forEach(item => {
if (
item.id !== `atom-drop${i}` &&
item.classList.contains('atomSelected')
) {
item.classList.replace(
'atomSelected',
'atomNotSelected',
);
} else if (
item.id !== `atom-drop${i}` &&
!item.classList.contains('atomNotSelected')
) {
item.classList.add('atomNotSelected');
}
});
setSelectedRecoilValue([atom, 'atom']);
setIsDropDownItem(true);
}}>
{atom}
</button>
</div>
))}
</div>
)}
<div className="SelectorLegend"></div>
<button
onClick={isDropDownItem ? resetNodes : openDropdown}
id="SelectorP"
className={
selectorButtonClicked
? 'SelectorP selectorSelected'
: 'SelectorP selectorLegendDefault'
}>
SELECTOR
</button>
{showSelectorMenu && (
<div id="selectorDrop" className="SelectorDropDown">
{selectorList.map((selector, i) => (
<div className="dropDownButtonDiv">
<button
id={`selector-drop${i}`}
className="selector-class selectorDropDown"
key={i}
onClick={event => {
if (
!(event.target as HTMLInputElement).classList.contains(
'selectorSelected',
) &&
(event.target as HTMLInputElement).classList.contains(
'selectorNotSelected',
)
) {
(event.target as HTMLInputElement).classList.replace(
'selectorNotSelected',
'selectorSelected',
);
} else if (
!(event.target as HTMLInputElement).classList.contains(
'selectorSelected',
) &&
!(event.target as HTMLInputElement).classList.contains(
'selectorNotSelected',
)
) {
(event.target as HTMLInputElement).classList.add(
'selectorSelected',
);
}
document
.querySelectorAll('.selector-class')
.forEach(item => {
if (
item.id !== `selector-drop${i}` &&
item.classList.contains('selectorSelected')
) {
item.classList.replace(
'selectorSelected',
'selectorNotSelected',
);
} else if (
item.id !== `selector-drop${i}` &&
!item.classList.contains('selectorNotSelected')
) {
item.classList.add('selectorNotSelected');
}
});
setSelectedRecoilValue([selector, 'selector']);
setIsDropDownItem(true);
}}>
{selector}
</button>
</div>
))}
</div>
)}
<div className="bothLegend"></div>
<button className="bothLegendDefault">BOTH</button>
<div className={hasSuspense ? 'suspenseLegend' : ''}></div>
<p>{hasSuspense ? 'SUSPENSE' : ''}</p>
<div className="tooltipContainer"></div>
</div>
</div>
);
};
export default AtomComponentVisual;
================================================
FILE: src/app/components/ComponentGraph/__tests__/AtomComponentContainer.unit.test.js
================================================
import React from 'react';
import AtomComponentVisualContainer from '../AtomComponentContainer';
import {cleanup, render} from '@testing-library/react';
afterEach(cleanup);
xit('testing to see if the component is properly rendered', () => {
// This test gets 'TypeError: Cannot read property 'baseVal' of undefined
// because Jest doesn't fully support testing SVGs yet
const atomTree = {children: []};
const {component, debug} = render(
<AtomComponentVisualContainer componentAtomTree={atomTree} />,
);
debug();
});
================================================
FILE: src/app/components/ComponentGraph/__tests__/AtomComponentVisual.unit.test.js
================================================
import React from 'react';
import AtomComponentVisualContainer from '../AtomComponentContainer';
import AtomComponentVisual from '../AtomComponentVisual';
import {render, cleanup} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import '@babel/polyfill';
import {
componentAtomTreeMock,
filteredCurSnapMock,
} from '../../../../../mock/snapshot';
afterEach(cleanup);
xit('testing to see if the component is properly rendered', () => {
// Now that we have the componentClassDiv appended, we get the same svg error as AtomComponentContainer test
const componentClassDiv = document.createElement('div');
componentClassDiv.className = 'Component';
document.body.appendChild(componentClassDiv);
// This test fails because it cannot find the element with class 'Component'
const atomTree = {children: []};
const {component, debug} = render(
// possibly test 'setStr', 'selectedRecoilValue', 'componentAtomTree' props
<AtomComponentVisual
selectors={[]}
atoms={[]}
componentAtomTree={atomTree}
/>,
componentClassDiv,
);
debug();
});
xit('renders & matches snapshot - no props', () => {
const {asFragment} = render(<AtomComponentVisualContainer />);
expect(asFragment()).toMatchSnapshot();
});
xit('renders & matches snapshot - componetAtomTree props', () => {
const {asFragment} = render(
<AtomComponentVisualContainer
filteredSnapshot={filteredCurSnapMock}
componentAtomTree={componentAtomTreeMock}
/>,
);
expect(asFragment()).toMatchSnapshot();
});
================================================
FILE: src/app/components/ComponentGraph/__tests__/AtomSelectorLegend.unit.test.js
================================================
import React from 'react';
import AtomSelectorLegend from '../AtomSelectorLegend';
import {cleanup, render} from '@testing-library/react';
afterEach(cleanup);
it('testing to see if the component is properly rendered', () => {
const {component, debug} = render(
// possibly test 'selectedRecoilValue', 'setSelectedRecoilValue', 'setStr' props
<AtomSelectorLegend selectors={[]} atoms={[]} str={[]} />,
);
});
================================================
FILE: src/app/components/Metrics/ComparisonGraph.tsx
================================================
import React, {useEffect, useRef} from 'react';
import * as d3 from 'd3';
import {dataDurationArr} from '../../../types';
import {useAppSelector} from '../../state-management/hooks';
interface ComparisonGraphProps {
data: dataDurationArr; // an array of object{name:, actualDuration}
width?: number;
height?: number;
}
const ComparisonGraph: React.FC<ComparisonGraphProps> = ({
data,
width,
height,
}: ComparisonGraphProps) => {
const snapshotHistory = useAppSelector(
(state: {snapshot: {snapshotHistory: any}}) =>
state.snapshot.snapshotHistory,
);
console.log('comparison snapshot ', snapshotHistory);
// declare an array that holds 2 objects: past and current
const displayData = [
{name: 'past', duration: 0},
{name: 'current', duration: 0},
];
// retrieve and get total duration for past serie from the local storage
const values: any[] = [];
const keys = Object.keys(localStorage);
let i = keys.length;
while (i--) {
const series = localStorage.getItem(keys[i]);
values.push(JSON.parse(series));
}
for (const element of values) {
displayData[0].duration += element.componentAtomTree.treeBaseDuration;
}
let total = 0;
for (const element of snapshotHistory) {
total += element.componentAtomTree.treeBaseDuration;
}
displayData[1].duration = total;
// delete series in local storage
const deleteSeries = () => {
for (const i of keys) {
localStorage.removeItem(i);
}
};
// svg
const svgRef = useRef();
useEffect(() => {
document.getElementById('canvas').innerHTML = '';
// set the dimensions and margins of the graph
let left = 80;
if (length > 13) {
left = 100;
}
if (length > 17) {
left = 120;
}
const margin = {top: 20, right: 20, bottom: 30, left};
// set range for y scale
const y = d3.scaleBand().range([height, 0]).padding(0.2);
// set range for x scale
const x = d3.scaleLinear().range([0, width * 0.8]);
// set range for durations
const z = d3.scaleBand().range([height, 0]).padding(0.2);
// determines the color based on actualDuration
function colorPicker(data: any) {
if (data <= 1) return '#51a8f0';
else if (data <= 2) return '#3a7bb0';
else return '#2d608a';
}
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
const svg = d3
.select(svgRef.current)
.classed('svg-container', true)
.append('svg')
.attr('class', 'chart')
.attr('viewBox', '-100 0 900 600')
.attr('preserveAspectRatio', 'xMinYMin meet')
.classed('svg-content-responsive', true)
.call(
d3.zoom().on('zoom', function () {
svg.attr('transform', d3.event.transform);
}),
)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Scale the range of the data in the domain
x.domain([
0,
d3.max(displayData, (d: any) => {
return d.duration;
}),
]);
// Scale the range of the data across the y-axis
y.domain(
displayData.map((d: any, i) => {
return d.name + '-' + i;
}),
);
// Scale actualDuration with the y-axis
z.domain(
displayData.map((d: any, i) => {
return d.duration.toFixed(2) + 'ms' + '-' + i;
}),
);
// append the rectangles for the bar chart
svg
.selectAll('.bar')
.data(displayData)
.enter()
.append('rect')
.attr('class', 'bar')
.on('mouseover', function () {
d3.select(this).attr('opacity', '0.85');
const backgroundConnection = chrome.runtime.connect();
const barName = this.data;
const payload = {
action: 'mouseover',
tabId: chrome.devtools.inspectedWindow.tabId,
payload: barName,
};
backgroundConnection.postMessage(payload);
})
// .transition()
// .duration(1300)
// .delay((d: any,i: any) => i * 100)
.attr('width', function (d: any) {
return x(d.duration);
})
.attr('fill', function (d: any) {
return colorPicker(d.duration);
})
.attr('y', function (d: any, i: any) {
return y(d.name + '-' + i);
})
.attr('height', y.bandwidth());
// add x axis
svg
.append('g')
.attr('transform', 'translate(0,' + height + '
gitextract_l6qwon48/ ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── README_KO.md ├── babel.config.js ├── docs/ │ └── pull_request_template.md ├── mock/ │ ├── snapshot.js │ └── state-snapshot.js ├── package/ │ ├── .npmignore │ ├── README.md │ ├── formatFiberNodes.js │ ├── formatFiberNodes.ts │ ├── index.js │ ├── index.ts │ └── package.json ├── package.json ├── src/ │ ├── README.md │ ├── app/ │ │ ├── Containers/ │ │ │ ├── ButtonsContainer.tsx │ │ │ ├── MainContainer.tsx │ │ │ ├── SnapshotContainer.tsx │ │ │ ├── TravelContainer.tsx │ │ │ ├── VisualContainer.tsx │ │ │ └── __tests__/ │ │ │ ├── MainContainer.unit.test.js │ │ │ ├── SnapshotContainer.unit.test.js │ │ │ └── VisualContainer.unit.test.js │ │ ├── components/ │ │ │ ├── App.tsx │ │ │ ├── AtomNetwork/ │ │ │ │ ├── AtomNetwork.tsx │ │ │ │ ├── AtomNetworkLegend.tsx │ │ │ │ ├── AtomNetworkVisual.tsx │ │ │ │ └── __tests__/ │ │ │ │ ├── Network.unit.test.js │ │ │ │ └── __snapshots__/ │ │ │ │ └── Network.unit.test.js.snap │ │ │ ├── ComponentGraph/ │ │ │ │ ├── AtomComponentContainer.tsx │ │ │ │ ├── AtomComponentVisual.tsx │ │ │ │ └── __tests__/ │ │ │ │ ├── AtomComponentContainer.unit.test.js │ │ │ │ ├── AtomComponentVisual.unit.test.js │ │ │ │ └── AtomSelectorLegend.unit.test.js │ │ │ ├── Metrics/ │ │ │ │ ├── ComparisonGraph.tsx │ │ │ │ ├── FlameGraph.js │ │ │ │ ├── MetricsContainer.tsx │ │ │ │ ├── RankedGraph.tsx │ │ │ │ └── __tests__/ │ │ │ │ ├── IcicleVerticle.unit.test.js │ │ │ │ ├── Metrics.unit.test.js │ │ │ │ └── Visualizer.unit.test.js │ │ │ ├── NavBar/ │ │ │ │ ├── NavBar.tsx │ │ │ │ └── __test__/ │ │ │ │ └── Navbar.unit.test.js │ │ │ ├── Settings/ │ │ │ │ ├── AtomSettings.tsx │ │ │ │ ├── SettingsContainer.tsx │ │ │ │ ├── ThrottleSettings.tsx │ │ │ │ └── __tests__/ │ │ │ │ ├── AtomSettings.unit.test.js │ │ │ │ ├── StateSettings.unit.test.js │ │ │ │ ├── ThrottleSettings.unit.test.js │ │ │ │ └── __snapshots__/ │ │ │ │ ├── StateSettings.unit.test.js.snap │ │ │ │ └── ThrottleSettings.unit.test.js.snap │ │ │ ├── Slider/ │ │ │ │ └── MainSlider.tsx │ │ │ ├── SnapshotList/ │ │ │ │ └── __tests__/ │ │ │ │ └── SnapshotList.unit.test.js │ │ │ ├── StateDiff/ │ │ │ │ ├── Diff.tsx │ │ │ │ └── __tests__/ │ │ │ │ ├── StateDiff.unit.test.js │ │ │ │ └── __snapshots__/ │ │ │ │ └── StateDiff.unit.test.js.snap │ │ │ ├── StateTree/ │ │ │ │ ├── Tree.tsx │ │ │ │ └── __tests__/ │ │ │ │ └── Tree.unit.test.js │ │ │ └── Testing/ │ │ │ ├── CodeResults.js │ │ │ ├── Editor.js │ │ │ ├── SelectorsButton.tsx │ │ │ ├── TestingContainer.tsx │ │ │ ├── displayTests.tsx │ │ │ ├── dummySelector.js │ │ │ └── testing.css │ │ ├── index.tsx │ │ ├── state-management/ │ │ │ ├── __tests__/ │ │ │ │ └── slices.test.tsx │ │ │ ├── hooks.tsx │ │ │ ├── index.tsx │ │ │ └── slices/ │ │ │ ├── AtomNetworkSlice.tsx │ │ │ ├── AtomsAndSelectorsSlice.tsx │ │ │ ├── FilterSlice.tsx │ │ │ ├── SelectedSlice.tsx │ │ │ ├── SnapshotSlice.tsx │ │ │ ├── ThrottleSlice.tsx │ │ │ └── ZoomSlice.tsx │ │ └── utils/ │ │ ├── cleanComponentAtomTree.ts │ │ ├── makeRelationshipLinks.ts │ │ └── makeTreeConversion.ts │ ├── extension/ │ │ ├── background.ts │ │ ├── build/ │ │ │ ├── devtools.html │ │ │ ├── devtools.js │ │ │ ├── diff.css │ │ │ ├── manifest.json │ │ │ ├── panel.html │ │ │ └── stylesheet.css │ │ └── contentScript.ts │ └── types/ │ └── index.d.ts ├── tsconfig.json └── webpack.config.js
SYMBOL INDEX (50 symbols across 24 files)
FILE: package/formatFiberNodes.ts
type node (line 4) | type node = {
type formattedNode (line 15) | type formattedNode = {
FILE: package/index.js
function RecoilizeDebugger (line 26) | function RecoilizeDebugger(props) {
function formatRecoilizeSelectors (line 271) | function formatRecoilizeSelectors(...selectors){
FILE: package/index.ts
function RecoilizeDebugger (line 12) | function RecoilizeDebugger(props: any) {
FILE: src/app/Containers/SnapshotContainer.tsx
function prevClr (line 124) | function prevClr() {
function fwrdClr (line 135) | function fwrdClr() {
FILE: src/app/Containers/VisualContainer.tsx
type navTypes (line 12) | type navTypes = {
FILE: src/app/components/App.tsx
constant LOGO_URL (line 22) | const LOGO_URL = './assets/Recoilize-v2.png';
FILE: src/app/components/AtomNetwork/AtomNetworkLegend.tsx
function openDropdown (line 40) | function openDropdown(e: React.MouseEvent) {
FILE: src/app/components/AtomNetwork/AtomNetworkVisual.tsx
function zoomActions (line 146) | function zoomActions() {
FILE: src/app/components/ComponentGraph/AtomComponentVisual.tsx
type AtomComponentVisualProps (line 11) | interface AtomComponentVisualProps {
function zoomed (line 141) | function zoomed() {
function update (line 157) | function update(source: any) {
function openDropdown (line 478) | function openDropdown(e: React.MouseEvent) {
FILE: src/app/components/Metrics/ComparisonGraph.tsx
type ComparisonGraphProps (line 6) | interface ComparisonGraphProps {
function colorPicker (line 73) | function colorPicker(data: any) {
FILE: src/app/components/Metrics/RankedGraph.tsx
type RankedGraphProps (line 5) | interface RankedGraphProps {
function colorPicker (line 36) | function colorPicker(data: any) {
FILE: src/app/components/NavBar/NavBar.tsx
type NavBarProps (line 3) | interface NavBarProps {
FILE: src/app/components/Slider/MainSlider.tsx
type handleProps (line 10) | interface handleProps {
function MainSlider (line 44) | function MainSlider() {
FILE: src/app/components/Testing/Editor.js
function handleRunCodeClick (line 39) | function handleRunCodeClick () {
function handleChange (line 53) | function handleChange (editor, data, value) {
FILE: src/app/components/Testing/displayTests.tsx
function handleToBeChange (line 15) | function handleToBeChange(e) {
function handleParameterChange (line 19) | function handleParameterChange(e) {
FILE: src/app/state-management/index.tsx
type RootState (line 41) | type RootState = ReturnType<typeof store.getState>;
type AppDispatch (line 42) | type AppDispatch = typeof store.dispatch;
FILE: src/app/state-management/slices/AtomsAndSelectorsSlice.tsx
type selectedState (line 4) | interface selectedState {
FILE: src/app/state-management/slices/FilterSlice.tsx
type FilterState (line 4) | interface FilterState {
FILE: src/app/state-management/slices/SelectedSlice.tsx
type selectedState (line 4) | interface selectedState {
FILE: src/app/state-management/slices/ZoomSlice.tsx
type ZoomState (line 4) | interface ZoomState {
FILE: src/app/utils/makeRelationshipLinks.ts
type relationships (line 1) | type relationships = {
type caches (line 6) | type caches = {
FILE: src/app/utils/makeTreeConversion.ts
type makeTreeObj (line 1) | type makeTreeObj = {
FILE: src/extension/background.ts
type Msg (line 4) | interface Msg {
type Connections (line 11) | interface Connections {
FILE: src/types/index.d.ts
type stateSnapshot (line 2) | type stateSnapshot = {
type stateSnapshotDiff (line 9) | type stateSnapshotDiff = {
type filteredSnapshot (line 15) | type filteredSnapshot = {
type filteredSnapshotDiff (line 20) | type filteredSnapshotDiff = {
type node (line 25) | type node = {
type nodeDiff (line 36) | type nodeDiff = {
type componentAtomTree (line 43) | type componentAtomTree = {
type componentAtomTreeDiff (line 53) | type componentAtomTreeDiff = {
type dataDuration (line 63) | type dataDuration = {
type dataDurationArr (line 67) | type dataDurationArr = dataDuration[];
type atom (line 76) | type atom = {
type selector (line 87) | type selector = {
type selectedTypes (line 91) | type selectedTypes = {
Condensed preview — 94 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (331K chars).
[
{
"path": ".eslintrc.js",
"chars": 557,
"preview": "module.exports = {\n env: {\n browser: true,\n es2020: true,\n },\n extends: [\n 'plugin:react/recommended',\n '"
},
{
"path": ".gitignore",
"chars": 99,
"preview": "node_modules\npackage-lock.json\n.vscode\nsrc/extension/build/bundles\n.DS_Store\n\npackage/node_modules/"
},
{
"path": ".prettierrc",
"chars": 279,
"preview": "{\n \"arrowParens\": \"avoid\",\n \"singleQuote\": true,\n \"trailingComma\": \"all\",\n \"bracketSpacing\": false,\n \"jsxBracketSam"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2020 OSLabs Beta\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 12554,
"preview": "<meta name='keywords' content='Recoil, Recoil.js, Recoil Dev Tool, Recoilize, Chrome Dev Tool, Recoil Chrome'>\n\n<p align"
},
{
"path": "README_KO.md",
"chars": 8199,
"preview": "<meta name='keywords' content='Recoil, Recoil.js, Recoil Dev Tool, Recoilize, Chrome Dev Tool, Recoil Chrome'>\n\n<p align"
},
{
"path": "babel.config.js",
"chars": 180,
"preview": "module.exports = {\n presets: [\n '@babel/preset-env',\n '@babel/preset-react',\n '@babel/preset-typescript',\n ],"
},
{
"path": "docs/pull_request_template.md",
"chars": 1050,
"preview": "## Types of changes\n<!--- What types of changes does your code introduce to Scratch Project? Put an `x` in the boxes tha"
},
{
"path": "mock/snapshot.js",
"chars": 2740,
"preview": "export const filteredCurSnapMock = {\n dummyAtom1: {\n contents: {hello: [], hi: []},\n nodeDeps: [],\n nodeToNode"
},
{
"path": "mock/state-snapshot.js",
"chars": 52411,
"preview": "export const snapshotHistoryMock = {\n snapshotHistory: [\n {\n componentAtomTree: {\n actualDuration: 1.675"
},
{
"path": "package/.npmignore",
"chars": 59,
"preview": "__tests__\n./*.ts\nformatFiberNodes.ts\nindex.ts\nnode_modules/"
},
{
"path": "package/README.md",
"chars": 10291,
"preview": "<meta name='keywords' content='Recoil, Recoil.js, Recoil Dev Tool, Recoilize, Chrome Dev Tool, Recoil Chrome'>\n\n<h1>Debu"
},
{
"path": "package/formatFiberNodes.js",
"chars": 3483,
"preview": "// node parameter should be root of the fiber node tree, can be grapped with startNode from below\n// const startNode = d"
},
{
"path": "package/formatFiberNodes.ts",
"chars": 4013,
"preview": "// node parameter should be root of the fiber node tree, can be grapped with startNode from below\n// const startNode = d"
},
{
"path": "package/index.js",
"chars": 10250,
"preview": "import React, {useState, useEffect} from 'react';\nimport {\n useRecoilTransactionObserver_UNSTABLE,\n useRecoilSnapshot,"
},
{
"path": "package/index.ts",
"chars": 4967,
"preview": "import {useState, useEffect} from 'react';\nimport {\n useRecoilTransactionObserver_UNSTABLE,\n useRecoilSnapshot,\n useG"
},
{
"path": "package/package.json",
"chars": 796,
"preview": "{\n \"name\": \"recoilize\",\n \"version\": \"1.0.0\",\n \"description\": \"Recoil Dev Tool\",\n \"main\": \"index.js\",\n \"repository\":"
},
{
"path": "package.json",
"chars": 3341,
"preview": "{\n \"name\": \"recoilize\",\n \"jest\": {\n \"setupFiles\": [\n \"jest-webextension-mock\"\n ]\n },\n \"version\": \"3.0.0\","
},
{
"path": "src/README.md",
"chars": 3317,
"preview": "# Developer README\n\n## Brief\nThough Recoil.js is still in the experimental state, it has proved its ability and catched "
},
{
"path": "src/app/Containers/ButtonsContainer.tsx",
"chars": 442,
"preview": "import React from 'react';\n\nconst ButtonsContainer = () => {\n const howToUseHandler = () => {\n window.open('https://"
},
{
"path": "src/app/Containers/MainContainer.tsx",
"chars": 398,
"preview": "import React from 'react';\nimport SnapshotsContainer from './SnapshotContainer';\nimport VisualContainer from './VisualCo"
},
{
"path": "src/app/Containers/SnapshotContainer.tsx",
"chars": 6534,
"preview": "import React, {useEffect, useState, useRef} from 'react';\nimport {selectFilterState} from '../state-management/slices/Fi"
},
{
"path": "src/app/Containers/TravelContainer.tsx",
"chars": 329,
"preview": "import React from 'react';\nimport MainSlider from '../components/Slider/MainSlider';\nimport ButtonsContainer from './But"
},
{
"path": "src/app/Containers/VisualContainer.tsx",
"chars": 1926,
"preview": "import React, {useState} from 'react';\nimport {RecoilRoot} from 'recoil';\nimport Diff from '../components/StateDiff/Diff"
},
{
"path": "src/app/Containers/__tests__/MainContainer.unit.test.js",
"chars": 447,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/Containers/__tests__/SnapshotContainer.unit.test.js",
"chars": 463,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/Containers/__tests__/VisualContainer.unit.test.js",
"chars": 455,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/components/App.tsx",
"chars": 6021,
"preview": "import React, {useEffect} from 'react';\nimport MainContainer from '../Containers/MainContainer';\nimport {selectedTypes} "
},
{
"path": "src/app/components/AtomNetwork/AtomNetwork.tsx",
"chars": 404,
"preview": "import React from 'react';\nimport AtomNetworkLegend from './AtomNetworkLegend';\nimport AtomNetworkVisual from './AtomNet"
},
{
"path": "src/app/components/AtomNetwork/AtomNetworkLegend.tsx",
"chars": 9308,
"preview": "import React, {useState} from 'react';\nimport {useAppSelector, useAppDispatch} from '../../state-management/hooks';\nimpo"
},
{
"path": "src/app/components/AtomNetwork/AtomNetworkVisual.tsx",
"chars": 6018,
"preview": "import React, {useState, useEffect} from 'react';\nimport * as d3 from 'd3';\nimport makeRelationshipLinks from '../../uti"
},
{
"path": "src/app/components/AtomNetwork/__tests__/Network.unit.test.js",
"chars": 944,
"preview": "import React from 'react';\nimport Network from '../AtomNetwork';\nimport {render, cleanup} from '@testing-library/react';"
},
{
"path": "src/app/components/AtomNetwork/__tests__/__snapshots__/Network.unit.test.js.snap",
"chars": 7614,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`if empty object props is passed into Network 1`] = `\n<DocumentFragm"
},
{
"path": "src/app/components/ComponentGraph/AtomComponentContainer.tsx",
"chars": 2162,
"preview": "import React, {useState} from 'react';\r\nimport AtomComponentVisual from './AtomComponentVisual';\r\nimport {filteredSnapsh"
},
{
"path": "src/app/components/ComponentGraph/AtomComponentVisual.tsx",
"chars": 24325,
"preview": "import React, {useState, useEffect} from 'react';\nimport * as d3 from 'd3';\nimport {componentAtomTree, atom, selector} f"
},
{
"path": "src/app/components/ComponentGraph/__tests__/AtomComponentContainer.unit.test.js",
"chars": 535,
"preview": "import React from 'react';\nimport AtomComponentVisualContainer from '../AtomComponentContainer';\nimport {cleanup, render"
},
{
"path": "src/app/components/ComponentGraph/__tests__/AtomComponentVisual.unit.test.js",
"chars": 1566,
"preview": "import React from 'react';\nimport AtomComponentVisualContainer from '../AtomComponentContainer';\nimport AtomComponentVis"
},
{
"path": "src/app/components/ComponentGraph/__tests__/AtomSelectorLegend.unit.test.js",
"chars": 421,
"preview": "import React from 'react';\nimport AtomSelectorLegend from '../AtomSelectorLegend';\nimport {cleanup, render} from '@testi"
},
{
"path": "src/app/components/Metrics/ComparisonGraph.tsx",
"chars": 5251,
"preview": "import React, {useEffect, useRef} from 'react';\nimport * as d3 from 'd3';\nimport {dataDurationArr} from '../../../types'"
},
{
"path": "src/app/components/Metrics/FlameGraph.js",
"chars": 5670,
"preview": "import React, {useState, useRef} from 'react';\nimport {scaleLinear} from 'd3-scale';\nimport {interpolate} from 'd3-inter"
},
{
"path": "src/app/components/Metrics/MetricsContainer.tsx",
"chars": 3590,
"preview": "import React, {useState} from 'react';\nimport {ParentSize} from '@vx/responsive';\nimport {dataDurationArr} from '../../."
},
{
"path": "src/app/components/Metrics/RankedGraph.tsx",
"chars": 3979,
"preview": "import React, {useEffect, useRef} from 'react';\nimport * as d3 from 'd3';\nimport {dataDurationArr} from '../../../types'"
},
{
"path": "src/app/components/Metrics/__tests__/IcicleVerticle.unit.test.js",
"chars": 2,
"preview": "\r\n"
},
{
"path": "src/app/components/Metrics/__tests__/Metrics.unit.test.js",
"chars": 1072,
"preview": "import React from 'react';\nimport { render, cleanup, findByTestId } from '@testing-library/react';\nimport { componentAto"
},
{
"path": "src/app/components/Metrics/__tests__/Visualizer.unit.test.js",
"chars": 1797,
"preview": "import React from 'react';\nimport RankedGraph from '../RankedGraph';\nimport {\n filteredCurSnapMock,\n componentAtomTree"
},
{
"path": "src/app/components/NavBar/NavBar.tsx",
"chars": 1007,
"preview": "import React from 'react';\n\ninterface NavBarProps {\n // setState functionality to update tab\n setTab: React.Dispatch<R"
},
{
"path": "src/app/components/NavBar/__test__/Navbar.unit.test.js",
"chars": 506,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/components/Settings/AtomSettings.tsx",
"chars": 2021,
"preview": "import React, {useEffect} from 'react';\nconst {Multiselect} = require('multiselect-react-dropdown');\nimport {selectedTyp"
},
{
"path": "src/app/components/Settings/SettingsContainer.tsx",
"chars": 416,
"preview": "import React from 'react';\n\n// Importing various settings components\nimport ThrottleSettings from './ThrottleSettings';\n"
},
{
"path": "src/app/components/Settings/ThrottleSettings.tsx",
"chars": 1982,
"preview": "import React, {useState, useEffect} from 'react';\nimport {useAppSelector, useAppDispatch} from '../../state-management/h"
},
{
"path": "src/app/components/Settings/__tests__/AtomSettings.unit.test.js",
"chars": 1058,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/components/Settings/__tests__/StateSettings.unit.test.js",
"chars": 1893,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport StateSettings from '../StateSettings';\n\nimport {rend"
},
{
"path": "src/app/components/Settings/__tests__/ThrottleSettings.unit.test.js",
"chars": 2336,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/components/Settings/__tests__/__snapshots__/StateSettings.unit.test.js.snap",
"chars": 1288,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should match snapshot when no props are passed in 1`] = `\n<Document"
},
{
"path": "src/app/components/Settings/__tests__/__snapshots__/ThrottleSettings.unit.test.js.snap",
"chars": 720,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Throttle Snapshots Testing renders & matches snapshots 1`] = `\n<Doc"
},
{
"path": "src/app/components/Slider/MainSlider.tsx",
"chars": 3759,
"preview": "import React from 'react';\nimport Slider from 'rc-slider';\nimport Tooltip from 'rc-tooltip';\nimport {useAppSelector, use"
},
{
"path": "src/app/components/SnapshotList/__tests__/SnapshotList.unit.test.js",
"chars": 3214,
"preview": "import React, {useState} from 'react';\nimport {configure, shallow} from 'enzyme';\nimport Adapter from 'enzyme-adapter-re"
},
{
"path": "src/app/components/StateDiff/Diff.tsx",
"chars": 1760,
"preview": "import React, {useState} from 'react';\nimport {diff, formatters} from 'jsondiffpatch';\nimport ReactHtmlParser from 'reac"
},
{
"path": "src/app/components/StateDiff/__tests__/StateDiff.unit.test.js",
"chars": 1432,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport Diff from '../Diff';\n\nimport {\n render,\n cleanup,\n"
},
{
"path": "src/app/components/StateDiff/__tests__/__snapshots__/StateDiff.unit.test.js.snap",
"chars": 7121,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should match snapshot when no props are passed in 1`] = `\n<Document"
},
{
"path": "src/app/components/StateTree/Tree.tsx",
"chars": 967,
"preview": "import React from 'react';\nimport JSONTree from 'react-json-tree';\nimport {useAppSelector} from '../../state-management/"
},
{
"path": "src/app/components/StateTree/__tests__/Tree.unit.test.js",
"chars": 348,
"preview": "import React, {useState} from 'react';\nimport ReactDOM from 'react-dom';\nimport {getQueriesForElement, getByText} from '"
},
{
"path": "src/app/components/Testing/CodeResults.js",
"chars": 246,
"preview": "/* eslint-disable prettier/prettier */\nimport React from 'react';\n\nconst CodeResults = props => {\nconst { evaluatedCode "
},
{
"path": "src/app/components/Testing/Editor.js",
"chars": 2708,
"preview": "/* eslint-disable prettier/prettier */\nimport React from 'react';\nimport {useState} from 'react';\n//import our css styli"
},
{
"path": "src/app/components/Testing/SelectorsButton.tsx",
"chars": 4930,
"preview": "/* eslint-disable prettier/prettier */\nimport React, {useState, useEffect} from 'react';\nimport DisplayTests from './dis"
},
{
"path": "src/app/components/Testing/TestingContainer.tsx",
"chars": 5919,
"preview": "/* eslint-disable prettier/prettier */\nimport React, { useState, useEffect } from 'react';\n\nimport Editor from './Editor"
},
{
"path": "src/app/components/Testing/displayTests.tsx",
"chars": 1512,
"preview": "/* eslint-disable prettier/prettier */\nimport React, {useState, useEffect} from 'react';\nimport {useRecoilState, useReco"
},
{
"path": "src/app/components/Testing/dummySelector.js",
"chars": 256,
"preview": "import {atom, selector} from 'recoil';\n\nexport const dummySelector = selector({\n key: 'dummySelector',\n get: ({ get })"
},
{
"path": "src/app/components/Testing/testing.css",
"chars": 368,
"preview": ".testing-container {\n display: flex;\n flex-direction: column;\n margin: 5px;\n height: 100%;\n width: 100%;\n}\n\n.code-m"
},
{
"path": "src/app/index.tsx",
"chars": 441,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './components/App';\nimport {store, persisto"
},
{
"path": "src/app/state-management/__tests__/slices.test.tsx",
"chars": 3799,
"preview": "import {setSearchValue} from '../slices/AtomNetworkSlice';\nimport {updateFilter} from '../slices/FilterSlice';\nimport {s"
},
{
"path": "src/app/state-management/hooks.tsx",
"chars": 270,
"preview": "import {TypedUseSelectorHook, useDispatch, useSelector} from 'react-redux';\nimport type {RootState, AppDispatch} from '."
},
{
"path": "src/app/state-management/index.tsx",
"chars": 1418,
"preview": "import {configureStore, getDefaultMiddleware} from '@reduxjs/toolkit';\nimport throttleReducer from './slices/ThrottleSli"
},
{
"path": "src/app/state-management/slices/AtomNetworkSlice.tsx",
"chars": 435,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\n\nconst initialState: any = {\n searchValue: '',\n};\n\nexport "
},
{
"path": "src/app/state-management/slices/AtomsAndSelectorsSlice.tsx",
"chars": 1039,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport {RootState} from '../index';\n\ninterface selectedStat"
},
{
"path": "src/app/state-management/slices/FilterSlice.tsx",
"chars": 640,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport {RootState} from '../index';\n\ninterface FilterState "
},
{
"path": "src/app/state-management/slices/SelectedSlice.tsx",
"chars": 784,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport {selectedTypes} from '../../../types';\n\ninterface se"
},
{
"path": "src/app/state-management/slices/SnapshotSlice.tsx",
"chars": 1004,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport generateCleanComponentAtomTree from '../../utils/cle"
},
{
"path": "src/app/state-management/slices/ThrottleSlice.tsx",
"chars": 509,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\n\nconst initialState: any = {\n throttleValue: '70',\n};\n\nexp"
},
{
"path": "src/app/state-management/slices/ZoomSlice.tsx",
"chars": 839,
"preview": "import {createSlice, PayloadAction} from '@reduxjs/toolkit';\nimport {RootState} from '../index';\n\ninterface ZoomState {\n"
},
{
"path": "src/app/utils/cleanComponentAtomTree.ts",
"chars": 1870,
"preview": "import {componentAtomTree} from '../../types';\n\nconst generateCleanComponentAtomTree = (\n inputObj: componentAtomTree"
},
{
"path": "src/app/utils/makeRelationshipLinks.ts",
"chars": 2133,
"preview": "type relationships = {\n nodes: any[];\n links: any[];\n};\n\ntype caches = {\n [key: string]: any;\n};\n\nconst makeRelations"
},
{
"path": "src/app/utils/makeTreeConversion.ts",
"chars": 1002,
"preview": "type makeTreeObj = {\n [name: string]: any;\n};\n\nconst makeTree = (obj: any) => {\n // function that parses and refactors"
},
{
"path": "src/extension/background.ts",
"chars": 6468,
"preview": "// TO CONSOLE LOG IN THIS FILE, we need to go to chrome://extensions/ and click inspect on the actual devTOOL!\n\n// Messa"
},
{
"path": "src/extension/build/devtools.html",
"chars": 385,
"preview": "<!-- The startup page which will link to the startup Javascript. -->\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta"
},
{
"path": "src/extension/build/devtools.js",
"chars": 253,
"preview": "// The initial script that loads the extension into the DevTools panel.\nchrome.devtools.panels.create(\n 'Recoilize', //"
},
{
"path": "src/extension/build/diff.css",
"chars": 3974,
"preview": ".jsondiffpatch-delta {\n font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;\n font"
},
{
"path": "src/extension/build/manifest.json",
"chars": 704,
"preview": "{\n \"name\": \"Recoilize_Testing\",\n \"version\": \"3.0.0\",\n \"devtools_page\": \"devtools.html\",\n \"description\": \"A Chrome ex"
},
{
"path": "src/extension/build/panel.html",
"chars": 483,
"preview": "<!-- The HTML displayed in the DevTools panel. -->\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <"
},
{
"path": "src/extension/build/stylesheet.css",
"chars": 17859,
"preview": "/* GLOBAL CSS */\nhtml,\nbody {\n color: #989898;\n margin: 0;\n height: 100%;\n background-color: #212121;\n}\n::-webkit-sc"
},
{
"path": "src/extension/contentScript.ts",
"chars": 812,
"preview": "// once chrome tab connects with our content-script\nwindow.postMessage({action: 'contentScriptStarted'}, '*');\n//console"
},
{
"path": "src/types/index.d.ts",
"chars": 2415,
"preview": "// snapshot taken by recoilize module\nexport type stateSnapshot = {\n filteredSnapshot: filteredSnapshot;\n componentAto"
},
{
"path": "tsconfig.json",
"chars": 410,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"./build\",\n \"module\": \"commonjs\",\n \"pretty\": true,\n \"noImplicitAny\": fal"
},
{
"path": "webpack.config.js",
"chars": 1392,
"preview": "const path = require('path');\nconst ChromeExtensionReloader = require('webpack-chrome-extension-reloader');\n\nconst confi"
}
]
About this extraction
This page contains the full source code of the oslabs-beta/Recoilize GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 94 files (303.2 KB), approximately 74.7k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.