@samitouri / QOS-React-2 / commits / 703706b40f

Add toggle-suspense button to all elements

This button is disabled if the renderer returns canToggleSuspense=false.

Brian Vaughn committed May 8, 2019 at 10:50 UTC 703706b40f4b2de3906b3f311f16d6b433e84e0f
15 files changed +335 -169
src/devtools/views/ButtonIcon.js
+9
@@ -25,6 +25,7 @@ export type IconType =
25 | 'save'
26 | 'search'
27 | 'settings'
28 + | 'suspend'
29 | 'undo'
30 | 'up'
31 | 'view-dom'
@@ -101,6 +102,9 @@ export default function ButtonIcon({ className = '', type }: Props) {
102 case 'settings':
103 pathData = PATH_SETTINGS;
104 break;
105 + case 'suspend':
106 + pathData = PATH_SUSPEND;
107 + break;
108 case 'undo':
109 pathData = PATH_UNDO;
110 break;
@@ -212,6 +216,11 @@ const PATH_SETTINGS = `
216 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z
217 `;
218
219 +const PATH_SUSPEND = `
220 + M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42C16.07 4.74 14.12 4 12 4c-4.97
221 + 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.12-.74-4.07-1.97-5.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z
222 +`;
223 +
224 const PATH_UNDO = `
225 M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88
226 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z
src/devtools/views/Components/Components.css
+1
@@ -1,4 +1,5 @@
1 .Components {
2 + position: relative;
3 width: 100%;
4 height: 100%;
5 display: flex;
src/devtools/views/Components/Components.js
+2
@@ -5,6 +5,7 @@ import Tree from './Tree';
5 import SelectedElement from './SelectedElement';
6 import { InspectedElementContextController } from './InspectedElementContext';
7 import portaledContent from '../portaledContent';
8 +import { ModalDialog } from '../ModalDialog';
9
10 import styles from './Components.css';
11
@@ -22,6 +23,7 @@ function Components(_: {||}) {
23 </Suspense>
24 </InspectedElementContextController>
25 </div>
26 + <ModalDialog />
27 </div>
28 );
29 }
src/devtools/views/Components/SelectedElement.css
+4
@@ -83,3 +83,7 @@
83 color: var(--color-dimmer);
84 font-style: italic;
85 }
86 +
87 +.CannotSuspendWarningMessage {
88 + font-size: var(--font-size-sans-large);
89 +}
src/devtools/views/Components/SelectedElement.js
+106 -6
@@ -6,11 +6,13 @@ import { BridgeContext, StoreContext } from '../context';
6 import Button from '../Button';
7 import ButtonIcon from '../ButtonIcon';
8 import HooksTree from './HooksTree';
9 +import { ModalDialogContext } from '../ModalDialog';
10 import InspectedElementTree from './InspectedElementTree';
11 import { InspectedElementContext } from './InspectedElementContext';
12 import ViewElementSourceContext from './ViewElementSourceContext';
12 -import styles from './SelectedElement.css';
13 +import Toggle from '../Toggle';
14 import {
15 + ComponentFilterElementType,
16 ElementTypeClass,
17 ElementTypeForwardRef,
18 ElementTypeFunction,
@@ -18,15 +20,19 @@ import {
20 ElementTypeSuspense,
21 } from 'src/types';
22
23 +import styles from './SelectedElement.css';
24 +
25 import type { Element, InspectedElement } from './types';
26
27 export type Props = {||};
28
29 export default function SelectedElement(_: Props) {
30 const { inspectedElementID } = useContext(TreeStateContext);
31 + const dispatch = useContext(TreeDispatcherContext);
32 const viewElementSource = useContext(ViewElementSourceContext);
33 const bridge = useContext(BridgeContext);
34 const store = useContext(StoreContext);
35 + const { dispatch: modalDialogDispatch } = useContext(ModalDialogContext);
36
37 const { read } = useContext(InspectedElementContext);
38
@@ -72,6 +78,63 @@ export default function SelectedElement(_: Props) {
78 }
79 }, [inspectedElementID, viewElementSource]);
80
81 + const canViewSource =
82 + inspectedElement &&
83 + inspectedElement.canViewSource &&
84 + viewElementSource !== null;
85 +
86 + const isSuspended =
87 + element !== null &&
88 + element.type === ElementTypeSuspense &&
89 + inspectedElement != null &&
90 + inspectedElement.state != null;
91 +
92 + const canToggleSuspense =
93 + inspectedElement != null && inspectedElement.canToggleSuspense;
94 +
95 + // TODO (suspense toggle) Would be nice to eventually use a two setState pattern here as well.
96 + const toggleSuspended = useCallback(() => {
97 + let nearestSuspenseElement = null;
98 + let currentElement = element;
99 + while (currentElement !== null) {
100 + if (currentElement.type === ElementTypeSuspense) {
101 + nearestSuspenseElement = currentElement;
102 + break;
103 + } else if (currentElement.parentID > 0) {
104 + currentElement = store.getElementByID(currentElement.parentID);
105 + } else {
106 + currentElement = null;
107 + }
108 + }
109 +
110 + // If we didn't find a Suspense ancestor, we can't suspend.
111 + // Instead we can show a warning to the user.
112 + if (nearestSuspenseElement === null) {
113 + modalDialogDispatch({
114 + type: 'SHOW',
115 + content: <CannotSuspendWarningMessage />,
116 + });
117 + } else {
118 + const nearestSuspenseElementID = nearestSuspenseElement.id;
119 +
120 + // If we're suspending from an arbitary (non-Suspense) component, select the nearest Suspense element in the Tree.
121 + // This way when the fallback UI is shown and the current element is hidden, something meaningful is selected.
122 + if (nearestSuspenseElement !== element) {
123 + dispatch({
124 + type: 'SELECT_ELEMENT_BY_ID',
125 + payload: nearestSuspenseElementID,
126 + });
127 + }
128 +
129 + // Toggle suspended
130 + bridge.send('overrideSuspense', {
131 + id: nearestSuspenseElementID,
132 + rendererID: store.getRendererIDForElement(nearestSuspenseElementID),
133 + forceFallback: !isSuspended,
134 + });
135 + }
136 + }, [bridge, dispatch, element, isSuspended, modalDialogDispatch, store]);
137 +
138 if (element === null) {
139 return (
140 <div className={styles.SelectedElement}>
@@ -80,11 +143,6 @@ export default function SelectedElement(_: Props) {
143 );
144 }
145
83 - const canViewSource =
84 - inspectedElement &&
85 - inspectedElement.canViewSource &&
86 - viewElementSource !== null;
87 -
146 return (
147 <div className={styles.SelectedElement}>
148 <div className={styles.TitleRow}>
@@ -94,6 +152,20 @@ export default function SelectedElement(_: Props) {
152 </div>
153 </div>
154
155 + {canToggleSuspense && (
156 + <Toggle
157 + className={styles.IconButton}
158 + isChecked={isSuspended}
159 + onChange={toggleSuspended}
160 + title={
161 + isSuspended
162 + ? 'Unsuspend the selected component'
163 + : 'Suspend the selected component'
164 + }
165 + >
166 + <ButtonIcon type="suspend" />
167 + </Toggle>
168 + )}
169 <Button
170 className={styles.IconButton}
171 onClick={highlightElement}
@@ -265,3 +337,31 @@ function OwnerView({ displayName, id }: { displayName: string, id: number }) {
337 </button>
338 );
339 }
340 +
341 +function CannotSuspendWarningMessage() {
342 + const store = useContext(StoreContext);
343 + const areSuspenseElementsHidden = !!store.componentFilters.find(
344 + filter =>
345 + filter.type === ComponentFilterElementType &&
346 + filter.value === ElementTypeSuspense &&
347 + filter.isEnabled
348 + );
349 +
350 + // Has the user filted out Suspense nodes from the tree?
351 + // If so, the selected element might actually be in a Suspense tree after all.
352 + if (areSuspenseElementsHidden) {
353 + return (
354 + <div className={styles.CannotSuspendWarningMessage}>
355 + Suspended state cannot be toggled while Suspense components are hidden.
356 + Disable the filter and try agan.
357 + </div>
358 + );
359 + } else {
360 + return (
361 + <div className={styles.CannotSuspendWarningMessage}>
362 + The selected element is not within a Suspense container. Suspending it
363 + would cause an error.
364 + </div>
365 + );
366 + }
367 +}
src/devtools/views/Components/ToggleComponentFiltersModalButton.css renamed
src/devtools/views/Components/ToggleComponentFiltersModalButton.js
+2 -2
@@ -8,11 +8,11 @@ import Toggle from '../Toggle';
8 import ButtonIcon from '../ButtonIcon';
9 import Store from 'src/devtools/store';
10
11 -import styles from './ToggleCommitFilterModalButton.css';
11 +import styles from './ToggleComponentFiltersModalButton.css';
12
13 import type { ComponentFilter } from 'src/types';
14
15 -export default function ToggleCommitFilterModalButton() {
15 +export default function ToggleComponentFiltersModalButton() {
16 const store = useContext(StoreContext);
17
18 const { isModalShowing, setIsModalShowing } = useContext(
src/devtools/views/DevTools.js
+56 -53
@@ -16,6 +16,7 @@ import { SettingsContextController } from './Settings/SettingsContext';
16 import { TreeContextController } from './Components/TreeContext';
17 import ViewElementSourceContext from './Components/ViewElementSourceContext';
18 import { ProfilerContextController } from './Profiler/ProfilerContext';
19 +import { ModalDialogContextController } from './ModalDialog';
20 import ReactLogo from './ReactLogo';
21
22 import styles from './DevTools.css';
@@ -116,62 +117,64 @@ export default function DevTools({
117 return (
118 <BridgeContext.Provider value={bridge}>
119 <StoreContext.Provider value={store}>
119 - <SettingsContextController
120 - browserTheme={browserTheme}
121 - componentsPortalContainer={componentsPortalContainer}
122 - profilerPortalContainer={profilerPortalContainer}
123 - settingsPortalContainer={settingsPortalContainer}
124 - >
125 - <ViewElementSourceContext.Provider value={viewElementSource}>
126 - <TreeContextController>
127 - <ProfilerContextController>
128 - <div className={styles.DevTools}>
129 - {showTabBar && (
130 - <div className={styles.TabBar}>
131 - <ReactLogo />
132 - <span className={styles.DevToolsVersion}>
133 - {process.env.DEVTOOLS_VERSION}
134 - </span>
135 - <div className={styles.Spacer} />
136 - <TabBar
137 - currentTab={tab}
138 - id="DevTools"
139 - selectTab={setTab}
140 - size="large"
141 - tabs={
142 - supportsProfiling
143 - ? tabsWithProfiler
144 - : tabsWithoutProfiler
145 - }
120 + <ModalDialogContextController>
121 + <SettingsContextController
122 + browserTheme={browserTheme}
123 + componentsPortalContainer={componentsPortalContainer}
124 + profilerPortalContainer={profilerPortalContainer}
125 + settingsPortalContainer={settingsPortalContainer}
126 + >
127 + <ViewElementSourceContext.Provider value={viewElementSource}>
128 + <TreeContextController>
129 + <ProfilerContextController>
130 + <div className={styles.DevTools}>
131 + {showTabBar && (
132 + <div className={styles.TabBar}>
133 + <ReactLogo />
134 + <span className={styles.DevToolsVersion}>
135 + {process.env.DEVTOOLS_VERSION}
136 + </span>
137 + <div className={styles.Spacer} />
138 + <TabBar
139 + currentTab={tab}
140 + id="DevTools"
141 + selectTab={setTab}
142 + size="large"
143 + tabs={
144 + supportsProfiling
145 + ? tabsWithProfiler
146 + : tabsWithoutProfiler
147 + }
148 + />
149 + </div>
150 + )}
151 + <div
152 + className={styles.TabContent}
153 + hidden={tab !== 'components'}
154 + >
155 + <Components portalContainer={componentsPortalContainer} />
156 + </div>
157 + <div
158 + className={styles.TabContent}
159 + hidden={tab !== 'profiler'}
160 + >
161 + <Profiler
162 + portalContainer={profilerPortalContainer}
163 + supportsProfiling={supportsProfiling}
164 />
165 </div>
148 - )}
149 - <div
150 - className={styles.TabContent}
151 - hidden={tab !== 'components'}
152 - >
153 - <Components portalContainer={componentsPortalContainer} />
154 - </div>
155 - <div
156 - className={styles.TabContent}
157 - hidden={tab !== 'profiler'}
158 - >
159 - <Profiler
160 - portalContainer={profilerPortalContainer}
161 - supportsProfiling={supportsProfiling}
162 - />
163 - </div>
164 - <div
165 - className={styles.TabContent}
166 - hidden={tab !== 'settings'}
167 - >
168 - <Settings portalContainer={settingsPortalContainer} />
166 + <div
167 + className={styles.TabContent}
168 + hidden={tab !== 'settings'}
169 + >
170 + <Settings portalContainer={settingsPortalContainer} />
171 + </div>
172 </div>
170 - </div>
171 - </ProfilerContextController>
172 - </TreeContextController>
173 - </ViewElementSourceContext.Provider>
174 - </SettingsContextController>
173 + </ProfilerContextController>
174 + </TreeContextController>
175 + </ViewElementSourceContext.Provider>
176 + </SettingsContextController>
177 + </ModalDialogContextController>
178 </StoreContext.Provider>
179 </BridgeContext.Provider>
180 );
src/devtools/views/ModalDialog.css renamed
+3 -8
@@ -13,6 +13,7 @@
13 .Dialog {
14 position: relative;
15 z-index: 3;
16 + width: 25rem;
17 min-width: 20rem;
18 max-width: 100%;
19 display: inline-block;
@@ -22,18 +23,12 @@
23 border-radius: 0.25rem;
24 }
25
25 -.Header {
26 +.Title {
27 font-size: var(--font-size-sans-large);
28 margin-bottom: 0.5rem;
29 }
30
31 .Buttons {
32 text-align: right;
32 -}
33 -
34 -.ErrorMessage {
35 - margin: 0.5rem 0;
36 - color: var(--color-dim);
37 - font-family: var(--font-family-monospace);
38 - font-size: var(--font-size-monospace-normal);
33 + margin-top: 0.5rem;
34 }
src/devtools/views/ModalDialog.js new
+121
@@ -0,0 +1,121 @@
1 +// @flow
2 +
3 +import React, {
4 + createContext,
5 + useCallback,
6 + useContext,
7 + useMemo,
8 + useReducer,
9 + useRef,
10 +} from 'react';
11 +import Button from './Button';
12 +import { useModalDismissSignal } from './hooks';
13 +
14 +import styles from './ModalDialog.css';
15 +
16 +type DIALOG_ACTION_HIDE = {|
17 + type: 'HIDE',
18 +|};
19 +type DIALOG_ACTION_SHOW = {|
20 + type: 'SHOW',
21 + content: React$Node,
22 + title?: React$Node | null,
23 +|};
24 +
25 +type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
26 +
27 +type Dispatch = (action: Action) => void;
28 +
29 +type State = {|
30 + content: React$Node | null,
31 + isVisible: boolean,
32 + title: React$Node | null,
33 +|};
34 +
35 +type ModalDialogContextType = {|
36 + ...State,
37 + dispatch: Dispatch,
38 +|};
39 +
40 +const ModalDialogContext = createContext<ModalDialogContextType>(
41 + ((null: any): ModalDialogContextType)
42 +);
43 +ModalDialogContext.displayName = 'ModalDialogContext';
44 +
45 +function dialogReducer(state, action) {
46 + switch (action.type) {
47 + case 'HIDE':
48 + return {
49 + content: null,
50 + isVisible: false,
51 + title: null,
52 + };
53 + case 'SHOW':
54 + return {
55 + content: action.content,
56 + isVisible: true,
57 + title: action.title || null,
58 + };
59 + default:
60 + throw new Error(`Invalid action "${action.type}"`);
61 + }
62 +}
63 +
64 +type Props = {|
65 + children: React$Node,
66 +|};
67 +
68 +function ModalDialogContextController({ children }: Props) {
69 + const [state, dispatch] = useReducer<State, Action>(dialogReducer, {
70 + content: null,
71 + isVisible: false,
72 + title: null,
73 + });
74 +
75 + const value = useMemo<ModalDialogContextType>(
76 + () => ({
77 + content: state.content,
78 + isVisible: state.isVisible,
79 + title: state.title,
80 + dispatch,
81 + }),
82 + [state, dispatch]
83 + );
84 +
85 + return (
86 + <ModalDialogContext.Provider value={value}>
87 + {children}
88 + </ModalDialogContext.Provider>
89 + );
90 +}
91 +
92 +function ModalDialog(_: {||}) {
93 + const { isVisible } = useContext(ModalDialogContext);
94 + return isVisible ? <ModalDialogImpl /> : null;
95 +}
96 +
97 +function ModalDialogImpl(_: {||}) {
98 + const { content, dispatch, title } = useContext(ModalDialogContext);
99 + const dismissModal = useCallback(() => dispatch({ type: 'HIDE' }), [
100 + dispatch,
101 + ]);
102 + const modalRef = useRef<HTMLDivElement | null>(null);
103 +
104 + useModalDismissSignal(modalRef, dismissModal);
105 +
106 + return (
107 + <div className={styles.Background}>
108 + <div className={styles.Dialog} ref={modalRef}>
109 + {title !== null && <div className={styles.Title}>{title}</div>}
110 + {content}
111 + <div className={styles.Buttons}>
112 + <Button autoFocus onClick={dismissModal}>
113 + Okay
114 + </Button>
115 + </div>
116 + </div>
117 + </div>
118 + );
119 +}
120 +
121 +export { ModalDialog, ModalDialogContext, ModalDialogContextController };
src/devtools/views/Profiler/ImportFailedModal.js deleted
-35
@@ -1,35 +0,0 @@
1 -// @flow
2 -
3 -import React, { useCallback, useContext, useRef } from 'react';
4 -import { ImportFailedModalContext } from './ImportFailedModalContext';
5 -import Button from '../Button';
6 -import { useModalDismissSignal } from '../hooks';
7 -
8 -import styles from './ImportFailedModal.css';
9 -
10 -export default function ImportFailedModal(_: {||}) {
11 - const { importError, setImportError } = useContext(ImportFailedModalContext);
12 - const dismissModal = useCallback(() => setImportError(null), [
13 - setImportError,
14 - ]);
15 - const modalRef = useRef<HTMLDivElement | null>(null);
16 -
17 - useModalDismissSignal(modalRef, dismissModal);
18 -
19 - return (
20 - <div className={styles.Background}>
21 - <div className={styles.Dialog} ref={modalRef}>
22 - <div className={styles.Header}>Import failed</div>
23 - <div>The profiling data you selected cannot be imported.</div>
24 - {importError !== null && (
25 - <div className={styles.ErrorMessage}>{importError.message}</div>
26 - )}
27 - <div className={styles.Buttons}>
28 - <Button autoFocus onClick={dismissModal}>
29 - Okay
30 - </Button>
31 - </div>
32 - </div>
33 - </div>
34 - );
35 -}
src/devtools/views/Profiler/ImportFailedModalContext.js deleted
-37
@@ -1,37 +0,0 @@
1 -// @flow
2 -
3 -import React, { createContext, useMemo, useState } from 'react';
4 -
5 -type Context = {|
6 - importError: Error | null,
7 - isModalShowing: boolean,
8 - setImportError: (error: Error | null) => void,
9 -|};
10 -
11 -const ImportFailedModalContext = createContext<Context>(((null: any): Context));
12 -ImportFailedModalContext.displayName = 'ImportFailedModalContext';
13 -
14 -type Props = {|
15 - children: React$Node,
16 -|};
17 -
18 -function ImportFailedModalContextController({ children }: Props) {
19 - const [importError, setImportError] = useState<Error | null>(null);
20 -
21 - const value = useMemo(
22 - () => ({
23 - importError,
24 - isModalShowing: !!importError,
25 - setImportError,
26 - }),
27 - [importError]
28 - );
29 -
30 - return (
31 - <ImportFailedModalContext.Provider value={value}>
32 - {children}
33 - </ImportFailedModalContext.Provider>
34 - );
35 -}
36 -
37 -export { ImportFailedModalContext, ImportFailedModalContextController };
src/devtools/views/Profiler/Profiler.js
+9 -24
@@ -5,17 +5,13 @@ import {
5 CommitFilterModalContext,
6 CommitFilterModalContextController,
7 } from './CommitFilterModalContext';
8 -import {
9 - ImportFailedModalContext,
10 - ImportFailedModalContextController,
11 -} from './ImportFailedModalContext';
8 +import { ModalDialog } from '../ModalDialog';
9 import { ProfilerContext } from './ProfilerContext';
10 import TabBar from '../TabBar';
11 import ClearProfilingDataButton from './ClearProfilingDataButton';
12 import CommitFlamegraph from './CommitFlamegraph';
13 import CommitRanked from './CommitRanked';
14 import CommitFilterModal from './CommitFilterModal';
18 -import ImportFailedModal from './ImportFailedModal';
15 import Interactions from './Interactions';
16 import RecordToggle from './RecordToggle';
17 import ReloadAndProfileButton from './ReloadAndProfileButton';
@@ -40,20 +36,16 @@ function Profiler({ supportsProfiling }: Props) {
36
37 if (isProfiling || !rootHasProfilingData) {
38 return (
43 - <ImportFailedModalContextController>
44 - <NonSuspendingProfiler
45 - hasProfilingData={hasProfilingData}
46 - isProfiling={isProfiling}
47 - supportsProfiling={supportsProfiling}
48 - />
49 - </ImportFailedModalContextController>
39 + <NonSuspendingProfiler
40 + hasProfilingData={hasProfilingData}
41 + isProfiling={isProfiling}
42 + supportsProfiling={supportsProfiling}
43 + />
44 );
45 } else {
46 return (
47 <CommitFilterModalContextController>
54 - <ImportFailedModalContextController>
55 - <SuspendingProfiler />
56 - </ImportFailedModalContextController>
48 + <SuspendingProfiler />
49 </CommitFilterModalContextController>
50 );
51 }
@@ -83,10 +75,6 @@ function NonSuspendingProfiler({
75 view = <NoProfilingDataForRoot />;
76 }
77
86 - const { isModalShowing: isImportFailedModalShowing } = useContext(
87 - ImportFailedModalContext
88 - );
89 -
78 return (
79 <div className={styles.Profiler}>
80 <div className={styles.LeftColumn}>
@@ -107,7 +95,7 @@ function NonSuspendingProfiler({
95 </div>
96 <div className={styles.Content}>
97 {view}
110 - {isImportFailedModalShowing && <ImportFailedModal />}
98 + <ModalDialog />
99 </div>
100 </div>
101 </div>
@@ -133,9 +121,6 @@ function SuspendingProfiler() {
121 const { isModalShowing: isFilterModalShowing } = useContext(
122 CommitFilterModalContext
123 );
136 - const { isModalShowing: isImportFailedModalShowing } = useContext(
137 - ImportFailedModalContext
138 - );
124
125 let view = null;
126 switch (selectedTabID) {
@@ -195,7 +180,7 @@ function SuspendingProfiler() {
180 <div className={styles.Content}>
181 <Suspense fallback={<ContentFallback />}>{view}</Suspense>
182 {isFilterModalShowing && <CommitFilterModal />}
198 - {isImportFailedModalShowing && <ImportFailedModal />}
183 + <ModalDialog />
184 </div>
185 </div>
186 <div className={styles.RightColumn}>
src/devtools/views/Profiler/ProfilingImportExportButtons.css
+7
@@ -16,3 +16,10 @@
16 overflow: hidden;
17 clip: rect(1px, 1px, 1px, 1px);
18 }
19 +
20 +.ErrorMessage {
21 + margin: 0.5rem 0;
22 + color: var(--color-dim);
23 + font-family: var(--font-family-monospace);
24 + font-size: var(--font-size-monospace-normal);
25 +}
src/devtools/views/Profiler/ProfilingImportExportButtons.js
+15 -4
@@ -2,7 +2,7 @@
2
3 import React, { Fragment, useContext, useCallback, useRef } from 'react';
4 import { ProfilerContext } from './ProfilerContext';
5 -import { ImportFailedModalContext } from './ImportFailedModalContext';
5 +import { ModalDialogContext } from '../ModalDialog';
6 import Button from '../Button';
7 import ButtonIcon from '../ButtonIcon';
8 import { BridgeContext, StoreContext } from '../context';
@@ -21,7 +21,7 @@ export default function ProfilingImportExportButtons() {
21
22 const inputRef = useRef<HTMLInputElement | null>(null);
23
24 - const { setImportError } = useContext(ImportFailedModalContext);
24 + const { dispatch: modalDialogDispatch } = useContext(ModalDialogContext);
25
26 const downloadData = useCallback(() => {
27 if (rendererID === null || rootID === null) {
@@ -62,12 +62,23 @@ export default function ProfilingImportExportButtons() {
62
63 store.importedProfilingData = ((data: any): ImportedProfilingData);
64 } catch (error) {
65 - setImportError(error);
65 + modalDialogDispatch({
66 + type: 'SHOW',
67 + title: 'Import failed',
68 + content: (
69 + <Fragment>
70 + <div>The profiling data you selected cannot be imported.</div>
71 + {error !== null && (
72 + <div className={styles.ErrorMessage}>{error.message}</div>
73 + )}
74 + </Fragment>
75 + ),
76 + });
77 }
78 });
79 fileReader.readAsText(input.files[0]);
80 }
70 - }, [store, setImportError]);
81 + }, [modalDialogDispatch, store]);
82
83 return (
84 <Fragment>