Improve ObjectEditor and PostmanTable UI/UX
- Enhanced JSON parsing with smart error handling for trailing commas - Fixed StructuredEditor array handling - array indices now non-editable - Improved PostmanTable styling: removed border radius, solid thead background - Enhanced icon spacing and alignment for better visual hierarchy - Added copy feedback system with green check icons (2500ms duration) - Restructured MindmapView node layout with dedicated action column - Added HTML rendering toggle for PostmanTable similar to MindmapView - Implemented consistent copy functionality across components - Removed debug console.log statements for cleaner codebase
This commit is contained in:
@@ -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 }) => {
|
||||
)}
|
||||
|
||||
<div className="flex items-start space-x-2 group">
|
||||
<div className="flex-shrink-0 flex flex-col items-center space-y-1">
|
||||
<div className="mt-0.5">
|
||||
{getIcon()}
|
||||
</div>
|
||||
{/* Copy button positioned below icon */}
|
||||
{data.value && (
|
||||
<button
|
||||
onClick={copyValue}
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white rounded-full p-1 opacity-80 hover:opacity-100 transition-all shadow-md"
|
||||
title="Copy value to clipboard"
|
||||
>
|
||||
<Copy className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
)}
|
||||
{/* Icon - First flex item */}
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
{getIcon()}
|
||||
</div>
|
||||
|
||||
{/* Content - Second flex item */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-xs break-words">
|
||||
{data.label}
|
||||
@@ -170,6 +168,28 @@ const CustomNode = ({ data, selected }) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions - Third flex item */}
|
||||
<div className="flex-shrink-0 flex flex-col items-center space-y-1">
|
||||
{data.value && (
|
||||
<button
|
||||
onClick={copyValue}
|
||||
className={`rounded-full p-1 opacity-80 hover:opacity-100 transition-all shadow-md ${
|
||||
isCopied
|
||||
? 'bg-green-500 hover:bg-green-600'
|
||||
: 'bg-blue-500 hover:bg-blue-600'
|
||||
} text-white`}
|
||||
title={isCopied ? "Copied!" : "Copy value to clipboard"}
|
||||
>
|
||||
{isCopied ? (
|
||||
<Check className="h-2.5 w-2.5" />
|
||||
) : (
|
||||
<Copy className="h-2.5 w-2.5" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{/* Future action buttons can be added here */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Output handle (right side) */}
|
||||
|
||||
@@ -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 (
|
||||
<span className={`inline-flex items-center space-x-1 px-2 py-1 rounded-full text-xs font-medium ${typeStyle.color}`}>
|
||||
<span className={`inline-flex items-center space-x-1 px-2 py-1 rounded text-xs font-medium ${typeStyle.color}`}>
|
||||
{typeStyle.icon}
|
||||
<span>{formattedValue}</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
// Render value with full text (for detail view)
|
||||
const renderFullValue = (value) => {
|
||||
const typeStyle = getTypeStyle(value);
|
||||
const formattedValue = formatFullValue(value);
|
||||
const hasHtml = isHtmlContent(value);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<span className={`inline-flex items-start space-x-2 px-2 py-1 rounded text-xs font-medium ${typeStyle.color}`}>
|
||||
<span className="flex-shrink-0 mt-0.5">{typeStyle.icon}</span>
|
||||
<span className="whitespace-pre-wrap break-words flex-1">
|
||||
{hasHtml && renderHtml ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: String(value) }} />
|
||||
) : (
|
||||
formattedValue
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{/* HTML Toggle Buttons */}
|
||||
{hasHtml && (
|
||||
<div className="absolute -top-1 -right-1 flex">
|
||||
<button
|
||||
onClick={() => setRenderHtml(true)}
|
||||
className={`px-1.5 py-0.5 text-xs rounded-l ${
|
||||
renderHtml
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-200 text-gray-600 hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500'
|
||||
}`}
|
||||
title="Render HTML"
|
||||
>
|
||||
<Eye className="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRenderHtml(false)}
|
||||
className={`px-1.5 py-0.5 text-xs rounded-r ${
|
||||
!renderHtml
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-200 text-gray-600 hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500'
|
||||
}`}
|
||||
title="Show Raw HTML"
|
||||
>
|
||||
<Code className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Get value type
|
||||
const getValueType = (value) => {
|
||||
if (value === null) return 'null';
|
||||
@@ -175,7 +291,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
{/* Header with Breadcrumb */}
|
||||
<div className="bg-gray-50 dark:bg-gray-700 px-4 py-3 border-b border-gray-200 dark:border-gray-600">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -220,7 +336,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
{isArrayView ? (
|
||||
// Horizontal table for arrays
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-12">
|
||||
#
|
||||
@@ -263,7 +379,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
) : isObjectView ? (
|
||||
// Vertical key-value table for objects
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0">
|
||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-1/3">
|
||||
Key
|
||||
@@ -271,6 +387,9 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
Value
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-16">
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@@ -290,7 +409,23 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
{key}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm">
|
||||
{renderValue(value)}
|
||||
{renderFullValue(value)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // Prevent row click
|
||||
copyToClipboard(value, `${currentPath.join('.')}.${key}`);
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-600 rounded transition-colors"
|
||||
title={copiedItems.has(`${currentPath.join('.')}.${key}`) ? "Copied!" : "Copy value"}
|
||||
>
|
||||
{copiedItems.has(`${currentPath.join('.')}.${key}`) ? (
|
||||
<Check className="h-3 w-3 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-3 w-3 text-gray-500 dark:text-gray-400" />
|
||||
)}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@@ -301,8 +436,8 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
// Fallback for primitive values
|
||||
<div className="p-4">
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">
|
||||
<div className="text-lg font-mono text-gray-900 dark:text-gray-100">
|
||||
{formatValue(currentData)}
|
||||
<div className="text-lg font-mono text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words">
|
||||
{formatFullValue(currentData)}
|
||||
</div>
|
||||
<div className="text-sm mt-2">
|
||||
Type: {getValueType(currentData)}
|
||||
|
||||
@@ -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 = {} }) => {
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
typeof value === 'string' && value.includes('\n') ? (
|
||||
(fieldTypes[path] === 'longtext' || (typeof value === 'string' && value.includes('\n'))) ? (
|
||||
<textarea
|
||||
value={getDisplayValue(value)}
|
||||
onChange={(e) => updateValue(e.target.value, path)}
|
||||
className="flex-1 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 min-w-0 resize-y"
|
||||
className="flex-1 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 min-w-0 resize-y items"
|
||||
placeholder="Long text value"
|
||||
rows={3}
|
||||
/>
|
||||
@@ -463,12 +458,14 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
<div className="flex items-center space-x-2 sm:space-x-2">
|
||||
<select
|
||||
value={
|
||||
value === null ? 'null' :
|
||||
value === undefined ? 'string' :
|
||||
typeof value === 'string' ? (value.includes('\n') ? 'longtext' : 'string') :
|
||||
typeof value === 'number' ? 'number' :
|
||||
typeof value === 'boolean' ? 'boolean' :
|
||||
Array.isArray(value) ? 'array' : 'object'
|
||||
fieldTypes[path] || (
|
||||
value === null ? 'null' :
|
||||
value === undefined ? 'string' :
|
||||
typeof value === 'string' ? (value.includes('\n') ? 'longtext' : 'string') :
|
||||
typeof value === 'number' ? 'number' :
|
||||
typeof value === 'boolean' ? 'boolean' :
|
||||
Array.isArray(value) ? 'array' : 'object'
|
||||
)
|
||||
}
|
||||
onChange={(e) => changeType(e.target.value, path)}
|
||||
className="px-2 py-1 text-xs border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 min-w-0"
|
||||
|
||||
Reference in New Issue
Block a user