From 50f062e24aad1acb8fc6871aa3c138048e5aed01 Mon Sep 17 00:00:00 2001 From: kofki <69553679+kofki@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:33:38 -0500 Subject: [PATCH] feat: add edit button that modifies node and text editor json --- .../views/GraphView/CustomNode/index.tsx | 8 +- src/features/modals/NodeModal/index.tsx | 155 ++++++++++++++++-- 2 files changed, 150 insertions(+), 13 deletions(-) diff --git a/src/features/editor/views/GraphView/CustomNode/index.tsx b/src/features/editor/views/GraphView/CustomNode/index.tsx index ea3ac6be981..8dd7bdd9834 100644 --- a/src/features/editor/views/GraphView/CustomNode/index.tsx +++ b/src/features/editor/views/GraphView/CustomNode/index.tsx @@ -48,9 +48,13 @@ const CustomNodeWrapper = (nodeProps: NodeProps) => { > {({ node, x, y }) => { const hasKey = nodeProps.properties.text[0].key; - if (!hasKey) return ; + const nodeComponent = !hasKey ? ( + + ) : ( + + ); - return ; + return nodeComponent; }} ); diff --git a/src/features/modals/NodeModal/index.tsx b/src/features/modals/NodeModal/index.tsx index caba85febac..7aa06baacee 100644 --- a/src/features/modals/NodeModal/index.tsx +++ b/src/features/modals/NodeModal/index.tsx @@ -1,16 +1,50 @@ 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, + Textarea, + Group, +} from "@mantine/core"; import { CodeHighlight } from "@mantine/code-highlight"; +import { modify, applyEdits } from "jsonc-parser"; +import useFile from "../../../store/useFile"; +import useJson from "../../../store/useJson"; import type { NodeData } from "../../../types/graph"; import useGraph from "../../editor/views/GraphView/stores/useGraph"; -// return object from json removing array and object fields +// get the value from the full json string at the node path (JSONPath array) +const getValueAtPath = (jsonStr: string, path?: NodeData["path"]) => { + try { + const root = JSON.parse(jsonStr); + if (!path || path.length === 0) return root; + + let cur: any = root; + for (const seg of path) { + if (typeof seg === "number") { + cur = cur?.[seg]; + } else { + cur = cur?.[seg as string]; + } + if (typeof cur === "undefined") return undefined; + } + return cur; + } catch (e) { + return undefined; + } +}; + +// fallback formatter: builds a small object from node rows for primitive-only nodes const normalizeNodeData = (nodeRows: NodeData["text"]) => { if (!nodeRows || nodeRows.length === 0) return "{}"; if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`; - const obj = {}; + const obj: Record = {}; nodeRows?.forEach(row => { if (row.type !== "array" && row.type !== "object") { if (row.key) obj[row.key] = row.value; @@ -28,6 +62,29 @@ const jsonPathToString = (path?: NodeData["path"]) => { export const NodeModal = ({ opened, onClose }: ModalProps) => { const nodeData = useGraph(state => state.selectedNode); + const json = useJson(state => state.json); + + const [editing, setEditing] = React.useState(false); + const [value, setValue] = React.useState(""); + + React.useEffect(() => { + // reset editing state and editor value when modal opens or selection changes + setEditing(false); + // prefer the actual subtree from the current json so nested children are preserved + const subtree = getValueAtPath(json, nodeData?.path); + if (typeof subtree !== "undefined") { + try { + setValue( + typeof subtree === "string" ? JSON.stringify(subtree) : JSON.stringify(subtree, null, 2) + ); + } catch (e) { + // fallback + setValue(normalizeNodeData(nodeData?.text ?? [])); + } + } else { + setValue(normalizeNodeData(nodeData?.text ?? [])); + } + }, [opened, nodeData]); return ( @@ -37,16 +94,92 @@ export const NodeModal = ({ opened, onClose }: ModalProps) => { Content - + + {!editing ? ( + + ) : ( + + + + + )} + + - + {!editing ? ( + + ) : ( +