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:
@@ -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('');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user