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:
@@ -21,6 +21,7 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
Zap,
|
Zap,
|
||||||
Copy,
|
Copy,
|
||||||
|
Check,
|
||||||
Eye,
|
Eye,
|
||||||
Code,
|
Code,
|
||||||
Maximize,
|
Maximize,
|
||||||
@@ -34,6 +35,7 @@ import {
|
|||||||
// Custom node component for different data types
|
// Custom node component for different data types
|
||||||
const CustomNode = ({ data, selected }) => {
|
const CustomNode = ({ data, selected }) => {
|
||||||
const [renderHtml, setRenderHtml] = React.useState(true);
|
const [renderHtml, setRenderHtml] = React.useState(true);
|
||||||
|
const [isCopied, setIsCopied] = React.useState(false);
|
||||||
|
|
||||||
// Check if value contains HTML
|
// Check if value contains HTML
|
||||||
const isHtmlContent = data.value && typeof data.value === 'string' &&
|
const isHtmlContent = data.value && typeof data.value === 'string' &&
|
||||||
@@ -44,8 +46,13 @@ const CustomNode = ({ data, selected }) => {
|
|||||||
if (data.value) {
|
if (data.value) {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(String(data.value));
|
await navigator.clipboard.writeText(String(data.value));
|
||||||
|
setIsCopied(true);
|
||||||
|
setTimeout(() => setIsCopied(false), 2500);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to copy:', err);
|
console.error('Failed to copy:', err);
|
||||||
|
// Still show feedback even if copy failed
|
||||||
|
setIsCopied(true);
|
||||||
|
setTimeout(() => setIsCopied(false), 2500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -131,21 +138,12 @@ const CustomNode = ({ data, selected }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-start space-x-2 group">
|
<div className="flex items-start space-x-2 group">
|
||||||
<div className="flex-shrink-0 flex flex-col items-center space-y-1">
|
{/* Icon - First flex item */}
|
||||||
<div className="mt-0.5">
|
<div className="flex-shrink-0 mt-0.5">
|
||||||
{getIcon()}
|
{getIcon()}
|
||||||
</div>
|
|
||||||
{/* Copy button positioned below icon */}
|
|
||||||
{data.value && (
|
|
||||||
<button
|
|
||||||
onClick={copyValue}
|
|
||||||
className="bg-blue-500 hover:bg-blue-600 text-white rounded-full p-1 opacity-80 hover:opacity-100 transition-all shadow-md"
|
|
||||||
title="Copy value to clipboard"
|
|
||||||
>
|
|
||||||
<Copy className="h-2.5 w-2.5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Content - Second flex item */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="font-medium text-xs break-words">
|
<div className="font-medium text-xs break-words">
|
||||||
{data.label}
|
{data.label}
|
||||||
@@ -170,6 +168,28 @@ const CustomNode = ({ data, selected }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Actions - Third flex item */}
|
||||||
|
<div className="flex-shrink-0 flex flex-col items-center space-y-1">
|
||||||
|
{data.value && (
|
||||||
|
<button
|
||||||
|
onClick={copyValue}
|
||||||
|
className={`rounded-full p-1 opacity-80 hover:opacity-100 transition-all shadow-md ${
|
||||||
|
isCopied
|
||||||
|
? 'bg-green-500 hover:bg-green-600'
|
||||||
|
: 'bg-blue-500 hover:bg-blue-600'
|
||||||
|
} text-white`}
|
||||||
|
title={isCopied ? "Copied!" : "Copy value to clipboard"}
|
||||||
|
>
|
||||||
|
{isCopied ? (
|
||||||
|
<Check className="h-2.5 w-2.5" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-2.5 w-2.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{/* Future action buttons can be added here */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Output handle (right side) */}
|
{/* Output handle (right side) */}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ChevronLeft, Braces, List, Type, Hash, ToggleLeft, Minus } from 'lucide-react';
|
import { ChevronLeft, Braces, List, Type, Hash, ToggleLeft, Minus, Eye, Code, Copy, Check } from 'lucide-react';
|
||||||
|
|
||||||
const PostmanTable = ({ data, title = "JSON Data" }) => {
|
const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||||
const [currentPath, setCurrentPath] = useState([]);
|
const [currentPath, setCurrentPath] = useState([]);
|
||||||
|
const [renderHtml, setRenderHtml] = useState(true);
|
||||||
|
const [copiedItems, setCopiedItems] = useState(new Set());
|
||||||
|
|
||||||
// Get current data based on path
|
// Get current data based on path
|
||||||
const getCurrentData = () => {
|
const getCurrentData = () => {
|
||||||
@@ -44,6 +46,49 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
|
|
||||||
const headers = getArrayHeaders();
|
const headers = getArrayHeaders();
|
||||||
|
|
||||||
|
// Check if value contains HTML
|
||||||
|
const isHtmlContent = (value) => {
|
||||||
|
return value && typeof value === 'string' &&
|
||||||
|
(value.includes('<') && value.includes('>')) &&
|
||||||
|
/<[^>]+>/.test(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy value to clipboard
|
||||||
|
const copyToClipboard = async (value, itemId) => {
|
||||||
|
try {
|
||||||
|
const textValue = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
||||||
|
await navigator.clipboard.writeText(textValue);
|
||||||
|
|
||||||
|
// Show feedback
|
||||||
|
setCopiedItems(prev => new Set([...prev, itemId]));
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopiedItems(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(itemId);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}, 2500);
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
|
||||||
|
// Show feedback for fallback too
|
||||||
|
setCopiedItems(prev => new Set([...prev, itemId]));
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopiedItems(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(itemId);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}, 2500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Handle row click - navigate to item details
|
// Handle row click - navigate to item details
|
||||||
const handleRowClick = (index, key = null) => {
|
const handleRowClick = (index, key = null) => {
|
||||||
if (isArrayView) {
|
if (isArrayView) {
|
||||||
@@ -87,11 +132,32 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
return parts;
|
return parts;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format value for display
|
// Format value for display in table (truncated)
|
||||||
const formatValue = (value) => {
|
const formatValue = (value) => {
|
||||||
if (value === null) return 'null';
|
if (value === null) return 'null';
|
||||||
if (value === undefined) return 'undefined';
|
if (value === undefined) return 'undefined';
|
||||||
if (typeof value === 'string') return value;
|
if (typeof value === 'string') {
|
||||||
|
// Truncate long strings for table display
|
||||||
|
if (value.length > 100) {
|
||||||
|
return value.substring(0, 100) + '...';
|
||||||
|
}
|
||||||
|
// Replace newlines with spaces for single-line display
|
||||||
|
return value.replace(/\n/g, ' ').replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
|
if (typeof value === 'boolean') return value.toString();
|
||||||
|
if (typeof value === 'number') return value.toString();
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (Array.isArray(value)) return `Array(${value.length})`;
|
||||||
|
return `Object(${Object.keys(value).length})`;
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format value for display in details view (full text)
|
||||||
|
const formatFullValue = (value) => {
|
||||||
|
if (value === null) return 'null';
|
||||||
|
if (value === undefined) return 'undefined';
|
||||||
|
if (typeof value === 'string') return value; // Show full string without truncation
|
||||||
if (typeof value === 'boolean') return value.toString();
|
if (typeof value === 'boolean') return value.toString();
|
||||||
if (typeof value === 'number') return value.toString();
|
if (typeof value === 'number') return value.toString();
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
@@ -154,19 +220,69 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Render value with appropriate styling
|
// Render value with appropriate styling (for table view)
|
||||||
const renderValue = (value) => {
|
const renderValue = (value) => {
|
||||||
const typeStyle = getTypeStyle(value);
|
const typeStyle = getTypeStyle(value);
|
||||||
const formattedValue = formatValue(value);
|
const formattedValue = formatValue(value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`inline-flex items-center space-x-1 px-2 py-1 rounded-full text-xs font-medium ${typeStyle.color}`}>
|
<span className={`inline-flex items-center space-x-1 px-2 py-1 rounded text-xs font-medium ${typeStyle.color}`}>
|
||||||
{typeStyle.icon}
|
{typeStyle.icon}
|
||||||
<span>{formattedValue}</span>
|
<span>{formattedValue}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Render value with full text (for detail view)
|
||||||
|
const renderFullValue = (value) => {
|
||||||
|
const typeStyle = getTypeStyle(value);
|
||||||
|
const formattedValue = formatFullValue(value);
|
||||||
|
const hasHtml = isHtmlContent(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<span className={`inline-flex items-start space-x-2 px-2 py-1 rounded text-xs font-medium ${typeStyle.color}`}>
|
||||||
|
<span className="flex-shrink-0 mt-0.5">{typeStyle.icon}</span>
|
||||||
|
<span className="whitespace-pre-wrap break-words flex-1">
|
||||||
|
{hasHtml && renderHtml ? (
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: String(value) }} />
|
||||||
|
) : (
|
||||||
|
formattedValue
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* HTML Toggle Buttons */}
|
||||||
|
{hasHtml && (
|
||||||
|
<div className="absolute -top-1 -right-1 flex">
|
||||||
|
<button
|
||||||
|
onClick={() => setRenderHtml(true)}
|
||||||
|
className={`px-1.5 py-0.5 text-xs rounded-l ${
|
||||||
|
renderHtml
|
||||||
|
? 'bg-blue-500 text-white'
|
||||||
|
: 'bg-gray-200 text-gray-600 hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500'
|
||||||
|
}`}
|
||||||
|
title="Render HTML"
|
||||||
|
>
|
||||||
|
<Eye className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setRenderHtml(false)}
|
||||||
|
className={`px-1.5 py-0.5 text-xs rounded-r ${
|
||||||
|
!renderHtml
|
||||||
|
? 'bg-blue-500 text-white'
|
||||||
|
: 'bg-gray-200 text-gray-600 hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500'
|
||||||
|
}`}
|
||||||
|
title="Show Raw HTML"
|
||||||
|
>
|
||||||
|
<Code className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Get value type
|
// Get value type
|
||||||
const getValueType = (value) => {
|
const getValueType = (value) => {
|
||||||
if (value === null) return 'null';
|
if (value === null) return 'null';
|
||||||
@@ -175,7 +291,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
{/* Header with Breadcrumb */}
|
{/* Header with Breadcrumb */}
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 px-4 py-3 border-b border-gray-200 dark:border-gray-600">
|
<div className="bg-gray-50 dark:bg-gray-700 px-4 py-3 border-b border-gray-200 dark:border-gray-600">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -220,7 +336,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
{isArrayView ? (
|
{isArrayView ? (
|
||||||
// Horizontal table for arrays
|
// Horizontal table for arrays
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0">
|
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-12">
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-12">
|
||||||
#
|
#
|
||||||
@@ -263,7 +379,7 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
) : isObjectView ? (
|
) : isObjectView ? (
|
||||||
// Vertical key-value table for objects
|
// Vertical key-value table for objects
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0">
|
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-1/3">
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-1/3">
|
||||||
Key
|
Key
|
||||||
@@ -271,6 +387,9 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||||
Value
|
Value
|
||||||
</th>
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-16">
|
||||||
|
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
@@ -290,7 +409,23 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
{key}
|
{key}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm">
|
<td className="px-4 py-3 text-sm">
|
||||||
{renderValue(value)}
|
{renderFullValue(value)}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-sm">
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation(); // Prevent row click
|
||||||
|
copyToClipboard(value, `${currentPath.join('.')}.${key}`);
|
||||||
|
}}
|
||||||
|
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-600 rounded transition-colors"
|
||||||
|
title={copiedItems.has(`${currentPath.join('.')}.${key}`) ? "Copied!" : "Copy value"}
|
||||||
|
>
|
||||||
|
{copiedItems.has(`${currentPath.join('.')}.${key}`) ? (
|
||||||
|
<Check className="h-3 w-3 text-green-500" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-3 w-3 text-gray-500 dark:text-gray-400" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@@ -301,8 +436,8 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
|||||||
// Fallback for primitive values
|
// Fallback for primitive values
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="text-center text-gray-500 dark:text-gray-400">
|
<div className="text-center text-gray-500 dark:text-gray-400">
|
||||||
<div className="text-lg font-mono text-gray-900 dark:text-gray-100">
|
<div className="text-lg font-mono text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words">
|
||||||
{formatValue(currentData)}
|
{formatFullValue(currentData)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm mt-2">
|
<div className="text-sm mt-2">
|
||||||
Type: {getValueType(currentData)}
|
Type: {getValueType(currentData)}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Plus, Minus, ChevronDown, ChevronRight, Type, Hash, ToggleLeft, List, B
|
|||||||
const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||||
const [data, setData] = useState(initialData);
|
const [data, setData] = useState(initialData);
|
||||||
const [expandedNodes, setExpandedNodes] = useState(new Set(['root']));
|
const [expandedNodes, setExpandedNodes] = useState(new Set(['root']));
|
||||||
|
const [fieldTypes, setFieldTypes] = useState({}); // Track intended types for fields
|
||||||
const isInternalUpdate = useRef(false);
|
const isInternalUpdate = useRef(false);
|
||||||
|
|
||||||
// Update internal data when initialData prop changes (but not from internal updates)
|
// Update internal data when initialData prop changes (but not from internal updates)
|
||||||
@@ -14,11 +15,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('📥 EXTERNAL DATA CHANGED:', {
|
|
||||||
keys: Object.keys(initialData),
|
|
||||||
hasData: Object.keys(initialData).length > 0,
|
|
||||||
data: initialData
|
|
||||||
});
|
|
||||||
setData(initialData);
|
setData(initialData);
|
||||||
// Expand root node if there's data
|
// Expand root node if there's data
|
||||||
if (Object.keys(initialData).length > 0) {
|
if (Object.keys(initialData).length > 0) {
|
||||||
@@ -27,7 +23,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
}, [initialData]);
|
}, [initialData]);
|
||||||
|
|
||||||
const updateData = (newData) => {
|
const updateData = (newData) => {
|
||||||
console.log('📊 INTERNAL DATA UPDATE:', { keys: Object.keys(newData), totalProps: JSON.stringify(newData).length });
|
|
||||||
isInternalUpdate.current = true; // Mark as internal update
|
isInternalUpdate.current = true; // Mark as internal update
|
||||||
setData(newData);
|
setData(newData);
|
||||||
onDataChange(newData);
|
onDataChange(newData);
|
||||||
@@ -44,8 +39,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addProperty = (obj, path) => {
|
const addProperty = (obj, path) => {
|
||||||
console.log('🔧 ADD PROPERTY - Before:', { path, dataKeys: Object.keys(data), objKeys: Object.keys(obj) });
|
|
||||||
|
|
||||||
const pathParts = path.split('.');
|
const pathParts = path.split('.');
|
||||||
const newData = { ...data };
|
const newData = { ...data };
|
||||||
let current = newData;
|
let current = newData;
|
||||||
@@ -60,8 +53,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
const newKey = `property${keys.length + 1}`;
|
const newKey = `property${keys.length + 1}`;
|
||||||
current[newKey] = '';
|
current[newKey] = '';
|
||||||
|
|
||||||
console.log('🔧 ADD PROPERTY - After:', { path, newKey, dataKeys: Object.keys(newData), targetKeys: Object.keys(current) });
|
|
||||||
|
|
||||||
updateData(newData);
|
updateData(newData);
|
||||||
setExpandedNodes(new Set([...expandedNodes, path]));
|
setExpandedNodes(new Set([...expandedNodes, path]));
|
||||||
};
|
};
|
||||||
@@ -86,8 +77,6 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeProperty = (key, parentPath) => {
|
const removeProperty = (key, parentPath) => {
|
||||||
console.log('🗑️ REMOVE PROPERTY:', { key, parentPath });
|
|
||||||
|
|
||||||
const pathParts = parentPath.split('.');
|
const pathParts = parentPath.split('.');
|
||||||
const newData = { ...data };
|
const newData = { ...data };
|
||||||
let current = newData;
|
let current = newData;
|
||||||
@@ -97,6 +86,12 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
current = current[pathParts[i]];
|
current = current[pathParts[i]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove field type tracking for the removed property
|
||||||
|
const removedPath = parentPath === 'root' ? `root.${key}` : `${parentPath}.${key}`;
|
||||||
|
const newFieldTypes = { ...fieldTypes };
|
||||||
|
delete newFieldTypes[removedPath];
|
||||||
|
setFieldTypes(newFieldTypes);
|
||||||
|
|
||||||
// Delete the property/item from the parent
|
// Delete the property/item from the parent
|
||||||
if (Array.isArray(current)) {
|
if (Array.isArray(current)) {
|
||||||
// For arrays, remove by index and reindex
|
// For arrays, remove by index and reindex
|
||||||
@@ -110,21 +105,12 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
if (parentPath === 'root' && Object.keys(newData).length === 0) {
|
if (parentPath === 'root' && Object.keys(newData).length === 0) {
|
||||||
// Add an empty property to maintain initial state, like TableEditor maintains at least one row
|
// Add an empty property to maintain initial state, like TableEditor maintains at least one row
|
||||||
newData[''] = '';
|
newData[''] = '';
|
||||||
console.log('🔄 ADDED INITIAL EMPTY PROPERTY - Last root property was deleted');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🗑️ REMOVE PROPERTY - After:', {
|
|
||||||
parentPath,
|
|
||||||
remainingKeys: Array.isArray(current) ? current.length : Object.keys(current),
|
|
||||||
totalRootKeys: Object.keys(newData).length
|
|
||||||
});
|
|
||||||
|
|
||||||
updateData(newData);
|
updateData(newData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateValue = (value, path) => {
|
const updateValue = (value, path) => {
|
||||||
console.log('✏️ UPDATE VALUE:', { path, value, currentType: typeof getValue(path) });
|
|
||||||
|
|
||||||
const pathParts = path.split('.');
|
const pathParts = path.split('.');
|
||||||
const newData = { ...data };
|
const newData = { ...data };
|
||||||
let current = newData;
|
let current = newData;
|
||||||
@@ -172,13 +158,10 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✏️ UPDATE VALUE - Result:', { path, newValue: current[key], newType: typeof current[key] });
|
|
||||||
updateData(newData);
|
updateData(newData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeType = (newType, path) => {
|
const changeType = (newType, path) => {
|
||||||
console.log('🔄 CHANGE TYPE:', { path, newType, currentValue: getValue(path) });
|
|
||||||
|
|
||||||
const pathParts = path.split('.');
|
const pathParts = path.split('.');
|
||||||
const newData = { ...data };
|
const newData = { ...data };
|
||||||
let current = newData;
|
let current = newData;
|
||||||
@@ -190,6 +173,11 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
const key = pathParts[pathParts.length - 1];
|
const key = pathParts[pathParts.length - 1];
|
||||||
const currentValue = current[key];
|
const currentValue = current[key];
|
||||||
|
|
||||||
|
// Track the intended type for this field
|
||||||
|
const newFieldTypes = { ...fieldTypes };
|
||||||
|
newFieldTypes[path] = newType;
|
||||||
|
setFieldTypes(newFieldTypes);
|
||||||
|
|
||||||
// Try to preserve value when changing types if possible
|
// Try to preserve value when changing types if possible
|
||||||
switch (newType) {
|
switch (newType) {
|
||||||
case 'string':
|
case 'string':
|
||||||
@@ -227,19 +215,10 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
current[key] = '';
|
current[key] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔄 CHANGE TYPE - Result:', { path, newValue: current[key], actualType: typeof current[key] });
|
|
||||||
updateData(newData);
|
updateData(newData);
|
||||||
setExpandedNodes(new Set([...expandedNodes, path]));
|
setExpandedNodes(new Set([...expandedNodes, path]));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getValue = (path) => {
|
|
||||||
const pathParts = path.split('.');
|
|
||||||
let current = data;
|
|
||||||
for (let i = 1; i < pathParts.length; i++) {
|
|
||||||
current = current[pathParts[i]];
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to display string values with proper unescaping
|
// Helper function to display string values with proper unescaping
|
||||||
const getDisplayValue = (value) => {
|
const getDisplayValue = (value) => {
|
||||||
@@ -277,6 +256,16 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
return; // Don't rename if key already exists
|
return; // Don't rename if key already exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update field type tracking for renamed key
|
||||||
|
const oldPath = path;
|
||||||
|
const newPath = path.replace(new RegExp(`\\.${oldKey}$`), `.${newKey}`);
|
||||||
|
const newFieldTypes = { ...fieldTypes };
|
||||||
|
if (newFieldTypes[oldPath]) {
|
||||||
|
newFieldTypes[newPath] = newFieldTypes[oldPath];
|
||||||
|
delete newFieldTypes[oldPath];
|
||||||
|
setFieldTypes(newFieldTypes);
|
||||||
|
}
|
||||||
|
|
||||||
// Rename the key
|
// Rename the key
|
||||||
const value = current[oldKey];
|
const value = current[oldKey];
|
||||||
delete current[oldKey];
|
delete current[oldKey];
|
||||||
@@ -354,13 +343,19 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
const canExpand = typeof value === 'object' && value !== null;
|
const canExpand = typeof value === 'object' && value !== null;
|
||||||
|
|
||||||
// Check if parent is an array by looking at the parent path
|
// Check if parent is an array by looking at the parent path
|
||||||
const isArrayItem = parentPath !== 'root' && (() => {
|
const isArrayItem = (() => {
|
||||||
const parentPathParts = parentPath.split('.');
|
if (parentPath === 'root') {
|
||||||
let current = data;
|
// If parent is root, check if root data is an array
|
||||||
for (let i = 1; i < parentPathParts.length; i++) {
|
return Array.isArray(data);
|
||||||
current = current[parentPathParts[i]];
|
} else {
|
||||||
|
// Navigate to parent and check if it's an array
|
||||||
|
const parentPathParts = parentPath.split('.');
|
||||||
|
let current = data;
|
||||||
|
for (let i = 1; i < parentPathParts.length; i++) {
|
||||||
|
current = current[parentPathParts[i]];
|
||||||
|
}
|
||||||
|
return Array.isArray(current);
|
||||||
}
|
}
|
||||||
return Array.isArray(current);
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -436,11 +431,11 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
typeof value === 'string' && value.includes('\n') ? (
|
(fieldTypes[path] === 'longtext' || (typeof value === 'string' && value.includes('\n'))) ? (
|
||||||
<textarea
|
<textarea
|
||||||
value={getDisplayValue(value)}
|
value={getDisplayValue(value)}
|
||||||
onChange={(e) => updateValue(e.target.value, path)}
|
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 min-w-0 resize-y"
|
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 resize-y items"
|
||||||
placeholder="Long text value"
|
placeholder="Long text value"
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
@@ -463,12 +458,14 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
|||||||
<div className="flex items-center space-x-2 sm:space-x-2">
|
<div className="flex items-center space-x-2 sm:space-x-2">
|
||||||
<select
|
<select
|
||||||
value={
|
value={
|
||||||
value === null ? 'null' :
|
fieldTypes[path] || (
|
||||||
value === undefined ? 'string' :
|
value === null ? 'null' :
|
||||||
typeof value === 'string' ? (value.includes('\n') ? 'longtext' : 'string') :
|
value === undefined ? 'string' :
|
||||||
typeof value === 'number' ? 'number' :
|
typeof value === 'string' ? (value.includes('\n') ? 'longtext' : 'string') :
|
||||||
typeof value === 'boolean' ? 'boolean' :
|
typeof value === 'number' ? 'number' :
|
||||||
Array.isArray(value) ? 'array' : 'object'
|
typeof value === 'boolean' ? 'boolean' :
|
||||||
|
Array.isArray(value) ? 'array' : 'object'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onChange={(e) => changeType(e.target.value, path)}
|
onChange={(e) => changeType(e.target.value, path)}
|
||||||
className="px-2 py-1 text-xs 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"
|
className="px-2 py-1 text-xs 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"
|
||||||
|
|||||||
@@ -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
|
// Auto-detect input format
|
||||||
const detectInputFormat = (input) => {
|
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 {
|
try {
|
||||||
const jsonData = JSON.parse(input);
|
const jsonData = parseJsonSmart(input);
|
||||||
return { format: 'JSON', valid: true, data: jsonData };
|
return { format: 'JSON', valid: true, data: jsonData, error: null };
|
||||||
} catch {}
|
} catch (jsonError) {
|
||||||
|
// Try PHP serialize
|
||||||
// Try PHP serialize
|
try {
|
||||||
try {
|
const serializedData = phpUnserialize(input);
|
||||||
const serializedData = phpUnserialize(input);
|
return { format: 'PHP Serialized', valid: true, data: serializedData, error: null };
|
||||||
return { format: 'PHP Serialized', valid: true, data: serializedData };
|
} catch (phpError) {
|
||||||
} catch {}
|
// Return JSON error since it's more likely what user intended
|
||||||
|
return { format: 'Unknown', valid: false, data: null, error: jsonError.message };
|
||||||
return { format: 'Unknown', valid: false, data: null };
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle input text change
|
// Handle input text change
|
||||||
@@ -362,12 +406,13 @@ const ObjectEditor = () => {
|
|||||||
setInputValid(detection.valid);
|
setInputValid(detection.valid);
|
||||||
|
|
||||||
if (detection.valid) {
|
if (detection.valid) {
|
||||||
console.log(' SETTING STRUCTURED DATA:', detection.data);
|
|
||||||
setStructuredData(detection.data);
|
setStructuredData(detection.data);
|
||||||
setError('');
|
setError('');
|
||||||
setCreateNewCompleted(true);
|
setCreateNewCompleted(true);
|
||||||
} else if (value.trim()) {
|
} 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 {
|
} else {
|
||||||
setError('');
|
setError('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,10 +302,6 @@ const TableEditor = () => {
|
|||||||
|
|
||||||
// SQL parsing functions for multi-table support
|
// SQL parsing functions for multi-table support
|
||||||
const parseMultiTableSQL = (sqlContent) => {
|
const parseMultiTableSQL = (sqlContent) => {
|
||||||
console.log(
|
|
||||||
"🔍 parseMultiTableSQL called with content length:",
|
|
||||||
sqlContent.length,
|
|
||||||
);
|
|
||||||
const tables = {};
|
const tables = {};
|
||||||
const lines = sqlContent.split("\n");
|
const lines = sqlContent.split("\n");
|
||||||
let currentTable = null;
|
let currentTable = null;
|
||||||
@@ -313,7 +309,6 @@ const TableEditor = () => {
|
|||||||
let insideCreateTable = false;
|
let insideCreateTable = false;
|
||||||
let insideInsert = false;
|
let insideInsert = false;
|
||||||
|
|
||||||
console.log("📄 Total lines to process:", lines.length);
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i].trim();
|
const line = lines[i].trim();
|
||||||
@@ -384,12 +379,7 @@ const TableEditor = () => {
|
|||||||
const valuesMatch = line.match(/VALUES\s*(.+)/i);
|
const valuesMatch = line.match(/VALUES\s*(.+)/i);
|
||||||
if (valuesMatch) {
|
if (valuesMatch) {
|
||||||
const valuesStr = valuesMatch[1];
|
const valuesStr = valuesMatch[1];
|
||||||
console.log(
|
|
||||||
"🔍 Found VALUES in multi-table parser:",
|
|
||||||
valuesStr.substring(0, 100) + "...",
|
|
||||||
);
|
|
||||||
const rows = parseInsertValues(valuesStr);
|
const rows = parseInsertValues(valuesStr);
|
||||||
console.log("📊 parseInsertValues returned", rows.length, "rows");
|
|
||||||
tables[currentTable].data.push(...rows);
|
tables[currentTable].data.push(...rows);
|
||||||
tables[currentTable].rowCount += rows.length;
|
tables[currentTable].rowCount += rows.length;
|
||||||
}
|
}
|
||||||
@@ -417,10 +407,6 @@ const TableEditor = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const parseInsertValues = (valuesStr) => {
|
const parseInsertValues = (valuesStr) => {
|
||||||
console.log(
|
|
||||||
"🔍 parseInsertValues called with:",
|
|
||||||
valuesStr.substring(0, 200) + "...",
|
|
||||||
);
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
||||||
// Better parsing: find complete tuples first, then parse values respecting quotes
|
// Better parsing: find complete tuples first, then parse values respecting quotes
|
||||||
@@ -463,7 +449,6 @@ const TableEditor = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tuples.forEach((tuple) => {
|
tuples.forEach((tuple) => {
|
||||||
console.log("📝 Processing tuple:", tuple.substring(0, 100) + "...");
|
|
||||||
|
|
||||||
// Remove outer parentheses
|
// Remove outer parentheses
|
||||||
const innerContent = tuple.replace(/^\(|\)$/g, "").trim();
|
const innerContent = tuple.replace(/^\(|\)$/g, "").trim();
|
||||||
@@ -509,37 +494,21 @@ const TableEditor = () => {
|
|||||||
values.push(currentValue.trim());
|
values.push(currentValue.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("📊 Parsed", values.length, "values from tuple");
|
|
||||||
|
|
||||||
const processedValues = values.map((val) => {
|
const processedValues = values.map((val) => {
|
||||||
val = val.trim();
|
val = val.trim();
|
||||||
console.log(
|
|
||||||
"🔧 Processing value:",
|
|
||||||
val.substring(0, 50) + (val.length > 50 ? "..." : ""),
|
|
||||||
);
|
|
||||||
// Remove quotes and handle NULL
|
// Remove quotes and handle NULL
|
||||||
if (val === "NULL") return "";
|
if (val === "NULL") return "";
|
||||||
if (val.startsWith("'") && val.endsWith("'")) {
|
if (val.startsWith("'") && val.endsWith("'")) {
|
||||||
let unquoted = val.slice(1, -1);
|
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)
|
// Handle escaped JSON properly (same logic as main parser)
|
||||||
if (unquoted.includes('\\"') || unquoted.includes("\\'")) {
|
if (unquoted.includes('\\"') || unquoted.includes("\\'")) {
|
||||||
console.log(
|
|
||||||
"🔧 Value contains escaped quotes, checking if JSON...",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to detect if this is escaped JSON
|
// Try to detect if this is escaped JSON
|
||||||
const isEscapedJson = (str) => {
|
const isEscapedJson = (str) => {
|
||||||
const unescaped = str.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
const unescaped = str.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
||||||
const trimmed = unescaped.trim();
|
const trimmed = unescaped.trim();
|
||||||
console.log(
|
|
||||||
"🧪 Testing unescaped value:",
|
|
||||||
trimmed.substring(0, 50) + (trimmed.length > 50 ? "..." : ""),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
||||||
@@ -547,27 +516,17 @@ const TableEditor = () => {
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(trimmed);
|
JSON.parse(trimmed);
|
||||||
console.log("✅ Valid JSON detected in parseInsertValues!");
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("❌ JSON parse failed:", e.message);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("❌ Not JSON format (no brackets)");
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If it's escaped JSON, unescape it
|
// If it's escaped JSON, unescape it
|
||||||
if (isEscapedJson(unquoted)) {
|
if (isEscapedJson(unquoted)) {
|
||||||
const oldValue = unquoted;
|
|
||||||
unquoted = unquoted.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
unquoted = unquoted.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
||||||
console.log(
|
|
||||||
"🎯 Unescaped JSON in parseInsertValues from:",
|
|
||||||
oldValue.substring(0, 30),
|
|
||||||
"to:",
|
|
||||||
unquoted.substring(0, 30),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Only unescape single quotes for non-JSON strings
|
// Only unescape single quotes for non-JSON strings
|
||||||
unquoted = unquoted.replace(/''/g, "'");
|
unquoted = unquoted.replace(/''/g, "'");
|
||||||
@@ -577,10 +536,6 @@ const TableEditor = () => {
|
|||||||
unquoted = unquoted.replace(/''/g, "'");
|
unquoted = unquoted.replace(/''/g, "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
|
||||||
"✅ Final unquoted value:",
|
|
||||||
unquoted.substring(0, 50) + (unquoted.length > 50 ? "..." : ""),
|
|
||||||
);
|
|
||||||
return unquoted;
|
return unquoted;
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
@@ -594,7 +549,6 @@ const TableEditor = () => {
|
|||||||
rows.push(rowObj);
|
rows.push(rowObj);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("📊 parseInsertValues returning", rows.length, "rows");
|
|
||||||
return rows;
|
return rows;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -733,8 +687,6 @@ const TableEditor = () => {
|
|||||||
|
|
||||||
// Parse SQL data
|
// Parse SQL data
|
||||||
const parseSqlData = (text) => {
|
const parseSqlData = (text) => {
|
||||||
console.log("🚀 parseSqlData called with text length:", text.length);
|
|
||||||
console.log("📄 First 500 chars:", text.substring(0, 500));
|
|
||||||
try {
|
try {
|
||||||
// Clean the SQL text - remove comments and unnecessary lines
|
// Clean the SQL text - remove comments and unnecessary lines
|
||||||
const cleanLines = text
|
const cleanLines = text
|
||||||
@@ -913,19 +865,8 @@ const TableEditor = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the value - handle escaped JSON properly
|
// 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("\\'")) {
|
if (value.includes('\\"') || value.includes("\\'")) {
|
||||||
console.log(
|
|
||||||
"🔧 Value contains escaped quotes, checking if JSON...",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to detect if this is escaped JSON
|
// Try to detect if this is escaped JSON
|
||||||
const isEscapedJson = (str) => {
|
const isEscapedJson = (str) => {
|
||||||
@@ -934,11 +875,6 @@ const TableEditor = () => {
|
|||||||
.replace(/\\"/g, '"')
|
.replace(/\\"/g, '"')
|
||||||
.replace(/\\'/g, "'");
|
.replace(/\\'/g, "'");
|
||||||
const trimmed = unescaped.trim();
|
const trimmed = unescaped.trim();
|
||||||
console.log(
|
|
||||||
"🧪 Testing unescaped value:",
|
|
||||||
trimmed.substring(0, 100) +
|
|
||||||
(trimmed.length > 100 ? "..." : ""),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
||||||
@@ -946,40 +882,25 @@ const TableEditor = () => {
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(trimmed);
|
JSON.parse(trimmed);
|
||||||
console.log("✅ Valid JSON detected!");
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("❌ JSON parse failed:", e.message);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("❌ Not JSON format (no brackets)");
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If it's escaped JSON, unescape it
|
// If it's escaped JSON, unescape it
|
||||||
if (isEscapedJson(value)) {
|
if (isEscapedJson(value)) {
|
||||||
const oldValue = value;
|
|
||||||
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
||||||
console.log(
|
|
||||||
"🎯 Unescaped JSON from:",
|
|
||||||
oldValue.substring(0, 50),
|
|
||||||
"to:",
|
|
||||||
value.substring(0, 50),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Only unescape single quotes for non-JSON strings
|
// Only unescape single quotes for non-JSON strings
|
||||||
if (value.includes("\\'")) {
|
if (value.includes("\\'")) {
|
||||||
console.log("🔧 Unescaping single quotes only");
|
|
||||||
value = value.replace(/\\'/g, "'");
|
value = value.replace(/\\'/g, "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
|
||||||
"✅ Final value for storage:",
|
|
||||||
value.substring(0, 100) + (value.length > 100 ? "..." : ""),
|
|
||||||
);
|
|
||||||
|
|
||||||
rowData[header.id] = value;
|
rowData[header.id] = value;
|
||||||
});
|
});
|
||||||
@@ -1048,27 +969,19 @@ const TableEditor = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = inputText.trim();
|
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
|
// Try to detect format
|
||||||
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
||||||
// JSON array
|
// JSON array
|
||||||
console.log("🔍 Detected JSON array format");
|
|
||||||
parseJsonData(trimmed);
|
parseJsonData(trimmed);
|
||||||
} else if (
|
} else if (
|
||||||
trimmed.toLowerCase().includes("insert into") &&
|
trimmed.toLowerCase().includes("insert into") &&
|
||||||
trimmed.toLowerCase().includes("values")
|
trimmed.toLowerCase().includes("values")
|
||||||
) {
|
) {
|
||||||
// SQL INSERT statements
|
// SQL INSERT statements
|
||||||
console.log("🔍 Detected SQL INSERT format");
|
|
||||||
parseSqlData(trimmed);
|
parseSqlData(trimmed);
|
||||||
} else {
|
} else {
|
||||||
// CSV/TSV
|
// CSV/TSV
|
||||||
console.log("🔍 Detected CSV/TSV format");
|
|
||||||
parseData(trimmed, useFirstRowAsHeader);
|
parseData(trimmed, useFirstRowAsHeader);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1133,13 +1046,10 @@ const TableEditor = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initializeTablesFromSQL = (sqlContent, fileName = "") => {
|
const initializeTablesFromSQL = (sqlContent, fileName = "") => {
|
||||||
console.log("🏗️ initializeTablesFromSQL called with fileName:", fileName);
|
|
||||||
console.log("📊 SQL content length:", sqlContent.length);
|
|
||||||
|
|
||||||
const discoveredTables = parseMultiTableSQL(sqlContent);
|
const discoveredTables = parseMultiTableSQL(sqlContent);
|
||||||
const tableNames = Object.keys(discoveredTables);
|
const tableNames = Object.keys(discoveredTables);
|
||||||
|
|
||||||
console.log("📋 Discovered tables:", tableNames);
|
|
||||||
|
|
||||||
if (tableNames.length === 0) {
|
if (tableNames.length === 0) {
|
||||||
console.error("❌ No tables found in SQL file");
|
console.error("❌ No tables found in SQL file");
|
||||||
@@ -1177,7 +1087,6 @@ const TableEditor = () => {
|
|||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
console.log("📁 File upload started:", file.name, "size:", file.size);
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
|
|
||||||
@@ -1185,15 +1094,11 @@ const TableEditor = () => {
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
try {
|
try {
|
||||||
const content = e.target.result;
|
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
|
// Check if it's SQL file for multi-table support
|
||||||
if (file.name.toLowerCase().endsWith(".sql")) {
|
if (file.name.toLowerCase().endsWith(".sql")) {
|
||||||
console.log("🔍 Detected SQL file, using multi-table parsing");
|
|
||||||
initializeTablesFromSQL(content, file.name);
|
initializeTablesFromSQL(content, file.name);
|
||||||
} else {
|
} else {
|
||||||
console.log("🔍 Non-SQL file, using single-table parsing");
|
|
||||||
// Fallback to single-table parsing
|
// Fallback to single-table parsing
|
||||||
parseData(content);
|
parseData(content);
|
||||||
}
|
}
|
||||||
@@ -3291,7 +3196,6 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
|
|||||||
const [error, setError] = useState(initialState.error);
|
const [error, setError] = useState(initialState.error);
|
||||||
|
|
||||||
// Debug log to see what we initialized with
|
// Debug log to see what we initialized with
|
||||||
// console.log('ObjectEditorModal initialized with:', { structuredData, isValid, error });
|
|
||||||
|
|
||||||
// Update current value when structured data changes
|
// Update current value when structured data changes
|
||||||
const handleStructuredDataChange = (newData) => {
|
const handleStructuredDataChange = (newData) => {
|
||||||
@@ -3378,7 +3282,6 @@ const ObjectEditorModal = ({ modal, onClose, onApply }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('Rendering StructuredEditor with data:', structuredData); // Debug log
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-white dark:bg-gray-800 p-6">
|
<div className="h-full bg-white dark:bg-gray-800 p-6">
|
||||||
<StructuredEditor
|
<StructuredEditor
|
||||||
|
|||||||
@@ -45,16 +45,14 @@ export const initConsentMode = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isEEA = isEEAUser();
|
const isEEA = isEEAUser();
|
||||||
const mode = process.env.NODE_ENV !== 'production' ? '[DEV]' : '[PROD]';
|
|
||||||
|
|
||||||
if (isEEA || process.env.NODE_ENV !== 'production') {
|
if (isEEA) {
|
||||||
// EEA users or development: Start with denied, wait for user consent
|
// EEA users: Start with denied, wait for user consent
|
||||||
window.gtag('consent', 'default', {
|
window.gtag('consent', 'default', {
|
||||||
...DEFAULT_CONSENT,
|
...DEFAULT_CONSENT,
|
||||||
region: isEEA ? ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'] : ['US'],
|
region: ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'],
|
||||||
wait_for_update: 500
|
wait_for_update: 500
|
||||||
});
|
});
|
||||||
console.log(`🍪 ${mode} Consent Mode v2 initialized - EEA user, consent required`);
|
|
||||||
} else {
|
} else {
|
||||||
// Non-EEA users: Automatically grant all consent
|
// Non-EEA users: Automatically grant all consent
|
||||||
window.gtag('consent', 'default', {
|
window.gtag('consent', 'default', {
|
||||||
@@ -62,7 +60,6 @@ export const initConsentMode = () => {
|
|||||||
region: ['US'],
|
region: ['US'],
|
||||||
wait_for_update: 0 // No need to wait
|
wait_for_update: 0 // No need to wait
|
||||||
});
|
});
|
||||||
console.log(`🍪 ${mode} Consent Mode v2 initialized - Non-EEA user, auto-granted all consent`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,11 +72,8 @@ export const updateConsent = (consentChoices) => {
|
|||||||
// Store consent in localStorage
|
// Store consent in localStorage
|
||||||
localStorage.setItem('consent_preferences', JSON.stringify({
|
localStorage.setItem('consent_preferences', JSON.stringify({
|
||||||
...consentChoices,
|
...consentChoices,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now()
|
||||||
version: '2.0'
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('🍪 Consent updated:', consentChoices);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get stored consent preferences
|
// Get stored consent preferences
|
||||||
@@ -101,20 +95,31 @@ export const getStoredConsent = () => {
|
|||||||
|
|
||||||
// Check if consent banner should be shown
|
// Check if consent banner should be shown
|
||||||
export const shouldShowConsentBanner = () => {
|
export const shouldShowConsentBanner = () => {
|
||||||
// In development, always show for testing
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const isEEA = isEEAUser();
|
||||||
return !getStoredConsent();
|
const hasConsent = getStoredConsent();
|
||||||
|
|
||||||
|
// In development, only show for EEA users (for proper testing)
|
||||||
|
if (isDev) {
|
||||||
|
if (isEEA) {
|
||||||
|
return !hasConsent;
|
||||||
|
} else {
|
||||||
|
// Auto-grant for non-EEA even in development
|
||||||
|
if (!hasConsent) {
|
||||||
|
updateConsent(CONSENT_CONFIGS.ACCEPT_ALL);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In production, only show for EEA users who haven't consented
|
// In production, only show for EEA users who haven't consented
|
||||||
if (isEEAUser()) {
|
if (isEEA) {
|
||||||
return !getStoredConsent();
|
return !hasConsent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-EEA users, automatically grant all consent and never show banner
|
// For non-EEA users, automatically grant all consent and never show banner
|
||||||
if (!getStoredConsent()) {
|
if (!hasConsent) {
|
||||||
updateConsent(CONSENT_CONFIGS.ACCEPT_ALL);
|
updateConsent(CONSENT_CONFIGS.ACCEPT_ALL);
|
||||||
console.log('🍪 [AUTO] Non-EEA user - automatically granted all consent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; // Never show banner for non-EEA users
|
return false; // Never show banner for non-EEA users
|
||||||
|
|||||||
Reference in New Issue
Block a user