Skip to content

Commit

Permalink
Add "Welcome to the new DevTools" notification
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Brian Vaughn committed Jul 31, 2019
1 parent c57d2a2 commit 4f8b786
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 15 deletions.
12 changes: 12 additions & 0 deletions packages/react-devtools-core/src/standalone.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ let nodeWaitingToConnectHTML: string = '';
let projectRoots: Array<string> = [];
let statusListener: StatusListener = (message: string) => {};

// Unlike browser extension users, people using the standalone have actively installed version 4,
// So we probably don't need to show them a changelog notice.
// We should give embedded users (e.g. Nuclide, Sonar) a way of showing this dialog though.
let showWelcomeToTheNewDevToolsDialog: boolean = false;

function setContentDOMNode(value: HTMLElement) {
node = value;

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

function setShowWelcomeToTheNewDevToolsDialog(value: boolean) {
showWelcomeToTheNewDevToolsDialog = value;
return DevtoolsUI;
}

let bridge: FrontendBridge | null = null;
let store: Store | null = null;
let root = null;
Expand Down Expand Up @@ -87,6 +97,7 @@ function reload() {
bridge: ((bridge: any): FrontendBridge),
canViewElementSourceFunction,
showTabBar: true,
showWelcomeToTheNewDevToolsDialog,
store: ((store: any): Store),
warnIfLegacyBackendDetected: true,
viewElementSourceFunction,
Expand Down Expand Up @@ -300,6 +311,7 @@ const DevtoolsUI = {
connectToSocket,
setContentDOMNode,
setProjectRoots,
setShowWelcomeToTheNewDevToolsDialog,
setStatusListener,
startServer,
};
Expand Down
1 change: 1 addition & 0 deletions shells/browser/shared/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ function createPanelIfReactLoaded() {
profilerPortalContainer,
settingsPortalContainer,
showTabBar: false,
showWelcomeToTheNewDevToolsDialog: true,
store,
viewElementSourceFunction,
})
Expand Down
1 change: 1 addition & 0 deletions shells/dev/src/devtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ inject('dist/app.js', () => {
bridge,
browserTheme: 'light',
showTabBar: true,
showWelcomeToTheNewDevToolsDialog: true,
store,
warnIfLegacyBackendDetected: true,
})
Expand Down
3 changes: 3 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ export const LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY =
'React::DevTools::appendComponentStack';

export const PROFILER_EXPORT_VERSION = 4;

export const CHANGE_LOG_URL =
'https://github.com/bvaughn/react-devtools-experimental/blob/master/CHANGELOG.md';
6 changes: 6 additions & 0 deletions src/devtools/views/DevTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ProfilerContextController } from './Profiler/ProfilerContext';
import { ModalDialogContextController } from './ModalDialog';
import ReactLogo from './ReactLogo';
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
import ShowWelcomeToTheNewDevToolsDialog from './ShowWelcomeToTheNewDevToolsDialog';

import styles from './DevTools.css';

