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>