Skip to content

Commit 9dc066c

Browse files
committed
Print selected values in terminal
1 parent 740bb38 commit 9dc066c

6 files changed

Lines changed: 208 additions & 15 deletions

File tree

423 Bytes
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from "react";
2+
import { Image, Pressable, StyleSheet } from "react-native";
3+
4+
const CopyButton: React.FC<{ onPress?: () => void }> = ({ onPress }) => {
5+
return (
6+
<Pressable onPress={onPress}>
7+
<Image
8+
source={require("../../../assets/copy.png")}
9+
style={styles.container}
10+
/>
11+
</Pressable>
12+
);
13+
};
14+
15+
const styles = StyleSheet.create({
16+
container: {
17+
width: 14,
18+
height: 14,
19+
marginRight: 5,
20+
},
21+
});
22+
23+
export default CopyButton;

packages/react-native-query-devtool/src/components/JSONTreeSearcheable/index.tsx

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,92 @@
1-
import React from 'react';
1+
import React, { ReactNode, useState } from "react";
22

3-
import { Text, View } from 'react-native';
3+
import { Text, View } from "react-native";
44

5-
import JSONTree from 'react-native-json-tree';
5+
import JSONTree from "react-native-json-tree";
6+
import getNodeValue from "../../utils/getNodeValue";
7+
import CopyButton from "../CopyButton";
68

7-
import theme from './treeTheme';
8-
import JSONTreeSearcheableProps from './types';
9+
import theme from "./treeTheme";
10+
import JSONTreeSearcheableProps from "./types";
911

10-
import { styles } from './styles';
12+
import { styles } from "./styles";
13+
import Toast from "../Toast";
1114

