Skip to content

Commit 41024f0

Browse files
authored
Merge pull request #35 from oslabs-beta/feature/garrett
Feature/garrett
2 parents d5c667c + 3fd54d8 commit 41024f0

File tree

7 files changed

+274
-16
lines changed

7 files changed

+274
-16
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"Cole Styron",
5757
"Daljit Gill",
5858
"Dane Corpion",
59+
"Daniel Ryczek",
5960
"David Bernstein",
6061
"David Chai",
6162
"David Kim",
@@ -64,10 +65,12 @@
6465
"Edar Liu",
6566
"Edwin Menendez",
6667
"Eivind Del Fierro",
68+
"Ellie Simens",
6769
"Ergi Shehu",
6870
"Eric Yun",
6971
"Freya Wu",
7072
"Gabriela Jardim Aquino",
73+
"Garrett Chow",
7174
"Gregory Panciera",
7275
"Haejin Jo",
7376
"Harry Fox",
@@ -104,9 +107,11 @@
104107
"Nathan Richardson",
105108
"Ngoc Zwolinski",
106109
"Nick Huemmer",
110+
"Patrice Pinardo",
107111
"Peter Lam",
108112
"Prasanna Malla",
109113
"Quan Le",
114+
"Ragad Mohammed",
110115
"Rajeeb Banstola",
111116
"Raymond Kwan",
112117
"Robby Tipton",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
3+
class ErrorHandler extends React.Component {
4+
constructor(props: unknown) {
5+
super(props);
6+
this.state = { errorOccurred: false };
7+
}
8+
9+
componentDidCatch(): void {
10+
this.setState({ errorOccurred: true });
11+
}
12+
13+
render(): JSX.Element {
14+
const { errorOccurred } = this.state;
15+
// eslint-disable-next-line react/prop-types
16+
const { children } = this.props;
17+
return errorOccurred ? <div>Unexpected Error</div> : children;
18+
}
19+
}
20+
21+
export default ErrorHandler;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* eslint-disable react/prop-types */
2+
import React from 'react';
3+
4+
/*
5+
This file determines what text will be displayed to the user if any one of the following fail to load:
6+
7+
1. if the content script has been launched on the current tab
8+
2. if React Dev Tools has been installed
9+
3. if target tab contains a compatible React app
10+
11+
*/
12+
13+
// parses loadingArray and status and returns the correct message
14+
function parseError(loadingArray: [], status: Record<string, unknown>): string {
15+
let stillLoading = true;
16+
loadingArray.forEach((e) => {
17+
if (e === false) stillLoading = false;
18+
});
19+
20+
if (stillLoading) return 'default'; // As long as everything is still loading dont diplay an error message
21+
22+
// If we're done loading everything, return the first status that fails
23+
if (!status.contentScriptLaunched) return 'Content Script Error';
24+
if (!status.reactDevToolsInstalled) return 'RDT Error';
25+
if (!status.targetPageisaReactApp) return 'Not React App';
26+
return 'default';
27+
}
28+
29+
function ErrorMsg({ loadingArray, status, launchContent, reinitialize }): JSX.Element {
30+
switch (
31+
parseError(loadingArray, status) // parseError returns a string based on the loadingArray and status. The returned string is matched to a case so that an appropriate error message will be displayed to the user
32+
) {
33+
case 'Content Script Error':
34+
return (
35+
<div>
36+
Target App not yet found...
37+
<br />
38+
If you encounter this error on the initial launch of Reactime, try refreshing the webpage
39+
you are developing.
40+
<br />
41+
<br />
42+
If Reactime is running as an iframe in your developer tools, right click on the Reactime
43+
application and click 'Reload Frame'
44+
<br />
45+
<br />
46+
NOTE: By default Reactime only launches the content script on URLS starting with
47+
localhost.
48+
<br />
49+
If your target URL does not match, you can manually launch the content script below.
50+
<br />
51+
<br />
52+
<button type='button' className='launchContentButton' onClick={launchContent}>
53+
{' '}
54+
Launch{' '}
55+
</button>
56+
</div>
57+
);
58+
case 'RDT Error':
59+
return (
60+
<div>
61+
React Dev Tools is not installed!
62+
<br />
63+
<br />
64+
If you encounter this error on the initial launch of Reactime, refresh the webpage you are
65+
developing.
66+
<br />
67+
If Reactime is running as an iframe in your developer tools, right click on the Reactime
68+
application and click 'Reload Frame'
69+
<br />
70+
<br />
71+
<a
72+
href='https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en'
73+
target='_blank'
74+
rel='noopener noreferrer'
75+
>
76+
NOTE: The React Developer Tools extension is required for Reactime to run, if you do not
77+
already have it installed on your browser.
78+
</a>
79+
</div>
80+
);
81+
case 'Not React App':
82+
return (
83+
<div>
84+
The Target app is either not a React application or is not compatible with Reactime.
85+
</div>
86+
);
87+
default:
88+
return null;
89+
}
90+
}
91+
92+
export default ErrorMsg;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// /* eslint-disable react/prop-types */
2+
3+
import React from 'react';
4+
import { ClipLoader } from 'react-spinners';
5+
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
6+
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
7+
8+
/*
9+
This file is what decides what icon (loading, checkmark, exclamation point) is displayed next to the checks in the ErrorContainer loading screen:
10+
11+
1. if the content script has been launched on the current tab
12+
2. if React Dev Tools has been installed
13+
3. if target tab contains a compatible React app
14+
*/
15+
16+
const handleResult = (result: boolean): JSX.Element =>
17+
result ? (
18+
<CheckCircleOutlineIcon className='check' /> // if result boolean is true, we display a checkmark icon
19+
) : (
20+
<ErrorOutlineIcon className='fail' /> // if the result boolean is false, we display a fail icon
21+
);
22+
23+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
24+
// Returns the 'Loader' component
25+
const Loader = ({ loading, result }): JSX.Element =>
26+
loading ? (
27+
<ClipLoader color='#123abc' size={30} loading={loading} /> // if the loadingArray value is true, we display a loading icon
28+
) : (
29+
handleResult(result) // else we display a component produced by 'handleResult' depending on if the result parameter (which takes an argument from the status object in 'ErrorContainer') is true or false
30+
);
31+
32+
export default Loader;

