diff --git a/src/features/editor/views/GraphView/stores/useGraph.ts b/src/features/editor/views/GraphView/stores/useGraph.ts index 6e067c3c2a7..66f3f7ec68d 100644 --- a/src/features/editor/views/GraphView/stores/useGraph.ts +++ b/src/features/editor/views/GraphView/stores/useGraph.ts @@ -43,6 +43,7 @@ interface GraphActions { centerView: () => void; clearGraph: () => void; setZoomFactor: (zoomFactor: number) => void; + updateNodeValues: (nodeId: string, updates: { index: number; value: any }[]) => void; } const useGraph = create((set, get) => ({ @@ -101,6 +102,41 @@ const useGraph = create((set, get) => ({ }, toggleFullscreen: fullscreen => set({ fullscreen }), setViewPort: viewPort => set({ viewPort }), + updateNodeValues: (nodeId: string, updates: { index: number; value: any }[]) => { + try { + const useJsonStore = require("../../../../../store/useJson").default; + const currentJson = useJsonStore.getState().json; + const parsed = JSON.parse(currentJson); + const nodes = get().nodes || []; + const nodeToUpdate = nodes.find(n => n.id === nodeId); + + if (!nodeToUpdate || !nodeToUpdate.path) { + console.error("Node not found or path is undefined"); + return; + } + + // Navigate to target using JSONPath + let target: any = parsed; + for (const seg of nodeToUpdate.path) { + target = target[seg as any]; + } + + // Apply each update + updates.forEach(u => { + const row = nodeToUpdate.text[u.index]; + if (!row || !row.key) return; + target[row.key] = u.value; + }); + + // Update JSON and force re-parse in graph + const updatedJson = JSON.stringify(parsed, null, 2); + useJsonStore.getState().setJson(updatedJson); + + console.log("Updated JSON:", updatedJson); // debug + } catch (error) { + console.error("Failed to update node values:", error); + } + }, })); export default useGraph; diff --git a/src/features/modals/NodeModal/index.tsx b/src/features/modals/NodeModal/index.tsx index caba85febac..f5a552852f6 100644 --- a/src/features/modals/NodeModal/index.tsx +++ b/src/features/modals/NodeModal/index.tsx @@ -1,9 +1,10 @@ import React from "react"; import type { ModalProps } from "@mantine/core"; -import { Modal, Stack, Text, ScrollArea, Flex, CloseButton } from "@mantine/core"; +import { Modal, Stack, Text, ScrollArea, Flex, CloseButton, Button, TextInput } from "@mantine/core"; import { CodeHighlight } from "@mantine/code-highlight"; import type { NodeData } from "../../../types/graph"; import useGraph from "../../editor/views/GraphView/stores/useGraph"; +import useFile from "../../../store/useFile"; // return object from json removing array and object fields const normalizeNodeData = (nodeRows: NodeData["text"]) => { @@ -28,27 +29,181 @@ const jsonPathToString = (path?: NodeData["path"]) => { export const NodeModal = ({ opened, onClose }: ModalProps) => { const nodeData = useGraph(state => state.selectedNode); + const updateNodeValues = useGraph(state => state.updateNodeValues); + const contents = useFile(state => state.contents); + const setContents = useFile(state => state.setContents); + const [isEditing, setIsEditing] = React.useState(false); + const [editFields, setEditFields] = React.useState>({}); + const [refreshKey, setRefreshKey] = React.useState(0); + const [codeRefreshKey, setCodeRefreshKey] = React.useState(0); + + // Initialize edit fields when entering edit mode + React.useEffect(() => { + if (isEditing && nodeData?.text) { + const fields: Record = {}; + nodeData.text.forEach(row => { + if (row.type !== "array" && row.type !== "object" && row.key) { + fields[row.key] = String(row.value ?? ""); + } + }); + setEditFields(fields); + } + }, [isEditing, nodeData]); + + const handleEditClick = () => { + setIsEditing(true); + }; + + const coerceValue = (val: string) => { + if (val === "null") return null; + if (val === "true") return true; + if (val === "false") return false; + const n = Number(val); + if (!Number.isNaN(n) && String(n) === val) return n; + return val; + }; + + const handleSaveClick = () => { + if (!nodeData) return; + + const updates = nodeData.text + .map((row, index) => { + if (row.type !== "array" && row.type !== "object" && row.key) { + const newValue = editFields[row.key]; + if (newValue !== undefined && String(row.value) !== newValue) { + return { index, value: coerceValue(newValue) }; + } + } + return null; + }) + .filter((u): u is { index: number; value: any } => u !== null); + + if (updates.length > 0 && updateNodeValues) { + updateNodeValues(nodeData.id, updates); + + try { + const parsed = JSON.parse(contents); + const nodes = useGraph.getState().nodes || []; + const nodeToUpdate = nodes.find(n => n.id === nodeData.id); + + if (nodeToUpdate && nodeToUpdate.path) { + let target: any = parsed; + for (const seg of nodeToUpdate.path) { + target = target[seg as any]; + } + + updates.forEach(u => { + const row = nodeToUpdate.text[u.index]; + if (row && row.key) { + target[row.key] = u.value; + } + }); + + const updatedJson = JSON.stringify(parsed, null, 2); + setContents({ contents: updatedJson, skipUpdate: false }); + } + } catch (error) { + console.error("Failed to sync to text editor:", error); + } + + // Refresh selectedNode from updated graph state to reflect new values + const updatedNode = useGraph.getState().nodes.find(n => n.id === nodeData.id); + if (updatedNode) { + useGraph.getState().setSelectedNode(updatedNode); + } + + setRefreshKey(prev => prev + 1); + setCodeRefreshKey(prev => prev + 1); + setTimeout(() => { + setIsEditing(false); + }, 100); + } else { + setIsEditing(false); + } + }; + + const handleCancelClick = () => { + setEditFields({}); + setIsEditing(false); + }; + + const handleFieldChange = (key: string, value: string) => { + setEditFields(prev => ({ + ...prev, + [key]: value, + })); + }; return ( - + Content - + + {!isEditing ? ( + + ) : ( + <> + + + + )} + + + - + {!isEditing ? ( + + ) : ( + + {nodeData?.text + ?.filter(row => row.type !== "array" && row.type !== "object" && row.key) + .map(row => ( + + + {row.key} + + handleFieldChange(row.key!, e.currentTarget.value)} + /> + + ))} + + )} + JSON Path