From cf3c0ec470e68bf92d17ce0d163479e3fe18343b Mon Sep 17 00:00:00 2001 From: "lorenzo.neumann" <36760115+ln-12@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:20:27 +0200 Subject: [PATCH] added copy functionality to JsonView component --- client/src/components/JsonView.tsx | 102 ++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index e9ef0d2d7..575b13758 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -1,4 +1,5 @@ import { useState, memo, useMemo, useCallback, useEffect } from "react"; +import type React from "react"; import type { JsonValue } from "@/utils/jsonUtils"; import clsx from "clsx"; import { Copy, CheckCheck } from "lucide-react"; @@ -114,6 +115,7 @@ const JsonNode = memo( initialExpandDepth, isError = false, }: JsonNodeProps) => { + const { toast } = useToast(); const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth); const [typeStyleMap] = useState>({ number: "text-blue-600", @@ -126,6 +128,52 @@ const JsonNode = memo( }); const dataType = getDataType(data); + const [copied, setCopied] = useState(false); + useEffect(() => { + let timeoutId: NodeJS.Timeout; + if (copied) { + timeoutId = setTimeout(() => setCopied(false), 500); + } + return () => { + if (timeoutId) clearTimeout(timeoutId); + }; + }, [copied]); + + const handleCopyValue = useCallback( + (value: JsonValue) => { + try { + let text: string; + const valueType = getDataType(value); + switch (valueType) { + case "string": + text = value as unknown as string; + break; + case "number": + case "boolean": + text = String(value); + break; + case "null": + text = "null"; + break; + case "undefined": + text = "undefined"; + break; + default: + text = JSON.stringify(value); + } + navigator.clipboard.writeText(text); + setCopied(true); + } catch (error) { + toast({ + title: "Error", + description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`, + variant: "destructive", + }); + } + }, + [toast], + ); + const renderCollapsible = (isArray: boolean) => { const items = isArray ? (data as JsonValue[]) @@ -219,7 +267,7 @@ const JsonNode = memo( if (!isTooLong) { return ( -
+
{name && ( {name}: @@ -233,12 +281,28 @@ const JsonNode = memo( > "{value}" +
); } return ( -
+
{name && ( {name}: @@ -254,6 +318,22 @@ const JsonNode = memo( > {isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`} +
); }; @@ -266,7 +346,7 @@ const JsonNode = memo( return renderString(data as string); default: return ( -
+
{name && ( {name}: @@ -275,6 +355,22 @@ const JsonNode = memo( {data === null ? "null" : String(data)} +
); }