11import React from "react" ;
22import type { ModalProps } from "@mantine/core" ;
3- import { Modal , Stack , Text , ScrollArea , Flex , CloseButton } from "@mantine/core" ;
3+ import { Modal , Stack , Text , ScrollArea , Flex , CloseButton , Button , Textarea } from "@mantine/core" ;
44import { CodeHighlight } from "@mantine/code-highlight" ;
55import type { NodeData } from "../../../types/graph" ;
66import useGraph from "../../editor/views/GraphView/stores/useGraph" ;
7+ import useJson from "../../../store/useJson" ;
78
89// return object from json removing array and object fields
910const normalizeNodeData = ( nodeRows : NodeData [ "text" ] ) => {
1011 if ( ! nodeRows || nodeRows . length === 0 ) return "{}" ;
1112 if ( nodeRows . length === 1 && ! nodeRows [ 0 ] . key ) return `${ nodeRows [ 0 ] . value } ` ;
1213
13- const obj = { } ;
14+ const obj : Record < string , any > = { } ;
1415 nodeRows ?. forEach ( row => {
1516 if ( row . type !== "array" && row . type !== "object" ) {
1617 if ( row . key ) obj [ row . key ] = row . value ;
@@ -28,6 +29,73 @@ const jsonPathToString = (path?: NodeData["path"]) => {
2829
2930export const NodeModal = ( { opened, onClose } : ModalProps ) => {
3031 const nodeData = useGraph ( state => state . selectedNode ) ;
32+ const getJson = useJson . getState ( ) . getJson ;
33+ const setJson = useJson . getState ( ) . setJson ;
34+
35+ const [ isEditing , setIsEditing ] = React . useState ( false ) ;
36+ const [ editedContent , setEditedContent ] = React . useState ( "" ) ;
37+ const [ error , setError ] = React . useState < string | null > ( null ) ;
38+
39+ React . useEffect ( ( ) => {
40+ // reset editing state whenever node changes or modal opened/closed
41+ setIsEditing ( false ) ;
42+ setError ( null ) ;
43+ setEditedContent ( "" ) ;
44+ } , [ nodeData , opened ] ) ;
45+
46+ const enterEditMode = ( ) => {
47+ setError ( null ) ;
48+ setEditedContent ( normalizeNodeData ( nodeData ?. text ?? [ ] ) ) ;
49+ setIsEditing ( true ) ;
50+ } ;
51+
52+ // helper to set value at path inside an object (mutates obj)
53+ const setValueAtPath = ( obj : any , path : NodeData [ "path" ] | undefined , value : any ) => {
54+ if ( ! path || path . length === 0 ) {
55+ return value ;
56+ }
57+
58+ let curr : any = obj ;
59+ for ( let i = 0 ; i < path . length - 1 ; i ++ ) {
60+ const seg = path [ i ] as string | number ;
61+ if ( typeof seg === "number" ) {
62+ if ( ! Array . isArray ( curr [ seg ] ) ) curr [ seg ] = [ ] ;
63+ } else {
64+ if ( curr [ seg ] === undefined ) curr [ seg ] = { } ;
65+ }
66+ curr = curr [ seg ] ;
67+ }
68+
69+ const last = path [ path . length - 1 ] as string | number ;
70+ curr [ last ] = value ;
71+ return obj ;
72+ } ;
73+
74+ const handleSave = ( ) => {
75+ setError ( null ) ;
76+ try {
77+ const newValue = JSON . parse ( editedContent ) ;
78+
79+ // get current app JSON
80+ const raw = getJson ( ) ;
81+ const parsed = JSON . parse ( raw ) ;
82+
83+ const updated = setValueAtPath ( parsed , nodeData ?. path , newValue ) ;
84+
85+ setJson ( JSON . stringify ( updated , null , 2 ) ) ;
86+
87+ setIsEditing ( false ) ;
88+ onClose ?.( ) ;
89+ } catch ( err : any ) {
90+ setError ( err ?. message ?? String ( err ) ) ;
91+ }
92+ } ;
93+
94+ const handleCancel = ( ) => {
95+ setIsEditing ( false ) ;
96+ setError ( null ) ;
97+ setEditedContent ( "" ) ;
98+ } ;
3199
32100 return (
33101 < Modal size = "auto" opened = { opened } onClose = { onClose } centered withCloseButton = { false } >
@@ -37,16 +105,46 @@ export const NodeModal = ({ opened, onClose }: ModalProps) => {
37105 < Text fz = "xs" fw = { 500 } >
38106 Content
39107 </ Text >
40- < CloseButton onClick = { onClose } />
108+ < Flex gap = "xs" align = "center" >
109+ { ! isEditing ? (
110+ < Button size = "xs" variant = "outline" onClick = { enterEditMode } >
111+ Edit
112+ </ Button >
113+ ) : (
114+ < >
115+ < Button size = "xs" color = "blue" onClick = { handleSave } >
116+ Save
117+ </ Button >
118+ < Button size = "xs" variant = "outline" onClick = { handleCancel } >
119+ Cancel
120+ </ Button >
121+ </ >
122+ ) }
123+ < CloseButton onClick = { onClose } />
124+ </ Flex >
41125 </ Flex >
42126 < ScrollArea . Autosize mah = { 250 } maw = { 600 } >
43- < CodeHighlight
44- code = { normalizeNodeData ( nodeData ?. text ?? [ ] ) }
45- miw = { 350 }
46- maw = { 600 }
47- language = "json"
48- withCopyButton
49- />
127+ { ! isEditing ? (
128+ < CodeHighlight
129+ code = { normalizeNodeData ( nodeData ?. text ?? [ ] ) }
130+ miw = { 350 }
131+ maw = { 600 }
132+ language = "json"
133+ withCopyButton
134+ />
135+ ) : (
136+ < Textarea
137+ minRows = { 6 }
138+ value = { editedContent }
139+ onChange = { e => setEditedContent ( e . currentTarget . value ) }
140+ styles = { { input : { fontFamily : "monospace" } } }
141+ />
142+ ) }
143+ { error && (
144+ < Text fz = "xs" color = "red" mt = "xs" >
145+ { error }
146+ </ Text >
147+ ) }
50148 </ ScrollArea . Autosize >
51149 </ Stack >
52150 < Text fz = "xs" fw = { 500 } >
0 commit comments