Skip to content

Commit 4f8b786

Browse files
author
Brian Vaughn
committed
Add "Welcome to the new DevTools" notification
This dialog is shown in the browser extension the first time a user views v4. It is off by default for the standalone extension, but can be enabled via a public API.
1 parent c57d2a2 commit 4f8b786

File tree

13 files changed

+180
-15
lines changed

13 files changed

+180
-15
lines changed

packages/react-devtools-core/src/standalone.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ let nodeWaitingToConnectHTML: string = '';
2929
let projectRoots: Array<string> = [];
3030
let statusListener: StatusListener = (message: string) => {};
3131

32+
// Unlike browser extension users, people using the standalone have actively installed version 4,
33+
// So we probably don't need to show them a changelog notice.
34+
// We should give embedded users (e.g. Nuclide, Sonar) a way of showing this dialog though.
35+
let showWelcomeToTheNewDevToolsDialog: boolean = false;
36+
3237
function setContentDOMNode(value: HTMLElement) {
3338
node = value;
3439

@@ -47,6 +52,11 @@ function setStatusListener(value: StatusListener) {
4752
return DevtoolsUI;
4853
}
4954

55+
function setShowWelcomeToTheNewDevToolsDialog(value: boolean) {
56+
showWelcomeToTheNewDevToolsDialog = value;
57+
return DevtoolsUI;
58+
}
59+
5060
let bridge: FrontendBridge | null = null;
5161
let store: Store | null = null;
5262
let root = null;
@@ -87,6 +97,7 @@ function reload() {
8797
bridge: ((bridge: any): FrontendBridge),
8898
canViewElementSourceFunction,
8999
showTabBar: true,
100+
showWelcomeToTheNewDevToolsDialog,
90101
store: ((store: any): Store),
91102
warnIfLegacyBackendDetected: true,
92103
viewElementSourceFunction,
@@ -300,6 +311,7 @@ const DevtoolsUI = {
300311
connectToSocket,
301312
setContentDOMNode,
302313
setProjectRoots,
314+
setShowWelcomeToTheNewDevToolsDialog,
303315
setStatusListener,
304316
startServer,
305317
};

shells/browser/shared/src/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ function createPanelIfReactLoaded() {
155155
profilerPortalContainer,
156156
settingsPortalContainer,
157157
showTabBar: false,
158+
showWelcomeToTheNewDevToolsDialog: true,
158159
store,
159160
viewElementSourceFunction,
160161
})

shells/dev/src/devtools.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ inject('dist/app.js', () => {
7676
bridge,
7777
browserTheme: 'light',
7878
showTabBar: true,
79+
showWelcomeToTheNewDevToolsDialog: true,
7980
store,
8081
warnIfLegacyBackendDetected: true,
8182
})

src/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ export const LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY =
2424
'React::DevTools::appendComponentStack';
2525

2626
export const PROFILER_EXPORT_VERSION = 4;
27+
28+
export const CHANGE_LOG_URL =
29+
'https://github.com/bvaughn/react-devtools-experimental/blob/master/CHANGELOG.md';

src/devtools/views/DevTools.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ProfilerContextController } from './Profiler/ProfilerContext';
1818
import { ModalDialogContextController } from './ModalDialog';
1919
import ReactLogo from './ReactLogo';
2020
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
21+
import ShowWelcomeToTheNewDevToolsDialog from './ShowWelcomeToTheNewDevToolsDialog';
2122

2223
import styles from './DevTools.css';
2324

