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:
dwindown
2025-09-24 14:05:10 +07:00
parent 57655410ab
commit 21d0406ece
6 changed files with 306 additions and 201 deletions

View File

@@ -335,23 +335,67 @@ const ObjectEditor = () => {
}
};
// Smart JSON parser that tolerates common mistakes
const parseJsonSmart = (input) => {
// First try normal JSON parsing
try {
return JSON.parse(input);
} catch (originalError) {
// Try to fix common issues
let fixedInput = input.trim();
// Fix trailing commas in objects and arrays
fixedInput = fixedInput
.replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas before } or ]
.replace(/,(\s*$)/g, ''); // Remove trailing comma at end
// Try parsing the fixed version
try {
return JSON.parse(fixedInput);
} catch (fixedError) {
// If still fails, throw original error with helpful message
const errorMessage = originalError.message;
// Provide specific error reasons
if (errorMessage.includes('Unexpected token')) {
if (errorMessage.includes('Unexpected token ,')) {
throw new Error('Invalid JSON: Unexpected comma. Check for trailing commas in objects or arrays.');
} else if (errorMessage.includes('Unexpected token }')) {
throw new Error('Invalid JSON: Unexpected closing brace. Check for missing quotes or commas.');
} else if (errorMessage.includes('Unexpected token ]')) {
throw new Error('Invalid JSON: Unexpected closing bracket. Check for missing quotes or commas.');
} else {
throw new Error(`Invalid JSON: ${errorMessage}`);
}
} else if (errorMessage.includes('Unexpected end of JSON input')) {
throw new Error('Invalid JSON: Incomplete JSON structure. Check for missing closing braces or brackets.');
} else if (errorMessage.includes('Unexpected string')) {
throw new Error('Invalid JSON: Unexpected string. Check for missing commas between properties.');
} else {
throw new Error(`Invalid JSON: ${errorMessage}`);
}
}
}
};
// Auto-detect input format
const detectInputFormat = (input) => {
if (!input.trim()) return { format: '', valid: false, data: null };
if (!input.trim()) return { format: '', valid: false, data: null, error: null };
// Try JSON first
// Try JSON first (with smart parsing)
try {
const jsonData = JSON.parse(input);
return { format: 'JSON', valid: true, data: jsonData };
} catch {}
// Try PHP serialize
try {
const serializedData = phpUnserialize(input);
return { format: 'PHP Serialized', valid: true, data: serializedData };
} catch {}
return { format: 'Unknown', valid: false, data: null };
const jsonData = parseJsonSmart(input);
return { format: 'JSON', valid: true, data: jsonData, error: null };
} catch (jsonError) {
// Try PHP serialize
try {
const serializedData = phpUnserialize(input);
return { format: 'PHP Serialized', valid: true, data: serializedData, error: null };
} catch (phpError) {
// Return JSON error since it's more likely what user intended
return { format: 'Unknown', valid: false, data: null, error: jsonError.message };
}
}
};
// Handle input text change
@@ -362,12 +406,13 @@ const ObjectEditor = () => {
setInputValid(detection.valid);
if (detection.valid) {
console.log(' SETTING STRUCTURED DATA:', detection.data);
setStructuredData(detection.data);
setError('');
setCreateNewCompleted(true);
} else if (value.trim()) {
setError('Invalid format. Please enter valid JSON or PHP serialized data.');
// Use specific error message if available, otherwise generic message
const errorMessage = detection.error || 'Invalid format. Please enter valid JSON or PHP serialized data.';
setError(errorMessage);
} else {
setError('');
}

View File

@@ -302,10 +302,6 @@ const TableEditor = () => {
// SQL parsing functions for multi-table support
const parseMultiTableSQL = (sqlContent) => {
console.log(
"🔍 parseMultiTableSQL called with content length:",
sqlContent.length,
);
const tables = {};
const lines = sqlContent.split("\n");
let currentTable = null;
@@ -313,7 +309,6 @@ const TableEditor = () => {
let insideCreateTable = false;
let insideInsert = false;
console.log("📄 Total lines to process:", lines.length);
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
@@ -384,12 +379,7 @@ const TableEditor = () => {
const valuesMatch = line.match(/VALUES\s*(.+)/i);
if (valuesMatch) {
const valuesStr = valuesMatch[1];
console.log(
"🔍 Found VALUES in multi-table parser:",
valuesStr.substring(0, 100) + "...",
);
const rows = parseInsertValues(valuesStr);
console.log("📊 parseInsertValues returned", rows.length, "rows");
tables[currentTable].data.push(...rows);
tables[currentTable].rowCount += rows.length;
}
@@ -417,10 +407,6 @@ const TableEditor = () => {
};
const parseInsertValues = (valuesStr) => {
console.log(
"🔍 parseInsertValues called with:",
valuesStr.substring(0, 200) + "...",
);
const rows = [];
// Better parsing: find complete tuples first, then parse values respecting quotes
@@ -463,7 +449,6 @@ const TableEditor = () => {
}
tuples.forEach((tuple) => {
console.log("📝 Processing tuple:", tuple.substring(0, 100) + "...");
// Remove outer parentheses
const innerContent = tuple.replace(/^\(|\)$/g, "").trim();
@@ -509,37 +494,21 @@ const TableEditor = () => {
values.push(currentValue.trim());
}
console.log("📊 Parsed", values.length, "values from tuple");
const processedValues = values.map((val) => {
val = val.trim();
console.log(
"🔧 Processing value:",
val.substring(0, 50) + (val.length > 50 ? "..." : ""),
);
// Remove quotes and handle NULL
if (val === "NULL") return "";
if (val.startsWith("'") && val.endsWith("'")) {
let unquoted = val.slice(1, -1);
console.log(
"📤 Unquoted value:",
unquoted.substring(0, 50) + (unquoted.length > 50 ? "..." : ""),
);
// Handle escaped JSON properly (same logic as main parser)
if (unquoted.includes('\\"') || unquoted.includes("\\'")) {
console.log(
"🔧 Value contains escaped quotes, checking if JSON...",
);
// Try to detect if this is escaped JSON
const isEscapedJson = (str) => {
const unescaped = str.replace(/\\"/g, '"').replace(/\\'/g, "'");
const trimmed = unescaped.trim();
console.log(
"🧪 Testing unescaped value:",
trimmed.substring(0, 50) + (trimmed.length > 50 ? "..." : ""),
);
if (
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
@@ -547,27 +516,17 @@ const TableEditor = () => {
) {
try {
JSON.parse(trimmed);
console.log("✅ Valid JSON detected in parseInsertValues!");
return true;
} catch (e) {
console.log("❌ JSON parse failed:", e.message);
return false;
}
}
console.log("❌ Not JSON format (no brackets)");
return false;
};
// If it's escaped JSON, unescape it
if (isEscapedJson(unquoted)) {
const oldValue = unquoted;
unquoted = unquoted.replace(/\\"/g, '"').replace(/\\'/g, "'");
console.log(
"🎯 Unescaped JSON in parseInsertValues from:",
oldValue.substring(0, 30),
"to:",
unquoted.substring(0, 30),
);
} else {
// Only unescape single quotes for non-JSON strings
unquoted = unquoted.replace(/''/g, "'");
@@ -577,10 +536,6 @@ const TableEditor = () => {
unquoted = unquoted.replace(/''/g, "'");
}
console.log(
"✅ Final unquoted value:",
unquoted.substring(0, 50) + (unquoted.length > 50 ? "..." : ""),
);
return unquoted;
}
return val;
@@ -594,7 +549,6 @@ const TableEditor = () => {
rows.push(rowObj);
});
console.log("📊 parseInsertValues returning", rows.length, "rows");
return rows;
};
@@ -733,8 +687,6 @@ const TableEditor = () => {
// Parse SQL data
const parseSqlData = (text) => {
console.log("🚀 parseSqlData called with text length:", text.length);
console.log("📄 First 500 chars:", text.substring(0, 500));
try {
// Clean the SQL text - remove comments and unnecessary lines
const cleanLines = text
@@ -913,19 +865,8 @@ const TableEditor = () => {
}
// Clean up the value - handle escaped JSON properly
console.log(
"🔍 Processing value for row",
allRows.length,
"column",
index,
":",
value.substring(0, 100) + (value.length > 100 ? "..." : ""),
);
if (value.includes('\\"') || value.includes("\\'")) {
console.log(
"🔧 Value contains escaped quotes, checking if JSON...",
);
// Try to detect if this is escaped JSON
const isEscapedJson = (str) => {
@@ -934,11 +875,6 @@ const TableEditor = () => {
.replace(/\\"/g, '"')
.replace(/\\'/g, "'");
const trimmed = unescaped.trim();
console.log(
"🧪 Testing unescaped value:",
trimmed.substring(0, 100) +
(trimmed.length > 100 ? "..." : ""),
);
if (
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
@@ -946,40 +882,25 @@ const TableEditor = () => {
) {
try {
JSON.parse(trimmed);
console.log("✅ Valid JSON detected!");
return true;
} catch (e) {
console.log("❌ JSON parse failed:", e.message);
return false;
}
}
console.log("❌ Not JSON format (no brackets)");
return false;
};
// If it's escaped JSON, unescape it
if (isEscapedJson(value)) {
const oldValue = value;
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
console.log(
"🎯 Unescaped JSON from:",
oldValue.substring(0, 50),
"to:",
value.substring(0, 50),
);
} else {
// Only unescape single quotes for non-JSON strings
if (value.includes("\\'")) {
console.log("🔧 Unescaping single quotes only");
value = value.replace(/\\'/g, "'");
}
}
}
console.log(
"✅ Final value for storage:",
value.substring(0, 100) + (value.length > 100 ? "..." : ""),
);
rowData[header.id] = value;
});
@@ -1048,27 +969,19 @@ const TableEditor = () => {
}
const trimmed = inputText.trim();
console.log(
"🎯 handleTextInput - detecting format for input length:",
trimmed.length,
);
console.log("📝 First 200 chars:", trimmed.substring(0, 200));
// Try to detect format
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
// JSON array
console.log("🔍 Detected JSON array format");
parseJsonData(trimmed);
} else if (
trimmed.toLowerCase().includes("insert into") &&
trimmed.toLowerCase().includes("values")
) {
// SQL INSERT statements
console.log("🔍 Detected SQL INSERT format");
parseSqlData(trimmed);
} else {
// CSV/TSV
console.log("🔍 Detected CSV/TSV format");
parseData(trimmed, useFirstRowAsHeader);
}
};
@@ -1133,13 +1046,10 @@ const TableEditor = () => {
};
const initializeTablesFromSQL = (sqlContent, fileName = "") => {
console.log("🏗️ initializeTablesFromSQL called with fileName:", fileName);
console.log("📊 SQL content length:", sqlContent.length);
const discoveredTables = parseMultiTableSQL(sqlContent);
const tableNames = Object.keys(discoveredTables);
console.log("📋 Discovered tables:", tableNames);
if (tableNames.length === 0) {
console.error("❌ No tables found in SQL file");
@@ -1177,7 +1087,6 @@ const TableEditor = () => {
const file = event.target.files[0];
if (!file) return;
console.log("📁 File upload started:", file.name, "size:", file.size);
setIsLoading(true);
setError("");
@@ -1185,15 +1094,11 @@ const TableEditor = () => {
reader.onload = (e) => {
try {
const content = e.target.result;
console.log("📄 File content loaded, length:", content.length);
console.log("📝 First 300 chars:", content.substring(0, 300));
// Check if it's SQL file for multi-table support
if (file.name.toLowerCase().endsWith(".sql")) {
console.log("🔍 Detected SQL file, using multi-table parsing");
initializeTablesFromSQL(content, file.name);
} else {
console.log("🔍 Non-SQL file, using single-table parsing");
// Fallback to single-table parsing
parseData(content);
}
@@ -3291,7 +3196,6 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
const [error, setError] = useState(initialState.error);
// Debug log to see what we initialized with
// console.log('ObjectEditorModal initialized with:', { structuredData, isValid, error });
// Update current value when structured data changes
const handleStructuredDataChange = (newData) => {
@@ -3378,7 +3282,6 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
);
}
// console.log('Rendering StructuredEditor with data:', structuredData); // Debug log
return (
<div className="h-full bg-white dark:bg-gray-800 p-6">
<StructuredEditor