19
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
20
import StackTraceView from './StackTraceView';
21
import OwnerView from './OwnerView';
22
+import {meta} from '../../../hydration';
23
23
-import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
24
+import type {
25
+ InspectedElement,
26
+ SerializedAsyncInfo,
27
+} from 'react-devtools-shared/src/frontend/types';
28
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
25
-import type {SerializedAsyncInfo} from 'react-devtools-shared/src/frontend/types';
29
30
type RowProps = {
31
bridge: FrontendBridge,
34
store: Store,
35
asyncInfo: SerializedAsyncInfo,
36
index: number,
37
+ minTime: number,
38
+ maxTime: number,
39
};
40
41
function SuspendedByRow({
45
store,
46
asyncInfo,
47
index,
48
+ minTime,
49
+ maxTime,
50
}: RowProps) {
51
const [isOpen, setIsOpen] = useState(false);
52
const name = asyncInfo.awaited.name;
59
stack = asyncInfo.stack;
60
owner = asyncInfo.owner;
61
}
62
+ const start = asyncInfo.awaited.start;
63
+ const end = asyncInfo.awaited.end;
64
+ const timeScale = 100 / (maxTime - minTime);
65
+ let left = (start - minTime) * timeScale;
66
+ let width = (end - start) * timeScale;
67
+ if (width < 5) {
68
+ // Use at least a 5% width to avoid showing too small indicators.
69
+ width = 5;
70
+ if (left > 95) {
71
+ left = 95;
72
+ }
73
+ }
74
+
75
+ const value: any = asyncInfo.awaited.value;
76
+ const isErrored =
77
+ value !== null &&
78
+ typeof value === 'object' &&
79
+ value[meta.name] === 'rejected Thenable';
80
+
81
return (
82
<div className={styles.CollapsableRow}>
83
<Button
84
className={styles.CollapsableHeader}
85
onClick={() => setIsOpen(prevIsOpen => !prevIsOpen)}
60
- title={`${isOpen ? 'Collapse' : 'Expand'}`}>
86
+ title={name + ' — ' + (end - start).toFixed(2) + ' ms'}>
87
<ButtonIcon
88
className={styles.CollapsableHeaderIcon}
89
type={isOpen ? 'expanded' : 'collapsed'}
90
/>
91
<span className={styles.CollapsableHeaderTitle}>{name}</span>
92
+ <div className={styles.TimeBarContainer}>
93
+ <div
94
+ className={
95
+ !isErrored ? styles.TimeBarSpan : styles.TimeBarSpanErrored
96
+ }
97
+ style={{
98
+ left: left.toFixed(2) + '%',
99
+ width: width.toFixed(2) + '%',
100
+ }}
101
+ />
102
+ </div>
103
</Button>
104
{isOpen && (
105
<div className={styles.CollapsableContent}>
166
() => copy(serializeDataForCopy(suspendedBy)),
167
);
168
169
+ let minTime = Infinity;
170
+ let maxTime = -Infinity;
171
+ for (let i = 0; i < suspendedBy.length; i++) {
172
+ const asyncInfo: SerializedAsyncInfo = suspendedBy[i];
173
+ if (asyncInfo.awaited.start < minTime) {
174
+ minTime = asyncInfo.awaited.start;
175
+ }
176
+ if (asyncInfo.awaited.end > maxTime) {
177
+ maxTime = asyncInfo.awaited.end;
178
+ }
179
+ }
180
+
181
+ if (maxTime - minTime < 25) {
182
+ // Stretch the time span a bit to ensure that we don't show
183
+ // large bars that represent very small timespans.
184
+ minTime = maxTime - 25;
185
+ }
186
+
187
return (
188
<div>
189
<div className={styles.HeaderRow}>
201
element={element}
202
inspectedElement={inspectedElement}
203
store={store}
204
+ minTime={minTime}
205
+ maxTime={maxTime}
206
/>
207
))}
208
</div>