@samitouri / QOS-React / commits / 5fd9cb5dfb

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"