Improve ObjectEditor and Add TableEditor
This commit is contained in:
75
src/components/ErrorBoundary.js
Normal file
75
src/components/ErrorBoundary.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null, errorInfo: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
// Update state so the next render will show the fallback UI
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
// Log the error for debugging
|
||||
this.setState({
|
||||
error: error,
|
||||
errorInfo: errorInfo
|
||||
});
|
||||
|
||||
// You can also log the error to an error reporting service here
|
||||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Fallback UI
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center p-4">
|
||||
<div className="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 text-center">
|
||||
<div className="mb-4">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900/20">
|
||||
<svg className="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
Something went wrong
|
||||
</h2>
|
||||
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
The application encountered an error. This might be due to browser compatibility issues.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors"
|
||||
>
|
||||
Reload Page
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => this.setState({ hasError: false, error: null, errorInfo: null })}
|
||||
className="w-full bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 font-medium py-2 px-4 rounded-md transition-colors"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-xs text-gray-500 dark:text-gray-400">
|
||||
If you're using Telegram's built-in browser, try opening this link in your default browser for better compatibility.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Home, Hash, FileSpreadsheet, Wand2, GitCompare, Menu, X, LinkIcon, Code2, ChevronDown, Type, Edit3 } from 'lucide-react';
|
||||
import { Home, Hash, FileSpreadsheet, Wand2, GitCompare, Menu, X, LinkIcon, Code2, ChevronDown, Type, Edit3, Table } from 'lucide-react';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
import ToolSidebar from './ToolSidebar';
|
||||
|
||||
@@ -36,6 +36,7 @@ const Layout = ({ children }) => {
|
||||
|
||||
const tools = [
|
||||
{ path: '/object-editor', name: 'Object Editor', icon: Edit3, description: 'Visual editor for JSON & PHP objects' },
|
||||
{ path: '/table-editor', name: 'Table Editor', icon: Table, description: 'Import, edit & export tabular data' },
|
||||
{ path: '/url', name: 'URL Tool', icon: LinkIcon, description: 'URL encode/decode' },
|
||||
{ path: '/base64', name: 'Base64 Tool', icon: Hash, description: 'Base64 encode/decode' },
|
||||
{ path: '/csv-json', name: 'CSV/JSON Tool', icon: FileSpreadsheet, description: 'Convert CSV ↔ JSON' },
|
||||
|
||||
@@ -106,9 +106,17 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
delete current[key];
|
||||
}
|
||||
|
||||
// Check if we're removing from root level and it's the last property
|
||||
if (parentPath === 'root' && Object.keys(newData).length === 0) {
|
||||
// Add an empty property to maintain initial state, like TableEditor maintains at least one row
|
||||
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)
|
||||
remainingKeys: Array.isArray(current) ? current.length : Object.keys(current),
|
||||
totalRootKeys: Object.keys(newData).length
|
||||
});
|
||||
|
||||
updateData(newData);
|
||||
@@ -223,6 +231,25 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
return current;
|
||||
};
|
||||
|
||||
// Helper function to display string values with proper unescaping
|
||||
const getDisplayValue = (value) => {
|
||||
if (value === null) return 'null';
|
||||
if (value === undefined) return '';
|
||||
|
||||
const stringValue = value.toString();
|
||||
|
||||
// If it's a string, unescape common JSON escape sequences for display
|
||||
if (typeof value === 'string') {
|
||||
return stringValue
|
||||
.replace(/\\"/g, '"') // Unescape quotes
|
||||
.replace(/\\'/g, "'") // Unescape single quotes
|
||||
.replace(/\\\//g, '/') // Unescape forward slashes
|
||||
.replace(/\\\\/g, '\\'); // Unescape backslashes (do this last)
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
};
|
||||
|
||||
const renameKey = (oldKey, newKey, path) => {
|
||||
if (oldKey === newKey || !newKey.trim()) return;
|
||||
|
||||
@@ -401,11 +428,7 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
) : (
|
||||
typeof value === 'string' && value.includes('\n') ? (
|
||||
<textarea
|
||||
value={
|
||||
value === null ? 'null' :
|
||||
value === undefined ? '' :
|
||||
value.toString()
|
||||
}
|
||||
value={getDisplayValue(value)}
|
||||
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"
|
||||
placeholder="Long text value"
|
||||
@@ -414,11 +437,7 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
value={
|
||||
value === null ? 'null' :
|
||||
value === undefined ? '' :
|
||||
value.toString()
|
||||
}
|
||||
value={getDisplayValue(value)}
|
||||
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"
|
||||
placeholder="Value"
|
||||
@@ -500,17 +519,9 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border border-gray-300 dark:border-gray-600 rounded-lg p-4 bg-white dark:bg-gray-800 min-h-96 w-full">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="min-h-96 w-full">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">Structured Data Editor</h3>
|
||||
<button
|
||||
onClick={() => addProperty(data, 'root')}
|
||||
className="flex items-center space-x-1 px-3 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors flex-shrink-0"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Add Property</span>
|
||||
<span className="sm:hidden">Add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-hidden">
|
||||
@@ -524,6 +535,15 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
|
||||
renderValue(value, key, `root.${key}`, 'root')
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Root level Add Property button */}
|
||||
<button
|
||||
onClick={() => addProperty(data, 'root')}
|
||||
className="flex items-center space-x-1 px-2 py-1 text-sm text-blue-600 hover:bg-blue-100 dark:hover:bg-blue-900 rounded"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
<span>Add Property</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Search, LinkIcon, Hash, FileSpreadsheet, Wand2, GitCompare, Home, ChevronLeft, ChevronRight, Type, Edit3 } from 'lucide-react';
|
||||
import { Search, LinkIcon, Hash, Table, Wand2, GitCompare, Home, ChevronLeft, ChevronRight, Type, Edit3 } from 'lucide-react';
|
||||
|
||||
const ToolSidebar = () => {
|
||||
const location = useLocation();
|
||||
@@ -10,9 +10,9 @@ const ToolSidebar = () => {
|
||||
const tools = [
|
||||
{ path: '/', name: 'Home', icon: Home, description: 'Back to homepage' },
|
||||
{ path: '/object-editor', name: 'Object Editor', icon: Edit3, description: 'Visual editor for JSON & PHP objects' },
|
||||
{ path: '/table-editor', name: 'Table Editor', icon: Table, description: 'Import, edit & export tabular data' },
|
||||
{ path: '/url', name: 'URL Tool', icon: LinkIcon, description: 'URL encode/decode' },
|
||||
{ path: '/base64', name: 'Base64 Tool', icon: Hash, description: 'Base64 encode/decode' },
|
||||
{ path: '/csv-json', name: 'CSV/JSON Tool', icon: FileSpreadsheet, description: 'Convert CSV ↔ JSON' },
|
||||
{ path: '/beautifier', name: 'Beautifier Tool', icon: Wand2, description: 'Beautify/minify code' },
|
||||
{ path: '/diff', name: 'Diff Tool', icon: GitCompare, description: 'Compare text differences' },
|
||||
{ path: '/text-length', name: 'Text Length Checker', icon: Type, description: 'Analyze text length & stats' },
|
||||
|
||||
Reference in New Issue
Block a user