1215
const JSONTreeSearcheable: React.FC<JSONTreeSearcheableProps> = ({
1316
data,
14-
searchTerm = '',
17+
searchTerm = "",
1518
}) => {
19+
const [showToast, setShowToast] = useState(false);
20+
21+
const handleShowToast = () => {
22+
setShowToast(true);
23+
24+
setTimeout(() => {
25+
setShowToast(false);
26+
}, 2000);
27+
};
28+
29+
/**
30+
* Returns a custom Text with the Copy Icon for Nodes
31+
* @param {string} type - The type of the node.
32+
* @param {any} data - The data of the node.
33+
* @param {ReactNode} itemType - The React element representing the type of the node {} or [].
34+
* @param {string | number | undefined} itemString - The string representation of the item.
35+
* @returns {ReactNode} Text with the Copy Icon
36+
*/
37+
const getItemStringRenderer = (
38+
type: any,
39+
data: any,
40+
itemType: ReactNode,
41+
itemString: string | number | undefined
42+
) => {
43+
return (
44+
<View style={styles.valueContainer}>
45+
<Text style={styles.itemString}>
46+
{itemType}
47+
<Text>{` ${itemString}`}</Text>
48+
</Text>
49+
50+
{type === "Object" || type === "Array" || type === "Iterable" ? (
51+
<CopyButton
52+
onPress={() => {
53+
console.log("📋", JSON.stringify(data));
54+
handleShowToast();
55+
}}
56+
/>
57+
) : null}
58+
</View>
59+
);
60+
};
61+
62+
/**
63+
* Returns a custom Text with the Copy Icon for label values (Primitives)
64+
* @param {string[]} keyPath - The key path for values.
65+
* @param {unknown} nodeType - The type of the node.
66+
* @returns {ReactNode} element containing the rendered custom Text with the Copy Icon for values.
67+
*/
68+
const labelRenderer = (keyPath: string[], nodeType?: unknown) => {
69+
return (
70+
<View style={styles.valueContainer}>
71+
{nodeType !== "Object" &&
72+
nodeType !== "Array" &&
73+
nodeType !== "Iterable" && (
74+
<CopyButton
75+
onPress={() => {
76+
console.log("📋", getNodeValue(data, keyPath));
77+
handleShowToast();
78+
}}
79+
/>
80+
)}
81+
<Text style={styles.textStyle}>{`${keyPath[0]}:`}</Text>
82+
</View>
83+
);
84+
};
85+
1686
const valueRenderer = (value: string) => {
1787
if (!searchTerm.trim()) return value;
1888

19-
const regex = new RegExp(searchTerm, 'gi');
89+
const regex = new RegExp(searchTerm, "gi");
2090
const parts = value.toString().split(regex);
2191

2292
return parts.map((part, index) => (
@@ -34,14 +104,18 @@ const JSONTreeSearcheable: React.FC<JSONTreeSearcheableProps> = ({
34104
theme={{
35105
...theme,
36106
valueLabel: {
37-
fontWeight: 'bold',
38-
fontFamily: 'Courier New',
107+
fontWeight: "bold",
108+
fontFamily: "Courier New",
39109
},
40110
}}
111+
keyPath={["data"]}
41112
invertTheme={false}
42-
// labelRenderer={labelRenderer}
43-
valueRenderer={valueRenderer}
113+
getItemString={getItemStringRenderer}
114+
labelRenderer={labelRenderer}
115+
// valueRenderer={valueRenderer}
44116
/>
117+
118+
{showToast && <Toast message="Value was printed in your terminal" />}
45119
</View>
46120
);
47121
};
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
1-
import { StyleSheet } from 'react-native';
1+
import { StyleSheet } from "react-native";
22

33
export const styles = StyleSheet.create({
44
container: {
5-
backgroundColor: '#0b1521',
5+
backgroundColor: "#0b1521",
66
},
77
yellowBackground: {
8-
backgroundColor: 'yellow',
8+
backgroundColor: "yellow",
9+
},
10+
valueContainer: {
11+
flexDirection: "row",
12+
alignItems: "center",
13+
paddingTop: 3,
14+
},
15+
itemString: {
16+
color: "#75715e",
17+
marginRight: 5,
18+
fontSize: 13,
19+
},
20+
textStyle: {
21+
color: "white",
22+
fontWeight: "bold",
23+
fontFamily: "Courier New",
924
},
1025
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { useState, useEffect } from "react";
2+
import { Text, StyleSheet, Animated } from "react-native";
3+
4+
const Toast: React.FC<{ message: string; duration?: number }> = ({
5+
message,
6+
duration = 5000,
7+
}) => {
8+
const [fadeAnim] = useState(new Animated.Value(0));
9+
10+
useEffect(() => {
11+
let timeoutId: NodeJS.Timeout;
12+
13+
Animated.timing(fadeAnim, {
14+
toValue: 1,
15+
duration: 500,
16+
useNativeDriver: true,
17+
}).start(() => {
18+
timeoutId = setTimeout(() => {
19+
Animated.timing(fadeAnim, {
20+
toValue: 0,
21+
duration: 500,
22+
useNativeDriver: true,
23+
}).start();
24+
}, duration);
25+
});
26+
27+
return () => {
28+
clearTimeout(timeoutId);
29+
};
30+
}, [fadeAnim, duration]);
31+
32+
return (
33+
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
34+
<Text style={styles.message}>{message}</Text>
35+
</Animated.View>
36+
);
37+
};
38+
39+
const styles = StyleSheet.create({
40+
container: {
41+
position: "absolute",
42+
top: 20,
43+
left: 20,
44+
right: 20,
45+
backgroundColor: "rgba(0, 0, 0, 0.8)",
46+
borderRadius: 5,
47+
padding: 10,
48+
},
49+
message: {
50+
textAlign: "center",
51+
color: "white",
52+
fontWeight: "bold",
53+
fontFamily: "Courier New",
54+
},
55+
});
56+
57+
export default Toast;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Retrieves the value from a nested object or array based on the provided key path
3+
* @param {any} data - The root object or array from which to retrieve the value
4+
* @param {string[]} keyPath - An array representing the path to the desired value
5+
* @returns {any} The value located at the specified key path, or undefined
6+
*/
7+
function getNodeValue(data: any, keyPath: string[]) {
8+
let value = data;
9+
10+
// This deletes the last `data` item of the array
11+
const reversedKeyPath = keyPath.slice(0, -1).reverse();
12+
13+
for (const key of reversedKeyPath) {
14+
if (typeof value[key] === "object") {
15+
value = value[key];
16+
} else {
17+
return value[key];
18+
}
19+
}
20+
21+
return value;
22+
}
23+
24+
export default getNodeValue;

0 commit comments

Comments
 (0)