src/app/containers/ErrorContainer.tsx

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,112 @@
1-
import React from 'react';
2-
import { useDispatch, useSelector } from 'react-redux';
1+
/* eslint-disable max-len */
2+
import React, { useState, useEffect, useRef } from 'react';
33
import { launchContentScript } from '../slices/mainSlice';
4+
import Loader from '../components/ErrorHandling/Loader';
5+
import ErrorMsg from '../components/ErrorHandling/ErrorMsg';
6+
import { useDispatch, useSelector } from 'react-redux';
47
import { MainState, RootState, ErrorContainerProps } from '../FrontendTypes';
8+
import { current } from '@reduxjs/toolkit';
59
import { RefreshCw, Github, PlayCircle } from 'lucide-react';
610

11+
/*
12+
This is the loading screen that a user may get when first initalizing the application. This page checks:
13+
14+
1. if the content script has been launched on the current tab
15+
2. if React Dev Tools has been installed
16+
3. if target tab contains a compatible React app
17+
*/
18+
719
function ErrorContainer(props: ErrorContainerProps): JSX.Element {
820
const dispatch = useDispatch();
921
const { tabs, currentTitle, currentTab }: MainState = useSelector(
1022
(state: RootState) => state.main,
1123
);
24+
const [loadingArray, setLoading] = useState([true, true, true]); // We create a local state "loadingArray" and set it to an array with three true elements. These will be used as hooks for error checking against a 'status' object that is declared later in a few lines. 'loadingArray' is used later in the return statement to display a spinning loader icon if it's true. If it's false, either a checkmark icon or an exclamation icon will be displayed to the user.
25+
const titleTracker = useRef(currentTitle); // useRef returns an object with a property 'initialValue' and a value of whatever was passed in. This allows us to reference a value that's not needed for rendering
26+
const timeout = useRef(null);
27+
const { port } = props;
1228

13-
// function that launches the main app and refreshes the page
29+
// function that launches the main app
1430
function launch(): void {
1531
dispatch(launchContentScript(tabs[currentTab]));
16-
// Allow the dispatch to complete before refreshing
17-
setTimeout(() => {
18-
chrome.tabs.reload(currentTab);
19-
}, 100);
2032
}
2133

34+
function reinitialize(): void {
35+
port.postMessage({
36+
action: 'reinitialize',
37+
tabId: currentTab,
38+
});
39+
}
40+
41+
let status = {
42+
// We create a status object that we may use later if tabs[currentTab] exists
43+
contentScriptLaunched: false,
44+
reactDevToolsInstalled: false,
45+
targetPageisaReactApp: false,
46+
};
47+
48+
if (tabs[currentTab]) {
49+
// If we do have a tabs[currentTab] object, we replace the status obj we declared above with the properties of the tabs[currentTab].status
50+
Object.assign(status, tabs[currentTab].status);
51+
}
52+
53+
// hook that sets timer while waiting for a snapshot from the background script, resets if the tab changes/reloads
54+
useEffect(() => {
55+
// We declare a function
56+
function setLoadingArray(i: number, value: boolean) {
57+
// 'setLoadingArray' checks an element in our 'loadingArray' local state and compares it with passed in boolean argument. If they don't match, we update our local state replacing the selected element with the boolean argument
58+
if (loadingArray[i] !== value) {
59+
// this conditional helps us avoid unecessary state changes if the element and the value are already the same
60+
const loadingArrayClone = [...loadingArray];
61+
loadingArrayClone[i] = value;
62+
setLoading(loadingArrayClone);
63+
}
64+
}
65+
66+
if (titleTracker.current !== currentTitle) {
67+
// if the current tab changes/reloads, we reset loadingArray to it's default [true, true, true]
68+
titleTracker.current = currentTitle;
69+
setLoadingArray(0, true);
70+
setLoadingArray(1, true);
71+
setLoadingArray(2, true);
72+
73+
if (timeout.current) {
74+
// if there is a current timeout set, we clear it
75+
clearTimeout(timeout.current);
76+
timeout.current = null;
77+
}
78+
}
79+
80+
if (!status.contentScriptLaunched) {
81+
// if content script hasnt been launched/found, set a timer or immediately update 'loadingArray' state
82+
83+
if (loadingArray[0] === true) {
84+
// if loadingArray[0] is true, then that means our timeout.current is still null so we now set it to a setTimeout function that will flip loadingArray[0] to false after 3 seconds
85+
timeout.current = setTimeout(() => {
86+
setLoadingArray(0, false);
87+
}, 3000); // increased from 1500
88+
}
89+
} else {
90+
setLoadingArray(0, false); // if status.contentScriptLaunched is true, that means timeout.current !== null. This means that useEffect was triggered previously.
91+
}
92+
93+
// The next two if statements are written in a way to allow the checking of 'content script hook', 'reactDevTools check', and 'target page is a react app' to be run in chronological order.
94+
if (loadingArray[0] === false && status.contentScriptLaunched === true) {
95+
timeout.current = setTimeout(() => {
96+
setLoadingArray(1, false);
97+
}, 3000); // increased from 1500
98+
setLoadingArray(1, false);
99+
}
100+
if (loadingArray[1] === false && status.reactDevToolsInstalled === true) {
101+
setLoadingArray(2, false);
102+
}
103+
104+
// Unload async function when Error Container is unmounted
105+
return () => {
106+
clearTimeout(timeout.current);
107+
};
108+
}, [status, currentTitle, timeout, loadingArray]); // within our dependency array, we're keeping track of if the status, currentTitle/tab, timeout, or loadingArray changes and we re-run the useEffect hook if they do
109+
22110
return (
23111
<div className='error-container'>
24112
<img src='../assets/whiteBlackSquareLogo.png' alt='Reactime Logo' className='error-logo' />
@@ -30,10 +118,21 @@ function ErrorContainer(props: ErrorContainerProps): JSX.Element {
30118
Welcome to Reactime
31119
</div>
32120

121+
<div className='loaderChecks'>
122+
<p>Checking if content script has been launched on current tab</p>
123+
<Loader loading={loadingArray[0]} result={status.contentScriptLaunched} />
124+
125+
<p>Checking if React Dev Tools has been installed</p>
126+
<Loader loading={loadingArray[1]} result={status.reactDevToolsInstalled} />
127+
128+
<p>Checking if target is a compatible React app</p>
129+
<Loader loading={loadingArray[2]} result={status.targetPageisaReactApp} />
130+
</div>
131+
33132
<p className='error-description'>
34-
To ensure Reactime works correctly with your React application, please either refresh
35-
your development page or click the launch button below. This allows Reactime to properly
36-
connect with your app and start monitoring state changes.
133+
To ensure Reactime works correctly with your React application, please refresh your
134+
development page. This allows Reactime to properly connect with your app and start
135+
monitoring state changes.
37136
</p>
38137
<p className='error-description'>
39138
Important: Reactime requires React Developer Tools to be installed. If you haven't
@@ -55,11 +154,6 @@ function ErrorContainer(props: ErrorContainerProps): JSX.Element {
55154
starting with localhost.
56155
</p>
57156

58-
<button type='button' className='launch-button' onClick={launch}>
59-
<PlayCircle size={20} />
60-
Launch Reactime
61-
</button>
62-
63157
<a
64158
href='https://github.com/open-source-labs/reactime'
65159
target='_blank'

src/app/styles/layout/_errorContainer.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@
4242
margin-bottom: 1rem;
4343
}
4444

45+
.loaderChecks {
46+
margin: 1.5rem 0;
47+
}
48+
49+
.loaderChecks p {
50+
color: var(--text-secondary);
51+
font-size: 0.875rem;
52+
line-height: 1.5;
53+
font-weight: 500;
54+
display: flex;
55+
align-items: center;
56+
gap: 0.5rem;
57+
}
58+
4559
.error-note {
4660
text-align: center;
4761
color: var(--text-tertiary);

src/extension/build/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"content_scripts": [
1616
{
17-
"matches": ["http://localhost/*", "<all_urls>"],
17+
"matches": ["http://localhost/*"],
1818
"js": ["bundles/content.bundle.js"]
1919
}
2020
],

0 commit comments

Comments
 (0)