@samitouri / QOS-React-2 / commits / 6207743168

[DevTools] Allow to continue dragging when leaving profiler picker (#18852)

Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>

Nick Reiley committed May 8, 2020 at 23:19 UTC 62077431686a5c276a37f185fc5ebd1e128a7601
2 files changed +78 -40
packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitList.js
+64 -31
@@ -8,7 +8,7 @@
8 */
9
10 import * as React from 'react';
11 -import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
11 +import {useEffect, useMemo, useRef, useState} from 'react';
12 import AutoSizer from 'react-virtualized-auto-sizer';
13 import {FixedSizeList} from 'react-window';
14 import SnapshotCommitListItem from './SnapshotCommitListItem';
@@ -20,11 +20,11 @@ export type ItemData = {|
20 commitDurations: Array<number>,
21 commitTimes: Array<number>,
22 filteredCommitIndices: Array<number>,
23 - isMouseDown: boolean,
23 maxDuration: number,
24 selectedCommitIndex: number | null,
25 selectedFilteredCommitIndex: number | null,
26 selectCommitIndex: (index: number) => void,
27 + startCommitDrag: (newDragState: DragState) => void,
28 |};
29
30 type Props = {|
@@ -73,6 +73,12 @@ type ListProps = {|
73 width: number,
74 |};
75
76 +type DragState = {
77 + commitIndex: number,
78 + left: number,
79 + sizeIncrement: number,
80 +};
81 +
82 function List({
83 commitDurations,
84 selectedCommitIndex,
@@ -97,28 +103,6 @@ function List({
103 }
104 }, [listRef, selectedFilteredCommitIndex]);
105
100 - // When the mouse is down, dragging over a commit should auto-select it.
101 - // This provides a nice way for users to swipe across a range of commits to compare them.
102 - const [isMouseDown, setIsMouseDown] = useState(false);
103 - const handleMouseDown = useCallback(() => {
104 - setIsMouseDown(true);
105 - }, []);
106 - const handleMouseUp = useCallback(() => {
107 - setIsMouseDown(false);
108 - }, []);
109 - useEffect(() => {
110 - if (divRef.current === null) {
111 - return () => {};
112 - }
113 -
114 - // It's important to listen to the ownerDocument to support the browser extension.
115 - // Here we use portals to render individual tabs (e.g. Profiler),
116 - // and the root document might belong to a different window.
117 - const ownerDocument = divRef.current.ownerDocument;
118 - ownerDocument.addEventListener('mouseup', handleMouseUp);
119 - return () => ownerDocument.removeEventListener('mouseup', handleMouseUp);
120 - }, [divRef, handleMouseUp]);
121 -
106 const itemSize = useMemo(
107 () => Math.max(minBarWidth, width / filteredCommitIndices.length),
108 [filteredCommitIndices, width],
@@ -128,23 +112,76 @@ function List({
112 [commitDurations],
113 );
114
115 + const maxCommitIndex = filteredCommitIndices.length - 1;
116 +
117 + const [dragState, setDragState] = useState<DragState | null>(null);
118 +
119 + const handleDragCommit = ({buttons, pageX}: any) => {
120 + if (buttons === 0) {
121 + setDragState(null);
122 + return;
123 + }
124 +
125 + if (dragState !== null) {
126 + const {commitIndex, left, sizeIncrement} = dragState;
127 +
128 + let newCommitIndex = commitIndex;
129 + let newCommitLeft = left;
130 +
131 + if (pageX < newCommitLeft) {
132 + while (pageX < newCommitLeft) {
133 + newCommitLeft -= sizeIncrement;
134 + newCommitIndex -= 1;
135 + }
136 + } else {
137 + let newCommitRectRight = newCommitLeft + sizeIncrement;
138 + while (pageX > newCommitRectRight) {
139 + newCommitRectRight += sizeIncrement;
140 + newCommitIndex += 1;
141 + }
142 + }
143 +
144 + if (newCommitIndex < 0) {
145 + newCommitIndex = 0;
146 + } else if (newCommitIndex > maxCommitIndex) {
147 + newCommitIndex = maxCommitIndex;
148 + }
149 +
150 + selectCommitIndex(newCommitIndex);
151 + }
152 + };
153 +
154 + useEffect(() => {
155 + if (dragState === null) {
156 + return;
157 + }
158 +
159 + const element = divRef.current;
160 + if (element !== null) {
161 + const ownerDocument = element.ownerDocument;
162 + ownerDocument.addEventListener('mousemove', handleDragCommit);
163 + return () => {
164 + ownerDocument.removeEventListener('mousemove', handleDragCommit);
165 + };
166 + }
167 + }, [dragState]);
168 +
169 // Pass required contextual data down to the ListItem renderer.
170 const itemData = useMemo<ItemData>(
171 () => ({
172 commitDurations,
173 commitTimes,
174 filteredCommitIndices,
137 - isMouseDown,
175 maxDuration,
176 selectedCommitIndex,
177 selectedFilteredCommitIndex,
178 selectCommitIndex,
179 + startCommitDrag: setDragState,
180 }),
181 [
182 commitDurations,
183 commitTimes,
184 filteredCommitIndices,
147 - isMouseDown,
185 maxDuration,
186 selectedCommitIndex,
187 selectedFilteredCommitIndex,
@@ -153,11 +190,7 @@ function List({
190 );
191
192 return (
156 - <div
157 - onMouseDown={handleMouseDown}
158 - onMouseUp={handleMouseUp}
159 - ref={divRef}
160 - style={{height, width}}>
193 + <div ref={divRef} style={{height, width}}>
194 <FixedSizeList
195 className={styles.List}
196 layout="horizontal"
packages/react-devtools-shared/src/devtools/views/Profiler/SnapshotCommitListItem.js
+14 -9
@@ -8,7 +8,7 @@
8 */
9
10 import * as React from 'react';
11 -import {memo, useCallback} from 'react';
11 +import {memo} from 'react';
12 import {areEqual} from 'react-window';
13 import {getGradientColor, formatDuration, formatTime} from './utils';
14
@@ -28,10 +28,10 @@ function SnapshotCommitListItem({data: itemData, index, style}: Props) {
28 commitDurations,
29 commitTimes,
30 filteredCommitIndices,
31 - isMouseDown,
31 maxDuration,
32 selectedCommitIndex,
33 selectCommitIndex,
34 + startCommitDrag,
35 } = itemData;
36
37 index = filteredCommitIndices[index];
@@ -39,11 +39,6 @@ function SnapshotCommitListItem({data: itemData, index, style}: Props) {
39 const commitDuration = commitDurations[index];
40 const commitTime = commitTimes[index];
41
42 - const handleClick = useCallback(() => selectCommitIndex(index), [
43 - index,
44 - selectCommitIndex,
45 - ]);
46 -
42 // Guard against commits with duration 0
43 const percentage =
44 Math.min(1, Math.max(0, commitDuration / maxDuration)) || 0;
@@ -52,11 +47,21 @@ function SnapshotCommitListItem({data: itemData, index, style}: Props) {
47 // Leave a 1px gap between snapshots
48 const width = parseFloat(style.width) - 1;
49
50 + const handleMouseDown = ({buttons, target}: any) => {
51 + if (buttons === 1) {
52 + selectCommitIndex(index);
53 + startCommitDrag({
54 + commitIndex: index,
55 + left: target.getBoundingClientRect().left,
56 + sizeIncrement: parseFloat(style.width),
57 + });
58 + }
59 + };
60 +
61 return (
62 <div
63 className={styles.Outer}
58 - onClick={handleClick}
59 - onMouseEnter={isMouseDown ? handleClick : null}
64 + onMouseDown={handleMouseDown}
65 style={{
66 ...style,
67 width,