11// for rendering the preview and download buttons in folder structure row
22import type { TreeNode } from "./types" ;
33import { formatLeafValue , isPreviewable } from "./utils" ;
4+ import CheckIcon from "@mui/icons-material/Check" ;
5+ // for copy button
6+ // add to imports
7+ import ContentCopyIcon from "@mui/icons-material/ContentCopy" ;
48import DownloadIcon from "@mui/icons-material/Download" ;
59import ExpandLess from "@mui/icons-material/ExpandLess" ;
610import ExpandMore from "@mui/icons-material/ExpandMore" ;
711import FolderIcon from "@mui/icons-material/Folder" ;
812import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile" ;
913import VisibilityIcon from "@mui/icons-material/Visibility" ;
1014import { Box , Button , Collapse , Typography } from "@mui/material" ;
15+ import { Tooltip , IconButton } from "@mui/material" ;
1116import { Colors } from "design/theme" ;
1217import React from "react" ;
1318
@@ -18,20 +23,53 @@ type Props = {
1823 // src is either an external URL(string) or the internal object
1924 onPreview : ( src : string | any , index : number , isInternal ?: boolean ) => void ;
2025 getInternalByPath : ( path : string ) => { data : any ; index : number } | undefined ;
26+ getJsonByPath ?: ( path : string ) => any ;
27+ } ;
28+
29+ // copy helper function
30+ const copyText = async ( text : string ) => {
31+ try {
32+ await navigator . clipboard . writeText ( text ) ;
33+ return true ;
34+ } catch {
35+ // fallback if the copy api not working
36+ const ta = document . createElement ( "textarea" ) ;
37+ ta . value = text ;
38+ ta . style . position = "fixed" ;
39+ ta . style . opacity = "0" ;
40+ document . body . appendChild ( ta ) ;
41+ ta . select ( ) ;
42+ const ok = document . execCommand ( "copy" ) ;
43+ document . body . removeChild ( ta ) ;
44+ return ok ;
45+ }
2146} ;
2247
2348const FileTreeRow : React . FC < Props > = ( {
2449 node,
2550 level,
2651 onPreview,
2752 getInternalByPath,
53+ getJsonByPath,
2854} ) => {
2955 const [ open , setOpen ] = React . useState ( false ) ;
56+ const [ copied , setCopied ] = React . useState ( false ) ;
3057 // const internal = getInternalByPath?.(node.path);
3158 // const internal = getInternalByPath ? getInternalByPath(node.path) : undefined;
3259 const internal = getInternalByPath ( node . path ) ;
3360 const externalUrl = node . link ?. url ;
3461
62+ const handleCopy = async ( e : React . MouseEvent ) => {
63+ e . stopPropagation ( ) ; // prevent expand/ collapse from firing when click the copy button
64+ const json = getJsonByPath ?.( node . path ) ; // call getJsonByPath(node.path)
65+ const asText = JSON . stringify ( json , null , 2 ) ; // subtree at this row
66+ if ( await copyText ( asText ?? "null" ) ) {
67+ // call copyText function
68+ setCopied ( true ) ;
69+ setTimeout ( ( ) => setCopied ( false ) , 1200 ) ;
70+ }
71+ } ;
72+
3573 // if (node.kind === "folder") {
3674 // return (
3775 // <>
@@ -103,7 +141,7 @@ const FileTreeRow: React.FC<Props> = ({
103141 { node . name }
104142 </ Typography >
105143
106- { /* ✅ Actions on folder if it carries a link (from linkHere) */ }
144+ { /* Actions on folder if it carries a link (from linkHere) */ }
107145 { node . link ?. url && (
108146 < Box
109147 sx = { { display : "flex" , gap : 1 , mr : 0.5 , flexShrink : 0 } }
@@ -150,6 +188,22 @@ const FileTreeRow: React.FC<Props> = ({
150188 </ Box >
151189 ) }
152190
191+ { /* Copy subtree JSON button */ }
192+ < Box
193+ sx = { { display : "flex" , gap : 1 , mr : 0.5 , flexShrink : 0 } }
194+ onClick = { ( e ) => e . stopPropagation ( ) }
195+ >
196+ < Tooltip title = { copied ? "Copied!" : "Copy subtree JSON" } arrow >
197+ < IconButton size = "small" onClick = { handleCopy } >
198+ { copied ? (
199+ < CheckIcon fontSize = "inherit" />
200+ ) : (
201+ < ContentCopyIcon fontSize = "inherit" />
202+ ) }
203+ </ IconButton >
204+ </ Tooltip >
205+ </ Box >
206+
153207 { open ? < ExpandLess /> : < ExpandMore /> }
154208 </ Box >
155209
@@ -162,6 +216,7 @@ const FileTreeRow: React.FC<Props> = ({
162216 level = { level + 1 }
163217 onPreview = { onPreview }
164218 getInternalByPath = { getInternalByPath }
219+ getJsonByPath = { getJsonByPath }
165220 />
166221 ) ) }
167222 </ Collapse >
@@ -218,7 +273,22 @@ const FileTreeRow: React.FC<Props> = ({
218273 </ Typography >
219274 ) }
220275 </ Box >
221-
276+ { /* ALWAYS show copy for files, even when no external/internal */ }
277+ < Tooltip title = { copied ? "Copied!" : "Copy JSON" } arrow >
278+ < span >
279+ < IconButton
280+ size = "small"
281+ onClick = { handleCopy }
282+ disabled = { ! getJsonByPath } // optional safety
283+ >
284+ { copied ? (
285+ < CheckIcon fontSize = "inherit" />
286+ ) : (
287+ < ContentCopyIcon fontSize = "inherit" />
288+ ) }
289+ </ IconButton >
290+ </ span >
291+ </ Tooltip >
222292 { ( externalUrl || internal ) && (
223293 < Box
224294 sx = { { display : "flex" , gap : 1 , flexShrink : 0 } }
@@ -261,6 +331,7 @@ const FileTreeRow: React.FC<Props> = ({
261331 Preview
262332 </ Button >
263333 ) }
334+
264335 { /* {node.link?.url && (
265336 <Box sx={{ display: "flex", gap: 1, flexShrink: 0 }}>
266337 <Button
0 commit comments