From 977e784df2f26e924625f201f9c7e6dfc96a5785 Mon Sep 17 00:00:00 2001 From: dwindown Date: Tue, 23 Sep 2025 14:17:13 +0700 Subject: [PATCH] Improve ObjectEditor and Add TableEditor --- src/App.js | 37 +- src/components/ErrorBoundary.js | 75 + src/components/Layout.js | 3 +- src/components/StructuredEditor.js | 62 +- src/components/ToolSidebar.js | 4 +- src/index.css | 9 + src/index.js | 4 + src/pages/Home.js | 16 +- src/pages/ObjectEditor.js | 1043 ++++++-- src/pages/TableEditor.js | 3637 ++++++++++++++++++++++++++++ src/pages/TextLengthTool.js | 232 +- src/utils/browserCompat.js | 147 ++ src/utils/contentExtractor.js | 371 +++ tailwind.config.js | 7 +- temp_export_tabs.js | 27 + 15 files changed, 5329 insertions(+), 345 deletions(-) create mode 100644 src/components/ErrorBoundary.js create mode 100644 src/pages/TableEditor.js create mode 100644 src/utils/browserCompat.js create mode 100644 src/utils/contentExtractor.js create mode 100644 temp_export_tabs.js 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') ? (