Split large InspectedElementTree file into separate component-files
Brian Vaughn committed
Feb 21, 2019 at 14:20 UTC
caf2eb973af0bad812aa0d9ae42e92f4899c6c13
7 files changed
+285
-249
src/devtools/views/EditableValue.css
new
+25
@@ -0,0 +1,25 @@
1
+.ValueInputLabel {
2
+ flex: 1 1 100%;
3
+}
4
+.ValueInputLabel:focus-within {
5
+ background-color: var(--color-button-background-focus);
6
+}
7
+
8
+.ValueInput {
9
+ width: 100%;
10
+ background: none;
11
+ border: 1px solid transparent;
12
+ color: var(--color-attribute-editable-value);
13
+ border-radius: 0.125rem;
14
+ font-family: var(--font-family-monospace);
15
+ font-size: var(--font-size-monospace-normal);
16
+}
17
+
18
+.ValueInput:focus {
19
+ background-color: var(--color-button-background-focus);
20
+ outline: none;
21
+}
22
+.ResetButton {
23
+ flex: 0 0 auto;
24
+ padding: 0 0.5rem;
25
+}
src/devtools/views/EditableValue.js
new
+113
@@ -0,0 +1,113 @@
1
+// @flow
2
+
3
+import React, { Fragment, useCallback, useRef, useState } from 'react';
4
+import Button from './Button';
5
+import ButtonIcon from './ButtonIcon';
6
+import styles from './EditableValue.css';
7
+
8
+type OverrideValueFn = (path: Array<string | number>, value: any) => void;
9
+
10
+type EditableValueProps = {|
11
+ dataType: string,
12
+ overrideValueFn: OverrideValueFn,
13
+ path: Array<string | number>,
14
+ value: any,
15
+|};
16
+
17
+export default function EditableValue({
18
+ dataType,
19
+ overrideValueFn,
20
+ path,
21
+ value,
22
+}: EditableValueProps) {
23
+ const [hasPendingChanges, setHasPendingChanges] = useState(false);
24
+ const [editableValue, setEditableValue] = useState(value);
25
+ const inputRef = useRef<HTMLInputElement | null>(null);
26
+
27
+ if (hasPendingChanges && editableValue === value) {
28
+ setHasPendingChanges(false);
29
+ }
30
+
31
+ const handleChange = useCallback(
32
+ ({ target }) => {
33
+ if (dataType === 'boolean') {
34
+ setEditableValue(target.checked);
35
+ overrideValueFn(path, target.checked);
36
+ } else if (dataType === 'number') {
37
+ setEditableValue(parseFloat(target.value));
38
+ } else {
39
+ setEditableValue(target.value);
40
+ }
41
+ setHasPendingChanges(true);
42
+ },
43
+ [dataType, overrideValueFn, path]
44
+ );
45
+
46
+ const handleReset = useCallback(() => {
47
+ setEditableValue(value);
48
+ setHasPendingChanges(false);
49
+
50
+ if (inputRef.current !== null) {
51
+ inputRef.current.focus();
52
+ }
53
+ }, [value]);
54
+
55
+ const handleKeyDown = useCallback(
56
+ event => {
57
+ // Prevent keydown events from e.g. change selected element in the tree
58
+ event.stopPropagation();
59
+
60
+ const { key } = event;
61
+
62
+ if (key === 'Enter') {
63
+ overrideValueFn(path, editableValue);
64
+
65
+ // Don't reset the pending change flag here.
66
+ // The inspected fiber won't be updated until after the next "inspectElement" message.
67
+ // We'll reset that flag during a subsequent render.
68
+ } else if (key === 'Escape') {
69
+ setEditableValue(value);
70
+ setHasPendingChanges(false);
71
+ }
72
+ },
73
+ [path, editableValue, overrideValueFn, value]
74
+ );
75
+
76
+ // Render different input types based on the dataType
77
+ let type = 'text';
78
+ if (dataType === 'boolean') {
79
+ type = 'checkbox';
80
+ } else if (dataType === 'number') {
81
+ type = 'number';
82
+ }
83
+
84
+ let inputValue = value == null ? '' : value;
85
+ if (hasPendingChanges) {
86
+ inputValue = editableValue == null ? '' : editableValue;
87
+ }
88
+
89
+ return (
90
+ <Fragment>
91
+ <label className={styles.ValueInputLabel}>
92
+ <input
93
+ checked={dataType === 'boolean' ? inputValue : undefined}
94
+ className={styles.ValueInput}
95
+ onChange={handleChange}
96
+ onKeyDown={handleKeyDown}
97
+ ref={inputRef}
98
+ type={type}
99
+ value={dataType === 'boolean' ? undefined : inputValue}
100
+ />
101
+ </label>
102
+ {hasPendingChanges && dataType !== 'boolean' && (
103
+ <Button
104
+ className={styles.ResetButton}
105
+ onClick={handleReset}
106
+ title="Reset value"
107
+ >
108
+ <ButtonIcon type="undo" />
109
+ </Button>
110
+ )}
111
+ </Fragment>
112
+ );
113
+}
src/devtools/views/HooksTree.js
+2
-1
@@ -2,7 +2,8 @@
2
3
import React, { useContext } from 'react';
4
import { BridgeContext, StoreContext } from './context';
5
-import { EditableValue, KeyValue } from './InspectedElementTree';
5
+import EditableValue from './EditableValue';
6
+import KeyValue from './KeyValue';
7
import styles from './HooksTree.css';
8
9
import type { HooksNode, HooksTree } from 'src/backend/types';
src/devtools/views/InspectedElementTree.css
-26
@@ -28,27 +28,6 @@
28
color: var(--color-attribute-value);
29
}
30
31
-.ValueInputLabel {
32
- flex: 1 1 100%;
33
-}
34
-.ValueInputLabel:focus-within {
35
- background-color: var(--color-button-background-focus);
36
-}
37
-
38
-.ValueInput {
39
- width: 100%;
40
- background: none;
41
- border: 1px solid transparent;
42
- color: var(--color-attribute-editable-value);
43
- border-radius: 0.125rem;
44
- font-family: var(--font-family-monospace);
45
- font-size: var(--font-size-monospace-normal);
46
-}
47
-.ValueInput:focus {
48
- background-color: var(--color-button-background-focus);
49
- outline: none;
50
-}
51
-
31
.None {
32
color: var(--color-dimmer);
33
font-style: italic;
@@ -59,8 +38,3 @@
38
font-style: italic;
39
padding-left: 0.75rem;
40
}
62
-
63
-.ResetButton {
64
- flex: 0 0 auto;
65
- padding: 0 0.5rem;
66
-}
src/devtools/views/InspectedElementTree.js
+2
-222
@@ -1,10 +1,7 @@
1
// @flow
2
3
-import React, { Fragment, useCallback, useRef, useState } from 'react';
4
-import Button from './Button';
5
-import ButtonIcon from './ButtonIcon';
6
-import { getMetaValueLabel } from './utils';
7
-import { meta } from '../../hydration';
3
+import React from 'react';
4
+import KeyValue from './KeyValue';
5
import styles from './InspectedElementTree.css';
6
7
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
@@ -47,220 +44,3 @@ export default function InspectedElementTree({
44
);
45
}
46
}
50
-
51
-type KeyValueProps = {|
52
- depth: number,
53
- name: string,
54
- nameClassName?: string,
55
- overrideValueFn?: ?OverrideValueFn,
56
- path?: Array<any>,
57
- value: any,
58
-|};
59
-
60
-export function KeyValue({
61
- depth,
62
- name,
63
- nameClassName = styles.Name,
64
- overrideValueFn,
65
- path = [],
66
- value,
67
-}: KeyValueProps) {
68
- const dataType = typeof value;
69
- const isSimpleType =
70
- dataType === 'number' ||
71
- dataType === 'string' ||
72
- dataType === 'boolean' ||
73
- value == null;
74
-
75
- const paddingLeft = `${depth * 0.75}rem`;
76
-
77
- let children = null;
78
- if (isSimpleType) {
79
- let displayValue = value;
80
- if (dataType === 'string') {
81
- displayValue = `"${value}"`;
82
- } else if (dataType === 'boolean') {
83
- displayValue = value ? 'true' : 'false';
84
- } else if (value === null) {
85
- displayValue = 'null';
86
- } else if (value === undefined) {
87
- displayValue = 'undefined';
88
- }
89
-
90
- children = (
91
- <div key="root" className={styles.Item} style={{ paddingLeft }}>
92
- <span className={nameClassName}>{name}</span>
93
- {typeof overrideValueFn === 'function' ? (
94
- <EditableValue
95
- dataType={dataType}
96
- overrideValueFn={overrideValueFn}
97
- path={path}
98
- value={value}
99
- />
100
- ) : (
101
- <span className={styles.Value}>{displayValue}</span>
102
- )}
103
- </div>
104
- );
105
- } else if (value.hasOwnProperty(meta.type)) {
106
- // TODO Is this type even necessary? Can we just drop it?
107
- children = (
108
- <div key="root" className={styles.Item} style={{ paddingLeft }}>
109
- <span className={nameClassName}>{name}</span>
110
- <span className={styles.Value}>{getMetaValueLabel(value)}</span>
111
- </div>
112
- );
113
- } else {
114
- if (Array.isArray(value)) {
115
- children = value.map((innerValue, index) => (
116
- <KeyValue
117
- key={index}
118
- depth={depth + 1}
119
- name={index}
120
- overrideValueFn={overrideValueFn}
121
- path={path.concat(index)}
122
- value={value[index]}
123
- />
124
- ));
125
- children.unshift(
126
- <div
127
- key={`${depth}-root`}
128
- className={styles.Item}
129
- style={{ paddingLeft }}
130
- >
131
- <span className={nameClassName}>{name}</span>
132
- <span>Array</span>
133
- </div>
134
- );
135
- } else {
136
- // $FlowFixMe "Missing type annotation for U" whatever that means
137
- children = Object.entries(value).map(([name, value]) => (
138
- <KeyValue
139
- key={name}
140
- depth={depth + 1}
141
- name={name}
142
- overrideValueFn={overrideValueFn}
143
- path={path.concat(name)}
144
- value={value}
145
- />
146
- ));
147
- children.unshift(
148
- <div
149
- key={`${depth}-root`}
150
- className={styles.Item}
151
- style={{ paddingLeft }}
152
- >
153
- <span className={nameClassName}>{name}</span>
154
- <span>Object</span>
155
- </div>
156
- );
157
- }
158
- }
159
-
160
- return children;
161
-}
162
-
163
-type EditableValueProps = {|
164
- dataType: string,
165
- overrideValueFn: OverrideValueFn,
166
- path: Array<string | number>,
167
- value: any,
168
-|};
169
-
170
-export function EditableValue({
171
- dataType,
172
- overrideValueFn,
173
- path,
174
- value,
175
-}: EditableValueProps) {
176
- const [hasPendingChanges, setHasPendingChanges] = useState(false);
177
- const [editableValue, setEditableValue] = useState(value);
178
- const inputRef = useRef<HTMLInputElement | null>(null);
179
-
180
- if (hasPendingChanges && editableValue === value) {
181
- setHasPendingChanges(false);
182
- }
183
-
184
- const handleChange = useCallback(
185
- ({ target }) => {
186
- if (dataType === 'boolean') {
187
- setEditableValue(target.checked);
188
- overrideValueFn(path, target.checked);
189
- } else if (dataType === 'number') {
190
- setEditableValue(parseFloat(target.value));
191
- } else {
192
- setEditableValue(target.value);
193
- }
194
- setHasPendingChanges(true);
195
- },
196
- [dataType, overrideValueFn, path]
197
- );
198
-
199
- const handleReset = useCallback(() => {
200
- setEditableValue(value);
201
- setHasPendingChanges(false);
202
-
203
- if (inputRef.current !== null) {
204
- inputRef.current.focus();
205
- }
206
- }, [value]);
207
-
208
- const handleKeyDown = useCallback(
209
- event => {
210
- // Prevent keydown events from e.g. change selected element in the tree
211
- event.stopPropagation();
212
-
213
- const { key } = event;
214
-
215
- if (key === 'Enter') {
216
- overrideValueFn(path, editableValue);
217
-
218
- // Don't reset the pending change flag here.
219
- // The inspected fiber won't be updated until after the next "inspectElement" message.
220
- // We'll reset that flag during a subsequent render.
221
- } else if (key === 'Escape') {
222
- setEditableValue(value);
223
- setHasPendingChanges(false);
224
- }
225
- },
226
- [path, editableValue, overrideValueFn, value]
227
- );
228
-
229
- // Render different input types based on the dataType
230
- let type = 'text';
231
- if (dataType === 'boolean') {
232
- type = 'checkbox';
233
- } else if (dataType === 'number') {
234
- type = 'number';
235
- }
236
-
237
- let inputValue = value == null ? '' : value;
238
- if (hasPendingChanges) {
239
- inputValue = editableValue == null ? '' : editableValue;
240
- }
241
-
242
- return (
243
- <Fragment>
244
- <label className={styles.ValueInputLabel}>
245
- <input
246
- checked={dataType === 'boolean' ? inputValue : undefined}
247
- className={styles.ValueInput}
248
- onChange={handleChange}
249
- onKeyDown={handleKeyDown}
250
- ref={inputRef}
251
- type={type}
252
- value={dataType === 'boolean' ? undefined : inputValue}
253
- />
254
- </label>
255
- {hasPendingChanges && dataType !== 'boolean' && (
256
- <Button
257
- className={styles.ResetButton}
258
- onClick={handleReset}
259
- title="Reset value"
260
- >
261
- <ButtonIcon type="undo" />
262
- </Button>
263
- )}
264
- </Fragment>
265
- );
266
-}
src/devtools/views/KeyValue.css
new
+22
@@ -0,0 +1,22 @@
1
+.Item {
2
+ display: flex;
3
+}
4
+
5
+.Name {
6
+ color: var(--color-attribute-name);
7
+ flex: 0 0 auto;
8
+}
9
+.Name:after {
10
+ content: ': ';
11
+ color: var(--color-text-color);
12
+ margin-right: 0.5rem;
13
+}
14
+
15
+.Value {
16
+ color: var(--color-attribute-value);
17
+}
18
+
19
+.None {
20
+ color: var(--color-dimmer);
21
+ font-style: italic;
22
+}
src/devtools/views/KeyValue.js
new
+121
@@ -0,0 +1,121 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import EditableValue from './EditableValue';
5
+import { getMetaValueLabel } from './utils';
6
+import { meta } from '../../hydration';
7
+import styles from './KeyValue.css';
8
+
9
+type OverrideValueFn = (path: Array<string | number>, value: any) => void;
10
+
11
+type KeyValueProps = {|
12
+ depth: number,
13
+ name: string,
14
+ nameClassName?: string,
15
+ overrideValueFn?: ?OverrideValueFn,
16
+ path?: Array<any>,
17
+ value: any,
18
+|};
19
+
20
+export default function KeyValue({
21
+ depth,
22
+ name,
23
+ nameClassName = styles.Name,
24
+ overrideValueFn,
25
+ path = [],
26
+ value,
27
+}: KeyValueProps) {
28
+ const dataType = typeof value;
29
+ const isSimpleType =
30
+ dataType === 'number' ||
31
+ dataType === 'string' ||
32
+ dataType === 'boolean' ||
33
+ value == null;
34
+
35
+ const paddingLeft = `${depth * 0.75}rem`;
36
+
37
+ let children = null;
38
+ if (isSimpleType) {
39
+ let displayValue = value;
40
+ if (dataType === 'string') {
41
+ displayValue = `"${value}"`;
42
+ } else if (dataType === 'boolean') {
43
+ displayValue = value ? 'true' : 'false';
44
+ } else if (value === null) {
45
+ displayValue = 'null';
46
+ } else if (value === undefined) {
47
+ displayValue = 'undefined';
48
+ }
49
+
50
+ children = (
51
+ <div key="root" className={styles.Item} style={{ paddingLeft }}>
52
+ <span className={nameClassName}>{name}</span>
53
+ {typeof overrideValueFn === 'function' ? (
54
+ <EditableValue
55
+ dataType={dataType}
56
+ overrideValueFn={overrideValueFn}
57
+ path={path}
58
+ value={value}
59
+ />
60
+ ) : (
61
+ <span className={styles.Value}>{displayValue}</span>
62
+ )}
63
+ </div>
64
+ );
65
+ } else if (value.hasOwnProperty(meta.type)) {
66
+ // TODO Is this type even necessary? Can we just drop it?
67
+ children = (
68
+ <div key="root" className={styles.Item} style={{ paddingLeft }}>
69
+ <span className={nameClassName}>{name}</span>
70
+ <span className={styles.Value}>{getMetaValueLabel(value)}</span>
71
+ </div>
72
+ );
73
+ } else {
74
+ if (Array.isArray(value)) {
75
+ children = value.map((innerValue, index) => (
76
+ <KeyValue
77
+ key={index}
78
+ depth={depth + 1}
79
+ name={index}
80
+ overrideValueFn={overrideValueFn}
81
+ path={path.concat(index)}
82
+ value={value[index]}
83
+ />
84
+ ));
85
+ children.unshift(
86
+ <div
87
+ key={`${depth}-root`}
88
+ className={styles.Item}
89
+ style={{ paddingLeft }}
90
+ >
91
+ <span className={nameClassName}>{name}</span>
92
+ <span>Array</span>
93
+ </div>
94
+ );
95
+ } else {
96
+ // $FlowFixMe "Missing type annotation for U" whatever that means
97
+ children = Object.entries(value).map(([name, value]) => (
98
+ <KeyValue
99
+ key={name}
100
+ depth={depth + 1}
101
+ name={name}
102
+ overrideValueFn={overrideValueFn}
103
+ path={path.concat(name)}
104
+ value={value}
105
+ />
106
+ ));
107
+ children.unshift(
108
+ <div
109
+ key={`${depth}-root`}
110
+ className={styles.Item}
111
+ style={{ paddingLeft }}
112
+ >
113
+ <span className={nameClassName}>{name}</span>
114
+ <span>Object</span>
115
+ </div>
116
+ );
117
+ }
118
+ }
119
+
120
+ return children;
121
+}