Skip to content

Commit ecb599c

Browse files
author
Brian Vaughn
authored
DevTools supports multiple modal dialogs at once (#21370)
1 parent 8e2bb3e commit ecb599c

File tree

7 files changed

+84
-45
lines changed

7 files changed

+84
-45
lines changed

packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export default function InspectedElementWrapper(_: Props) {
120120
// Instead we can show a warning to the user.
121121
if (nearestSuspenseElement === null) {
122122
modalDialogDispatch({
123+
id: 'InspectedElement',
123124
type: 'SHOW',
124125
content: <CannotSuspendWarningMessage />,
125126
});

packages/react-devtools-shared/src/devtools/views/ModalDialog.css

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
width: 100%;
44
height: 100%;
55
display: flex;
6+
flex-direction: row;
67
align-items: flex-start;
78
justify-content: center;
89
padding: 1rem;
@@ -13,6 +14,7 @@
1314
.Dialog {
1415
position: relative;
1516
z-index: 3;
17+
margin: 0 0.25rem;
1618
width: 25rem;
1719
min-width: 20rem;
1820
max-width: 100%;

packages/react-devtools-shared/src/devtools/views/ModalDialog.js

+69-43
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,35 @@ import {useModalDismissSignal} from './hooks';
2121

2222
import styles from './ModalDialog.css';
2323

24+
type ID = any;
25+
2426
type DIALOG_ACTION_HIDE = {|
2527
type: 'HIDE',
28+
id: ID,
2629
|};
2730
type DIALOG_ACTION_SHOW = {|
2831
type: 'SHOW',
2932
canBeDismissed?: boolean,
3033
content: React$Node,
34+
id: ID,
3135
title?: React$Node | null,
3236
|};
3337

3438
type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
3539

3640
type Dispatch = (action: Action) => void;
3741

38-
type State = {|
42+
type Dialog = {|
3943
canBeDismissed: boolean,
4044
content: React$Node | null,
41-
isVisible: boolean,
45+
id: ID,
4246
title: React$Node | null,
4347
|};
4448

49+
type State = {|
50+
dialogs: Array<Dialog>,
51+
|};
52+
4553
type ModalDialogContextType = {|
4654
...State,
4755
dispatch: Dispatch,
@@ -56,17 +64,19 @@ function dialogReducer(state, action) {
5664
switch (action.type) {
5765
case 'HIDE':
5866
return {
59-
canBeDismissed: true,
60-
content: null,
61-
isVisible: false,
62-
title: null,
67+
dialogs: state.dialogs.filter(dialog => dialog.id !== action.id),
6368
};
6469
case 'SHOW':
6570
return {
66-
canBeDismissed: action.canBeDismissed !== false,
67-
content: action.content,
68-
isVisible: true,
69-
title: action.title || null,
71+
dialogs: [
72+
...state.dialogs,
73+
{
74+
canBeDismissed: action.canBeDismissed !== false,
75+
content: action.content,
76+
id: action.id,
77+
title: action.title || null,
78+
},
79+
],
7080
};
7181
default:
7282
throw new Error(`Invalid action "${action.type}"`);
@@ -79,18 +89,12 @@ type Props = {|
7989

8090
function ModalDialogContextController({children}: Props) {
8191
const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
82-
canBeDismissed: true,
83-
content: null,
84-
isVisible: false,
85-
title: null,
92+
dialogs: [],
8693
});
8794

8895
const value = useMemo<ModalDialogContextType>(
8996
() => ({
90-
canBeDismissed: state.canBeDismissed,
91-
content: state.content,
92-
isVisible: state.isVisible,
93-
title: state.title,
97+
dialogs: state.dialogs,
9498
dispatch,
9599
}),
96100
[state, dispatch],
@@ -104,17 +108,44 @@ function ModalDialogContextController({children}: Props) {
104108
}
105109

106110
function ModalDialog(_: {||}) {
107-
const {isVisible} = useContext(ModalDialogContext);
108-
return isVisible ? <ModalDialogImpl /> : null;
109-
}
111+
const {dialogs, dispatch} = useContext(ModalDialogContext);
112+
113+
if (dialogs.length === 0) {
114+
return null;
115+
}
110116

111-
function ModalDialogImpl(_: {||}) {
112-
const {canBeDismissed, content, dispatch, title} = useContext(
113-
ModalDialogContext,
117+
return (
118+
<div className={styles.Background}>
119+
{dialogs.map(dialog => (
120+
<ModalDialogImpl
121+
key={dialog.id}
122+
canBeDismissed={dialog.canBeDismissed}
123+
content={dialog.content}
124+
dispatch={dispatch}
125+
id={dialog.id}
126+
title={dialog.title}
127+
/>
128+
))}
129+
</div>
114130
);
131+
}
132+
133+
function ModalDialogImpl({
134+
canBeDismissed,
135+
content,
136+
dispatch,
137+
id,
138+
title,
139+
}: {|
140+
canBeDismissed: boolean,
141+
content: React$Node | null,
142+
dispatch: Dispatch,
143+
id: ID,
144+
title: React$Node | null,
145+
|}) {
115146
const dismissModal = useCallback(() => {
116147
if (canBeDismissed) {
117-
dispatch({type: 'HIDE'});
148+
dispatch({type: 'HIDE', id});
118149
}
119150
}, [canBeDismissed, dispatch]);
120151
const dialogRef = useRef<HTMLDivElement | null>(null);
@@ -135,24 +166,19 @@ function ModalDialogImpl(_: {||}) {
135166
};
136167

137168
return (
138-
<div className={styles.Background} onClick={dismissModal}>
139-
<div
140-
ref={dialogRef}
141-
className={styles.Dialog}
142-
onClick={handleDialogClick}>
143-
{title !== null && <div className={styles.Title}>{title}</div>}
144-
{content}
145-
{canBeDismissed && (
146-
<div className={styles.Buttons}>
147-
<Button
148-
autoFocus={true}
149-
className={styles.Button}
150-
onClick={dismissModal}>
151-
Okay
152-
</Button>
153-
</div>
154-
)}
155-
</div>
169+
<div ref={dialogRef} className={styles.Dialog} onClick={handleDialogClick}>
170+
{title !== null && <div className={styles.Title}>{title}</div>}
171+
{content}
172+
{canBeDismissed && (
173+
<div className={styles.Buttons}>
174+
<Button
175+
autoFocus={true}
176+
className={styles.Button}
177+
onClick={dismissModal}>
178+
Okay
179+
</Button>
180+
</div>
181+
)}
156182
</div>
157183
);
158184
}

packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export default function ProfilingImportExportButtons() {
8585
);
8686
} catch (error) {
8787
modalDialogDispatch({
88+
id: 'ProfilingImportExportButtons',
8889
type: 'SHOW',
8990
title: 'Import failed',
9091
content: (

packages/react-devtools-shared/src/devtools/views/UnsupportedBridgeProtocolDialog.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,21 @@ import type {BridgeProtocol} from 'react-devtools-shared/src/bridge';
2121

2222
const DEVTOOLS_VERSION = process.env.DEVTOOLS_VERSION;
2323
const INSTRUCTIONS_FB_URL = 'https://fburl.com/devtools-bridge-protocol';
24+
const MODAL_DIALOG_ID = 'UnsupportedBridgeProtocolDialog';
2425

2526
export default function UnsupportedBridgeProtocolDialog(_: {||}) {
26-
const {dispatch, isVisible} = useContext(ModalDialogContext);
27+
const {dialogs, dispatch} = useContext(ModalDialogContext);
2728
const store = useContext(StoreContext);
2829

30+
const isVisible = !!dialogs.find(dialog => dialog.id === MODAL_DIALOG_ID);
31+
2932
useEffect(() => {
3033
const updateDialog = () => {
3134
if (!isVisible) {
3235
if (store.unsupportedBridgeProtocol !== null) {
3336
dispatch({
3437
canBeDismissed: false,
38+
id: MODAL_DIALOG_ID,
3539
type: 'SHOW',
3640
content: (
3741
<DialogContent
@@ -42,7 +46,10 @@ export default function UnsupportedBridgeProtocolDialog(_: {||}) {
4246
}
4347
} else {
4448
if (store.unsupportedBridgeProtocol === null) {
45-
dispatch({type: 'HIDE'});
49+
dispatch({
50+
type: 'HIDE',
51+
id: MODAL_DIALOG_ID,
52+
});
4653
}
4754
}
4855
};

packages/react-devtools-shared/src/devtools/views/UnsupportedVersionDialog.js

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default function UnsupportedVersionDialog(_: {||}) {
3030
setState('show-dialog');
3131
dispatch({
3232
canBeDismissed: true,
33+
id: 'UnsupportedVersionDialog',
3334
type: 'SHOW',
3435
content: <DialogContent />,
3536
});

packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.js

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function WarnIfLegacyBackendDetected(_: {||}) {
3131
// Any of these types indicate the v3 backend.
3232
dispatch({
3333
canBeDismissed: false,
34+
id: 'WarnIfLegacyBackendDetected',
3435
type: 'SHOW',
3536
title: 'DevTools v4 is incompatible with this version of React',
3637
content: <InvalidBackendDetected />,

0 commit comments

Comments
 (0)