[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,