@@ -42,6 +43,7 @@ export type Props = {|
4243
canViewElementSourceFunction?: ?CanViewElementSource,
4344
defaultTab?: TabID,
4445
showTabBar?: boolean,
46+
showWelcomeToTheNewDevToolsDialog?: boolean,
4547
store: Store,
4648
warnIfLegacyBackendDetected?: boolean,
4749
viewElementSourceFunction?: ?ViewElementSource,
@@ -85,6 +87,7 @@ export default function DevTools({
8587
profilerPortalContainer,
8688
settingsPortalContainer,
8789
showTabBar = false,
90+
showWelcomeToTheNewDevToolsDialog = false,
8891
store,
8992
warnIfLegacyBackendDetected = false,
9093
viewElementSourceFunction = null,
@@ -150,6 +153,9 @@ export default function DevTools({
150153
</ViewElementSourceContext.Provider>
151154
</SettingsContextController>
152155
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
156+
{showWelcomeToTheNewDevToolsDialog && (
157+
<ShowWelcomeToTheNewDevToolsDialog />
158+
)}
153159
</ModalDialogContextController>
154160
</StoreContext.Provider>
155161
</BridgeContext.Provider>

src/devtools/views/ModalDialog.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@
3434
text-align: right;
3535
margin-top: 0.5rem;
3636
}
37+
38+
.Button {
39+
font-size: var(--font-size-sans-large);
40+
}

src/devtools/views/ModalDialog.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,35 @@ function ModalDialogImpl(_: {||}) {
109109
dispatch({ type: 'HIDE' });
110110
}
111111
}, [canBeDismissed, dispatch]);
112-
const modalRef = useRef<HTMLDivElement | null>(null);
112+
const dialogRef = useRef<HTMLDivElement | null>(null);
113113

114-
useModalDismissSignal(modalRef, dismissModal);
114+
// It's important to trap click events within the dialog,
115+
// so the dismiss hook will use it for click hit detection.
116+
// Because multiple tabs may be showing this ModalDialog,
117+
// the normal `dialog.contains(target)` check would fail on a background tab.
118+
useModalDismissSignal(dialogRef, dismissModal, false);
119+
120+
// Clicks on the dialog should not bubble.
121+
// This way we can dismiss by listening to clicks on the background.
122+
const handleDialogClick = (event: any) => {
123+
event.stopPropagation();
124+
125+
// It is important that we don't also prevent default,
126+
// or clicks within the dialog (e.g. on links) won't work.
127+
};
115128

116129
return (
117-
<div className={styles.Background}>
118-
<div className={styles.Dialog} ref={modalRef}>
130+
<div className={styles.Background} onClick={dismissModal}>
131+
<div
132+
ref={dialogRef}
133+
className={styles.Dialog}
134+
onClick={handleDialogClick}
135+
>
119136
{title !== null && <div className={styles.Title}>{title}</div>}
120137
{content}
121138
{canBeDismissed && (
122139
<div className={styles.Buttons}>
123-
<Button autoFocus onClick={dismissModal}>
140+
<Button autoFocus className={styles.Button} onClick={dismissModal}>
124141
Okay
125142
</Button>
126143
</div>

src/devtools/views/ReactLogo.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import React from 'react';
44

55
import styles from './ReactLogo.css';
66

7-
export default function ReactLogo() {
7+
type Props = {|
8+
className?: string,
9+
|};
10+
11+
export default function ReactLogo({ className }: Props) {
812
return (
913
<svg
1014
xmlns="http://www.w3.org/2000/svg"
11-
className={styles.ReactLogo}
15+
className={`${styles.ReactLogo} ${className || ''}`}
1216
viewBox="-11.5 -10.23174 23 20.46348"
1317
>
1418
<circle cx="0" cy="0" r="2.05" fill="currentColor" />

src/devtools/views/Settings/GeneralSettings.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import React, { useContext } from 'react';
44
import { SettingsContext } from './SettingsContext';
5+
import { CHANGE_LOG_URL } from 'src/constants';
56

67
import styles from './SettingsShared.css';
78

@@ -54,6 +55,18 @@ export default function GeneralSettings(_: {||}) {
5455
Append component stacks to console warnings and errors.
5556
</label>
5657
</div>
58+
59+
<div className={styles.ReleaseNotes}>
60+
<a
61+
className={styles.ReleaseNotesLink}
62+
target="_blank"
63+
rel="noopener noreferrer"
64+
href={CHANGE_LOG_URL}
65+
>
66+
View release notes
67+
</a>{' '}
68+
for DevTools version {process.env.DEVTOOLS_VERSION}
69+
</div>
5770
</div>
5871
);
5972
}

src/devtools/views/Settings/SettingsShared.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,14 @@
134134
height: 0.375rem;
135135
background-color: var(--color-toggle-text);
136136
}
137+
138+
.ReleaseNotes {
139+
width: 100%;
140+
background-color: var(--color-background-hover);
141+
padding: 0.25rem 0.5rem;
142+
border-radius: 0.25rem;
143+
}
144+
145+
.ReleaseNotesLink {
146+
color: var(--color-button-active);
147+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.Row {
2+
display: flex;
3+
flex-direction: row;
4+
align-items: center;
5+
}
6+
7+
.Column {
8+
display: flex;
9+
flex-direction: column;
10+
align-items: center;
11+
}
12+
13+
.Logo {
14+
height: 4rem;
15+
width: 4rem;
16+
margin: 1rem;
17+
}
18+
19+
.Title {
20+
font-size: var(--font-size-sans-large);
21+
margin-bottom: 0.5rem;
22+
}
23+
24+
.ReleaseNotesLink {
25+
color: var(--color-button-active);
26+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// @flow
2+
3+
import React, { Fragment, useContext, useEffect } from 'react';
4+
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';
5+
import { useLocalStorage } from './hooks';
6+
import { ModalDialogContext } from './ModalDialog';
7+
import ReactLogo from './ReactLogo';
8+
import { CHANGE_LOG_URL } from 'src/constants';
9+
10+
import styles from './ShowWelcomeToTheNewDevToolsDialog.css';
11+
12+
const LOCAL_STORAGE_KEY =
13+
'React::DevTools::hasShownWelcomeToTheNewDevToolsDialog';
14+
15+
export default function ShowWelcomeToTheNewDevToolsDialog(_: {||}) {
16+
const { dispatch } = useContext(ModalDialogContext);
17+
const [
18+
hasShownWelcomeToTheNewDevToolsDialog,
19+
setHasShownWelcomeToTheNewDevToolsDialog,
20+
] = useLocalStorage<boolean>(LOCAL_STORAGE_KEY, false);
21+
22+
useEffect(() => {
23+
if (!hasShownWelcomeToTheNewDevToolsDialog) {
24+
batchedUpdates(() => {
25+
setHasShownWelcomeToTheNewDevToolsDialog(true);
26+
dispatch({
27+
canBeDismissed: true,
28+
type: 'SHOW',
29+
content: <DialogContent />,
30+
});
31+
});
32+
}
33+
}, [
34+
dispatch,
35+
hasShownWelcomeToTheNewDevToolsDialog,
36+
setHasShownWelcomeToTheNewDevToolsDialog,
37+
]);
38+
39+
return null;
40+
}
41+
42+
function DialogContent(_: {||}) {
43+
return (
44+
<Fragment>
45+
<div className={styles.Row}>
46+
<ReactLogo className={styles.Logo} />
47+
<div>
48+
<div className={styles.Title}>Welcome to the new React DevTools!</div>
49+
<div>
50+
<a
51+
className={styles.ReleaseNotesLink}
52+
target="_blank"
53+
rel="noopener noreferrer"
54+
href={CHANGE_LOG_URL}
55+
>
56+
Learn more
57+
</a>{' '}
58+
about changes in this version.
59+
</div>
60+
</div>
61+
</div>
62+
</Fragment>
63+
);
64+
}

src/devtools/views/hooks.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,21 @@ export function useLocalStorage<T>(
9595

9696
export function useModalDismissSignal(
9797
modalRef: { current: HTMLDivElement | null },
98-
dismissCallback: () => void
98+
dismissCallback: () => void,
99+
dismissOnClickOutside?: boolean = true
99100
): void {
100101
useEffect(() => {
101102
if (modalRef.current === null) {
102103
return () => {};
103104
}
104105

105-
const handleKeyDown = ({ key }: any) => {
106+
const handleDocumentKeyDown = ({ key }: any) => {
106107
if (key === 'Escape') {
107108
dismissCallback();
108109
}
109110
};
110111

111-
const handleClick = (event: any) => {
112+
const handleDocumentClick = (event: any) => {
112113
// $FlowFixMe
113114
if (
114115
modalRef.current !== null &&
@@ -125,14 +126,16 @@ export function useModalDismissSignal(
125126
// Here we use portals to render individual tabs (e.g. Profiler),
126127
// and the root document might belong to a different window.
127128
const ownerDocument = modalRef.current.ownerDocument;
128-
ownerDocument.addEventListener('keydown', handleKeyDown);
129-
ownerDocument.addEventListener('click', handleClick);
129+
ownerDocument.addEventListener('keydown', handleDocumentKeyDown);
130+
if (dismissOnClickOutside) {
131+
ownerDocument.addEventListener('click', handleDocumentClick);
132+
}
130133

131134
return () => {
132-
ownerDocument.removeEventListener('keydown', handleKeyDown);
133-
ownerDocument.removeEventListener('click', handleClick);
135+
ownerDocument.removeEventListener('keydown', handleDocumentKeyDown);
136+
ownerDocument.removeEventListener('click', handleDocumentClick);
134137
};
135-
}, [modalRef, dismissCallback]);
138+
}, [modalRef, dismissCallback, dismissOnClickOutside]);
136139
}
137140

138141
// Copied from https://github.com/facebook/react/pull/15022

0 commit comments

Comments
 (0)