/
ashipov
/
react
Обзор
Документация
Войти
/
ashipov
/
react
Код
Запросы
0
Задачи 2.0
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
packages/react-devtools-timeline/src/EventTooltip.js
513 строк
13 KB
Sophie Alpert
Use .slice() for all substring-ing (#26677)
20 апр 2023, 00:26
Не верифицирован
20 апр 2023, 00:26
767f522
Код
Авторство
О чём код?
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import type {Point} from './view-base'; import type { FlamechartStackFrame, NativeEvent, NetworkMeasure, ReactComponentMeasure, ReactEventInfo, ReactMeasure, ReactMeasureType, SchedulingEvent, Snapshot, SuspenseEvent, ThrownError, TimelineData, UserTimingMark, } from './types'; import * as React from 'react'; import { formatDuration, formatTimestamp, trimString, getSchedulingEventLabel, } from './utils/formatting'; import {getBatchRange} from './utils/getBatchRange'; import useSmartTooltip from './utils/useSmartTooltip'; import styles from './EventTooltip.css'; const MAX_TOOLTIP_TEXT_LENGTH = 60; type Props = { canvasRef: {current: HTMLCanvasElement | null}, data: TimelineData, height: number, hoveredEvent: ReactEventInfo | null, origin: Point, width: number, }; function getReactMeasureLabel(type: ReactMeasureType): string | null { switch (type) { case 'commit': return 'react commit'; case 'render-idle': return 'react idle'; case 'render': return 'react render'; case 'layout-effects': return 'react layout effects'; case 'passive-effects': return 'react passive effects'; default: return null; } } export default function EventTooltip({ canvasRef, data, height, hoveredEvent, origin, width, }: Props): React.Node { const ref = useSmartTooltip({ canvasRef, mouseX: origin.x, mouseY: origin.y, }); if (hoveredEvent === null) { return null; } const { componentMeasure, flamechartStackFrame, measure, nativeEvent, networkMeasure, schedulingEvent, snapshot, suspenseEvent, thrownError, userTimingMark, } = hoveredEvent; let content = null; if (componentMeasure !== null) { content = ( <TooltipReactComponentMeasure componentMeasure={componentMeasure} /> ); } else if (nativeEvent !== null) { content = <TooltipNativeEvent nativeEvent={nativeEvent} />; } else if (networkMeasure !== null) { content = <TooltipNetworkMeasure networkMeasure={networkMeasure} />; } else if (schedulingEvent !== null) { content = ( <TooltipSchedulingEvent data={data} schedulingEvent={schedulingEvent} /> ); } else if (snapshot !== null) { content = ( <TooltipSnapshot height={height} snapshot={snapshot} width={width} /> ); } else if (suspenseEvent !== null) { content = <TooltipSuspenseEvent suspenseEvent={suspenseEvent} />; } else if (measure !== null) { content = <TooltipReactMeasure data={data} measure={measure} />; } else if (flamechartStackFrame !== null) { content = <TooltipFlamechartNode stackFrame={flamechartStackFrame} />; } else if (userTimingMark !== null) { content = <TooltipUserTimingMark mark={userTimingMark} />; } else if (thrownError !== null) { content = <TooltipThrownError thrownError={thrownError} />; } if (content !== null) { return ( <div className={styles.Tooltip} ref={ref}> {content} </div> ); } else { return null; } } const TooltipReactComponentMeasure = ({ componentMeasure, }: { componentMeasure: ReactComponentMeasure, }) => { const {componentName, duration, timestamp, type, warning} = componentMeasure; let label = componentName; switch (type) { case 'render': label += ' rendered'; break; case 'layout-effect-mount': label += ' mounted layout effect'; break; case 'layout-effect-unmount': label += ' unmounted layout effect'; break; case 'passive-effect-mount': label += ' mounted passive effect'; break; case 'passive-effect-unmount': label += ' unmounted passive effect'; break; } return ( <> <div className={styles.TooltipSection}> {trimString(label, 768)} <div className={styles.Divider} /> <div className={styles.DetailsGrid}> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> <div className={styles.DetailsGridLabel}>Duration:</div> <div>{formatDuration(duration)}</div> </div> </div> {warning !== null && ( <div className={styles.TooltipWarningSection}> <div className={styles.WarningText}>{warning}</div> </div> )} </> ); }; const TooltipFlamechartNode = ({ stackFrame, }: { stackFrame: FlamechartStackFrame, }) => { const {name, timestamp, duration, locationLine, locationColumn} = stackFrame; return ( <div className={styles.TooltipSection}> <span className={styles.FlamechartStackFrameName}>{name}</span> <div className={styles.DetailsGrid}> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> <div className={styles.DetailsGridLabel}>Duration:</div> <div>{formatDuration(duration)}</div> {(locationLine !== undefined || locationColumn !== undefined) && ( <> <div className={styles.DetailsGridLabel}>Location:</div> <div> line {locationLine}, column {locationColumn} </div> </> )} </div> </div> ); }; const TooltipNativeEvent = ({nativeEvent}: {nativeEvent: NativeEvent}) => { const {duration, timestamp, type, warning} = nativeEvent; return ( <> <div className={styles.TooltipSection}> <span className={styles.NativeEventName}>{trimString(type, 768)}</span> event <div className={styles.Divider} /> <div className={styles.DetailsGrid}> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> <div className={styles.DetailsGridLabel}>Duration:</div> <div>{formatDuration(duration)}</div> </div> </div> {warning !== null && ( <div className={styles.TooltipWarningSection}> <div className={styles.WarningText}>{warning}</div> </div> )} </> ); }; const TooltipNetworkMeasure = ({ networkMeasure, }: { networkMeasure: NetworkMeasure, }) => { const { finishTimestamp, lastReceivedDataTimestamp, priority, sendRequestTimestamp, url, } = networkMeasure; let urlToDisplay = url; if (urlToDisplay.length > MAX_TOOLTIP_TEXT_LENGTH) { const half = Math.floor(MAX_TOOLTIP_TEXT_LENGTH / 2); urlToDisplay = url.slice(0, half) + '…' + url.slice(url.length - half); } const timestampBegin = sendRequestTimestamp; const timestampEnd = finishTimestamp || lastReceivedDataTimestamp; const duration = timestampEnd > 0 ? formatDuration(finishTimestamp - timestampBegin) : '(incomplete)'; return ( <div className={styles.SingleLineTextSection}> {duration} <span className={styles.DimText}>{priority}</span>{' '} {urlToDisplay} </div> ); }; const TooltipSchedulingEvent = ({ data, schedulingEvent, }: { data: TimelineData, schedulingEvent: SchedulingEvent, }) => { const label = getSchedulingEventLabel(schedulingEvent); if (!label) { if (__DEV__) { console.warn( 'Unexpected schedulingEvent type "%s"', schedulingEvent.type, ); } return null; } let laneLabels = null; let lanes = null; switch (schedulingEvent.type) { case 'schedule-render': case 'schedule-state-update': case 'schedule-force-update': lanes = schedulingEvent.lanes; laneLabels = lanes.map( lane => ((data.laneToLabelMap.get(lane): any): string), ); break; } const {componentName, timestamp, warning} = schedulingEvent; return ( <> <div className={styles.TooltipSection}> {componentName && ( <span className={styles.ComponentName}> {trimString(componentName, 100)} </span> )} {label} <div className={styles.Divider} /> <div className={styles.DetailsGrid}> {laneLabels !== null && lanes !== null && ( <> <div className={styles.DetailsGridLabel}>Lanes:</div> <div> {laneLabels.join(', ')} ({lanes.join(', ')}) </div> </> )} <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> </div> </div> {warning !== null && ( <div className={styles.TooltipWarningSection}> <div className={styles.WarningText}>{warning}</div> </div> )} </> ); }; const TooltipSnapshot = ({ height, snapshot, width, }: { height: number, snapshot: Snapshot, width: number, }) => { const aspectRatio = snapshot.width / snapshot.height; // Zoomed in view should not be any bigger than the DevTools viewport. let safeWidth = snapshot.width; let safeHeight = snapshot.height; if (safeWidth > width) { safeWidth = width; safeHeight = safeWidth / aspectRatio; } if (safeHeight > height) { safeHeight = height; safeWidth = safeHeight * aspectRatio; } return ( <img className={styles.Image} src={snapshot.imageSource} style={{height: safeHeight, width: safeWidth}} /> ); }; const TooltipSuspenseEvent = ({ suspenseEvent, }: { suspenseEvent: SuspenseEvent, }) => { const { componentName, duration, phase, promiseName, resolution, timestamp, warning, } = suspenseEvent; let label = 'suspended'; if (phase !== null) { label += ` during ${phase}`; } return ( <> <div className={styles.TooltipSection}> {componentName && ( <span className={styles.ComponentName}> {trimString(componentName, 100)} </span> )} {label} <div className={styles.Divider} /> <div className={styles.DetailsGrid}> {promiseName !== null && ( <> <div className={styles.DetailsGridLabel}>Resource:</div> <div className={styles.DetailsGridLongValue}>{promiseName}</div> </> )} <div className={styles.DetailsGridLabel}>Status:</div> <div>{resolution}</div> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> {duration !== null && ( <> <div className={styles.DetailsGridLabel}>Duration:</div> <div>{formatDuration(duration)}</div> </> )} </div> </div> {warning !== null && ( <div className={styles.TooltipWarningSection}> <div className={styles.WarningText}>{warning}</div> </div> )} </> ); }; const TooltipReactMeasure = ({ data, measure, }: { data: TimelineData, measure: ReactMeasure, }) => { const label = getReactMeasureLabel(measure.type); if (!label) { if (__DEV__) { console.warn('Unexpected measure type "%s"', measure.type); } return null; } const {batchUID, duration, timestamp, lanes} = measure; const [startTime, stopTime] = getBatchRange(batchUID, data); const laneLabels = lanes.map( lane => ((data.laneToLabelMap.get(lane): any): string), ); return ( <div className={styles.TooltipSection}> <span className={styles.ReactMeasureLabel}>{label}</span> <div className={styles.Divider} /> <div className={styles.DetailsGrid}> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> {measure.type !== 'render-idle' && ( <> <div className={styles.DetailsGridLabel}>Duration:</div> <div>{formatDuration(duration)}</div> </> )} <div className={styles.DetailsGridLabel}>Batch duration:</div> <div>{formatDuration(stopTime - startTime)}</div> <div className={styles.DetailsGridLabel}> Lane{lanes.length === 1 ? '' : 's'}: </div> <div> {laneLabels.length > 0 ? `${laneLabels.join(', ')} (${lanes.join(', ')})` : lanes.join(', ')} </div> </div> </div> ); }; const TooltipUserTimingMark = ({mark}: {mark: UserTimingMark}) => { const {name, timestamp} = mark; return ( <div className={styles.TooltipSection}> <span className={styles.UserTimingLabel}>{name}</span> <div className={styles.Divider} /> <div className={styles.DetailsGrid}> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> </div> </div> ); }; const TooltipThrownError = ({thrownError}: {thrownError: ThrownError}) => { const {componentName, message, phase, timestamp} = thrownError; const label = `threw an error during ${phase}`; return ( <div className={styles.TooltipSection}> {componentName && ( <span className={styles.ComponentName}> {trimString(componentName, 100)} </span> )} <span className={styles.UserTimingLabel}>{label}</span> <div className={styles.Divider} /> <div className={styles.DetailsGrid}> <div className={styles.DetailsGridLabel}>Timestamp:</div> <div>{formatTimestamp(timestamp)}</div> {message !== '' && ( <> <div className={styles.DetailsGridLabel}>Error:</div> <div>{message}</div> </> )} </div> </div> ); };