mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
DevTools supports multiple modal dialogs at once (#21370)
This commit is contained in:
+1
@@ -120,6 +120,7 @@ export default function InspectedElementWrapper(_: Props) {
|
||||
// Instead we can show a warning to the user.
|
||||
if (nearestSuspenseElement === null) {
|
||||
modalDialogDispatch({
|
||||
id: 'InspectedElement',
|
||||
type: 'SHOW',
|
||||
content: <CannotSuspendWarningMessage />,
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
@@ -13,6 +14,7 @@
|
||||
.Dialog {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
margin: 0 0.25rem;
|
||||
width: 25rem;
|
||||
min-width: 20rem;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -21,13 +21,17 @@ import {useModalDismissSignal} from './hooks';
|
||||
|
||||
import styles from './ModalDialog.css';
|
||||
|
||||
type ID = any;
|
||||
|
||||
type DIALOG_ACTION_HIDE = {|
|
||||
type: 'HIDE',
|
||||
id: ID,
|
||||
|};
|
||||
type DIALOG_ACTION_SHOW = {|
|
||||
type: 'SHOW',
|
||||
canBeDismissed?: boolean,
|
||||
content: React$Node,
|
||||
id: ID,
|
||||
title?: React$Node | null,
|
||||
|};
|
||||
|
||||
@@ -35,13 +39,17 @@ type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
|
||||
|
||||
type Dispatch = (action: Action) => void;
|
||||
|
||||
type State = {|
|
||||
type Dialog = {|
|
||||
canBeDismissed: boolean,
|
||||
content: React$Node | null,
|
||||
isVisible: boolean,
|
||||
id: ID,
|
||||
title: React$Node | null,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
dialogs: Array<Dialog>,
|
||||
|};
|
||||
|
||||
type ModalDialogContextType = {|
|
||||
...State,
|
||||
dispatch: Dispatch,
|
||||
@@ -56,17 +64,19 @@ function dialogReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'HIDE':
|
||||
return {
|
||||
canBeDismissed: true,
|
||||
content: null,
|
||||
isVisible: false,
|
||||
title: null,
|
||||
dialogs: state.dialogs.filter(dialog => dialog.id !== action.id),
|
||||
};
|
||||
case 'SHOW':
|
||||
return {
|
||||
canBeDismissed: action.canBeDismissed !== false,
|
||||
content: action.content,
|
||||
isVisible: true,
|
||||
title: action.title || null,
|
||||
dialogs: [
|
||||
...state.dialogs,
|
||||
{
|
||||
canBeDismissed: action.canBeDismissed !== false,
|
||||
content: action.content,
|
||||
id: action.id,
|
||||
title: action.title || null,
|
||||
},
|
||||
],
|
||||
};
|
||||
default:
|
||||
throw new Error(`Invalid action "${action.type}"`);
|
||||
@@ -79,18 +89,12 @@ type Props = {|
|
||||
|
||||
function ModalDialogContextController({children}: Props) {
|
||||
const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
|
||||
canBeDismissed: true,
|
||||
content: null,
|
||||
isVisible: false,
|
||||
title: null,
|
||||
dialogs: [],
|
||||
});
|
||||
|
||||
const value = useMemo<ModalDialogContextType>(
|
||||
() => ({
|
||||
canBeDismissed: state.canBeDismissed,
|
||||
content: state.content,
|
||||
isVisible: state.isVisible,
|
||||
title: state.title,
|
||||
dialogs: state.dialogs,
|
||||
dispatch,
|
||||
}),
|
||||
[state, dispatch],
|
||||
@@ -104,17 +108,44 @@ function ModalDialogContextController({children}: Props) {
|
||||
}
|
||||
|
||||
function ModalDialog(_: {||}) {
|
||||
const {isVisible} = useContext(ModalDialogContext);
|
||||
return isVisible ? <ModalDialogImpl /> : null;
|
||||
const {dialogs, dispatch} = useContext(ModalDialogContext);
|
||||
|
||||
if (dialogs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.Background}>
|
||||
{dialogs.map(dialog => (
|
||||
<ModalDialogImpl
|
||||
key={dialog.id}
|
||||
canBeDismissed={dialog.canBeDismissed}
|
||||
content={dialog.content}
|
||||
dispatch={dispatch}
|
||||
id={dialog.id}
|
||||
title={dialog.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalDialogImpl(_: {||}) {
|
||||
const {canBeDismissed, content, dispatch, title} = useContext(
|
||||
ModalDialogContext,
|
||||
);
|
||||
function ModalDialogImpl({
|
||||
canBeDismissed,
|
||||
content,
|
||||
dispatch,
|
||||
id,
|
||||
title,
|
||||
}: {|
|
||||
canBeDismissed: boolean,
|
||||
content: React$Node | null,
|
||||
dispatch: Dispatch,
|
||||
id: ID,
|
||||
title: React$Node | null,
|
||||
|}) {
|
||||
const dismissModal = useCallback(() => {
|
||||
if (canBeDismissed) {
|
||||
dispatch({type: 'HIDE'});
|
||||
dispatch({type: 'HIDE', id});
|
||||
}
|
||||
}, [canBeDismissed, dispatch]);
|
||||
const dialogRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -135,24 +166,19 @@ function ModalDialogImpl(_: {||}) {
|
||||
};
|
||||
|
||||
return (
|
||||
<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={true}
|
||||
className={styles.Button}
|
||||
onClick={dismissModal}>
|
||||
Okay
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div ref={dialogRef} className={styles.Dialog} onClick={handleDialogClick}>
|
||||
{title !== null && <div className={styles.Title}>{title}</div>}
|
||||
{content}
|
||||
{canBeDismissed && (
|
||||
<div className={styles.Buttons}>
|
||||
<Button
|
||||
autoFocus={true}
|
||||
className={styles.Button}
|
||||
onClick={dismissModal}>
|
||||
Okay
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+1
@@ -85,6 +85,7 @@ export default function ProfilingImportExportButtons() {
|
||||
);
|
||||
} catch (error) {
|
||||
modalDialogDispatch({
|
||||
id: 'ProfilingImportExportButtons',
|
||||
type: 'SHOW',
|
||||
title: 'Import failed',
|
||||
content: (
|
||||
|
||||
+9
-2
@@ -21,17 +21,21 @@ import type {BridgeProtocol} from 'react-devtools-shared/src/bridge';
|
||||
|
||||
const DEVTOOLS_VERSION = process.env.DEVTOOLS_VERSION;
|
||||
const INSTRUCTIONS_FB_URL = 'https://fburl.com/devtools-bridge-protocol';
|
||||
const MODAL_DIALOG_ID = 'UnsupportedBridgeProtocolDialog';
|
||||
|
||||
export default function UnsupportedBridgeProtocolDialog(_: {||}) {
|
||||
const {dispatch, isVisible} = useContext(ModalDialogContext);
|
||||
const {dialogs, dispatch} = useContext(ModalDialogContext);
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const isVisible = !!dialogs.find(dialog => dialog.id === MODAL_DIALOG_ID);
|
||||
|
||||
useEffect(() => {
|
||||
const updateDialog = () => {
|
||||
if (!isVisible) {
|
||||
if (store.unsupportedBridgeProtocol !== null) {
|
||||
dispatch({
|
||||
canBeDismissed: false,
|
||||
id: MODAL_DIALOG_ID,
|
||||
type: 'SHOW',
|
||||
content: (
|
||||
<DialogContent
|
||||
@@ -42,7 +46,10 @@ export default function UnsupportedBridgeProtocolDialog(_: {||}) {
|
||||
}
|
||||
} else {
|
||||
if (store.unsupportedBridgeProtocol === null) {
|
||||
dispatch({type: 'HIDE'});
|
||||
dispatch({
|
||||
type: 'HIDE',
|
||||
id: MODAL_DIALOG_ID,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function UnsupportedVersionDialog(_: {||}) {
|
||||
setState('show-dialog');
|
||||
dispatch({
|
||||
canBeDismissed: true,
|
||||
id: 'UnsupportedVersionDialog',
|
||||
type: 'SHOW',
|
||||
content: <DialogContent />,
|
||||
});
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ export default function WarnIfLegacyBackendDetected(_: {||}) {
|
||||
// Any of these types indicate the v3 backend.
|
||||
dispatch({
|
||||
canBeDismissed: false,
|
||||
id: 'WarnIfLegacyBackendDetected',
|
||||
type: 'SHOW',
|
||||
title: 'DevTools v4 is incompatible with this version of React',
|
||||
content: <InvalidBackendDetected />,
|
||||
|
||||
Reference in New Issue
Block a user