Split large InspectedElementTree file into separate component-files

This commit is contained in:
Brian Vaughn
2019-02-21 14:20:14 -08:00
parent 9898db02d1
commit caf2eb973a
7 changed files with 285 additions and 249 deletions
+25
View File
@@ -0,0 +1,25 @@
.ValueInputLabel {
flex: 1 1 100%;
}
.ValueInputLabel:focus-within {
background-color: var(--color-button-background-focus);
}
.ValueInput {
width: 100%;
background: none;
border: 1px solid transparent;
color: var(--color-attribute-editable-value);
border-radius: 0.125rem;
font-family: var(--font-family-monospace);
font-size: var(--font-size-monospace-normal);
}
.ValueInput:focus {
background-color: var(--color-button-background-focus);
outline: none;
}
.ResetButton {
flex: 0 0 auto;
padding: 0 0.5rem;
}
+113
View File
@@ -0,0 +1,113 @@
// @flow
import React, { Fragment, useCallback, useRef, useState } from 'react';
import Button from './Button';
import ButtonIcon from './ButtonIcon';
import styles from './EditableValue.css';
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
type EditableValueProps = {|
dataType: string,
overrideValueFn: OverrideValueFn,
path: Array<string | number>,
value: any,
|};
export default function EditableValue({
dataType,
overrideValueFn,
path,
value,
}: EditableValueProps) {
const [hasPendingChanges, setHasPendingChanges] = useState(false);
const [editableValue, setEditableValue] = useState(value);
const inputRef = useRef<HTMLInputElement | null>(null);
if (hasPendingChanges && editableValue === value) {
setHasPendingChanges(false);
}
const handleChange = useCallback(
({ target }) => {
if (dataType === 'boolean') {
setEditableValue(target.checked);
overrideValueFn(path, target.checked);
} else if (dataType === 'number') {
setEditableValue(parseFloat(target.value));
} else {
setEditableValue(target.value);
}
setHasPendingChanges(true);
},
[dataType, overrideValueFn, path]
);
const handleReset = useCallback(() => {
setEditableValue(value);
setHasPendingChanges(false);
if (inputRef.current !== null) {
inputRef.current.focus();
}
}, [value]);
const handleKeyDown = useCallback(
event => {
// Prevent keydown events from e.g. change selected element in the tree
event.stopPropagation();
const { key } = event;
if (key === 'Enter') {
overrideValueFn(path, editableValue);
// Don't reset the pending change flag here.
// The inspected fiber won't be updated until after the next "inspectElement" message.
// We'll reset that flag during a subsequent render.
} else if (key === 'Escape') {
setEditableValue(value);
setHasPendingChanges(false);
}
},
[path, editableValue, overrideValueFn, value]
);
// Render different input types based on the dataType
let type = 'text';
if (dataType === 'boolean') {
type = 'checkbox';
} else if (dataType === 'number') {
type = 'number';
}
let inputValue = value == null ? '' : value;
if (hasPendingChanges) {
inputValue = editableValue == null ? '' : editableValue;
}
return (
<Fragment>
<label className={styles.ValueInputLabel}>
<input
checked={dataType === 'boolean' ? inputValue : undefined}
className={styles.ValueInput}
onChange={handleChange}
onKeyDown={handleKeyDown}
ref={inputRef}
type={type}
value={dataType === 'boolean' ? undefined : inputValue}
/>
</label>
{hasPendingChanges && dataType !== 'boolean' && (
<Button
className={styles.ResetButton}
onClick={handleReset}
title="Reset value"
>
<ButtonIcon type="undo" />
</Button>
)}
</Fragment>
);
}
+2 -1
View File
@@ -2,7 +2,8 @@
import React, { useContext } from 'react';
import { BridgeContext, StoreContext } from './context';
import { EditableValue, KeyValue } from './InspectedElementTree';
import EditableValue from './EditableValue';
import KeyValue from './KeyValue';
import styles from './HooksTree.css';
import type { HooksNode, HooksTree } from 'src/backend/types';
@@ -28,27 +28,6 @@
color: var(--color-attribute-value);
}
.ValueInputLabel {
flex: 1 1 100%;
}
.ValueInputLabel:focus-within {
background-color: var(--color-button-background-focus);
}
.ValueInput {
width: 100%;
background: none;
border: 1px solid transparent;
color: var(--color-attribute-editable-value);
border-radius: 0.125rem;
font-family: var(--font-family-monospace);
font-size: var(--font-size-monospace-normal);
}
.ValueInput:focus {
background-color: var(--color-button-background-focus);
outline: none;
}
.None {
color: var(--color-dimmer);
font-style: italic;
@@ -59,8 +38,3 @@
font-style: italic;
padding-left: 0.75rem;
}
.ResetButton {
flex: 0 0 auto;
padding: 0 0.5rem;
}
+2 -222
View File
@@ -1,10 +1,7 @@
// @flow
import React, { Fragment, useCallback, useRef, useState } from 'react';
import Button from './Button';
import ButtonIcon from './ButtonIcon';
import { getMetaValueLabel } from './utils';
import { meta } from '../../hydration';
import React from 'react';
import KeyValue from './KeyValue';
import styles from './InspectedElementTree.css';
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
@@ -47,220 +44,3 @@ export default function InspectedElementTree({
);
}
}
type KeyValueProps = {|
depth: number,
name: string,
nameClassName?: string,
overrideValueFn?: ?OverrideValueFn,
path?: Array<any>,
value: any,
|};
export function KeyValue({
depth,
name,
nameClassName = styles.Name,
overrideValueFn,
path = [],
value,
}: KeyValueProps) {
const dataType = typeof value;
const isSimpleType =
dataType === 'number' ||
dataType === 'string' ||
dataType === 'boolean' ||
value == null;
const paddingLeft = `${depth * 0.75}rem`;
let children = null;
if (isSimpleType) {
let displayValue = value;
if (dataType === 'string') {
displayValue = `"${value}"`;
} else if (dataType === 'boolean') {
displayValue = value ? 'true' : 'false';
} else if (value === null) {
displayValue = 'null';
} else if (value === undefined) {
displayValue = 'undefined';
}
children = (
<div key="root" className={styles.Item} style={{ paddingLeft }}>
<span className={nameClassName}>{name}</span>
{typeof overrideValueFn === 'function' ? (
<EditableValue
dataType={dataType}
overrideValueFn={overrideValueFn}
path={path}
value={value}
/>
) : (
<span className={styles.Value}>{displayValue}</span>
)}
</div>
);
} else if (value.hasOwnProperty(meta.type)) {
// TODO Is this type even necessary? Can we just drop it?
children = (
<div key="root" className={styles.Item} style={{ paddingLeft }}>
<span className={nameClassName}>{name}</span>
<span className={styles.Value}>{getMetaValueLabel(value)}</span>
</div>
);
} else {
if (Array.isArray(value)) {
children = value.map((innerValue, index) => (
<KeyValue
key={index}
depth={depth + 1}
name={index}
overrideValueFn={overrideValueFn}
path={path.concat(index)}
value={value[index]}
/>
));
children.unshift(
<div
key={`${depth}-root`}
className={styles.Item}
style={{ paddingLeft }}
>
<span className={nameClassName}>{name}</span>
<span>Array</span>
</div>
);
} else {
// $FlowFixMe "Missing type annotation for U" whatever that means
children = Object.entries(value).map(([name, value]) => (
<KeyValue
key={name}
depth={depth + 1}
name={name}
overrideValueFn={overrideValueFn}
path={path.concat(name)}
value={value}
/>
));
children.unshift(
<div
key={`${depth}-root`}
className={styles.Item}
style={{ paddingLeft }}
>
<span className={nameClassName}>{name}</span>
<span>Object</span>
</div>
);
}
}
return children;
}
type EditableValueProps = {|
dataType: string,
overrideValueFn: OverrideValueFn,
path: Array<string | number>,
value: any,
|};
export function EditableValue({
dataType,
overrideValueFn,
path,
value,
}: EditableValueProps) {
const [hasPendingChanges, setHasPendingChanges] = useState(false);
const [editableValue, setEditableValue] = useState(value);
const inputRef = useRef<HTMLInputElement | null>(null);
if (hasPendingChanges && editableValue === value) {
setHasPendingChanges(false);
}
const handleChange = useCallback(
({ target }) => {
if (dataType === 'boolean') {
setEditableValue(target.checked);
overrideValueFn(path, target.checked);
} else if (dataType === 'number') {
setEditableValue(parseFloat(target.value));
} else {
setEditableValue(target.value);
}
setHasPendingChanges(true);
},
[dataType, overrideValueFn, path]
);
const handleReset = useCallback(() => {
setEditableValue(value);
setHasPendingChanges(false);
if (inputRef.current !== null) {
inputRef.current.focus();
}
}, [value]);
const handleKeyDown = useCallback(
event => {
// Prevent keydown events from e.g. change selected element in the tree
event.stopPropagation();
const { key } = event;
if (key === 'Enter') {
overrideValueFn(path, editableValue);
// Don't reset the pending change flag here.
// The inspected fiber won't be updated until after the next "inspectElement" message.
// We'll reset that flag during a subsequent render.
} else if (key === 'Escape') {
setEditableValue(value);
setHasPendingChanges(false);
}
},
[path, editableValue, overrideValueFn, value]
);
// Render different input types based on the dataType
let type = 'text';
if (dataType === 'boolean') {
type = 'checkbox';
} else if (dataType === 'number') {
type = 'number';
}
let inputValue = value == null ? '' : value;
if (hasPendingChanges) {
inputValue = editableValue == null ? '' : editableValue;
}
return (
<Fragment>
<label className={styles.ValueInputLabel}>
<input
checked={dataType === 'boolean' ? inputValue : undefined}
className={styles.ValueInput}
onChange={handleChange}
onKeyDown={handleKeyDown}
ref={inputRef}
type={type}
value={dataType === 'boolean' ? undefined : inputValue}
/>
</label>
{hasPendingChanges && dataType !== 'boolean' && (
<Button
className={styles.ResetButton}
onClick={handleReset}
title="Reset value"
>
<ButtonIcon type="undo" />
</Button>
)}
</Fragment>
);
}
+22
View File
@@ -0,0 +1,22 @@
.Item {
display: flex;
}
.Name {
color: var(--color-attribute-name);
flex: 0 0 auto;
}
.Name:after {
content: ': ';
color: var(--color-text-color);
margin-right: 0.5rem;
}
.Value {
color: var(--color-attribute-value);
}
.None {
color: var(--color-dimmer);
font-style: italic;
}
+121
View File
@@ -0,0 +1,121 @@
// @flow
import React from 'react';
import EditableValue from './EditableValue';
import { getMetaValueLabel } from './utils';
import { meta } from '../../hydration';
import styles from './KeyValue.css';
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
type KeyValueProps = {|
depth: number,
name: string,
nameClassName?: string,
overrideValueFn?: ?OverrideValueFn,
path?: Array<any>,
value: any,
|};
export default function KeyValue({
depth,
name,
nameClassName = styles.Name,
overrideValueFn,
path = [],
value,
}: KeyValueProps) {
const dataType = typeof value;
const isSimpleType =
dataType === 'number' ||
dataType === 'string' ||
dataType === 'boolean' ||
value == null;
const paddingLeft = `${depth * 0.75}rem`;
let children = null;
if (isSimpleType) {
let displayValue = value;
if (dataType === 'string') {
displayValue = `"${value}"`;
} else if (dataType === 'boolean') {
displayValue = value ? 'true' : 'false';
} else if (value === null) {
displayValue = 'null';
} else if (value === undefined) {
displayValue = 'undefined';
}
children = (
<div key="root" className={styles.Item} style={{ paddingLeft }}>
<span className={nameClassName}>{name}</span>
{typeof overrideValueFn === 'function' ? (
<EditableValue
dataType={dataType}
overrideValueFn={overrideValueFn}
path={path}
value={value}
/>
) : (
<span className={styles.Value}>{displayValue}</span>
)}
</div>
);
} else if (value.hasOwnProperty(meta.type)) {
// TODO Is this type even necessary? Can we just drop it?
children = (
<div key="root" className={styles.Item} style={{ paddingLeft }}>
<span className={nameClassName}>{name}</span>
<span className={styles.Value}>{getMetaValueLabel(value)}</span>
</div>
);
} else {
if (Array.isArray(value)) {
children = value.map((innerValue, index) => (
<KeyValue
key={index}
depth={depth + 1}
name={index}
overrideValueFn={overrideValueFn}
path={path.concat(index)}
value={value[index]}
/>
));
children.unshift(
<div
key={`${depth}-root`}
className={styles.Item}
style={{ paddingLeft }}
>
<span className={nameClassName}>{name}</span>
<span>Array</span>
</div>
);
} else {
// $FlowFixMe "Missing type annotation for U" whatever that means
children = Object.entries(value).map(([name, value]) => (
<KeyValue
key={name}
depth={depth + 1}
name={name}
overrideValueFn={overrideValueFn}
path={path.concat(name)}
value={value}
/>
));
children.unshift(
<div
key={`${depth}-root`}
className={styles.Item}
style={{ paddingLeft }}
>
<span className={nameClassName}>{name}</span>
<span>Object</span>
</div>
);
}
}
return children;
}