@samitouri / QOS-React / commits / caf2eb973a

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 +}