diff --git a/frontend/Node.js b/frontend/Node.js index f0eef23ef8..f82fff87d2 100644 --- a/frontend/Node.js +++ b/frontend/Node.js @@ -157,7 +157,9 @@ class Node extends React.Component { ensureInView() { var node = this.props.isBottomTagSelected ? this._tail : this._head; if (node != null) { - if (typeof node.scrollIntoView === 'function') { + if (typeof node.scrollIntoViewIfNeeded === 'function') { + node.scrollIntoViewIfNeeded(); + } else if (typeof node.scrollIntoView === 'function') { node.scrollIntoView({ // $FlowFixMe Flow does not realize block:"nearest" is a valid option block: 'nearest', @@ -167,6 +169,13 @@ class Node extends React.Component { } } + _setTailRef = tail => { + this._tail = tail; + }; + _setHeadRef = head => { + this._head = head; + }; + render() { const {theme} = this.context; const { @@ -251,7 +260,7 @@ class Node extends React.Component { } return (
this._head = h} + ref={this._setHeadRef} style={sharedHeadStyle} {...headEvents} > @@ -293,9 +302,9 @@ class Node extends React.Component { const isCollapsed = content === null || content === undefined; return (
-
this._head = h} style={sharedHeadStyle} {...headEvents}> +
< - {name} + {name} {node.get('key') && } @@ -323,7 +332,7 @@ class Node extends React.Component { const closeTag = ( </ - {name} + {name} > {selected && ((collapsed && !this.props.isBottomTagSelected) || this.props.isBottomTagSelected) &&  == $r @@ -335,7 +344,7 @@ class Node extends React.Component { const jsxOpenTagStyle = jsxTagStyle(inverted && (!isBottomTagSelected || collapsed), nodeType, theme); const head = ( -
this._head = h} style={sharedHeadStyle} {...headEvents}> +
{ {collapsed ? '▶' : '▼'} < - {name} + {name} {node.get('key') && } @@ -394,7 +403,7 @@ class Node extends React.Component { }}> {children.map(id => )}
-
this._tail = t} style={tailStyleActual} {...tailEvents}> +
{closeTag}
diff --git a/package.json b/package.json index 9b74be359f..daeb29dade 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "jest": "22.1.4", "json-loader": "0.5.4", "log-update": "^2.0.0", + "lru-cache": "^4.1.3", "memoize-one": "^3.1.1", "node-libs-browser": "0.5.3", "nullthrows": "^1.0.0", diff --git a/plugins/Profiler/ProfilerStore.js b/plugins/Profiler/ProfilerStore.js index 8035c9aaf6..818ff64548 100644 --- a/plugins/Profiler/ProfilerStore.js +++ b/plugins/Profiler/ProfilerStore.js @@ -16,8 +16,8 @@ import type {ChartType, Interaction, RootProfilerData, Snapshot} from './Profile const {List} = require('immutable'); const {EventEmitter} = require('events'); const {get, set} = require('../../utils/storage'); +const LRU = require('lru-cache'); -const LOCAL_STORAGE_CHART_TYPE_KEY = 'profiler:selectedChartType'; const LOCAL_STORAGE_COMMIT_THRESHOLD = 'profiler:commitThreshold'; const LOCAL_STORAGE_HIDE_COMMITS_BELOW_THRESHOLD = 'profiler:hideCommitsBelowThreshold'; const LOCAL_STORAGE_SHOW_NATIVE_NODES_KEY = 'profiler:showNativeNodes'; @@ -26,7 +26,7 @@ class ProfilerStore extends EventEmitter { _bridge: Bridge; _mainStore: Object; - cachedData = {}; + cachedData = LRU(50); // Evict items from the cache after this number commitThreshold: number = ((get(LOCAL_STORAGE_COMMIT_THRESHOLD, 0): any): number); hideCommitsBelowThreshold: boolean = ((get(LOCAL_STORAGE_HIDE_COMMITS_BELOW_THRESHOLD, false): any): boolean); isRecording: boolean = false; @@ -34,7 +34,7 @@ class ProfilerStore extends EventEmitter { processedInteractions: {[id: string]: Interaction} = {}; rootsToProfilerData: Map = new Map(); roots: List = new List(); - selectedChartType: ChartType = ((get(LOCAL_STORAGE_CHART_TYPE_KEY, 'flamegraph'): any): ChartType); + selectedChartType: ChartType = 'flamegraph'; selectedRoot: string | null = null; showNativeNodes: boolean = ((get(LOCAL_STORAGE_SHOW_NATIVE_NODES_KEY, false): any): boolean); @@ -54,26 +54,26 @@ class ProfilerStore extends EventEmitter { } cacheDataForSnapshot(snapshotIndex: number, snapshotRootID: string, key: string, data: any): void { - this.cachedData[`${snapshotIndex}-${snapshotRootID}-${key}`] = data; + this.cachedData.set(`${snapshotIndex}-${snapshotRootID}-${key}`, data); } cacheInteractionData(rootID: string, data: any): void { - this.cachedData[`${rootID}-interactions`] = data; + this.cachedData.set(`${rootID}-interactions`, data); } clearSnapshots = () => { - this.cachedData = {}; + this.cachedData.reset(); this.processedInteractions = {}; this.rootsToProfilerData = new Map(); this.emit('profilerData', this.rootsToProfilerData); }; getCachedDataForSnapshot(snapshotIndex: number, snapshotRootID: string, key: string): any { - return this.cachedData[`${snapshotIndex}-${snapshotRootID}-${key}`] || null; + return this.cachedData.get(`${snapshotIndex}-${snapshotRootID}-${key}`) || null; } getCachedInteractionData(rootID: string): any { - return this.cachedData[`${rootID}-interactions`] || null; + return this.cachedData.get(`${rootID}-interactions`) || null; } processInteraction(interaction: Interaction): Interaction { @@ -117,7 +117,6 @@ class ProfilerStore extends EventEmitter { setSelectedChartType(selectedChartType: ChartType) { this.selectedChartType = selectedChartType; this.emit('selectedChartType', selectedChartType); - set(LOCAL_STORAGE_CHART_TYPE_KEY, selectedChartType); } setShowNativeNodes(showNativeNodes: boolean) { diff --git a/plugins/Profiler/views/FiberRenderDurations.js b/plugins/Profiler/views/FiberRenderDurations.js index 823bcfffe7..5751befe5f 100644 --- a/plugins/Profiler/views/FiberRenderDurations.js +++ b/plugins/Profiler/views/FiberRenderDurations.js @@ -36,7 +36,7 @@ type ChartData = {| type ItemData = {| height: number, nodes: Array, - scaleY: (value: number) => number, + scaleY: (value: number, fallbackValue: number) => number, selectedSnapshot: Snapshot, selectSnapshot: SelectSnapshot, stopInspecting: Function, @@ -179,7 +179,7 @@ class ListItem extends PureComponent { const { height, nodes, scaleY, selectedSnapshot, selectSnapshot, stopInspecting, theme } = itemData; const node = nodes[index]; - const safeHeight = Math.max(minBarHeight, scaleY(node.value)); + const safeHeight = Math.max(minBarHeight, scaleY(node.value, minBarHeight)); // List items are absolutely positioned using the CSS "left" attribute. // The "top" value will always be 0. diff --git a/plugins/Profiler/views/InteractionTimeline.js b/plugins/Profiler/views/InteractionTimeline.js index 78967d58b0..4ead6fe0d0 100644 --- a/plugins/Profiler/views/InteractionTimeline.js +++ b/plugins/Profiler/views/InteractionTimeline.js @@ -18,14 +18,13 @@ import React, { PureComponent } from 'react'; import { FixedSizeList as List } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; import NoInteractionsMessage from './NoInteractionsMessage'; -import { scale } from './constants'; +import { getGradientColor, scale } from './constants'; const INTERACTION_SIZE = 4; const ITEM_SIZE = 25; const SNAPSHOT_SIZE = 10; type SelectInteraction = (interaction: Interaction) => void; -type ViewSnapshot = (snapshot: Snapshot) => void; type ChartItem = {| interaction: Interaction, @@ -35,6 +34,7 @@ type ChartItem = {| type ChartData = {| items: Array, + maxDuration: number, stopTime: number, |}; @@ -42,12 +42,11 @@ type ItemData = {| chartData: ChartData, labelColumnWidth: number, graphColumnWidth: number, - scaleX: (value: number) => number, + scaleX: (value: number, fallbackValue: number) => number, selectedInteraction: Interaction | null, selectedSnapshot: Snapshot, selectInteraction: SelectInteraction, theme: Theme, - viewSnapshot: ViewSnapshot, |}; type Props = {| @@ -55,12 +54,12 @@ type Props = {| getCachedInteractionData: GetCachedInteractionData, hasMultipleRoots: boolean, interactionsToSnapshots: Map>, + maxDuration: number, selectedInteraction: Interaction | null, selectedSnapshot: Snapshot, selectInteraction: SelectInteraction, theme: Theme, timestampsToInteractions: Map>, - viewSnapshot: ViewSnapshot, |}; const InteractionTimeline = ({ @@ -68,17 +67,17 @@ const InteractionTimeline = ({ getCachedInteractionData, hasMultipleRoots, interactionsToSnapshots, + maxDuration, selectedInteraction, selectedSnapshot, selectInteraction, theme, timestampsToInteractions, - viewSnapshot, }: Props) => { // Cache data in ProfilerStore so we only have to compute it the first time the interactions tab is shown let chartData = getCachedInteractionData(selectedSnapshot.root); if (chartData === null) { - chartData = getChartData(interactionsToSnapshots, timestampsToInteractions); + chartData = getChartData(interactionsToSnapshots, maxDuration, timestampsToInteractions); cacheInteractionData(selectedSnapshot.root, chartData); } @@ -94,7 +93,6 @@ const InteractionTimeline = ({ selectInteraction={selectInteraction} theme={theme} width={width} - viewSnapshot={viewSnapshot} /> )} @@ -109,7 +107,6 @@ type InteractionsListProps = {| selectedSnapshot: Snapshot, selectInteraction: SelectInteraction, theme: Theme, - viewSnapshot: ViewSnapshot, width: number, |}; @@ -153,7 +150,6 @@ class InteractionsList extends PureComponent { selectedSnapshot, selectInteraction, theme, - viewSnapshot, width, } = this.props; @@ -176,7 +172,6 @@ class InteractionsList extends PureComponent { selectedSnapshot, selectInteraction, theme, - viewSnapshot, width, ); @@ -214,44 +209,36 @@ type ListItemProps = {| |}; type ListItemState = {| isHovered: boolean, - hoveredSnapshot: Snapshot | null, |}; class ListItem extends PureComponent { state = { isHovered: false, - hoveredSnapshot: null, }; - handleMouseEnter = (snapshot: Snapshot | null) => this.setState({ - isHovered: true, - hoveredSnapshot: snapshot, - }); - + handleMouseEnter = () => this.setState({isHovered: true}); handleMouseLeave = () => this.setState({isHovered: false}); render() { const { data: itemData, index: itemIndex, style } = this.props; - const { isHovered, hoveredSnapshot } = this.state; + const { isHovered } = this.state; const { chartData, labelColumnWidth, scaleX, selectedInteraction, selectedSnapshot, theme } = itemData; - const { items } = chartData; + const { items, maxDuration } = chartData; const item: ChartItem = items[itemIndex]; const { interaction, lastSnapshotCommitTime } = item; - const showRowHover = isHovered && hoveredSnapshot === null; - return (
itemData.selectInteraction(interaction)} - onMouseEnter={() => this.handleMouseEnter(null)} + onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={{ ...style, display: 'flex', alignItems: 'center', - backgroundColor: showRowHover ? theme.state03 : (selectedInteraction === interaction ? theme.base01 : 'transparent'), + backgroundColor: isHovered ? theme.state03 : (selectedInteraction === interaction ? theme.base01 : 'transparent'), borderBottom: `1px solid ${theme.base01}`, cursor: 'pointer', }} @@ -265,8 +252,8 @@ class ListItem extends PureComponent { lineHeight: `${ITEM_SIZE}px`, boxSizing: 'border-box', padding: '0 0.25rem', - color: showRowHover ? theme.state00 : theme.base05, - textDecoration: showRowHover ? 'underline' : 'none', + color: isHovered ? theme.state00 : theme.base05, + textDecoration: isHovered ? 'underline' : 'none', userSelect: 'none', }} title={interaction.name} @@ -276,32 +263,34 @@ class ListItem extends PureComponent {
- {item.snapshots.map((snapshot, snapshotIndex) => ( -
itemData.viewSnapshot(snapshot)} - onMouseEnter={() => this.handleMouseEnter(snapshot)} - onMouseLeave={() => this.handleMouseEnter(null)} - style={{ - position: 'absolute', - left: `${labelColumnWidth + scaleX(snapshot.commitTime)}px`, - width: `${SNAPSHOT_SIZE}px`, - height: `${SNAPSHOT_SIZE}px`, - borderRadius: `${SNAPSHOT_SIZE}px`, - backgroundColor: hoveredSnapshot === snapshot || selectedSnapshot === snapshot ? theme.state00 : theme.base00, - border: `2px solid ${theme.state00}`, - boxSizing: 'border-box', - cursor: 'pointer', - }} - /> - ))} + {item.snapshots.map((snapshot, snapshotIndex) => { + // Guard against commits with duration 0 + const percentage = Math.min(1, Math.max(0, snapshot.duration / maxDuration)) || 0; + + return ( +
+ ); + })}
); } @@ -309,6 +298,7 @@ class ListItem extends PureComponent { const getChartData = memoize(( interactionsToSnapshots: Map>, + maxDuration: number, timestampsToInteractions: Map>, ): ChartData => { const items: Array = []; @@ -332,6 +322,7 @@ const getChartData = memoize(( return { items, + maxDuration, stopTime, }; }); @@ -342,7 +333,6 @@ const getItemData = memoize(( selectedSnapshot: Snapshot, selectInteraction: SelectInteraction, theme: Theme, - viewSnapshot: ViewSnapshot, width: number, ): ItemData => { const labelColumnWidth = Math.min(200, width / 5); @@ -357,7 +347,6 @@ const getItemData = memoize(( selectedSnapshot, selectInteraction, theme, - viewSnapshot, }; }); diff --git a/plugins/Profiler/views/NoInteractionsMessage.js b/plugins/Profiler/views/NoInteractionsMessage.js index e7e40495ab..fbc93ead53 100644 --- a/plugins/Profiler/views/NoInteractionsMessage.js +++ b/plugins/Profiler/views/NoInteractionsMessage.js @@ -49,7 +49,7 @@ export default ({ hasMultipleRoots, height, width }: Props) => { )}

- Learn more about the interaction-tracking API here + Learn more about the interaction tracking API here .

diff --git a/plugins/Profiler/views/NoSnapshotDataMessage.js b/plugins/Profiler/views/NoSnapshotDataMessage.js index d239c16561..9c0b3cdb78 100644 --- a/plugins/Profiler/views/NoSnapshotDataMessage.js +++ b/plugins/Profiler/views/NoSnapshotDataMessage.js @@ -35,6 +35,7 @@ export default ({ height, width }: Props) => (

This can indicate that a render occurred too quickly for the timing API to measure. + Try selecting another commit in the upper, right-hand corner.

); diff --git a/plugins/Profiler/views/ProfilerFiberDetailPane.js b/plugins/Profiler/views/ProfilerFiberDetailPane.js index 72fe38fa59..a777e941ba 100644 --- a/plugins/Profiler/views/ProfilerFiberDetailPane.js +++ b/plugins/Profiler/views/ProfilerFiberDetailPane.js @@ -44,29 +44,33 @@ const ProfilerFiberDetailPane = ({ }: Props) => (
-
- {name} -
-
+
+
+ {name} +
void; type Props = {| interaction: Interaction, + maxDuration: number, selectedSnapshot: Snapshot | null, snapshots: Set, theme: Theme, @@ -30,6 +31,7 @@ type Props = {| const ProfilerInteractionDetailPane = ({ interaction, + maxDuration, selectedSnapshot, snapshots, theme, @@ -41,8 +43,8 @@ const ProfilerInteractionDetailPane = ({
- {interaction.name} + {interaction.name} at {formatTime(interaction.timestamp)}s
-
Timestamp: {formatTime(interaction.timestamp)}s
-
Renders:
+
+ Commits: +
    viewSnapshot(snapshot)} previousTimestamp={previousTimestamp} selectedSnapshot={selectedSnapshot} @@ -89,6 +95,7 @@ const ProfilerInteractionDetailPane = ({ const SnapshotLink = Hoverable(({ isHovered, + maxDuration, onClick, onMouseEnter, onMouseLeave, @@ -98,9 +105,6 @@ const SnapshotLink = Hoverable(({ theme, viewSnapshot, }) => { - const cpuPercentage = Math.max(0, Math.min(snapshot.duration / (snapshot.commitTime - previousTimestamp))); - const cpuSvg = CPU_SVGS[Math.round(cpuPercentage * (CPU_SVGS.length - 1))]; - return (
  • - {cpuSvg} +
      -
    • Timestamp: {formatTime(snapshot.commitTime)}s
    • -
    • Duration: {formatDuration(snapshot.duration)}ms
    • -
    • CPU: {formatPercentage(cpuPercentage)}%
    • +
    • + Timestamp: {formatTime(snapshot.commitTime)}s +
    • +
    • + Duration: {formatDuration(snapshot.duration)}ms +
  • ); }); -const CPU_STYLE = { - width: '1.5rem', - height: '1.5rem', - fill: 'currentColor', - marginRight: '0.5rem', -}; - -const CPU_0 = ( - - - -); - -const CPU_25 = ( - - - - -); - -const CPU_50 = ( - - - - -); - -const CPU_75 = ( - - - - -); - -const CPU_100 = ( - - - -); - -const CPU_SVGS = [CPU_0, CPU_25, CPU_50, CPU_75, CPU_100]; - export default ProfilerInteractionDetailPane; diff --git a/plugins/Profiler/views/ProfilerSnapshotDetailPane.js b/plugins/Profiler/views/ProfilerSnapshotDetailPane.js index 4e2c6c5a41..53e909d410 100644 --- a/plugins/Profiler/views/ProfilerSnapshotDetailPane.js +++ b/plugins/Profiler/views/ProfilerSnapshotDetailPane.js @@ -34,8 +34,8 @@ const ProfilerSnapshotDetailPane = ({
    { const snapshot = snapshots[snapshotIndex]; const snapshotFiber = selectedFiberID && snapshot.nodes.get(selectedFiberID) || null; + const maxDuration = getMaxDuration(snapshots); let content; if (isRecording) { @@ -238,12 +240,12 @@ class ProfilerTab extends React.Component { getCachedInteractionData={getCachedInteractionData} hasMultipleRoots={hasMultipleRoots} interactionsToSnapshots={interactionsToSnapshots} + maxDuration={maxDuration} selectedInteraction={selectedInteraction} selectedSnapshot={snapshot} selectInteraction={this.selectInteraction} theme={theme} timestampsToInteractions={timestampsToInteractions} - viewSnapshot={this.viewSnapshot} /> ); } else { @@ -296,6 +298,7 @@ class ProfilerTab extends React.Component { details = ( )} theme={theme} diff --git a/plugins/Profiler/views/ProfilerTabToolbar.js b/plugins/Profiler/views/ProfilerTabToolbar.js index 6151f86fa8..5d9012ecf8 100644 --- a/plugins/Profiler/views/ProfilerTabToolbar.js +++ b/plugins/Profiler/views/ProfilerTabToolbar.js @@ -97,6 +97,7 @@ const ProfilerTabToolbar = ({ position: 'relative', boxSizing: 'border-box', width, + userSelect: 'none', }}> number, + scaleX: (value: number, fallbackValue: number) => number, selectedFiberID: string | null, selectFiber: SelectOrInspectFiber, snapshot: Snapshot, @@ -174,7 +174,7 @@ const Flamegraph = ({ // If a commit is small and fast enough, it's possible for it to contain no base time values > 0. // In this case, we could only display an empty graph. - if (flameGraphDepth === 0 || itemData.maxTreeBaseDuration === 0) { + if (flameGraphDepth === 0) { return ; } @@ -214,7 +214,7 @@ class ListItem extends PureComponent { const { index, style } = this.props; const itemData: ItemData = ((this.props.data: any): ItemData); - const { flamegraphData, scaleX, selectedFiberID, snapshot } = itemData; + const { flamegraphData, scaleX, selectedFiberID, snapshot, width } = itemData; const { lazyIDToDepthMap, lazyIDToXMap, maxDuration } = flamegraphData; const { committedNodes, nodes } = snapshot; @@ -230,7 +230,7 @@ class ListItem extends PureComponent { let focusedNodeX = 0; if (selectedFiberID !== null) { focusedNodeIndex = lazyIDToDepthMap[selectedFiberID] || 0; - focusedNodeX = scaleX(lazyIDToXMap[selectedFiberID]) || 0; + focusedNodeX = scaleX(lazyIDToXMap[selectedFiberID], 0) || 0; } return ( @@ -238,7 +238,7 @@ class ListItem extends PureComponent { {ids.map(id => { const fiber = nodes.get(id); const treeBaseDuration = fiber.get('treeBaseDuration'); - const nodeWidth = scaleX(treeBaseDuration); + const nodeWidth = scaleX(treeBaseDuration, width); // Filter out nodes that are too small to see or click. // This also helps render large trees faster. @@ -246,12 +246,12 @@ class ListItem extends PureComponent { return null; } - const nodeX = scaleX(lazyIDToXMap[id]); + const nodeX = scaleX(lazyIDToXMap[id], 0); // Filter out nodes that are outside of the horizontal window. if ( nodeX + nodeWidth < focusedNodeX || - nodeX > focusedNodeX + itemData.width + nodeX > focusedNodeX + width ) { return null; } diff --git a/plugins/Profiler/views/SnapshotRanked.js b/plugins/Profiler/views/SnapshotRanked.js index ba64395f63..281550ce45 100644 --- a/plugins/Profiler/views/SnapshotRanked.js +++ b/plugins/Profiler/views/SnapshotRanked.js @@ -37,10 +37,11 @@ type ItemData = {| inspectFiber: SelectOrInspectFiber, nodes: Array, maxValue: number, - scaleX: (value: number) => number, + scaleX: (value: number, fallbackValue: number) => number, selectFiber: SelectOrInspectFiber, snapshot: Snapshot, theme: Theme, + width: number, |}; type Props = {| @@ -182,7 +183,7 @@ class ListItem extends PureComponent { const node = data.nodes[index]; - const { scaleX } = data; + const { scaleX, width } = data; // List items are absolutely positioned using the CSS "top" attribute. // The "left" value will always be 0. @@ -201,7 +202,7 @@ class ListItem extends PureComponent { onDoubleClick={this.handleDoubleClick} theme={data.theme} title={node.title} - width={Math.max(minBarWidth, scaleX(node.value))} + width={Math.max(minBarWidth, scaleX(node.value, width))} x={0} y={top} /> @@ -227,6 +228,7 @@ const getItemData = memoize(( selectFiber, snapshot, theme, + width, })); const getNodeIndex = memoize((rankedData: RankedData, id: string | null): number => { @@ -247,10 +249,7 @@ const convertSnapshotToChartData = (snapshot: Snapshot, showNativeNodes: boolean .filter(nodeID => { const node = snapshot.nodes.get(nodeID); const nodeType = node && node.get('nodeType'); - return ( - (nodeType === 'Composite' || (nodeType === 'Native' && showNativeNodes)) && - node.get('actualDuration') > 0 - ); + return (nodeType === 'Composite' || (nodeType === 'Native' && showNativeNodes)); }) .map((nodeID, index) => { const node = snapshot.nodes.get(nodeID).toJSON(); diff --git a/plugins/Profiler/views/constants.js b/plugins/Profiler/views/constants.js index b6a7b36893..5cf953e43b 100644 --- a/plugins/Profiler/views/constants.js +++ b/plugins/Profiler/views/constants.js @@ -29,8 +29,10 @@ export const minBarWidth = 5; export const textHeight = 18; export const scale = (minValue: number, maxValue: number, minRange: number, maxRange: number) => - (value: number) => - ((value - minValue) / (maxValue - minValue)) * (maxRange - minRange); + (value: number, fallbackValue: number) => + maxValue - minValue === 0 + ? fallbackValue + : ((value - minValue) / (maxValue - minValue)) * (maxRange - minRange); const gradientMaxIndex = gradient.length - 1; export const getGradientColor = (value: number) => { @@ -50,6 +52,10 @@ export const formatDuration = (duration: number) => Math.round(duration * 10) / export const formatPercentage = (percentage: number) => Math.round(percentage * 100); export const formatTime = (timestamp: number) => Math.round(Math.round(timestamp) / 100) / 10; +export const getMaxDuration = (snapshots: Array): number => + snapshots.reduce((maxDuration: number, snapshot: Snapshot) => + Math.max(maxDuration, snapshot.duration || 0), 0); + type FilteredSnapshotData = {| snapshotIndex: number, snapshots: Array, diff --git a/yarn.lock b/yarn.lock index c591900478..0bda58c64c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4769,6 +4769,13 @@ lru-cache@^4.0.0, lru-cache@^4.0.1: pseudomap "^1.0.1" yallist "^2.0.0" +lru-cache@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + make-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" @@ -5580,7 +5587,7 @@ prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" -pseudomap@^1.0.1: +pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -7334,6 +7341,10 @@ yallist@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.0.0.tgz#306c543835f09ee1a4cb23b7bce9ab341c91cdd4" +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + yargs-parser@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"