mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Added copy props/state/hooks/context button
This commit is contained in:
+1
-1
@@ -50,7 +50,7 @@
|
||||
"chrome-launch": "^1.1.4",
|
||||
"classnames": "2.2.1",
|
||||
"cli-spinners": "^1.0.0",
|
||||
"clipboard-js": "^0.3.3",
|
||||
"clipboard-js": "^0.3.6",
|
||||
"css-loader": "^1.0.1",
|
||||
"error-stack-parser": "^2.0.2",
|
||||
"es6-symbol": "3.0.2",
|
||||
|
||||
@@ -7,6 +7,7 @@ type Props = {|
|
||||
type:
|
||||
| 'back'
|
||||
| 'close'
|
||||
| 'copy'
|
||||
| 'down'
|
||||
| 'more'
|
||||
| 'search'
|
||||
@@ -25,6 +26,9 @@ export default function ButtonIcon({ type }: Props) {
|
||||
case 'close':
|
||||
pathData = PATH_CLOSE;
|
||||
break;
|
||||
case 'copy':
|
||||
pathData = PATH_COPY;
|
||||
break;
|
||||
case 'down':
|
||||
pathData = PATH_DOWN;
|
||||
break;
|
||||
@@ -74,6 +78,11 @@ const PATH_BACK = `
|
||||
const PATH_CLOSE =
|
||||
'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z';
|
||||
|
||||
const PATH_COPY = `
|
||||
M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3a2 2 0 0 0 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2
|
||||
2v10a2 2 0 0 0 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z
|
||||
`;
|
||||
|
||||
const PATH_DOWN = 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z';
|
||||
|
||||
const PATH_MORE = `
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.HeaderRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Header {
|
||||
flex: 1 1;
|
||||
font-family: var(--font-family-sans);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { copy } from 'clipboard-js';
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import { BridgeContext, StoreContext } from './context';
|
||||
import Button from './Button';
|
||||
import ButtonIcon from './ButtonIcon';
|
||||
import EditableValue from './EditableValue';
|
||||
import KeyValue from './KeyValue';
|
||||
import { serializeHooksForCopy } from './utils';
|
||||
import styles from './HooksTree.css';
|
||||
|
||||
import type { HooksNode, HooksTree } from 'src/backend/types';
|
||||
@@ -15,12 +19,21 @@ type HooksTreeViewProps = {|
|
||||
|};
|
||||
|
||||
export function HooksTreeView({ canEditHooks, hooks, id }: HooksTreeViewProps) {
|
||||
const handleCopy = useCallback(() => copy(serializeHooksForCopy(hooks)), [
|
||||
hooks,
|
||||
]);
|
||||
|
||||
if (hooks === null) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.HooksTreeView}>
|
||||
<div className={styles.Header}>hooks</div>
|
||||
<div className={styles.HeaderRow}>
|
||||
<div className={styles.Header}>hooks</div>
|
||||
<Button onClick={handleCopy}>
|
||||
<ButtonIcon type="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
<InnerHooksTreeView canEditHooks={canEditHooks} hooks={hooks} id={id} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,13 @@
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.HeaderRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Header {
|
||||
flex: 1 1;
|
||||
font-family: var(--font-family-sans);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { copy } from 'clipboard-js';
|
||||
import React, { useCallback } from 'react';
|
||||
import Button from './Button';
|
||||
import ButtonIcon from './ButtonIcon';
|
||||
import KeyValue from './KeyValue';
|
||||
import { serializeDataForCopy } from './utils';
|
||||
import styles from './InspectedElementTree.css';
|
||||
|
||||
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
|
||||
@@ -21,13 +25,24 @@ export default function InspectedElementTree({
|
||||
}: Props) {
|
||||
const isEmpty = data === null || Object.keys(data).length === 0;
|
||||
|
||||
const handleCopy = useCallback(() => copy(serializeDataForCopy(data)), [
|
||||
data,
|
||||
]);
|
||||
|
||||
if (isEmpty && !showWhenEmpty) {
|
||||
return null;
|
||||
} else {
|
||||
// TODO Add click and key handlers for toggling element open/close state.
|
||||
return (
|
||||
<div className={styles.InspectedElementTree}>
|
||||
<div className={styles.Header}>{label}</div>
|
||||
<div className={styles.HeaderRow}>
|
||||
<div className={styles.Header}>{label}</div>
|
||||
{!isEmpty && (
|
||||
<Button onClick={handleCopy}>
|
||||
<ButtonIcon type="copy" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEmpty && <div className={styles.Empty}>None</div>}
|
||||
{!isEmpty &&
|
||||
Object.keys((data: any)).map(name => (
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import escapeStringRegExp from 'escape-string-regexp';
|
||||
import { meta } from '../../hydration';
|
||||
|
||||
import type { HooksTree } from 'src/backend/types';
|
||||
|
||||
export function createRegExp(string: string): RegExp {
|
||||
return new RegExp(escapeStringRegExp(string), 'i');
|
||||
}
|
||||
@@ -29,3 +31,58 @@ export function getMetaValueLabel(data: Object): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitize(data: Object): void {
|
||||
for (const key in data) {
|
||||
const value = data[key];
|
||||
|
||||
if (value && value[meta.type]) {
|
||||
data[key] = getMetaValueLabel(value);
|
||||
} else if (value != null) {
|
||||
if (Array.isArray(value)) {
|
||||
sanitize(value);
|
||||
} else if (typeof value === 'object') {
|
||||
sanitize(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeDataForCopy(props: Object): string {
|
||||
const cloned = Object.assign({}, props);
|
||||
|
||||
sanitize(cloned);
|
||||
|
||||
try {
|
||||
return JSON.stringify(cloned, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeHooksForCopy(hooks: HooksTree | null): string {
|
||||
// $FlowFixMe "HooksTree is not an object"
|
||||
const cloned = Object.assign([], hooks);
|
||||
|
||||
const queue = [...cloned];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.pop();
|
||||
|
||||
// These aren't meaningful
|
||||
delete current.isEditable;
|
||||
delete current.index;
|
||||
|
||||
if (current.subHooks.length > 0) {
|
||||
queue.push(...current.subHooks);
|
||||
}
|
||||
}
|
||||
|
||||
sanitize(cloned);
|
||||
|
||||
try {
|
||||
return JSON.stringify(cloned, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2636,9 +2636,10 @@ cli-width@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
|
||||
|
||||
clipboard-js@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/clipboard-js/-/clipboard-js-0.3.3.tgz#62264c17c80ebf84f4022286f032501e890b8922"
|
||||
clipboard-js@^0.3.6:
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/clipboard-js/-/clipboard-js-0.3.6.tgz#6260add69b5318fde40b80f9d3c8c863efdaf339"
|
||||
integrity sha512-hyrmvbrYCeRBHdiR3KrEz0tmrUTXXEU8HLeGW0Y0icUSwYmAsmc+d6wfE4EDb/TxZmAVJG0eTfMlulCIT+ecfw==
|
||||
|
||||
cliui@^2.1.0:
|
||||
version "2.1.0"
|
||||
|
||||
Reference in New Issue
Block a user