13
import Button from '../Button';
14
import ButtonIcon from '../ButtonIcon';
15
import KeyValue from './KeyValue';
16
-import {serializeDataForCopy} from '../utils';
16
+import {serializeDataForCopy, pluralize} from '../utils';
17
import Store from '../../store';
18
import styles from './InspectedElementSharedStyles.css';
19
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
44
index: number,
45
minTime: number,
46
maxTime: number,
47
+ skipName?: boolean,
48
};
49
50
function getShortDescription(name: string, description: string): string {
100
index,
101
minTime,
102
maxTime,
103
+ skipName,
104
}: RowProps) {
105
const [isOpen, setIsOpen] = useState(false);
106
const [openIsPending, startOpenTransition] = useTransition();
168
className={styles.CollapsableHeaderIcon}
169
type={isOpen ? 'expanded' : 'collapsed'}
170
/>
169
- <span className={styles.CollapsableHeaderTitle}>{name}</span>
170
- {shortDescription === '' ? null : (
171
+ <span className={styles.CollapsableHeaderTitle}>
172
+ {skipName ? shortDescription : name}
173
+ </span>
174
+ {skipName || shortDescription === '' ? null : (
175
<>
176
<span className={styles.CollapsableHeaderSeparator}>{' ('}</span>
177
<span className={styles.CollapsableHeaderTitle}>
335
return ioA.start - ioB.start;
336
}
337
338
+type GroupProps = {
339
+ bridge: FrontendBridge,
340
+ element: Element,
341
+ inspectedElement: InspectedElement,
342
+ store: Store,
343
+ name: string,
344
+ suspendedBy: Array<{
345
+ index: number,
346
+ value: SerializedAsyncInfo,
347
+ }>,
348
+ minTime: number,
349
+ maxTime: number,
350
+};
351
+
352
+function SuspendedByGroup({
353
+ bridge,
354
+ element,
355
+ inspectedElement,
356
+ store,
357
+ name,
358
+ suspendedBy,
359
+ minTime,
360
+ maxTime,
361
+}: GroupProps) {
362
+ const [isOpen, setIsOpen] = useState(false);
363
+ let start = Infinity;
364
+ let end = -Infinity;
365
+ let isRejected = false;
366
+ for (let i = 0; i < suspendedBy.length; i++) {
367
+ const asyncInfo: SerializedAsyncInfo = suspendedBy[i].value;
368
+ const ioInfo = asyncInfo.awaited;
369
+ if (ioInfo.start < start) {
370
+ start = ioInfo.start;
371
+ }
372
+ if (ioInfo.end > end) {
373
+ end = ioInfo.end;
374
+ }
375
+ const value: any = ioInfo.value;
376
+ if (
377
+ value !== null &&
378
+ typeof value === 'object' &&
379
+ value[meta.name] === 'rejected Thenable'
380
+ ) {
381
+ isRejected = true;
382
+ }
383
+ }
384
+ const timeScale = 100 / (maxTime - minTime);
385
+ let left = (start - minTime) * timeScale;
386
+ let width = (end - start) * timeScale;
387
+ if (width < 5) {
388
+ // Use at least a 5% width to avoid showing too small indicators.
389
+ width = 5;
390
+ if (left > 95) {
391
+ left = 95;
392
+ }
393
+ }
394
+ const pluralizedName = pluralize(name);
395
+ return (
396
+ <div className={styles.CollapsableRow}>
397
+ <Button
398
+ className={styles.CollapsableHeader}
399
+ onClick={() => {
400
+ setIsOpen(prevIsOpen => !prevIsOpen);
401
+ }}
402
+ title={pluralizedName}>
403
+ <ButtonIcon
404
+ className={styles.CollapsableHeaderIcon}
405
+ type={isOpen ? 'expanded' : 'collapsed'}
406
+ />
407
+ <span className={styles.CollapsableHeaderTitle}>{pluralizedName}</span>
408
+ <div className={styles.CollapsableHeaderFiller} />
409
+ {isOpen ? null : (
410
+ <div className={styles.TimeBarContainer}>
411
+ <div
412
+ className={
413
+ !isRejected ? styles.TimeBarSpan : styles.TimeBarSpanErrored
414
+ }
415
+ style={{
416
+ left: left.toFixed(2) + '%',
417
+ width: width.toFixed(2) + '%',
418
+ }}
419
+ />
420
+ </div>
421
+ )}
422
+ </Button>
423
+ {isOpen &&
424
+ suspendedBy.map(({value, index}) => (
425
+ <SuspendedByRow
426
+ key={index}
427
+ index={index}
428
+ asyncInfo={value}
429
+ bridge={bridge}
430
+ element={element}
431
+ inspectedElement={inspectedElement}
432
+ store={store}
433
+ minTime={minTime}
434
+ maxTime={maxTime}
435
+ skipName={true}
436
+ />
437
+ ))}
438
+ </div>
439
+ );
440
+}
441
+
442
export default function InspectedElementSuspendedBy({
443
bridge,
444
element,
498
suspendedBy === null ? [] : suspendedBy.map(withIndex);
499
sortedSuspendedBy.sort(compareTime);
500
501
+ // Organize into groups of consecutive entries with the same name.
502
+ const groups = [];
503
+ let currentGroup = null;
504
+ let currentGroupName = null;
505
+ for (let i = 0; i < sortedSuspendedBy.length; i++) {
506
+ const entry = sortedSuspendedBy[i];
507
+ const name = entry.value.awaited.name;
508
+ if (
509
+ currentGroupName !== name ||
510
+ !name ||
511
+ name === 'Promise' ||
512
+ currentGroup === null
513
+ ) {
514
+ // Create a new group.
515
+ currentGroupName = name;
516
+ currentGroup = [];
517
+ groups.push(currentGroup);
518
+ }
519
+ currentGroup.push(entry);
520
+ }
521
+
522
let unknownSuspenders = null;
523
switch (inspectedElement.unknownSuspenders) {
524
case UNKNOWN_SUSPENDERS_REASON_PRODUCTION:
559
<ButtonIcon type="copy" />
560
</Button>
561
</div>
433
- {sortedSuspendedBy.map(({value, index}) => (
434
- <SuspendedByRow
435
- key={index}
436
- index={index}
437
- asyncInfo={value}
438
- bridge={bridge}
439
- element={element}
440
- inspectedElement={inspectedElement}
441
- store={store}
442
- minTime={minTime}
443
- maxTime={maxTime}
444
- />
445
- ))}
562
+ {groups.length === 1
563
+ ? // If it's only one type of suspender we can flatten it.
564
+ groups[0].map(entry => (
565
+ <SuspendedByRow
566
+ key={entry.index}
567
+ index={entry.index}
568
+ asyncInfo={entry.value}
569
+ bridge={bridge}
570
+ element={element}
571
+ inspectedElement={inspectedElement}
572
+ store={store}
573
+ minTime={minTime}
574
+ maxTime={maxTime}
575
+ />
576
+ ))
577
+ : groups.map((entries, index) =>
578
+ entries.length === 1 ? (
579
+ <SuspendedByRow
580
+ key={entries[0].index}
581
+ index={entries[0].index}
582
+ asyncInfo={entries[0].value}
583
+ bridge={bridge}
584
+ element={element}
585
+ inspectedElement={inspectedElement}
586
+ store={store}
587
+ minTime={minTime}
588
+ maxTime={maxTime}
589
+ />
590
+ ) : (
591
+ <SuspendedByGroup
592
+ key={entries[0].index}
593
+ name={entries[0].value.awaited.name}
594
+ suspendedBy={entries}
595
+ bridge={bridge}
596
+ element={element}
597
+ inspectedElement={inspectedElement}
598
+ store={store}
599
+ minTime={minTime}
600
+ maxTime={maxTime}
601
+ />
602
+ ),
603
+ )}
604
{unknownSuspenders}
605
</div>
606
);