Skip to content

Commit fade44a

Browse files
committed
small changes
1 parent eb409d2 commit fade44a

File tree

1 file changed

+126
-9
lines changed

1 file changed

+126
-9
lines changed

src/features/modals/NodeModal/index.tsx

Lines changed: 126 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React from "react";
22
import 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, Group } from "@mantine/core";
44
import { CodeHighlight } from "@mantine/code-highlight";
55
import type { NodeData } from "../../../types/graph";
66
import useGraph from "../../editor/views/GraphView/stores/useGraph";
7+
import useFile from "../../../store/useFile";
8+
import useJson from "../../../store/useJson";
9+
import { contentToJson } from "../../../lib/utils/jsonAdapter";
710

811
// return object from json removing array and object fields
912
const normalizeNodeData = (nodeRows: NodeData["text"]) => {
@@ -28,6 +31,20 @@ const jsonPathToString = (path?: NodeData["path"]) => {
2831

2932
export const NodeModal = ({ opened, onClose }: ModalProps) => {
3033
const nodeData = useGraph(state => state.selectedNode);
34+
const contents = useFile(state => state.contents);
35+
const setContents = useFile(state => state.setContents);
36+
const setJson = useJson(state => state.setJson);
37+
38+
const [editing, setEditing] = React.useState(false);
39+
const [localValue, setLocalValue] = React.useState("");
40+
41+
React.useEffect(() => {
42+
// when opening modal reset editing state and local value
43+
if (opened) {
44+
setEditing(false);
45+
setLocalValue(normalizeNodeData(nodeData?.text ?? []));
46+
}
47+
}, [opened, nodeData]);
3148

3249
return (
3350
<Modal size="auto" opened={opened} onClose={onClose} centered withCloseButton={false}>
@@ -37,16 +54,116 @@ export const NodeModal = ({ opened, onClose }: ModalProps) => {
3754
<Text fz="xs" fw={500}>
3855
Content
3956
</Text>
40-
<CloseButton onClick={onClose} />
57+
<Group spacing="xs">
58+
{!editing && (
59+
<Button size="xs" variant="default" onClick={() => setEditing(true)}>
60+
Edit
61+
</Button>
62+
)}
63+
{editing && (
64+
<>
65+
<Button
66+
size="xs"
67+
color="green"
68+
onClick={async () => {
69+
try {
70+
// parse the left editor contents into an object
71+
const root = await contentToJson(contents);
72+
// parse the edited content
73+
const edited = await contentToJson(localValue);
74+
75+
// helper to get value by path
76+
const getByPath = (obj: any, path?: any[]) => {
77+
if (!path || path.length === 0) return obj;
78+
return path.reduce((acc: any, cur: any) => (acc ? acc[cur] : undefined), obj);
79+
};
80+
81+
// helper to set value at path
82+
const setByPath = (obj: any, path: any[] | undefined, value: any) => {
83+
if (!path || path.length === 0) return value;
84+
const last = path[path.length - 1];
85+
const parentPath = path.slice(0, -1);
86+
const parent = parentPath.length === 0 ? obj : getByPath(obj, parentPath);
87+
if (!parent) return;
88+
parent[last] = value;
89+
};
90+
91+
const path = nodeData?.path;
92+
93+
const currentTarget = getByPath(root, path as any[] | undefined);
94+
95+
if (currentTarget && typeof currentTarget === "object" && !Array.isArray(currentTarget) && edited && typeof edited === "object") {
96+
// merge primitive fields into object node
97+
Object.keys(edited).forEach(k => {
98+
currentTarget[k] = edited[k];
99+
});
100+
} else if (typeof path !== "undefined" && path && path.length > 0) {
101+
setByPath(root, path as any[], edited);
102+
} else {
103+
// root replacement
104+
// if edited is object merge, otherwise replace root
105+
if (edited && typeof edited === "object" && !Array.isArray(edited)) {
106+
Object.keys(edited).forEach(k => {
107+
(root as any)[k] = edited[k];
108+
});
109+
} else {
110+
// replace root completely
111+
// setContents expects a string
112+
await setContents({ contents: JSON.stringify(edited, null, 2), hasChanges: true });
113+
setJson(JSON.stringify(edited, null, 2));
114+
setEditing(false);
115+
return;
116+
}
117+
}
118+
119+
const newContents = JSON.stringify(root, null, 2);
120+
await setContents({ contents: newContents, hasChanges: true });
121+
setJson(newContents);
122+
setEditing(false);
123+
} catch (error) {
124+
// if parsing fails, keep editing mode
125+
// ideally show an error toast
126+
// console.error(error);
127+
}
128+
}}
129+
>
130+
Save
131+
</Button>
132+
<Button
133+
size="xs"
134+
color="gray"
135+
variant="outline"
136+
onClick={() => {
137+
// discard changes
138+
setLocalValue(normalizeNodeData(nodeData?.text ?? []));
139+
setEditing(false);
140+
}}
141+
>
142+
Cancel
143+
</Button>
144+
</>
145+
)}
146+
<CloseButton onClick={onClose} />
147+
</Group>
41148
</Flex>
42149
<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-
/>
150+
{!editing ? (
151+
<CodeHighlight
152+
code={normalizeNodeData(nodeData?.text ?? [])}
153+
miw={350}
154+
maw={600}
155+
language="json"
156+
withCopyButton
157+
/>
158+
) : (
159+
<Textarea
160+
styles={{ input: { fontFamily: "monospace" } }}
161+
minRows={6}
162+
maw={600}
163+
value={localValue}
164+
onChange={e => setLocalValue(e.currentTarget.value)}
165+
/>
166+
)}
50167
</ScrollArea.Autosize>
51168
</Stack>
52169
<Text fz="xs" fw={500}>

0 commit comments

Comments
 (0)