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.
This commit is contained in:
parent
c57d2a2901
commit
4f8b7864ee
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -87,6 +97,7 @@ function reload() {
|
|||
bridge: ((bridge: any): FrontendBridge),
|
||||
canViewElementSourceFunction,
|
||||
showTabBar: true,
|
||||
showWelcomeToTheNewDevToolsDialog,
|
||||
store: ((store: any): Store),
|
||||
warnIfLegacyBackendDetected: true,
|
||||
viewElementSourceFunction,
|
||||
|
@ -300,6 +311,7 @@ const DevtoolsUI = {
|
|||
connectToSocket,
|
||||
setContentDOMNode,
|
||||
setProjectRoots,
|
||||
setShowWelcomeToTheNewDevToolsDialog,
|
||||
setStatusListener,
|
||||
startServer,
|
||||
};
|
||||
|
|
|
@ -155,6 +155,7 @@ function createPanelIfReactLoaded() {
|
|||
profilerPortalContainer,
|
||||
settingsPortalContainer,
|
||||
showTabBar: false,
|
||||
showWelcomeToTheNewDevToolsDialog: true,
|
||||
store,
|
||||
viewElementSourceFunction,
|
||||
})
|
||||
|
|
|
@ -76,6 +76,7 @@ inject('dist/app.js', () => {
|
|||
bridge,
|
||||
browserTheme: 'light',
|
||||
showTabBar: true,
|
||||
showWelcomeToTheNewDevToolsDialog: true,
|
||||
store,
|
||||
warnIfLegacyBackendDetected: true,
|
||||
})
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -42,6 +43,7 @@ export type Props = {|
|
|||
canViewElementSourceFunction?: ?CanViewElementSource,
|
||||
defaultTab?: TabID,
|
||||
showTabBar?: boolean,
|
||||
showWelcomeToTheNewDevToolsDialog?: boolean,
|
||||
store: Store,
|
||||
warnIfLegacyBackendDetected?: boolean,
|
||||
viewElementSourceFunction?: ?ViewElementSource,
|
||||
|
@ -85,6 +87,7 @@ export default function DevTools({
|
|||
profilerPortalContainer,
|
||||
settingsPortalContainer,
|
||||
showTabBar = false,
|
||||
showWelcomeToTheNewDevToolsDialog = false,
|
||||
store,
|
||||
warnIfLegacyBackendDetected = false,
|
||||
viewElementSourceFunction = null,
|
||||
|
@ -150,6 +153,9 @@ export default function DevTools({
|
|||
</ViewElementSourceContext.Provider>
|
||||
</SettingsContextController>
|
||||
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
|
||||
{showWelcomeToTheNewDevToolsDialog && (
|
||||
<ShowWelcomeToTheNewDevToolsDialog />
|
||||
)}
|
||||
</ModalDialogContextController>
|
||||
</StoreContext.Provider>
|
||||
</BridgeContext.Provider>
|
||||
|
|
|
@ -34,3 +34,7 @@
|
|||
text-align: right;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.Button {
|
||||
font-size: var(--font-size-sans-large);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 &&
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue