1- import { memo , useCallback , useEffect , useMemo , useRef } from 'react'
2- import { NodeDataRow } from '../../utils/useNodeGraph'
1+ import { memo , useCallback , useContext , useEffect , useMemo , useRef } from 'react'
32import { Handle , Position } from '@xyflow/react'
43import { useSetRecoilState } from 'recoil'
54import saveDataNodeSizesState from '../../state/saveDataNodeSizesState'
65import clsx from 'clsx'
6+ import Button from '../Button'
7+ import { IconCopy } from '@tabler/icons-react'
8+ import ToastContext from '../toast/ToastContext'
9+ import { NodeDataRow } from '../../utils/nodeGraphHelpers'
10+
11+ const MIN_NODE_WIDTH = 'min-w-[200px]'
12+ const MAX_NODE_WIDTH = 'max-w-[600px]'
713
814type SaveDataNodeProps = {
915 id : string
10- data : { rows : NodeDataRow [ ] , search : string , formatVersion : string }
16+ data : {
17+ rows : NodeDataRow [ ]
18+ search : string
19+ formatVersion : string
20+ isHovered : boolean
21+ }
1122}
1223
1324function SaveDataNode ( { id, data } : SaveDataNodeProps ) {
@@ -30,7 +41,8 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
3041 } , [ id , setNodeSizes ] )
3142
3243 const valueIsNumber = useCallback ( ( value : unknown ) => {
33- return ! isNaN ( Number ( value ) )
44+ const str = String ( value )
45+ return str !== '' && ! isNaN ( Number ( value ) )
3446 } , [ ] )
3547
3648 const valueIsBoolean = useCallback ( ( value : unknown ) => {
@@ -51,6 +63,9 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
5163
5264 const renderItem = useCallback ( ( item : string ) => {
5365 if ( valueIsString ( item ) ) {
66+ if ( item === '' ) {
67+ return '""'
68+ }
5469 if ( data . formatVersion === 'godot.v2' && item . includes ( '"' ) ) {
5570 return item
5671 }
@@ -65,15 +80,39 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
6580 } )
6681 } , [ data . search , data . rows ] )
6782
83+ const valueRow = useMemo ( ( ) => {
84+ return data . rows . find ( ( row ) => row . item . startsWith ( 'value' ) )
85+ } , [ data . rows ] )
86+
87+ const canShowCopyButton = data . isHovered && valueRow
88+
89+ const toast = useContext ( ToastContext )
90+
91+ const copyValue = useCallback ( ( ) => {
92+ if ( valueRow ) {
93+ const value = valueRow . item . split ( ': ' ) [ 1 ] || ''
94+ navigator . clipboard . writeText ( value )
95+ toast . trigger ( 'Value copied to clipboard' )
96+ }
97+ } , [ valueRow , toast ] )
98+
6899 return (
69- < div ref = { ref } className = { clsx ( 'py-4 px-8 rounded bg-gray-900 border-2 border-gray-500 transition-all' , {
70- 'bg-opacity-30' : data . search !== '' && ! searchMatches
71- } ) } >
72- < div className = { clsx ( 'transition-opacity -mx-4' , { 'opacity-30' : data . search !== '' && ! searchMatches } ) } >
100+ < div
101+ ref = { ref }
102+ className = { clsx (
103+ 'py-4 px-8 rounded bg-gray-900 border-2 border-gray-500 transition-all' ,
104+ MIN_NODE_WIDTH ,
105+ MAX_NODE_WIDTH ,
106+ {
107+ 'bg-opacity-30' : data . search !== '' && ! searchMatches
108+ }
109+ ) }
110+ >
111+ < div className = { clsx ( 'relative transition-opacity -mx-4' , { 'opacity-30' : data . search !== '' && ! searchMatches } ) } >
73112 { data . rows . map ( ( row , idx ) => (
74113 < div
75114 key = { idx }
76- className = { clsx ( 'text-sm text-white font-mono text-nowrap ' , {
115+ className = { clsx ( 'text-sm text-white font-mono wrap-break-word ' , {
77116 'text-center' : data . rows . length === 1
78117 } ) }
79118 >
@@ -83,15 +122,15 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
83122 < span className = 'text-indigo-300' > { row . item . split ( ' ' ) [ 1 ] } </ span >
84123 </ >
85124 }
86- { row . type !== 'array' && / ( . * : . * ) \w + / . test ( row . item ) &&
125+ { row . type !== 'array' && row . item . includes ( ': ' ) &&
87126 < >
88- < span className = 'font-medium' > { row . item . split ( ' ' ) [ 0 ] } </ span >
89- < span className = { composeClassNames ( row . item . split ( ' ' ) [ 1 ] ) } >
90- { renderItem ( row . item . split ( ': ' ) [ 1 ] ) }
127+ < span className = 'font-medium' > { row . item . split ( ': ' ) [ 0 ] } : </ span >
128+ < span className = { composeClassNames ( row . item . split ( ': ' ) [ 1 ] || '' ) } >
129+ { renderItem ( row . item . split ( ': ' ) [ 1 ] || '' ) }
91130 </ span >
92131 </ >
93132 }
94- { row . type !== 'array' && ! / ( . * : . * ) \w + / . test ( row . item ) &&
133+ { row . type !== 'array' && ! row . item . includes ( ': ' ) &&
95134 < span
96135 className = { composeClassNames ( row . item ) } >
97136 { renderItem ( row . item ) }
@@ -100,6 +139,15 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
100139 </ div >
101140 ) ) }
102141
142+ { canShowCopyButton &&
143+ < Button
144+ type = 'button'
145+ className = 'absolute right-0 top-0 w-auto!'
146+ icon = { < IconCopy size = { 20 } /> }
147+ onClick = { copyValue }
148+ />
149+ }
150+
103151 < Handle type = 'target' position = { Position . Top } className = 'invisible mt-1' />
104152 < Handle type = 'source' position = { Position . Bottom } className = 'invisible' />
105153 </ div >
0 commit comments