Added copy props/state/hooks/context button
Brian Vaughn committed
Feb 22, 2019 at 09:39 UTC
5fd9cb5dfb1cde8aa1665befdb3be06a0f4ef988
8 files changed
+115
-8
package.json
+1
-1
@@ -50,7 +50,7 @@
50
"chrome-launch": "^1.1.4",
51
"classnames": "2.2.1",
52
"cli-spinners": "^1.0.0",
53
- "clipboard-js": "^0.3.3",
53
+ "clipboard-js": "^0.3.6",
54
"css-loader": "^1.0.1",
55
"error-stack-parser": "^2.0.2",
56
"es6-symbol": "3.0.2",
src/devtools/views/ButtonIcon.js
+9
@@ -7,6 +7,7 @@ type Props = {|
7
type:
8
| 'back'
9
| 'close'
10
+ | 'copy'
11
| 'down'
12
| 'more'
13
| 'search'
@@ -25,6 +26,9 @@ export default function ButtonIcon({ type }: Props) {
26
case 'close':
27
pathData = PATH_CLOSE;
28
break;
29
+ case 'copy':
30
+ pathData = PATH_COPY;
31
+ break;
32
case 'down':
33
pathData = PATH_DOWN;
34
break;
@@ -74,6 +78,11 @@ const PATH_BACK = `
78
const PATH_CLOSE =
79
'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';
80
81
+const PATH_COPY = `
82
+ M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3a2 2 0 0 0 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2
83
+ 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
84
+`;
85
+
86
const PATH_DOWN = 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z';
87
88
const PATH_MORE = `
src/devtools/views/HooksTree.css
+6
@@ -7,7 +7,13 @@
7
padding-left: 0.75rem;
8
}
9
10
+.HeaderRow {
11
+ display: flex;
12
+ align-items: center;
13
+}
14
+
15
.Header {
16
+ flex: 1 1;
17
font-family: var(--font-family-sans);
18
}
19
src/devtools/views/HooksTree.js
+15
-2
@@ -1,9 +1,13 @@
1
// @flow
2
3
-import React, { useContext } from 'react';
3
+import { copy } from 'clipboard-js';
4
+import React, { useCallback, useContext } from 'react';
5
import { BridgeContext, StoreContext } from './context';
6
+import Button from './Button';
7
+import ButtonIcon from './ButtonIcon';
8
import EditableValue from './EditableValue';
9
import KeyValue from './KeyValue';
10
+import { serializeHooksForCopy } from './utils';
11
import styles from './HooksTree.css';
12
13
import type { HooksNode, HooksTree } from 'src/backend/types';
@@ -15,12 +19,21 @@ type HooksTreeViewProps = {|
19
|};
20
21
export function HooksTreeView({ canEditHooks, hooks, id }: HooksTreeViewProps) {
22
+ const handleCopy = useCallback(() => copy(serializeHooksForCopy(hooks)), [
23
+ hooks,
24
+ ]);
25
+
26
if (hooks === null) {
27
return null;
28
} else {
29
return (
30
<div className={styles.HooksTreeView}>
23
- <div className={styles.Header}>hooks</div>
31
+ <div className={styles.HeaderRow}>
32
+ <div className={styles.Header}>hooks</div>
33
+ <Button onClick={handleCopy}>
34
+ <ButtonIcon type="copy" />
35
+ </Button>
36
+ </div>
37
<InnerHooksTreeView canEditHooks={canEditHooks} hooks={hooks} id={id} />
38
</div>
39
);
src/devtools/views/InspectedElementTree.css
+6
@@ -6,7 +6,13 @@
6
border-top: none;
7
}
8
9
+.HeaderRow {
10
+ display: flex;
11
+ align-items: center;
12
+}
13
+
14
.Header {
15
+ flex: 1 1;
16
font-family: var(--font-family-sans);
17
}
18
src/devtools/views/InspectedElementTree.js
+17
-2
@@ -1,7 +1,11 @@
1
// @flow
2
3
-import React from 'react';
3
+import { copy } from 'clipboard-js';
4
+import React, { useCallback } from 'react';
5
+import Button from './Button';
6
+import ButtonIcon from './ButtonIcon';
7
import KeyValue from './KeyValue';
8
+import { serializeDataForCopy } from './utils';
9
import styles from './InspectedElementTree.css';
10
11
type OverrideValueFn = (path: Array<string | number>, value: any) => void;
@@ -21,13 +25,24 @@ export default function InspectedElementTree({
25
}: Props) {
26
const isEmpty = data === null || Object.keys(data).length === 0;
27
28
+ const handleCopy = useCallback(() => copy(serializeDataForCopy(data)), [
29
+ data,
30
+ ]);
31
+
32
if (isEmpty && !showWhenEmpty) {
33
return null;
34
} else {
35
// TODO Add click and key handlers for toggling element open/close state.
36
return (
37
<div className={styles.InspectedElementTree}>
30
- <div className={styles.Header}>{label}</div>
38
+ <div className={styles.HeaderRow}>
39
+ <div className={styles.Header}>{label}</div>
40
+ {!isEmpty && (
41
+ <Button onClick={handleCopy}>
42
+ <ButtonIcon type="copy" />
43
+ </Button>
44
+ )}
45
+ </div>
46
{isEmpty && <div className={styles.Empty}>None</div>}
47
{!isEmpty &&
48
Object.keys((data: any)).map(name => (
src/devtools/views/utils.js
+57
@@ -3,6 +3,8 @@
3
import escapeStringRegExp from 'escape-string-regexp';
4
import { meta } from '../../hydration';
5
6
+import type { HooksTree } from 'src/backend/types';
7
+
8
export function createRegExp(string: string): RegExp {
9
return new RegExp(escapeStringRegExp(string), 'i');
10
}
@@ -29,3 +31,58 @@ export function getMetaValueLabel(data: Object): string | null {
31
return null;
32
}
33
}
34
+
35
+function sanitize(data: Object): void {
36
+ for (const key in data) {
37
+ const value = data[key];
38
+
39
+ if (value && value[meta.type]) {
40
+ data[key] = getMetaValueLabel(value);
41
+ } else if (value != null) {
42
+ if (Array.isArray(value)) {
43
+ sanitize(value);
44
+ } else if (typeof value === 'object') {
45
+ sanitize(value);
46
+ }
47
+ }
48
+ }
49
+}
50
+
51
+export function serializeDataForCopy(props: Object): string {
52
+ const cloned = Object.assign({}, props);
53
+
54
+ sanitize(cloned);
55
+
56
+ try {
57
+ return JSON.stringify(cloned, null, 2);
58
+ } catch (error) {
59
+ return '';
60
+ }
61
+}
62
+
63
+export function serializeHooksForCopy(hooks: HooksTree | null): string {
64
+ // $FlowFixMe "HooksTree is not an object"
65
+ const cloned = Object.assign([], hooks);
66
+
67
+ const queue = [...cloned];
68
+
69
+ while (queue.length > 0) {
70
+ const current = queue.pop();
71
+
72
+ // These aren't meaningful
73
+ delete current.isEditable;
74
+ delete current.index;
75
+
76
+ if (current.subHooks.length > 0) {
77
+ queue.push(...current.subHooks);
78
+ }
79
+ }
80
+
81
+ sanitize(cloned);
82
+
83
+ try {
84
+ return JSON.stringify(cloned, null, 2);
85
+ } catch (error) {
86
+ return '';
87
+ }
88
+}
yarn.lock
+4
-3
@@ -2636,9 +2636,10 @@ cli-width@^2.0.0:
2636
version "2.1.0"
2637
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
2638
2639
-clipboard-js@^0.3.3:
2640
- version "0.3.3"
2641
- resolved "https://registry.yarnpkg.com/clipboard-js/-/clipboard-js-0.3.3.tgz#62264c17c80ebf84f4022286f032501e890b8922"
2639
+clipboard-js@^0.3.6:
2640
+ version "0.3.6"
2641
+ resolved "https://registry.yarnpkg.com/clipboard-js/-/clipboard-js-0.3.6.tgz#6260add69b5318fde40b80f9d3c8c863efdaf339"
2642
+ integrity sha512-hyrmvbrYCeRBHdiR3KrEz0tmrUTXXEU8HLeGW0Y0icUSwYmAsmc+d6wfE4EDb/TxZmAVJG0eTfMlulCIT+ecfw==
2643
2644
cliui@^2.1.0:
2645
version "2.1.0"