Expand All @@ -42,6 +43,7 @@ export type Props = {|
canViewElementSourceFunction?: ?CanViewElementSource,
defaultTab?: TabID,
showTabBar?: boolean,
showWelcomeToTheNewDevToolsDialog?: boolean,
store: Store,
warnIfLegacyBackendDetected?: boolean,
viewElementSourceFunction?: ?ViewElementSource,
Expand Down Expand Up @@ -85,6 +87,7 @@ export default function DevTools({
profilerPortalContainer,
settingsPortalContainer,
showTabBar = false,
showWelcomeToTheNewDevToolsDialog = false,
store,
warnIfLegacyBackendDetected = false,
viewElementSourceFunction = null,
Expand Down Expand Up @@ -150,6 +153,9 @@ export default function DevTools({
</ViewElementSourceContext.Provider>
</SettingsContextController>
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
{showWelcomeToTheNewDevToolsDialog && (
<ShowWelcomeToTheNewDevToolsDialog />
)}
</ModalDialogContextController>
</StoreContext.Provider>
</BridgeContext.Provider>
Expand Down
4 changes: 4 additions & 0 deletions src/devtools/views/ModalDialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@
text-align: right;
margin-top: 0.5rem;
}

.Button {
font-size: var(--font-size-sans-large);
}
27 changes: 22 additions & 5 deletions src/devtools/views/ModalDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,35 @@ function ModalDialogImpl(_: {||}) {
dispatch({ type: 'HIDE' });
}
}, [canBeDismissed, dispatch]);
const modalRef = useRef<HTMLDivElement | null>(null);
const dialogRef = useRef<HTMLDivElement | null>(null);

useModalDismissSignal(modalRef, dismissModal);
// It's important to trap click events within the dialog,
// so the dismiss hook will use it for click hit detection.
// Because multiple tabs may be showing this ModalDialog,
// the normal `dialog.contains(target)` check would fail on a background tab.
useModalDismissSignal(dialogRef, dismissModal, false);

// Clicks on the dialog should not bubble.
// This way we can dismiss by listening to clicks on the background.
const handleDialogClick = (event: any) => {
event.stopPropagation();

// It is important that we don't also prevent default,
// or clicks within the dialog (e.g. on links) won't work.
};

return (
<div className={styles.Background}>
<div className={styles.Dialog} ref={modalRef}>
<div className={styles.Background} onClick={dismissModal}>
<div
ref={dialogRef}
className={styles.Dialog}
onClick={handleDialogClick}
>
{title !== null && <div className={styles.Title}>{title}</div>}
{content}
{canBeDismissed && (
<div className={styles.Buttons}>
<Button autoFocus onClick={dismissModal}>
<Button autoFocus className={styles.Button} onClick={dismissModal}>
Okay
</Button>
</div>
Expand Down
8 changes: 6 additions & 2 deletions src/devtools/views/ReactLogo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import React from 'react';

import styles from './ReactLogo.css';

export default function ReactLogo() {
type Props = {|
className?: string,
|};

export default function ReactLogo({ className }: Props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.ReactLogo}
className={`${styles.ReactLogo} ${className || ''}`}
viewBox="-11.5 -10.23174 23 20.46348"
>
<circle cx="0" cy="0" r="2.05" fill="currentColor" />
Expand Down
13 changes: 13 additions & 0 deletions src/devtools/views/Settings/GeneralSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { useContext } from 'react';
import { SettingsContext } from './SettingsContext';
import { CHANGE_LOG_URL } from 'src/constants';

import styles from './SettingsShared.css';

Expand Down Expand Up @@ -54,6 +55,18 @@ export default function GeneralSettings(_: {||}) {
Append component stacks to console warnings and errors.
</label>
</div>

<div className={styles.ReleaseNotes}>
<a
className={styles.ReleaseNotesLink}
target="_blank"
rel="noopener noreferrer"
href={CHANGE_LOG_URL}
>
View release notes
</a>{' '}
for DevTools version {process.env.DEVTOOLS_VERSION}
</div>
</div>
);
}
11 changes: 11 additions & 0 deletions src/devtools/views/Settings/SettingsShared.css
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,14 @@
height: 0.375rem;
background-color: var(--color-toggle-text);
}

.ReleaseNotes {
width: 100%;
background-color: var(--color-background-hover);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}

.ReleaseNotesLink {
color: var(--color-button-active);
}
26 changes: 26 additions & 0 deletions src/devtools/views/ShowWelcomeToTheNewDevToolsDialog.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.Row {
display: flex;
flex-direction: row;
align-items: center;
}

.Column {
display: flex;
flex-direction: column;
align-items: center;
}

.Logo {
height: 4rem;
width: 4rem;
margin: 1rem;
}

.Title {
font-size: var(--font-size-sans-large);
margin-bottom: 0.5rem;
}

.ReleaseNotesLink {
color: var(--color-button-active);
}
64 changes: 64 additions & 0 deletions src/devtools/views/ShowWelcomeToTheNewDevToolsDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// @flow

import React, { Fragment, useContext, useEffect } from 'react';
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';
import { useLocalStorage } from './hooks';
import { ModalDialogContext } from './ModalDialog';
import ReactLogo from './ReactLogo';
import { CHANGE_LOG_URL } from 'src/constants';

import styles from './ShowWelcomeToTheNewDevToolsDialog.css';

const LOCAL_STORAGE_KEY =
'React::DevTools::hasShownWelcomeToTheNewDevToolsDialog';

export default function ShowWelcomeToTheNewDevToolsDialog(_: {||}) {
const { dispatch } = useContext(ModalDialogContext);
const [
hasShownWelcomeToTheNewDevToolsDialog,
setHasShownWelcomeToTheNewDevToolsDialog,
] = useLocalStorage<boolean>(LOCAL_STORAGE_KEY, false);

useEffect(() => {
if (!hasShownWelcomeToTheNewDevToolsDialog) {
batchedUpdates(() => {
setHasShownWelcomeToTheNewDevToolsDialog(true);
dispatch({
canBeDismissed: true,
type: 'SHOW',
content: <DialogContent />,
});
});
}
}, [
dispatch,
hasShownWelcomeToTheNewDevToolsDialog,
setHasShownWelcomeToTheNewDevToolsDialog,
]);

return null;
}

function DialogContent(_: {||}) {
return (
<Fragment>
<div className={styles.Row}>
<ReactLogo className={styles.Logo} />
<div>
<div className={styles.Title}>Welcome to the new React DevTools!</div>
<div>
<a
className={styles.ReleaseNotesLink}
target="_blank"
rel="noopener noreferrer"
href={CHANGE_LOG_URL}
>
Learn more
</a>{' '}
about changes in this version.
</div>
</div>
</div>
</Fragment>
);
}
19 changes: 11 additions & 8 deletions src/devtools/views/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,21 @@ export function useLocalStorage<T>(

export function useModalDismissSignal(
modalRef: { current: HTMLDivElement | null },
dismissCallback: () => void
dismissCallback: () => void,
dismissOnClickOutside?: boolean = true
): void {
useEffect(() => {
if (modalRef.current === null) {
return () => {};
}

const handleKeyDown = ({ key }: any) => {
const handleDocumentKeyDown = ({ key }: any) => {
if (key === 'Escape') {
dismissCallback();
}
};

const handleClick = (event: any) => {
const handleDocumentClick = (event: any) => {
// $FlowFixMe
if (
modalRef.current !== null &&
Expand All @@ -125,14 +126,16 @@ export function useModalDismissSignal(
// Here we use portals to render individual tabs (e.g. Profiler),
// and the root document might belong to a different window.
const ownerDocument = modalRef.current.ownerDocument;
ownerDocument.addEventListener('keydown', handleKeyDown);
ownerDocument.addEventListener('click', handleClick);
ownerDocument.addEventListener('keydown', handleDocumentKeyDown);
if (dismissOnClickOutside) {
ownerDocument.addEventListener('click', handleDocumentClick);
}

return () => {
ownerDocument.removeEventListener('keydown', handleKeyDown);
ownerDocument.removeEventListener('click', handleClick);
ownerDocument.removeEventListener('keydown', handleDocumentKeyDown);
ownerDocument.removeEventListener('click', handleDocumentClick);
};
}, [modalRef, dismissCallback]);
}, [modalRef, dismissCallback, dismissOnClickOutside]);
}

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

0 comments on commit 4f8b786

Please sign in to comment.