Compare commits
3 Commits
e1c74e4a4e
...
cf750114f7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf750114f7 | ||
|
|
ece5ffc63f | ||
|
|
f2163c9814 |
@@ -1,9 +1,8 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronLeft, Braces, List, Type, Hash, ToggleLeft, Minus } from 'lucide-react';
|
||||
|
||||
const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
const [currentPath, setCurrentPath] = useState([]);
|
||||
const [selectedRowIndex, setSelectedRowIndex] = useState(null);
|
||||
|
||||
// Get current data based on path
|
||||
const getCurrentData = () => {
|
||||
@@ -48,7 +47,6 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
// Handle row click - navigate to item details
|
||||
const handleRowClick = (index, key = null) => {
|
||||
if (isArrayView) {
|
||||
setSelectedRowIndex(index);
|
||||
setCurrentPath([...currentPath, index]);
|
||||
} else if (isObjectView && key) {
|
||||
// Navigate into object property
|
||||
@@ -61,7 +59,6 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
if (currentPath.length > 0) {
|
||||
const newPath = currentPath.slice(0, -1);
|
||||
setCurrentPath(newPath);
|
||||
setSelectedRowIndex(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,11 +69,9 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
setCurrentPath([]);
|
||||
} else {
|
||||
// Click on specific breadcrumb - navigate to that level
|
||||
// Adjust for the "Root" prefix in breadcrumb
|
||||
const newPath = currentPath.slice(0, index);
|
||||
setCurrentPath(newPath);
|
||||
}
|
||||
setSelectedRowIndex(null);
|
||||
};
|
||||
|
||||
// Generate breadcrumb
|
||||
@@ -158,10 +153,6 @@ const PostmanTable = ({ data, title = "JSON Data" }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Check if value is a complex object (not a primitive)
|
||||
const isComplexValue = (value) => {
|
||||
return value !== null && typeof value === 'object';
|
||||
};
|
||||
|
||||
// Render value with appropriate styling
|
||||
const renderValue = (value) => {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
<span>Load Sample</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowFetch(!showFetch)}
|
||||
className="flex items-center space-x-2 tool-button-secondary"
|
||||
>
|
||||
<Globe className="h-4 w-4" />
|
||||
<span>{showFetch ? 'Hide Fetch' : 'Fetch Data'}</span>
|
||||
</button>
|
||||
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -383,8 +455,42 @@ const ObjectEditor = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* View Mode Toggle */}
|
||||
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
|
||||
{/* Fetch URL Input */}
|
||||
{showFetch && (
|
||||
<div className="mb-6 space-y-3">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Fetch Data from URL
|
||||
</label>
|
||||
<div className="flex space-x-3">
|
||||
<input
|
||||
type="text"
|
||||
value={fetchUrl}
|
||||
onChange={(e) => setFetchUrl(e.target.value)}
|
||||
placeholder="https://api.telegram.org/bot<token>/getMe"
|
||||
className="tool-input flex-1"
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleFetchData()}
|
||||
/>
|
||||
<button
|
||||
onClick={handleFetchData}
|
||||
disabled={fetching || !fetchUrl.trim()}
|
||||
className={`flex items-center space-x-2 px-4 py-2 rounded-md font-medium transition-colors ${
|
||||
fetching || !fetchUrl.trim()
|
||||
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
||||
: 'bg-primary-600 text-white hover:bg-primary-700'
|
||||
}`}
|
||||
>
|
||||
<Globe className="h-4 w-4" />
|
||||
<span>{fetching ? 'Fetching...' : 'Fetch'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Enter any URL that returns JSON data. Examples: Telegram Bot API, JSONPlaceholder, GitHub API, etc.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* View Mode Toggle - Desktop Tabs */}
|
||||
<div className="hidden sm:flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
|
||||
<button
|
||||
onClick={() => setViewMode('visual')}
|
||||
className={`flex items-center space-x-2 px-4 py-2 rounded-md font-medium transition-colors ${
|
||||
@@ -420,6 +526,22 @@ const ObjectEditor = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* View Mode Toggle - Mobile Select */}
|
||||
<div className="sm:hidden mb-6">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
View Mode
|
||||
</label>
|
||||
<select
|
||||
value={viewMode}
|
||||
onChange={(e) => setViewMode(e.target.value)}
|
||||
className="tool-input w-full"
|
||||
>
|
||||
<option value="visual">📝 Visual Editor</option>
|
||||
<option value="mindmap">🗺️ Mindmap View</option>
|
||||
<option value="table">📊 Table View</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Input Section */}
|
||||
{showInput && (
|
||||
<div className="mb-6 space-y-2">
|
||||
@@ -460,11 +582,13 @@ const ObjectEditor = () => {
|
||||
</h3>
|
||||
|
||||
{viewMode === 'visual' && (
|
||||
<div className="min-h-96 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<StructuredEditor
|
||||
initialData={structuredData}
|
||||
onDataChange={handleStructuredDataChange}
|
||||
/>
|
||||
<div className="min-h-96 border border-gray-200 dark:border-gray-700 rounded-lg overflow-x-auto">
|
||||
<div className="min-w-max">
|
||||
<StructuredEditor
|
||||
initialData={structuredData}
|
||||
onDataChange={handleStructuredDataChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -554,6 +678,7 @@ const ObjectEditor = () => {
|
||||
<li>• <strong>Table View:</strong> Browse data like Postman - click arrays for horizontal tables, objects for key-value pairs</li>
|
||||
<li>• <strong>Navigation:</strong> Use breadcrumbs and Back button to navigate through nested data structures</li>
|
||||
<li>• <strong>Input Data:</strong> Paste JSON/PHP serialized data with auto-detection in the input field</li>
|
||||
<li>• <strong>Fetch Data:</strong> Load JSON from any URL - perfect for APIs like Telegram Bot, GitHub, JSONPlaceholder</li>
|
||||
<li>• Import data from files or use the sample data to get started</li>
|
||||
<li>• Toggle output formats visibility with the "Show/Hide Output Formats" button</li>
|
||||
<li>• Export your data in any format: JSON pretty, minified, or PHP serialized</li>
|
||||
|
||||
Reference in New Issue
Block a user