diff --git a/src/components/StructuredEditor.js b/src/components/StructuredEditor.js index a6fc267e..7057d9dc 100644 --- a/src/components/StructuredEditor.js +++ b/src/components/StructuredEditor.js @@ -1,13 +1,20 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Plus, Minus, ChevronDown, ChevronRight, Type, Hash, ToggleLeft, List, Braces } from 'lucide-react'; const StructuredEditor = ({ onDataChange, initialData = {} }) => { const [data, setData] = useState(initialData); const [expandedNodes, setExpandedNodes] = useState(new Set(['root'])); + const isInternalUpdate = useRef(false); - // Update internal data when initialData prop changes + // Update internal data when initialData prop changes (but not from internal updates) useEffect(() => { - console.log('π₯ INITIAL DATA CHANGED:', { + // Skip update if this change came from internal editor actions + if (isInternalUpdate.current) { + isInternalUpdate.current = false; + return; + } + + console.log('π₯ EXTERNAL DATA CHANGED:', { keys: Object.keys(initialData), hasData: Object.keys(initialData).length > 0, data: initialData @@ -20,7 +27,8 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { }, [initialData]); const updateData = (newData) => { - console.log('π DATA UPDATE:', { keys: Object.keys(newData), totalProps: JSON.stringify(newData).length }); + console.log('π INTERNAL DATA UPDATE:', { keys: Object.keys(newData), totalProps: JSON.stringify(newData).length }); + isInternalUpdate.current = true; // Mark as internal update setData(newData); onDataChange(newData); }; @@ -78,22 +86,31 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => { }; const removeProperty = (key, parentPath) => { + console.log('ποΈ REMOVE PROPERTY:', { key, parentPath }); + const pathParts = parentPath.split('.'); const newData = { ...data }; let current = newData; + // Navigate to the parent object/array for (let i = 1; i < pathParts.length; i++) { - if (i === pathParts.length - 1) { - delete current[key]; - } else { - current = current[pathParts[i]]; - } + current = current[pathParts[i]]; } - if (pathParts.length === 1) { - delete newData[key]; + // Delete the property/item from the parent + if (Array.isArray(current)) { + // For arrays, remove by index and reindex + current.splice(parseInt(key), 1); + } else { + // For objects, delete the property + delete current[key]; } + console.log('ποΈ REMOVE PROPERTY - After:', { + parentPath, + remainingKeys: Array.isArray(current) ? current.length : Object.keys(current) + }); + updateData(newData); }; diff --git a/src/pages/ObjectEditor.js b/src/pages/ObjectEditor.js index fa50c1c5..9e13eaed 100644 --- a/src/pages/ObjectEditor.js +++ b/src/pages/ObjectEditor.js @@ -1,5 +1,5 @@ import React, { useState, useRef, useCallback } from 'react'; -import { Edit3, Upload, FileText, Map, Table } from 'lucide-react'; +import { Edit3, Upload, FileText, Map, Table, Globe } from 'lucide-react'; import ToolLayout from '../components/ToolLayout'; import CopyButton from '../components/CopyButton'; import StructuredEditor from '../components/StructuredEditor'; @@ -21,6 +21,9 @@ const ObjectEditor = () => { jsonMinified: '', serialized: '' }); + const [showFetch, setShowFetch] = useState(false); + const [fetchUrl, setFetchUrl] = useState(''); + const [fetching, setFetching] = useState(false); const fileInputRef = useRef(null); // PHP serialize implementation (reused from SerializeTool) @@ -337,6 +340,67 @@ const ObjectEditor = () => { generateOutputs(sample); }; + // Fetch data from URL + const handleFetchData = async () => { + if (!fetchUrl.trim()) { + setError('Please enter a valid URL'); + return; + } + + setFetching(true); + setError(''); + + try { + // Add protocol if missing + let url = fetchUrl.trim(); + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'https://' + url; + } + + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + // Try to parse as JSON anyway, some APIs don't set correct content-type + const text = await response.text(); + try { + const data = JSON.parse(text); + setStructuredData(data); + generateOutputs(data); + setInputText(JSON.stringify(data, null, 2)); + setInputFormat('JSON'); + setInputValid(true); + setShowInput(false); // Hide input on successful fetch + setShowFetch(false); + } catch { + throw new Error('Response is not valid JSON. Content-Type: ' + (contentType || 'unknown')); + } + } else { + const data = await response.json(); + setStructuredData(data); + generateOutputs(data); + setInputText(JSON.stringify(data, null, 2)); + setInputFormat('JSON'); + setInputValid(true); + setShowInput(false); // Hide input on successful fetch + setShowFetch(false); + } + } catch (err) { + console.error('Fetch error:', err); + if (err.name === 'TypeError' && err.message.includes('fetch')) { + setError('Network error: Unable to fetch data. Check the URL and try again.'); + } else { + setError(`Fetch failed: ${err.message}`); + } + } finally { + setFetching(false); + } + }; + // Initialize outputs when component mounts or data changes React.useEffect(() => { generateOutputs(structuredData); @@ -374,6 +438,14 @@ const ObjectEditor = () => { Load Sample + + { /> - {/* View Mode Toggle */} -
+ Enter any URL that returns JSON data. Examples: Telegram Bot API, JSONPlaceholder, GitHub API, etc. +
+