From ecb599cd87b39c23b44c2cc7daad9df42f4c2385 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 27 Apr 2021 20:33:11 -0400 Subject: [PATCH] DevTools supports multiple modal dialogs at once (#21370) --- .../views/Components/InspectedElement.js | 1 + .../src/devtools/views/ModalDialog.css | 2 + .../src/devtools/views/ModalDialog.js | 112 +++++++++++------- .../Profiler/ProfilingImportExportButtons.js | 1 + .../views/UnsupportedBridgeProtocolDialog.js | 11 +- .../views/UnsupportedVersionDialog.js | 1 + .../views/WarnIfLegacyBackendDetected.js | 1 + 7 files changed, 84 insertions(+), 45 deletions(-) diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index 2c4b0698c77e3..e7cda475052ec 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -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: , }); diff --git a/packages/react-devtools-shared/src/devtools/views/ModalDialog.css b/packages/react-devtools-shared/src/devtools/views/ModalDialog.css index 378e8e73b4b3e..754b40b96f29a 100644 --- a/packages/react-devtools-shared/src/devtools/views/ModalDialog.css +++ b/packages/react-devtools-shared/src/devtools/views/ModalDialog.css @@ -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%; diff --git a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js index 770c106d8c871..5c4acc58cfa40 100644 --- a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js +++ b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js @@ -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, +|}; + 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(dialogReducer, { - canBeDismissed: true, - content: null, - isVisible: false, - title: null, + dialogs: [], }); const value = useMemo( () => ({ - 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 ? : null; -} + const {dialogs, dispatch} = useContext(ModalDialogContext); + + if (dialogs.length === 0) { + return null; + } -function ModalDialogImpl(_: {||}) { - const {canBeDismissed, content, dispatch, title} = useContext( - ModalDialogContext, + return ( +
+ {dialogs.map(dialog => ( + + ))} +
); +} + +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(null); @@ -135,24 +166,19 @@ function ModalDialogImpl(_: {||}) { }; return ( -
-
- {title !== null &&
{title}
} - {content} - {canBeDismissed && ( -
- -
- )} -
+
+ {title !== null &&
{title}
} + {content} + {canBeDismissed && ( +
+ +
+ )}
); } diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js index 7f14438aaa6ec..d6570dd5ab34b 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ProfilingImportExportButtons.js @@ -85,6 +85,7 @@ export default function ProfilingImportExportButtons() { ); } catch (error) { modalDialogDispatch({ + id: 'ProfilingImportExportButtons', type: 'SHOW', title: 'Import failed', content: ( diff --git a/packages/react-devtools-shared/src/devtools/views/UnsupportedBridgeProtocolDialog.js b/packages/react-devtools-shared/src/devtools/views/UnsupportedBridgeProtocolDialog.js index b5c1e7e8baeb9..ddbde14fad118 100644 --- a/packages/react-devtools-shared/src/devtools/views/UnsupportedBridgeProtocolDialog.js +++ b/packages/react-devtools-shared/src/devtools/views/UnsupportedBridgeProtocolDialog.js @@ -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: ( , }); diff --git a/packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.js b/packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.js index 12d88c0562205..4bc539d8975cd 100644 --- a/packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.js +++ b/packages/react-devtools-shared/src/devtools/views/WarnIfLegacyBackendDetected.js @@ -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: ,