diff --git a/src/App.js b/src/App.js index 2f55c788..35073856 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import React from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import Layout from './components/Layout'; +import ErrorBoundary from './components/ErrorBoundary'; import Home from './pages/Home'; import JsonTool from './pages/JsonTool'; import SerializeTool from './pages/SerializeTool'; @@ -11,28 +12,32 @@ import BeautifierTool from './pages/BeautifierTool'; import DiffTool from './pages/DiffTool'; import TextLengthTool from './pages/TextLengthTool'; import ObjectEditor from './pages/ObjectEditor'; +import TableEditor from './pages/TableEditor'; import './index.css'; function App() { return ( - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> - - - + + + + ); } diff --git a/src/components/ErrorBoundary.js b/src/components/ErrorBoundary.js new file mode 100644 index 00000000..cbcf4ee7 --- /dev/null +++ b/src/components/ErrorBoundary.js @@ -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 ( +
+
+
+
+ + + +
+
+ +

+ Something went wrong +

+ +

+ The application encountered an error. This might be due to browser compatibility issues. +

+ +
+ + + +
+ +
+ If you're using Telegram's built-in browser, try opening this link in your default browser for better compatibility. +
+
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/components/Layout.js b/src/components/Layout.js index 80e764e3..1e90f331 100644 --- a/src/components/Layout.js +++ b/src/components/Layout.js @@ -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' }, diff --git a/src/components/StructuredEditor.js b/src/components/StructuredEditor.js index 7057d9dc..130e8c5f 100644 --- a/src/components/StructuredEditor.js +++ b/src/components/StructuredEditor.js @@ -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') ? (