From 97459ea313f5a7b4286c8449a05be7cfa4db0d9e Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 7 Aug 2025 20:05:11 +0700 Subject: [PATCH] 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 --- package-lock.json | 45 ++++++++++ package.json | 1 + src/components/StructuredEditor.js | 94 ++++++++++++------- src/pages/CsvJsonTool.js | 97 ++++++++++++++------ src/pages/DiffTool.js | 139 +++++++++++++++++------------ src/pages/SerializeTool.js | 105 +++++++++++++++++++--- src/styles/diff-theme.css | 114 +++++++++++++++++++++++ 7 files changed, 462 insertions(+), 133 deletions(-) create mode 100644 src/styles/diff-theme.css diff --git a/package-lock.json b/package-lock.json index f177efec..479b592c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "papaparse": "^5.4.1", "react": "^18.2.0", "react-codemirror2": "^8.0.1", + "react-diff-view": "^3.3.2", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", @@ -6888,6 +6889,12 @@ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -10162,6 +10169,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gitdiff-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/gitdiff-parser/-/gitdiff-parser-0.3.1.tgz", + "integrity": "sha512-YQJnY8aew65id8okGxKCksH3efDCJ9HzV7M9rsvd65habf39Pkh4cgYJ27AaoDMqo1X98pgNJhNMrm/kpV7UVQ==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -17508,6 +17521,23 @@ "node": ">=8" } }, + "node_modules/react-diff-view": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/react-diff-view/-/react-diff-view-3.3.2.tgz", + "integrity": "sha512-wPVq4ktTcGOHbhnWKU/gHLtd3N2Xd+OZ/XQWcKA06dsxlSsESePAumQILwHtiak2nMCMiWcIfBpqZ5OiharUPA==", + "license": "MIT", + "dependencies": { + "classnames": "^2.3.2", + "diff-match-patch": "^1.0.5", + "gitdiff-parser": "^0.3.1", + "lodash": "^4.17.21", + "shallow-equal": "^3.1.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -18624,6 +18654,12 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -20429,6 +20465,15 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", diff --git a/package.json b/package.json index 6b112f83..a88e0aae 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "papaparse": "^5.4.1", "react": "^18.2.0", "react-codemirror2": "^8.0.1", + "react-diff-view": "^3.3.2", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", diff --git a/src/components/StructuredEditor.js b/src/components/StructuredEditor.js index e97bd03b..54a935a3 100644 --- a/src/components/StructuredEditor.js +++ b/src/components/StructuredEditor.js @@ -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 ( -
+
{canExpand && (