diff --git a/src/components/MindmapView.js b/src/components/MindmapView.js index cd2f3af5..21f026f2 100644 --- a/src/components/MindmapView.js +++ b/src/components/MindmapView.js @@ -21,6 +21,7 @@ import { FileText, Zap, Copy, + Check, Eye, Code, Maximize, @@ -34,6 +35,7 @@ import { // Custom node component for different data types const CustomNode = ({ data, selected }) => { const [renderHtml, setRenderHtml] = React.useState(true); + const [isCopied, setIsCopied] = React.useState(false); // Check if value contains HTML const isHtmlContent = data.value && typeof data.value === 'string' && @@ -44,8 +46,13 @@ const CustomNode = ({ data, selected }) => { if (data.value) { try { await navigator.clipboard.writeText(String(data.value)); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2500); } catch (err) { console.error('Failed to copy:', err); + // Still show feedback even if copy failed + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2500); } } }; @@ -131,21 +138,12 @@ const CustomNode = ({ data, selected }) => { )}
-
-
- {getIcon()} -
- {/* Copy button positioned below icon */} - {data.value && ( - - )} + {/* Icon - First flex item */} +
+ {getIcon()}
+ + {/* Content - Second flex item */}
{data.label} @@ -170,6 +168,28 @@ const CustomNode = ({ data, selected }) => {
)}
+ + {/* Actions - Third flex item */} +
+ {data.value && ( + + )} + {/* Future action buttons can be added here */} +
{/* Output handle (right side) */} diff --git a/src/components/PostmanTable.js b/src/components/PostmanTable.js index 84de41f9..d17fdbb2 100644 --- a/src/components/PostmanTable.js +++ b/src/components/PostmanTable.js @@ -1,8 +1,10 @@ import React, { useState } from 'react'; -import { ChevronLeft, Braces, List, Type, Hash, ToggleLeft, Minus } from 'lucide-react'; +import { ChevronLeft, Braces, List, Type, Hash, ToggleLeft, Minus, Eye, Code, Copy, Check } from 'lucide-react'; const PostmanTable = ({ data, title = "JSON Data" }) => { const [currentPath, setCurrentPath] = useState([]); + const [renderHtml, setRenderHtml] = useState(true); + const [copiedItems, setCopiedItems] = useState(new Set()); // Get current data based on path const getCurrentData = () => { @@ -44,6 +46,49 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { const headers = getArrayHeaders(); + // Check if value contains HTML + const isHtmlContent = (value) => { + return value && typeof value === 'string' && + (value.includes('<') && value.includes('>')) && + /<[^>]+>/.test(value); + }; + + // Copy value to clipboard + const copyToClipboard = async (value, itemId) => { + try { + const textValue = typeof value === 'string' ? value : JSON.stringify(value, null, 2); + await navigator.clipboard.writeText(textValue); + + // Show feedback + setCopiedItems(prev => new Set([...prev, itemId])); + setTimeout(() => { + setCopiedItems(prev => { + const newSet = new Set(prev); + newSet.delete(itemId); + return newSet; + }); + }, 2500); + } catch (err) { + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = typeof value === 'string' ? value : JSON.stringify(value, null, 2); + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + + // Show feedback for fallback too + setCopiedItems(prev => new Set([...prev, itemId])); + setTimeout(() => { + setCopiedItems(prev => { + const newSet = new Set(prev); + newSet.delete(itemId); + return newSet; + }); + }, 2500); + } + }; + // Handle row click - navigate to item details const handleRowClick = (index, key = null) => { if (isArrayView) { @@ -87,11 +132,32 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { return parts; }; - // Format value for display + // Format value for display in table (truncated) const formatValue = (value) => { if (value === null) return 'null'; if (value === undefined) return 'undefined'; - if (typeof value === 'string') return value; + if (typeof value === 'string') { + // Truncate long strings for table display + if (value.length > 100) { + return value.substring(0, 100) + '...'; + } + // Replace newlines with spaces for single-line display + return value.replace(/\n/g, ' ').replace(/\s+/g, ' '); + } + if (typeof value === 'boolean') return value.toString(); + if (typeof value === 'number') return value.toString(); + if (typeof value === 'object') { + if (Array.isArray(value)) return `Array(${value.length})`; + return `Object(${Object.keys(value).length})`; + } + return String(value); + }; + + // Format value for display in details view (full text) + const formatFullValue = (value) => { + if (value === null) return 'null'; + if (value === undefined) return 'undefined'; + if (typeof value === 'string') return value; // Show full string without truncation if (typeof value === 'boolean') return value.toString(); if (typeof value === 'number') return value.toString(); if (typeof value === 'object') { @@ -154,19 +220,69 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { }; - // Render value with appropriate styling + // Render value with appropriate styling (for table view) const renderValue = (value) => { const typeStyle = getTypeStyle(value); const formattedValue = formatValue(value); return ( - + {typeStyle.icon} {formattedValue} ); }; + // Render value with full text (for detail view) + const renderFullValue = (value) => { + const typeStyle = getTypeStyle(value); + const formattedValue = formatFullValue(value); + const hasHtml = isHtmlContent(value); + + return ( +
+ + {typeStyle.icon} + + {hasHtml && renderHtml ? ( +
+ ) : ( + formattedValue + )} + + + + {/* HTML Toggle Buttons */} + {hasHtml && ( +
+ + +
+ )} +
+ ); + }; + // Get value type const getValueType = (value) => { if (value === null) return 'null'; @@ -175,7 +291,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { }; return ( -
+
{/* Header with Breadcrumb */}
@@ -220,7 +336,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { {isArrayView ? ( // Horizontal table for arrays - +
# @@ -263,7 +379,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { ) : isObjectView ? ( // Vertical key-value table for objects - + + @@ -290,7 +409,23 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { {key} + ); @@ -301,8 +436,8 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { // Fallback for primitive values
-
- {formatValue(currentData)} +
+ {formatFullValue(currentData)}
Type: {getValueType(currentData)} diff --git a/src/components/StructuredEditor.js b/src/components/StructuredEditor.js index c0dca7c1..74919de0 100644 --- a/src/components/StructuredEditor.js +++ b/src/components/StructuredEditor.js @@ -4,6 +4,7 @@ import { Plus, Minus, ChevronDown, ChevronRight, Type, Hash, ToggleLeft, List, B const StructuredEditor = ({ onDataChange, initialData = {} }) => { const [data, setData] = useState(initialData); const [expandedNodes, setExpandedNodes] = useState(new Set(['root'])); + const [fieldTypes, setFieldTypes] = useState({}); // Track intended types for fields const isInternalUpdate = useRef(false); // Update internal data when initialData prop changes (but not from internal updates) @@ -14,11 +15,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { return; } - console.log('๐Ÿ“ฅ EXTERNAL DATA CHANGED:', { - keys: Object.keys(initialData), - hasData: Object.keys(initialData).length > 0, - data: initialData - }); setData(initialData); // Expand root node if there's data if (Object.keys(initialData).length > 0) { @@ -27,7 +23,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { }, [initialData]); const updateData = (newData) => { - console.log('๐Ÿ“Š INTERNAL DATA UPDATE:', { keys: Object.keys(newData), totalProps: JSON.stringify(newData).length }); isInternalUpdate.current = true; // Mark as internal update setData(newData); onDataChange(newData); @@ -44,8 +39,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { }; const addProperty = (obj, path) => { - console.log('๐Ÿ”ง ADD PROPERTY - Before:', { path, dataKeys: Object.keys(data), objKeys: Object.keys(obj) }); - const pathParts = path.split('.'); const newData = { ...data }; let current = newData; @@ -60,8 +53,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { const newKey = `property${keys.length + 1}`; current[newKey] = ''; - console.log('๐Ÿ”ง ADD PROPERTY - After:', { path, newKey, dataKeys: Object.keys(newData), targetKeys: Object.keys(current) }); - updateData(newData); setExpandedNodes(new Set([...expandedNodes, path])); }; @@ -86,8 +77,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { }; const removeProperty = (key, parentPath) => { - console.log('๐Ÿ—‘๏ธ REMOVE PROPERTY:', { key, parentPath }); - const pathParts = parentPath.split('.'); const newData = { ...data }; let current = newData; @@ -97,6 +86,12 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { current = current[pathParts[i]]; } + // Remove field type tracking for the removed property + const removedPath = parentPath === 'root' ? `root.${key}` : `${parentPath}.${key}`; + const newFieldTypes = { ...fieldTypes }; + delete newFieldTypes[removedPath]; + setFieldTypes(newFieldTypes); + // Delete the property/item from the parent if (Array.isArray(current)) { // For arrays, remove by index and reindex @@ -110,21 +105,12 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { if (parentPath === 'root' && Object.keys(newData).length === 0) { // Add an empty property to maintain initial state, like TableEditor maintains at least one row newData[''] = ''; - console.log('๐Ÿ”„ ADDED INITIAL EMPTY PROPERTY - Last root property was deleted'); } - console.log('๐Ÿ—‘๏ธ REMOVE PROPERTY - After:', { - parentPath, - remainingKeys: Array.isArray(current) ? current.length : Object.keys(current), - totalRootKeys: Object.keys(newData).length - }); - updateData(newData); }; const updateValue = (value, path) => { - console.log('โœ๏ธ UPDATE VALUE:', { path, value, currentType: typeof getValue(path) }); - const pathParts = path.split('.'); const newData = { ...data }; let current = newData; @@ -172,13 +158,10 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { } } - console.log('โœ๏ธ UPDATE VALUE - Result:', { path, newValue: current[key], newType: typeof current[key] }); updateData(newData); }; const changeType = (newType, path) => { - console.log('๐Ÿ”„ CHANGE TYPE:', { path, newType, currentValue: getValue(path) }); - const pathParts = path.split('.'); const newData = { ...data }; let current = newData; @@ -190,6 +173,11 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { const key = pathParts[pathParts.length - 1]; const currentValue = current[key]; + // Track the intended type for this field + const newFieldTypes = { ...fieldTypes }; + newFieldTypes[path] = newType; + setFieldTypes(newFieldTypes); + // Try to preserve value when changing types if possible switch (newType) { case 'string': @@ -227,19 +215,10 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { current[key] = ''; } - console.log('๐Ÿ”„ CHANGE TYPE - Result:', { path, newValue: current[key], actualType: typeof current[key] }); updateData(newData); setExpandedNodes(new Set([...expandedNodes, path])); }; - const getValue = (path) => { - const pathParts = path.split('.'); - let current = data; - for (let i = 1; i < pathParts.length; i++) { - current = current[pathParts[i]]; - } - return current; - }; // Helper function to display string values with proper unescaping const getDisplayValue = (value) => { @@ -277,6 +256,16 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { return; // Don't rename if key already exists } + // Update field type tracking for renamed key + const oldPath = path; + const newPath = path.replace(new RegExp(`\\.${oldKey}$`), `.${newKey}`); + const newFieldTypes = { ...fieldTypes }; + if (newFieldTypes[oldPath]) { + newFieldTypes[newPath] = newFieldTypes[oldPath]; + delete newFieldTypes[oldPath]; + setFieldTypes(newFieldTypes); + } + // Rename the key const value = current[oldKey]; delete current[oldKey]; @@ -354,13 +343,19 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { const canExpand = typeof value === 'object' && value !== null; // Check if parent is an array by looking at the parent path - const isArrayItem = parentPath !== 'root' && (() => { - const parentPathParts = parentPath.split('.'); - let current = data; - for (let i = 1; i < parentPathParts.length; i++) { - current = current[parentPathParts[i]]; + const isArrayItem = (() => { + if (parentPath === 'root') { + // If parent is root, check if root data is an array + return Array.isArray(data); + } else { + // Navigate to parent and check if it's an array + const parentPathParts = parentPath.split('.'); + let current = data; + for (let i = 1; i < parentPathParts.length; i++) { + current = current[parentPathParts[i]]; + } + return Array.isArray(current); } - return Array.isArray(current); })(); return ( @@ -436,11 +431,11 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
) : ( - typeof value === 'string' && value.includes('\n') ? ( + (fieldTypes[path] === 'longtext' || (typeof value === 'string' && value.includes('\n'))) ? (
Key @@ -271,6 +387,9 @@ const PostmanTable = ({ data, title = "JSON Data" }) => { Value + +
- {renderValue(value)} + {renderFullValue(value)} + +