Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 174 additions & 11 deletions src/features/modals/NodeModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from "react";
import React, { useEffect, useState } 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 useJson from "../../../store/useJson";

// return object from json removing array and object fields
const normalizeNodeData = (nodeRows: NodeData["text"]) => {
Expand All @@ -26,8 +27,127 @@ const jsonPathToString = (path?: NodeData["path"]) => {
return `$[${segments.join("][")}]`;
};

// Extract editable fields from node data
const getEditableFields = (nodeRows?: NodeData["text"]) => {
if (!nodeRows || nodeRows.length === 0) return [];

return nodeRows.filter(row =>
row.type !== "array" && row.type !== "object" && row.key
).map(row => ({
key: row.key,
value: row.value
}));
};

export const NodeModal = ({ opened, onClose }: ModalProps) => {
const nodeData = useGraph(state => state.selectedNode);
const nodes = useGraph(state => state.nodes);
const edges = useGraph(state => state.edges);
const setSelectedNode = useGraph(state => state.setSelectedNode);
const setJson = useJson(state => state.setJson);

const [isEditing, setIsEditing] = useState(false);
const [editableFields, setEditableFields] = useState<Array<{ key: string; value: string }>>([]);

// Initialize editable fields when modal opens or nodeData changes
useEffect(() => {
if (nodeData?.text) {
setEditableFields(getEditableFields(nodeData.text).map(field => ({
key: field.key || '',
value: String(field.value)
})));
}
}, [nodeData]);

const handleEdit = () => {
setIsEditing(true);
};

const handleSave = () => {
if (!nodeData) return;

// Create updated node data with new values
const updatedText = nodeData.text.map(row => {
const editedField = editableFields.find(field => field.key === row.key);
if (editedField) {
return { ...row, value: editedField.value };
}
return row;
});

// Update the nodes array with the modified node
const updatedNodes = nodes.map(node => {
if (node.id === nodeData.id) {
const updatedNode = { ...node, text: updatedText };
// Also update the selected node
setSelectedNode(updatedNode);
return updatedNode;
}
return node;
});

// Update the store with the new nodes array
useGraph.setState({ nodes: updatedNodes });

try {
const currentJson = useJson.getState().json;
const jsonObject = JSON.parse(currentJson);

const updatedJsonObject = JSON.parse(JSON.stringify(jsonObject));

// Navigate to the correct path in the JSON and update it
if (nodeData.path && nodeData.path.length > 0) {
let target = updatedJsonObject;

// Navigate to the parent of the target location
for (let i = 0; i < nodeData.path.length - 1; i++) {
target = target[nodeData.path[i]];
}

const finalKey = nodeData.path[nodeData.path.length - 1];
const targetObject = target[finalKey];

if (targetObject && typeof targetObject === 'object' && !Array.isArray(targetObject)) {
editableFields.forEach(field => {
targetObject[field.key] = field.value;
});
}
} else {
editableFields.forEach(field => {
updatedJsonObject[field.key] = field.value;
});
}

// Update the JSON store with the new object using the setter from the hook
const updatedJson = JSON.stringify(updatedJsonObject, null, 2);
setJson(updatedJson);

console.log('JSON updated successfully:', updatedJson);
} catch (error) {
console.error('Failed to update JSON:', error);
}

setIsEditing(false);
};

const handleCancel = () => {
// Reset fields to original values
if (nodeData?.text) {
setEditableFields(getEditableFields(nodeData.text).map(field => ({
key: field.key || '',
value: String(field.value)
})));
}
setIsEditing(false);
};

const handleFieldChange = (key: string, value: string) => {
setEditableFields(prev =>
prev.map(field =>
field.key === key ? { ...field, value } : field
)
);
};

return (
<Modal size="auto" opened={opened} onClose={onClose} centered withCloseButton={false}>
Expand All @@ -37,16 +157,59 @@ export const NodeModal = ({ opened, onClose }: ModalProps) => {
<Text fz="xs" fw={500}>
Content
</Text>
<CloseButton onClick={onClose} />
<Flex gap="xs" align="center">
{isEditing ? (
<>
<Button
size="xs"
variant="filled"
color="green"
onClick={handleSave}
>
Save
</Button>
<Button
size="xs"
variant="filled"
color="red"
onClick={handleCancel}
>
Cancel
</Button>
</>
) : (
<Button
size="xs"
variant="filled"
onClick={handleEdit}
>
Edit
</Button>
)}
<CloseButton onClick={onClose} />
</Flex>
</Flex>
<ScrollArea.Autosize mah={250} maw={600}>
<CodeHighlight
code={normalizeNodeData(nodeData?.text ?? [])}
miw={350}
maw={600}
language="json"
withCopyButton
/>
{isEditing ? (
<Stack gap="sm" p="sm" style={{ minWidth: 350, maxWidth: 600 }}>
{editableFields.map((field) => (
<TextInput
key={field.key}
label={field.key}
value={field.value}
onChange={(e) => handleFieldChange(field.key, e.currentTarget.value)}
/>
))}
</Stack>
) : (
<CodeHighlight
code={normalizeNodeData(nodeData?.text ?? [])}
miw={350}
maw={600}
language="json"
withCopyButton
/>
)}
</ScrollArea.Autosize>
</Stack>
<Text fz="xs" fw={500}>
Expand All @@ -66,4 +229,4 @@ export const NodeModal = ({ opened, onClose }: ModalProps) => {
</Stack>
</Modal>
);
};
};
3 changes: 3 additions & 0 deletions src/store/useJson.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { create } from "zustand";
import useGraph from "../features/editor/views/GraphView/stores/useGraph";
import useFile from "./useFile";

interface JsonActions {
setJson: (json: string) => void;
Expand All @@ -20,7 +21,9 @@ const useJson = create<JsonStates & JsonActions>()((set, get) => ({
setJson: json => {
set({ json, loading: false });
useGraph.getState().setGraph(json);
useFile.getState().setContents({ contents: json, hasChanges: false }); // ← add this line
},

clear: () => {
set({ json: "", loading: false });
useGraph.getState().clearGraph();
Expand Down