feat: Enhanced developer tools UX with visual improvements
- Fixed StructuredEditor auto-type detection to only trigger on empty fields - Made array keys readonly with proper index display and full-width value inputs - Enhanced PHP unserialize to handle empty strings, NULL values, and complex data - Added JSON to CSV support for single objects as Key-Value format - Upgraded DiffTool with react-diff-view for professional GitHub-style diffs - Added theme-synchronized diff colors with proper contrast in light/dark modes - Implemented red/green text on matching backgrounds for optimal readability
This commit is contained in:
@@ -78,15 +78,21 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
}
|
||||
|
||||
const key = pathParts[pathParts.length - 1];
|
||||
const currentValue = current[key];
|
||||
|
||||
// Auto-detect type
|
||||
if (value === 'true' || value === 'false') {
|
||||
current[key] = value === 'true';
|
||||
} else if (value === 'null') {
|
||||
current[key] = null;
|
||||
} else if (!isNaN(value) && value !== '') {
|
||||
current[key] = Number(value);
|
||||
// Auto-detect type only when current value is empty/null/undefined
|
||||
if (currentValue === '' || currentValue === null || currentValue === undefined) {
|
||||
if (value === 'true' || value === 'false') {
|
||||
current[key] = value === 'true';
|
||||
} else if (value === 'null') {
|
||||
current[key] = null;
|
||||
} else if (!isNaN(value) && value !== '') {
|
||||
current[key] = Number(value);
|
||||
} else {
|
||||
current[key] = value;
|
||||
}
|
||||
} else {
|
||||
// If current value exists, preserve as string unless explicitly changed via type dropdown
|
||||
current[key] = value;
|
||||
}
|
||||
|
||||
@@ -170,9 +176,19 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
const renderValue = (value, key, path, parentPath) => {
|
||||
const isExpanded = expandedNodes.has(path);
|
||||
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]];
|
||||
}
|
||||
return Array.isArray(current);
|
||||
})();
|
||||
|
||||
return (
|
||||
<div key={path} className="ml-4 border-l border-gray-200 dark:border-gray-700 pl-4 mb-2 overflow-hidden">
|
||||
<div key={path} className="ml-4 border-l border-gray-200 dark:border-gray-700 pl-4 overflow-hidden">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
{canExpand && (
|
||||
<button
|
||||
@@ -189,30 +205,40 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
|
||||
{!canExpand && <div className="w-6" />}
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0 sm:space-x-2 flex-1">
|
||||
<div className="flex items-center space-x-2 flex-1">
|
||||
{getTypeIcon(value)}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={key}
|
||||
onBlur={(e) => {
|
||||
const newKey = e.target.value.trim();
|
||||
if (newKey && newKey !== key) {
|
||||
renameKey(key, newKey, path);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.target.blur(); // Trigger blur to save changes
|
||||
}
|
||||
}}
|
||||
className="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 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 min-w-0 flex-1"
|
||||
placeholder="Property name"
|
||||
/>
|
||||
|
||||
<span className="text-gray-500 hidden sm:inline">:</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 flex-1">
|
||||
{isArrayItem ? (
|
||||
// Array items: icon + index span (compact)
|
||||
<>
|
||||
{getTypeIcon(value)}
|
||||
<span className="px-2 py-1 text-sm text-gray-600 dark:text-gray-400 font-mono whitespace-nowrap">
|
||||
[{key}]
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
// Object properties: icon + editable key + colon (compact)
|
||||
<>
|
||||
{getTypeIcon(value)}
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={key}
|
||||
onBlur={(e) => {
|
||||
const newKey = e.target.value.trim();
|
||||
if (newKey && newKey !== key) {
|
||||
renameKey(key, newKey, path);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.target.blur(); // Trigger blur to save changes
|
||||
}
|
||||
}}
|
||||
className="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 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 min-w-0"
|
||||
placeholder="Property name"
|
||||
style={{width: '120px'}} // Fixed width for consistency
|
||||
/>
|
||||
<span className="text-gray-500 hidden sm:inline">:</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!canExpand ? (
|
||||
<input
|
||||
@@ -223,11 +249,11 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
value.toString()
|
||||
}
|
||||
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"
|
||||
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"
|
||||
placeholder="Value"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||
<span className="flex-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
{Array.isArray(value) ? `Array (${value.length} items)` : `Object (${Object.keys(value).length} properties)`}
|
||||
</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user