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:
dwindown
2025-08-07 20:05:11 +07:00
parent bc7e2a8986
commit 97459ea313
7 changed files with 462 additions and 133 deletions

View File

@@ -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>
)}