diff --git a/src/App.js b/src/App.js
index 1cc3178c..8354e191 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,7 +9,7 @@ import Base64Tool from './pages/Base64Tool';
import CsvJsonTool from './pages/CsvJsonTool';
import BeautifierTool from './pages/BeautifierTool';
import DiffTool from './pages/DiffTool';
-import HtmlPreviewTool from './pages/HtmlPreviewTool';
+
import './index.css';
function App() {
@@ -25,7 +25,7 @@ function App() {
} />
} />
} />
- } />
+
diff --git a/src/components/Layout.js b/src/components/Layout.js
index 199ed2d2..20daef56 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, FileText, Key, Palette, QrCode, FileSpreadsheet, Wand2, GitCompare, Menu, X, Database, LinkIcon, Code2, ChevronDown } from 'lucide-react';
+import { Home, Hash, FileText, FileSpreadsheet, Wand2, GitCompare, Menu, X, Database, LinkIcon, Code2, ChevronDown } from 'lucide-react';
import ThemeToggle from './ThemeToggle';
const Layout = ({ children }) => {
diff --git a/src/pages/HtmlPreviewTool.js b/src/pages/HtmlPreviewTool.js
deleted file mode 100644
index 742ff58d..00000000
--- a/src/pages/HtmlPreviewTool.js
+++ /dev/null
@@ -1,451 +0,0 @@
-import React, { useState, useEffect, useCallback, useRef } from 'react';
-import ToolLayout from '../components/ToolLayout';
-import PreviewFrame from './components/PreviewFrame.fresh';
-import Toolbar from './components/Toolbar';
-import CodeInputs from './components/CodeInputs';
-import InspectorSidebar from './components/InspectorSidebar';
-import ElementEditor from './components/ElementEditor';
-import '../styles/device-frames.css';
-
-const HtmlPreviewTool = () => {
- const [htmlInput, setHtmlInput] = useState('');
- const [cssInput, setCssInput] = useState('');
- const [jsInput, setJsInput] = useState('');
- const [selectedDevice, setSelectedDevice] = useState('mobile');
- const [inspectMode, setInspectMode] = useState(false);
- const [inspectedElementInfo, setInspectedElementInfo] = useState(null);
- const [isFullscreen, setIsFullscreen] = useState(false);
- const [showSidebar, setShowSidebar] = useState(true);
- const [forceRender, setForceRender] = useState(0);
-
- // Separate inspector state to prevent iframe updates during inspector operations
- const [inspectorHtmlState, setInspectorHtmlState] = useState('');
- const [isInspectorActive, setIsInspectorActive] = useState(false);
-
- // ENHANCED OPTION A: PreviewFrame API reference
- const previewFrameRef = useRef(null);
-
- // Debug: Monitor inspectedElementInfo changes and force re-render
- useEffect(() => {
- console.log('๐ STATE CHANGE: inspectedElementInfo updated to:', inspectedElementInfo);
- if (inspectedElementInfo) {
- console.log('๐ FORCING COMPONENT RE-RENDER for inspector sidebar');
- // Force a re-render by updating a dummy state
- setForceRender(prev => prev + 1);
- }
- }, [inspectedElementInfo, forceRender]);
-
- const handleElementClick = useCallback((elementInfo) => {
- console.log('๐ ENHANCED ELEMENT CLICK:', elementInfo);
- if (elementInfo) {
- console.log('โ
ENHANCED INSPECTOR: Activating with cascade-id:', elementInfo.cascadeId);
- setInspectedElementInfo(elementInfo);
- setIsInspectorActive(true);
- console.log('๐ฏ ENHANCED INSPECTOR: Sidebar activated, iframe DOM is source of truth');
-
- // Debug: Force re-render check
- setTimeout(() => {
- console.log('๐ POST-SET DEBUG: inspectedElementInfo should now be:', elementInfo);
- setForceRender(prev => prev + 1); // Force re-render after state is set
- }, 10);
- }
- }, []);
-
- const cleanupInspectorState = useCallback(() => {
- console.log('๐งน ENHANCED OPTION A: Cleaning up inspector state without triggering iframe refresh');
- console.log('๐จ DEBUG: cleanupInspectorState called - clearing inspectedElementInfo');
- console.trace('๐ STACK TRACE: cleanupInspectorState called from:');
- setInspectedElementInfo(null);
- setInspectMode(false);
-
- // ENHANCED OPTION A: Don't call setHtmlInput during cleanup
- // The iframe DOM cleanup will be handled by PreviewFrame directly
- // Only clean up React state, not HTML input
- console.log('โ
ENHANCED CLEANUP: Inspector state cleared without iframe refresh');
- }, []);
-
- // ESC key handler to deactivate inspect mode
- useEffect(() => {
- const handleKeyDown = (event) => {
- if (event.key === 'Escape' && inspectMode) {
- console.log('โจ๏ธ ESC key pressed - deactivating inspect mode');
- cleanupInspectorState();
- }
- };
-
- // Add event listener
- document.addEventListener('keydown', handleKeyDown);
-
- // Cleanup
- return () => {
- document.removeEventListener('keydown', handleKeyDown);
- };
- }, [inspectMode, cleanupInspectorState]);
-
- useEffect(() => {
- // ENHANCED OPTION A: Skip cascade ID injection during inspector operations
- if (inspectedElementInfo) {
- console.log('๐ซ ENHANCED OPTION A: Skipping cascade ID injection during inspector operations');
- return;
- }
- if (!htmlInput.trim()) return;
-
- const isFullDocument = htmlInput.trim().toLowerCase().includes(' {
- const idNum = parseInt(el.getAttribute('data-cascade-id').split('-')[1], 10);
- if (!isNaN(idNum) && idNum >= idCounter) {
- idCounter = idNum + 1;
- }
- });
-
- // Add cascade IDs to elements that don't have them
- doc.querySelectorAll('*').forEach(el => {
- if (!el.hasAttribute('data-cascade-id') &&
- el.tagName.toLowerCase() !== 'body' &&
- el.tagName.toLowerCase() !== 'html' &&
- el.tagName.toLowerCase() !== 'head' &&
- el.tagName.toLowerCase() !== 'title' &&
- el.tagName.toLowerCase() !== 'meta' &&
- el.tagName.toLowerCase() !== 'link' &&
- el.tagName.toLowerCase() !== 'style' &&
- el.tagName.toLowerCase() !== 'script') {
- el.setAttribute('data-cascade-id', `cascade-${idCounter++}`);
- modified = true;
- }
- });
-
- if (modified) {
- const newHtml = doc.documentElement.outerHTML;
- console.log('โ
Full document processed, cascade IDs added');
- console.log('๐จ CASCADE ID INJECTION: About to call setHtmlInput (POTENTIAL IFRAME REFRESH TRIGGER)');
- setHtmlInput(newHtml);
- }
- } else {
- // It's a fragment, process normally
- const parser = new DOMParser();
- const doc = parser.parseFromString(htmlInput, 'text/html');
- let modified = false;
- let idCounter = 0;
-
- doc.body.querySelectorAll('[data-cascade-id]').forEach(el => {
- const idNum = parseInt(el.getAttribute('data-cascade-id').split('-')[1], 10);
- if (!isNaN(idNum) && idNum >= idCounter) {
- idCounter = idNum + 1;
- }
- });
-
- doc.body.querySelectorAll('*').forEach(el => {
- if (!el.hasAttribute('data-cascade-id') && el.tagName.toLowerCase() !== 'body' && el.tagName.toLowerCase() !== 'html' && el.tagName.toLowerCase() !== 'head') {
- el.setAttribute('data-cascade-id', `cascade-${idCounter++}`);
- modified = true;
- }
- });
-
- if (modified) {
- const newHtml = doc.body.innerHTML;
- console.log('โ
Fragment processed, cascade IDs added');
- console.log('๐จ CASCADE ID INJECTION: About to call setHtmlInput (POTENTIAL IFRAME REFRESH TRIGGER)');
- setHtmlInput(newHtml);
- }
- }
- }, [htmlInput]);
-
- const createDuplicateInCodeBox = useCallback((elementInfo) => {
- const cascadeId = elementInfo.attributes['data-cascade-id'];
- if (!cascadeId) {
- console.error('โ Cannot create duplicate: Element is missing data-cascade-id.');
- return false;
- }
-
- // Use stored iframe DOM that contains the cascade-id, fallback to inspector state
- const currentHtml = window.currentIframeDom || inspectorHtmlState || htmlInput;
- console.log('๐ฏ INSPECTOR: Creating duplicates using iframe DOM with cascade-id');
-
- if (window.currentIframeDom) {
- console.log('โ
Using current iframe DOM with cascade-id');
- } else {
- console.log('โ ๏ธ Fallback to inspector state (cascade-id may be missing)');
- }
-
- const processHtml = (currentHtml) => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(currentHtml, 'text/html');
- const originalElement = doc.querySelector(`[data-cascade-id="${cascadeId}"]`);
-
- if (!originalElement) {
- console.error(`โ Could not find element with ${cascadeId} in HTML.`);
- return currentHtml;
- }
-
- const hiddenElement = originalElement.cloneNode(true);
- hiddenElement.setAttribute('data-original', 'true');
- hiddenElement.style.display = 'none';
-
- const visibleElement = originalElement.cloneNode(true);
- visibleElement.setAttribute('data-original', 'false');
-
- originalElement.parentNode.insertBefore(hiddenElement, originalElement);
- originalElement.parentNode.insertBefore(visibleElement, originalElement);
- originalElement.remove();
-
- console.log(`โ
Successfully created duplicates for ${cascadeId}`);
-
- // Preserve the original HTML structure (full document vs fragment)
- const isFragment = !currentHtml.trim().toLowerCase().startsWith(' {
- // Refresh is handled by PreviewFrame component
- console.log('๐ Refreshing preview...');
- }, []);
-
- const toggleFullscreen = useCallback((targetDevice = null) => {
- setIsFullscreen(prev => {
- const newFullscreen = !prev;
-
- // When exiting fullscreen (going to non-fullscreen), always switch to mobile
- if (!newFullscreen) {
- setSelectedDevice('mobile');
- console.log('๐ฑ Exiting fullscreen: Switched to mobile view');
- }
-
- return newFullscreen;
- });
-
- if (targetDevice) {
- setSelectedDevice(targetDevice);
- }
- }, []);
-
- const toggleSidebar = useCallback(() => {
- setShowSidebar(prev => !prev);
- }, []);
-
- // ENHANCED OPTION A: Commit iframe DOM changes to HTML input using new API
- const saveInspectorChanges = useCallback(() => {
- console.log('๐พ ENHANCED COMMIT: Using PreviewFrame API to commit changes');
-
- if (!previewFrameRef.current) {
- console.error('โ COMMIT FAILED: PreviewFrame ref not available');
- return;
- }
-
- try {
- // Use Enhanced Option A API to get iframe DOM content
- const committedHtml = previewFrameRef.current.getIframeContent();
-
- if (committedHtml) {
- // ENHANCED OPTION A: Update HTML input with committed changes
- // This is an EXPLICIT SAVE operation, so iframe refresh is expected and correct
- setHtmlInput(committedHtml);
- console.log('โ
ENHANCED COMMIT: Changes committed successfully');
- console.log('๐ COMMIT: HTML updated with iframe DOM content');
-
- // Close inspector and reset state
- console.log('๐จ DEBUG: saveInspectorChanges called - clearing inspectedElementInfo');
- setInspectedElementInfo(null);
- setInspectMode(false);
- setIsInspectorActive(false);
-
- console.log('๐ ENHANCED COMMIT: Inspector closed, iframe will refresh with new content');
- } else {
- console.error('โ ENHANCED COMMIT: Failed to extract iframe DOM content');
- }
- } catch (error) {
- console.error('โ ENHANCED COMMIT ERROR:', error);
- }
- }, [previewFrameRef]);
-
- // ENHANCED OPTION A: Close inspector and reset state
- const closeInspector = useCallback(() => {
- console.log('โ ENHANCED CLOSE: Closing inspector and resetting state');
-
- // Reset all inspector state
- console.log('๐จ DEBUG: closeInspector called - clearing inspectedElementInfo');
- setInspectedElementInfo(null);
- setInspectMode(false);
- setIsInspectorActive(false);
- setInspectorHtmlState('');
-
- // Use PreviewFrame API to cancel changes if available
- if (previewFrameRef?.current?.cancelChanges) {
- previewFrameRef.current.cancelChanges();
- }
-
- console.log('โ
ENHANCED CLOSE: Inspector closed, iframe DOM reset');
- }, [previewFrameRef]);
-
- const closeInspectorLegacy = useCallback(() => {
- // ENHANCED OPTION A: Close inspector and clear active flag (legacy)
- setIsInspectorActive(false);
- setInspectorHtmlState('');
- window.isInspectorActive = false;
- console.log('โ INSPECTOR CLOSED: Iframe refreshes re-enabled');
- cleanupInspectorState();
- }, [cleanupInspectorState]);
-
- if (isFullscreen) {
- return (
-
- {/* Main content area */}
-
- {/* Left sidebar - Code inputs */}
- {showSidebar && (
-
-
-
Code Editor
-
-
-
-
-
- )}
-
- {/* Center - Preview */}
-
-
- {/* Inspector Sidebar */}
- {inspectedElementInfo && (
-
- )}
-
-
- {/* Bottom toolbar */}
-
-
- );
- }
-
- return (
-
-
- {/* Left column - Code inputs */}
-
-
-
-
- {/* Middle column - Preview */}
-
-
- {/* ENHANCED OPTION A: Inspector Sidebar */}
- {inspectedElementInfo && (
-
-
-
- )}
-
-
- {/* Bottom toolbar */}
-
-
-
-
- );
-};
-
-export default HtmlPreviewTool;
diff --git a/src/pages/HtmlPreviewTool.js.backup b/src/pages/HtmlPreviewTool.js.backup
deleted file mode 100644
index 3d802ed1..00000000
--- a/src/pages/HtmlPreviewTool.js.backup
+++ /dev/null
@@ -1,461 +0,0 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import ToolLayout from '../components/ToolLayout';
-import PreviewFrame from './components/PreviewFrame';
-import Toolbar from './components/Toolbar';
-import CodeInputs from './components/CodeInputs';
-import InspectorSidebar from './components/InspectorSidebar';
-import '../styles/device-frames.css';
-
-const HtmlPreviewTool = () => {
- const [htmlInput, setHtmlInput] = useState('');
- const [cssInput, setCssInput] = useState('');
- const [jsInput, setJsInput] = useState('');
- const [selectedDevice, setSelectedDevice] = useState('mobile');
- const [inspectMode, setInspectMode] = useState(false);
- const [inspectedElementInfo, setInspectedElementInfo] = useState(null);
- const [isFullscreen, setIsFullscreen] = useState(false);
- const [showSidebar, setShowSidebar] = useState(true);
-
- const cleanupInspectorState = useCallback(() => {
- console.log('๐งน Cleaning up inspector state and data-original attributes');
- setInspectedElementInfo(null);
- setInspectMode(false);
-
- setHtmlInput(currentHtml => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(currentHtml, 'text/html');
- let modified = false;
-
- // Remove hidden backup elements
- doc.querySelectorAll('[data-original="true"]').forEach(el => {
- el.remove();
- modified = true;
- });
-
- // Clean attributes from visible elements
- doc.querySelectorAll('[data-original="false"]').forEach(el => {
- el.removeAttribute('data-original');
- modified = true;
- });
-
- if (modified) {
- const newHtml = doc.body.innerHTML;
- console.log('๐งน Cleaned HTML from', currentHtml.length, 'to', newHtml.length, 'chars');
- return newHtml;
- }
- return currentHtml;
- });
- }, []);
-
- useEffect(() => {
- if (inspectedElementInfo) return;
-
- const parser = new DOMParser();
- const doc = parser.parseFromString(htmlInput, 'text/html');
- let modified = false;
- let idCounter = 0;
-
- doc.body.querySelectorAll('[data-cascade-id]').forEach(el => {
- const idNum = parseInt(el.getAttribute('data-cascade-id').split('-')[1], 10);
- if (!isNaN(idNum) && idNum >= idCounter) {
- idCounter = idNum + 1;
- }
- });
-
- doc.body.querySelectorAll('*').forEach(el => {
- if (!el.hasAttribute('data-cascade-id') && el.tagName.toLowerCase() !== 'body' && el.tagName.toLowerCase() !== 'html' && el.tagName.toLowerCase() !== 'head') {
- el.setAttribute('data-cascade-id', `cascade-${idCounter++}`);
- modified = true;
- }
- });
-
- if (modified) {
- const isFragment = !htmlInput.trim().toLowerCase().startsWith(' {
- const cascadeId = elementInfo.attributes['data-cascade-id'];
- if (!cascadeId) {
- console.error('โ Cannot create duplicate: Element is missing data-cascade-id.');
- return false;
- }
-
- setHtmlInput(currentHtml => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(currentHtml, 'text/html');
- const originalElement = doc.querySelector(`[data-cascade-id="${cascadeId}"]`);
-
- if (!originalElement) {
- console.error(`โ Could not find element with ${cascadeId} in HTML.`);
- return currentHtml;
- }
-
- const hiddenElement = originalElement.cloneNode(true);
- hiddenElement.setAttribute('data-original', 'true');
- hiddenElement.style.display = 'none';
-
- const visibleElement = originalElement.cloneNode(true);
- visibleElement.setAttribute('data-original', 'false');
-
- originalElement.parentNode.insertBefore(hiddenElement, originalElement);
- originalElement.parentNode.insertBefore(visibleElement, originalElement);
-
- console.log(`โ
Successfully created duplicates for ${cascadeId}`);
- return doc.body.innerHTML;
- });
- return true;
- }, []);
-
- const handleElementClick = useCallback((elementInfo) => {
- console.log('๐ Element selected for inspection:', elementInfo);
-
- if (createDuplicateInCodeBox(elementInfo)) {
- setInspectedElementInfo(elementInfo);
- setInspectMode(false);
- }
- }, [createDuplicateInCodeBox]);
- doc.removeEventListener('click', handleIframeClick, true);
- };
- }
- }
- }, [htmlInput, cssInput, jsInput, inspectMode, createDuplicateInCodeBox, cleanupInspectorState]);
-
- const handleRefresh = useCallback(() => {
- const handleRefresh = () => {
- setHtmlInput(prev => prev);
- setCssInput(prev => prev);
- setJsInput(prev => prev);
- };
-
- const toggleFullscreen = (targetDevice = null) => {
- setIsFullscreen(!isFullscreen);
- if (!isFullscreen) {
- setSidebarCollapsed(false);
- if (targetDevice) {
- setSelectedDevice(targetDevice);
- }
- }
- };
-
- const toggleSidebar = () => {
- setSidebarCollapsed(!sidebarCollapsed);
- };
-
- return (
-
- {/* Left Sidebar - Code Input */}
-
-
-
-
-
-
-
-
-
- {showCss && (
-
-
-
-
- )}
- {showJs && (
-
-
-
-
- )}
-
-
- {/* Preview Area */}
-
- {!isFullscreen && (
-
-
Preview
-
- )}
-
-
- {/* Preview Tools - Bottom Toolbar */}
-
-
- {isFullscreen && (
-
- )}
-
-
-
- {isFullscreen && (
-
- )}
- {Object.entries(devices).map(([key, { icon: DeviceIcon }]) => (
-
- ))}
-
-
-
-
-
-
-
- {isFullscreen && inspectedElementInfo && (
-
- )}
-
- }
- />
- );
-};
-
-// ##################################################################################
-// # ELEMENT EDITOR COMPONENT (REWRITTEN BASED ON USER'S EXACT LOGIC)
-// ##################################################################################
-const ElementEditor = ({ htmlInput, setHtmlInput, onClose }) => {
- const [edited, setEdited] = useState(null);
-
- // On mount, parse the element being edited (the one with data-original="false")
- useEffect(() => {
- const editableElementRegex = /<([a-zA-Z0-9]+)((?:\s+[\w-]+(?:="[^"]*")?)*?)\s+data-original="false"((?:\s+[\w-]+(?:="[^"]*")?)*?)>(.*?)<\/\1>|<([a-zA-Z0-9]+)((?:\s+[\w-]+(?:="[^"]*")?)*?)\s+data-original="false"((?:\s+[\w-]+(?:="[^"]*")?)*?)\/>/s;
- const match = htmlInput.match(editableElementRegex);
-
- if (match) {
- const isSelfClosing = !!match[5];
- const tagName = isSelfClosing ? match[5] : match[1];
- const allAttributesString = (isSelfClosing ? (match[6] || '') + (match[7] || '') : (match[2] || '') + (match[3] || ''));
- const innerHTML = isSelfClosing ? '' : match[4] || '';
-
- const attributes = {};
- const attrRegex = /([\w-]+)(?:="([^"]*)")?/g;
- let attrMatch;
- while ((attrMatch = attrRegex.exec(allAttributesString)) !== null) {
- attributes[attrMatch[1]] = attrMatch[2] === undefined ? true : attrMatch[2];
- }
-
- const tempDiv = document.createElement('div');
- tempDiv.innerHTML = innerHTML;
-
- const initialState = {
- tagName: tagName,
- id: attributes.id || '',
- className: attributes.class || '',
- innerText: tempDiv.textContent || '',
- ...Object.fromEntries(Object.entries(attributes).filter(([key]) => key !== 'id' && key !== 'class')),
- };
- setEdited(initialState);
- }
- }, [htmlInput]);
-
- // 3.A: On field change, update the element with data-original="false" in the code box
- const handleFieldChange = (field, value) => {
- const newEditedState = { ...edited, [field]: value };
- setEdited(newEditedState);
-
- setHtmlInput(currentHtml => {
- const newElementHtml = buildElementHtml(newEditedState);
- const editableElementRegex = /<[^>]+data-original="false"[^>]*>.*?<\/[^>]+>|<[^>]+data-original="false"[^>]*\/>/s;
-
- if (!editableElementRegex.test(currentHtml)) {
- console.error("Live Update Error: Cannot find element with data-original='false' to replace.");
- return currentHtml;
- }
-
- return currentHtml.replace(editableElementRegex, newElementHtml);
- });
- };
-
- const buildElementHtml = (state) => {
- const { tagName, id, className, innerText, ...otherAttrs } = state;
- const isSelfClosing = ['img', 'input', 'br', 'hr', 'meta', 'link'].includes(tagName.toLowerCase());
-
- let attrs = 'data-original="false"';
- if (id) attrs += ` id="${id}"`;
- if (className) attrs += ` class="${className}"`;
-
- for (const [key, value] of Object.entries(otherAttrs)) {
- if (value === true) {
- attrs += ` ${key}`;
- } else if (value) {
- attrs += ` ${key}="${value}"`;
- }
- }
-
- if (isSelfClosing) {
- return `<${tagName} ${attrs.trim()} />`;
- } else {
- const tempDiv = document.createElement('div');
- tempDiv.innerText = innerText || '';
- return `<${tagName} ${attrs.trim()}>${tempDiv.innerHTML}${tagName}>`;
- }
- };
-
- // 4. On Save, finalize the changes
- const handleSave = () => {
- setHtmlInput(currentHtml => {
- const pairRegex = /(<[^>]+data-original="true"[^>]*style="display:none;"[^>]*>.*?<\/[^>]+>|<[^>]+data-original="true"[^>]*style="display:none;"[^>]*\/>)\s*(<[^>]+data-original="false"[^>]*>.*?<\/[^>]+>|<[^>]+data-original="false"[^>]*\/>)/s;
- const pairMatch = currentHtml.match(pairRegex);
-
- if (!pairMatch) {
- console.error("Save Error: Could not find the hidden/visible element pair.");
- return currentHtml;
- }
-
- const visibleElement = pairMatch[2];
- const savedElement = visibleElement.replace(/\s*data-original="false"\s*/, ' ').replace(/\s{2,}/g, ' ');
- return currentHtml.replace(pairRegex, savedElement);
- });
- onClose();
- };
-
- // 5. On Cancel, revert the changes
- const handleCancel = () => {
- setHtmlInput(currentHtml => {
- const pairRegex = /(<[^>]+data-original="true"[^>]*style="display:none;"[^>]*>.*?<\/[^>]+>|<[^>]+data-original="true"[^>]*style="display:none;"[^>]*\/>)\s*(<[^>]+data-original="false"[^>]*>.*?<\/[^>]+>|<[^>]+data-original="false"[^>]*\/>)/s;
- const pairMatch = currentHtml.match(pairRegex);
-
- if (!pairMatch) {
- console.error("Cancel Error: Could not find the hidden/visible element pair.");
- return currentHtml;
- }
-
- const hiddenElement = pairMatch[1];
- const unhiddenElement = hiddenElement
- .replace(/\s*data-original="true"\s*/, ' ')
- .replace(/\s*style="display:none;"\s*/, ' ')
- .replace(/\s{2,}/g, ' ');
-
- return currentHtml.replace(pairRegex, unhiddenElement);
- });
- onClose();
- };
-
- if (!edited) return Loading editor...
;
-
- const otherAttributes = Object.keys(edited).filter(
- key => key !== 'tagName' && key !== 'id' && key !== 'className' && key !== 'innerText'
- );
-
- return (
-
- {['tagName', 'id', 'className'].map(field => (
-
-
- handleFieldChange(field, e.target.value)}
- className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm"
- />
-
- ))}
-
-
-
- {otherAttributes.map(attr => (
-
-
- handleFieldChange(attr, e.target.value)}
- className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm"
- />
-
- ))}
-
-
-
-
-
- );
-};
-
-export default HtmlPreviewTool;
diff --git a/src/pages/components/CodeInputs.js b/src/pages/components/CodeInputs.js
deleted file mode 100644
index 821c091a..00000000
--- a/src/pages/components/CodeInputs.js
+++ /dev/null
@@ -1,213 +0,0 @@
-import React, { useState, useRef } from 'react';
-import { Search, Copy, Download } from 'lucide-react';
-import { Controlled as CodeMirror } from 'react-codemirror2';
-import 'codemirror/lib/codemirror.css';
-import 'codemirror/theme/material.css';
-import 'codemirror/mode/xml/xml';
-import 'codemirror/mode/css/css';
-import 'codemirror/mode/javascript/javascript';
-import 'codemirror/addon/search/search';
-import 'codemirror/addon/search/searchcursor';
-import 'codemirror/addon/dialog/dialog';
-import 'codemirror/addon/dialog/dialog.css';
-
-const CodeInputs = ({
- htmlInput,
- setHtmlInput,
- cssInput,
- setCssInput,
- jsInput,
- setJsInput,
- isFullscreen
-}) => {
- const [activeTab, setActiveTab] = useState('html');
- const htmlEditorRef = useRef(null);
- const cssEditorRef = useRef(null);
- const jsEditorRef = useRef(null);
-
- // Handle search functionality
- const handleSearch = (editorRef) => {
- if (editorRef.current && editorRef.current.editor) {
- editorRef.current.editor.execCommand('find');
- }
- };
-
- // Handle copy functionality
- const handleCopy = async (content) => {
- try {
- await navigator.clipboard.writeText(content);
- } catch (err) {
- console.error('Failed to copy:', err);
- }
- };
-
- // Handle export functionality
- const handleExport = (content, filename) => {
- const blob = new Blob([content], { type: 'text/plain' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- };
-
- // Get current editor ref based on active tab
- const getCurrentEditorRef = () => {
- switch (activeTab) {
- case 'html': return htmlEditorRef;
- case 'css': return cssEditorRef;
- case 'js': return jsEditorRef;
- default: return htmlEditorRef;
- }
- };
-
- // Get current content based on active tab
- const getCurrentContent = () => {
- switch (activeTab) {
- case 'html': return htmlInput;
- case 'css': return cssInput;
- case 'js': return jsInput;
- default: return htmlInput;
- }
- };
-
- // Get filename for export based on active tab
- const getExportFilename = () => {
- switch (activeTab) {
- case 'html': return 'code.html';
- case 'css': return 'styles.css';
- case 'js': return 'script.js';
- default: return 'code.txt';
- }
- };
-
- return (
-
- {/* Tab Navigation */}
-
- {[
- { id: 'html', label: 'HTML' },
- { id: 'css', label: 'CSS' },
- { id: 'js', label: 'JavaScript' }
- ].map((tab) => (
-
- ))}
-
-
- {/* Action buttons above editor */}
-
-
-
-
-
-
- {/* Code Editor */}
-
- {activeTab === 'html' && (
- setHtmlInput(value)}
- options={{
- mode: 'xml',
- theme: 'material',
- lineNumbers: true,
- lineWrapping: true,
- autoCloseTags: true,
- matchBrackets: true,
- indentUnit: 2,
- tabSize: 2,
- extraKeys: {
- 'Ctrl-F': 'findPersistent',
- 'Cmd-F': 'findPersistent'
- }
- }}
- className="h-full"
- />
- )}
-
- {activeTab === 'css' && (
- setCssInput(value)}
- options={{
- mode: 'css',
- theme: 'material',
- lineNumbers: true,
- lineWrapping: true,
- autoCloseBrackets: true,
- matchBrackets: true,
- indentUnit: 2,
- tabSize: 2,
- extraKeys: {
- 'Ctrl-F': 'findPersistent',
- 'Cmd-F': 'findPersistent'
- }
- }}
- className="h-full"
- />
- )}
-
- {activeTab === 'js' && (
- setJsInput(value)}
- options={{
- mode: 'javascript',
- theme: 'material',
- lineNumbers: true,
- lineWrapping: true,
- autoCloseBrackets: true,
- matchBrackets: true,
- indentUnit: 2,
- tabSize: 2,
- extraKeys: {
- 'Ctrl-F': 'findPersistent',
- 'Cmd-F': 'findPersistent'
- }
- }}
- className="h-full"
- />
- )}
-
-
- );
-};
-
-export default CodeInputs;
diff --git a/src/pages/components/InspectorSidebar.js b/src/pages/components/InspectorSidebar.js
deleted file mode 100644
index bdcb3bf0..00000000
--- a/src/pages/components/InspectorSidebar.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import { X } from 'lucide-react';
-import ElementEditor from './ElementEditor';
-
-const InspectorSidebar = ({
- inspectedElementInfo,
- htmlInput,
- setHtmlInput,
- onClose,
- onSave,
- previewFrameRef
-}) => {
- if (!inspectedElementInfo) return null;
-
- return (
-
- {/* Header */}
-
-
- Inspector
-
-
-
-
- {/* Content */}
-
-
- {/* Element Info */}
-
-
- Selected Element
-
-
-
- <{inspectedElementInfo.tagName}>
-
-
-
-
- {/* Element Editor */}
-
-
- Edit Properties
-
-
-
-
-
-
- );
-};
-
-export default InspectorSidebar;
diff --git a/src/pages/components/PreviewFrame.fresh.js b/src/pages/components/PreviewFrame.fresh.js
deleted file mode 100644
index e68296e8..00000000
--- a/src/pages/components/PreviewFrame.fresh.js
+++ /dev/null
@@ -1,852 +0,0 @@
-import React, { useState, useEffect, useRef, useCallback, forwardRef, useImperativeHandle } from 'react';
-
-// Device Frame CSS - Converted from SCSS
-const deviceFrameCSS = `
- /* iPhone 14 Pro Device Frame */
- .device-iphone-14-pro {
- height: 780px;
- width: 384px;
- transform-origin: center;
- position: relative;
- margin: 0 auto;
- }
-
- .device-iphone-14-pro .device-frame {
- background: #010101;
- border: 1px solid #2a242f;
- border-radius: 61px;
- box-shadow: inset 0 0 4px 2px #a8a4b0, inset 0 0 0 5px #342C3F;
- height: 780px;
- padding: 17px;
- width: 384px;
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- .device-iphone-14-pro .device-screen {
- border-radius: 56px;
- height: 746px;
- width: 350px;
- overflow: hidden;
- scale: 0.75;
- min-width: 130%;
- height: 130%;
- }
-
- .device-iphone-14-pro .device-screen iframe {
- width: 130%; /* 100% / 0.75 = 133.33% to compensate for 0.75 scale */
- height: 130%;
- transform: scale(0.75);
- transform-origin: top left;
- }
-
- /* Mobile scrollbar styling for iPhone */
- .device-iphone-14-pro .device-screen::-webkit-scrollbar {
- width: 2px;
- }
-
- .device-iphone-14-pro .device-screen::-webkit-scrollbar-track {
- background: transparent;
- }
-
- .device-iphone-14-pro .device-screen::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.2);
- border-radius: 1px;
- }
-
- .device-iphone-14-pro .device-stripe::after,
- .device-iphone-14-pro .device-stripe::before {
- border: solid rgba(1, 1, 1, 0.25);
- border-width: 0 7px;
- content: "";
- height: 7px;
- left: 0;
- position: absolute;
- width: 100%;
- z-index: 9;
- }
-
- .device-iphone-14-pro .device-stripe::after {
- top: 77px;
- }
-
- .device-iphone-14-pro .device-stripe::before {
- bottom: 77px;
- }
-
- .device-iphone-14-pro .device-header {
- background: #010101;
- border-radius: 18px;
- height: 31px;
- left: 50%;
- margin-left: -54px;
- position: absolute;
- top: 32px;
- width: 108px;
- z-index: 10;
- }
-
- .device-iphone-14-pro .device-sensors::after,
- .device-iphone-14-pro .device-sensors::before {
- content: "";
- position: absolute;
- }
-
- .device-iphone-14-pro .device-sensors::after {
- background: #010101;
- border-radius: 16px;
- height: 30px;
- left: 50%;
- margin-left: -54px;
- top: 33px;
- width: 67px;
- z-index: 10;
- }
-
- .device-iphone-14-pro .device-sensors::before {
- background: radial-gradient(farthest-corner at 20% 20%, #6074BF 0, transparent 40%),
- radial-gradient(farthest-corner at 80% 80%, #513785 0, #24555E 20%, transparent 50%);
- box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.05);
- border-radius: 50%;
- height: 8px;
- left: 50%;
- margin-left: 24px;
- top: 44px;
- width: 8px;
- z-index: 10;
- }
-
- .device-iphone-14-pro .device-btns {
- background: #2a242f;
- border-radius: 1px;
- height: 24px;
- left: -2px;
- position: absolute;
- top: 86px;
- width: 2px;
- }
-
- .device-iphone-14-pro .device-btns::after,
- .device-iphone-14-pro .device-btns::before {
- background: #2a242f;
- border-radius: 1px;
- content: "";
- height: 46px;
- left: 0;
- position: absolute;
- }
-
- .device-iphone-14-pro .device-btns::after {
- top: 45px;
- }
-
- .device-iphone-14-pro .device-btns::before {
- top: 105px;
- }
-
- .device-iphone-14-pro .device-power {
- background: #2a242f;
- border-radius: 1px;
- height: 75px;
- right: -2px;
- position: absolute;
- top: 150px;
- width: 2px;
- }
-
- /* iPad Pro Device Frame */
- .device-ipad-pro {
- height: 840px;
- width: 600px;
- transform-origin: center;
- margin-top: 40px;
- position: relative;
- margin-left: auto;
- margin-right: auto;
- }
-
- .device-ipad-pro .device-frame {
- background: #0d0d0d;
- border-radius: 32px;
- box-shadow: inset 0 0 0 1px #c1c2c3, inset 0 0 1px 2px #e2e3e4;
- height: 800px;
- padding: 24px;
- width: 576px;
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- .device-ipad-pro .device-screen {
- border: 2px solid #0f0f0f;
- border-radius: 10px;
- overflow: hidden;
- min-width: 200%;
- height: 200%;
- scale: 0.5;
- }
-
- .device-ipad-pro .device-screen iframe {
- /* Set the iframe to the actual device resolution and scale it down */
- width: 834px; /* iPad Pro 11" logical width */
- height: 1194px; /* iPad Pro 11" logical height */
- transform: scale(0.6331); /* 528px (screen width) / 834px (logical width) */
- transform-origin: top left;
- background: #fff; /* Ensure bg color for content */
- }
-
- /* Mobile scrollbar styling for iPad */
- .device-ipad-pro .device-screen::-webkit-scrollbar {
- width: 3px;
- }
-
- .device-ipad-pro .device-screen::-webkit-scrollbar-track {
- background: transparent;
- }
-
- .device-ipad-pro .device-screen::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.2);
- border-radius: 2px;
- }
-
- .device-ipad-pro .device-power {
- background: #2a242f;
- border-radius: 2px;
- height: 2px;
- width: 38px;
- right: 76px;
- top: -2px;
- position: absolute;
- }
-
- /* Reposition buttons specifically for iPad Pro */
- .device-ipad-pro .device-btns {
- background: #2a242f;
- border-radius: 2px;
- height: 30px; /* Volume up */
- width: 2px;
- right: 22px;
- top: 90px;
- position: absolute;
- }
-
- .device-ipad-pro .device-btns::after {
- content: "";
- background: #2a242f;
- border-radius: 2px;
- height: 30px; /* Volume down */
- width: 2px;
- left: 0;
- top: 40px; /* Space between buttons */
- position: absolute;
- }
-
- .device-ipad-pro .device-btns::before {
- display: none; /* Hide the third button from iPhone */
- }
-
- .device-ipad-pro .device-sensors::after,
- .device-ipad-pro .device-sensors::before {
- content: "";
- position: absolute;
- }
-
- .device-ipad-pro .device-sensors::after {
- background: #141414;
- border-radius: 16px;
- box-shadow: -18px 0 #141414, 64px 0 #141414;
- height: 10px;
- left: 50%;
- margin-left: -28px;
- top: 11px;
- width: 10px;
- }
-
- .device-ipad-pro .device-sensors::before {
- background: radial-gradient(farthest-corner at 20% 20%, #6074BF 0, transparent 40%),
- radial-gradient(farthest-corner at 80% 80%, #513785 0, #24555E 20%, transparent 50%);
- box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.05);
- border-radius: 50%;
- height: 6px;
- left: 50%;
- margin-left: -3px;
- top: 13px;
- width: 5px;
- }
-
- /* Enable smooth scrolling on iOS */
- .device-iphone-14-pro .device-screen,
- .device-ipad-pro .device-screen {
- -webkit-overflow-scrolling: touch; /* smooth momentum scroll on iOS */
- overflow-y: auto;
- }
-
- /* Mobile custom scrollbar */
- .device-iphone-14-pro .device-screen::-webkit-scrollbar,
- .device-ipad-pro .device-screen::-webkit-scrollbar {
- width: 4px;
- height: 4px;
- }
-
- .device-iphone-14-pro .device-screen::-webkit-scrollbar-track,
- .device-ipad-pro .device-screen::-webkit-scrollbar-track {
- background: transparent;
- }
-
- .device-iphone-14-pro .device-screen::-webkit-scrollbar-thumb,
- .device-ipad-pro .device-screen::-webkit-scrollbar-thumb {
- background-color: rgba(0, 0, 0, 0.15);
- border-radius: 8px;
- border: 1px solid rgba(0, 0, 0, 0.05);
- }
-
- /* Optional: Hide scrollbar on larger screens for desktop */
- /* This media query hides the scrollbar on desktops where touch scrolling is not needed */
- @media (pointer: fine) and (hover: hover) {
- .device-iphone-14-pro .device-screen::-webkit-scrollbar,
- .device-ipad-pro .device-screen::-webkit-scrollbar {
- display: none;
- }
- }
-`;
-
-const injectCascadeIds = (rootElement) => {
- if (!rootElement) return;
- let idCounter = 0;
- const elements = rootElement.querySelectorAll('*');
- elements.forEach(el => {
- if (!el.hasAttribute('data-cascade-id')) {
- el.setAttribute('data-cascade-id', `cascade-${idCounter++}`);
- }
- });
-};
-
-// Inspector mode CSS
-const domSelectorCSS = `
- /* Hover effect for all elements in inspect mode */
- body[cascade-inspect-mode] *:hover {
- outline: 2px solid #0066ff !important;
- outline-offset: 2px !important;
- box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.3) !important;
- cursor: crosshair !important;
- transition: all 0.1s ease !important;
- }
-
- /* Selected element styling */
- .cascade-selected {
- outline: 3px solid #00cc66 !important;
- outline-offset: 3px !important;
- box-shadow: 0 0 0 3px rgba(0, 204, 102, 0.3) !important;
- position: relative !important;
- }
-
- /* Ensure inspector styles override any existing styles */
- body[cascade-inspect-mode] * {
- cursor: crosshair !important;
- }
-`;
-
-const cursorCSS = `
- /* Additional cursor styling for inspect mode */
- body[cascade-inspect-mode] {
- cursor: crosshair !important;
- }
-`;
-
-const PreviewFrame = forwardRef((props, ref) => {
- const {
- htmlInput,
- cssInput,
- jsInput,
- onElementClick,
- isInspectModeActive,
- selectedDevice,
- isFullscreen
- } = props;
- const iframeRef = useRef(null);
- const [originalScrollPosition, setOriginalScrollPosition] = useState({ x: 0, y: 0 });
-
- const storeScrollPosition = useCallback(() => {
- const iframe = iframeRef.current;
- if (!iframe?.contentWindow) return;
- const scrollX = iframe.contentWindow.scrollX || 0;
- const scrollY = iframe.contentWindow.scrollY || 0;
- setOriginalScrollPosition({ x: scrollX, y: scrollY });
- console.log('๐ SCROLL STORED:', { x: scrollX, y: scrollY });
- }, []);
-
- const restoreScrollPosition = useCallback(() => {
- const iframe = iframeRef.current;
- if (!iframe) return;
-
- const attemptRestore = (retryCount = 0) => {
- if (retryCount > 3) {
- console.warn('โ ๏ธ SCROLL RESTORE: Max retries reached, giving up');
- return;
- }
-
- if (iframe.contentWindow && iframe.contentDocument) {
- try {
- iframe.contentWindow.scrollTo(originalScrollPosition.x, originalScrollPosition.y);
- console.log('๐ SCROLL RESTORED:', originalScrollPosition);
- } catch (error) {
- console.error('โ SCROLL RESTORE ERROR:', error);
- }
- } else {
- console.log(`๐ SCROLL RESTORE: Iframe not ready, retrying... (${retryCount + 1}/3)`);
- setTimeout(() => attemptRestore(retryCount + 1), 100);
- }
- };
-
- attemptRestore();
- }, [originalScrollPosition]);
-
- const handleIframeClick = useCallback((e) => {
- if (!isInspectModeActive) return;
- e.preventDefault();
- e.stopPropagation();
-
- const iframe = iframeRef.current;
- if (!iframe) return;
-
- const doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
- if (!doc) return;
-
- storeScrollPosition();
-
- const target = e.target;
- if (!target) return;
-
- let cascadeId = target.getAttribute('data-cascade-id');
- if (!cascadeId) {
- cascadeId = `cascade-${Date.now()}`;
- target.setAttribute('data-cascade-id', cascadeId);
- }
-
- const elementInfo = {
- tagName: target.tagName.toLowerCase(),
- attributes: {},
- textContent: target.textContent,
- innerHTML: target.innerHTML,
- cascadeId: cascadeId,
- };
-
- Array.from(target.attributes).forEach(attr => {
- elementInfo.attributes[attr.name] = attr.value;
- });
-
- // Ensure className is properly mapped for ElementEditor compatibility
- if (target.className) {
- elementInfo.attributes.class = target.className;
- }
-
- console.log('๐ ENHANCED SELECTION:', elementInfo);
- console.log('๐ฏ INSPECTOR ACTIVATED: DOM manipulation mode enabled');
-
- setTimeout(() => restoreScrollPosition(), 10);
- onElementClick(elementInfo);
- }, [isInspectModeActive, onElementClick, storeScrollPosition, restoreScrollPosition]);
-
- const setupInspectModeStyles = useCallback(() => {
- const iframe = iframeRef.current;
- if (!iframe) return;
-
- const doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
- if (!doc) return;
-
- // Declare variables outside if block for cleanup function access
- let styleElement = null;
- let cursorStyleElement = null;
-
- // Add inspector styles if not already present
- if (!doc.getElementById('dom-selector-styles')) {
- styleElement = doc.createElement('style');
- styleElement.id = 'dom-selector-styles';
- styleElement.textContent = domSelectorCSS;
- doc.head.appendChild(styleElement);
-
- // Add cursor styles
- cursorStyleElement = doc.createElement('style');
- cursorStyleElement.id = 'cursor-styles';
- cursorStyleElement.textContent = cursorCSS;
- doc.head.appendChild(cursorStyleElement);
-
- console.log('โ
DEBUG: Inspector styles injected');
- } else {
- console.log('โ
DEBUG: Inspector styles already present');
- // Get references to existing elements for cleanup
- styleElement = doc.getElementById('dom-selector-styles');
- cursorStyleElement = doc.getElementById('cursor-styles');
- }
-
- // ALWAYS attach click event listener (this was the bug - it was being skipped)
- if (doc.body) {
- // Remove any existing listener first to prevent duplicates
- doc.body.removeEventListener('click', handleIframeClick, true);
- doc.body.addEventListener('click', handleIframeClick, true);
- console.log('โ
DEBUG: Click handler attached successfully');
- } else {
- console.error('โ DEBUG: No iframe body found, cannot attach click handler');
- }
-
- const observer = new MutationObserver((mutationsList) => {
- for (const mutation of mutationsList) {
- if (mutation.type === 'childList') {
- mutation.addedNodes.forEach(node => {
- if (node.nodeType === Node.ELEMENT_NODE) {
- injectCascadeIds(node);
- }
- });
- }
- }
- });
-
- // Observe the iframe document for changes
- observer.observe(doc.body || doc, { childList: true, subtree: true });
-
- // Cleanup function
- return () => {
- try {
- observer.disconnect();
- if (styleElement && styleElement.parentNode) {
- styleElement.remove();
- }
- if (cursorStyleElement && cursorStyleElement.parentNode) {
- cursorStyleElement.remove();
- }
- console.log('๐งน Inspector styles and listeners cleaned up');
- } catch (error) {
- console.warn('โ ๏ธ Cleanup warning (safe to ignore):', error.message);
- }
- };
- }, [handleIframeClick]);
-
- const generateHtmlContent = useCallback(() => {
- // Always generate content - the parent component controls when to refresh
- console.log('๐ GENERATING HTML CONTENT for iframe');
-
- const isFullHtml = htmlInput.trim().toLowerCase().startsWith('`;
- const customStyles = ``;
- const scripts = ``;
-
- if (isFullHtml) {
- let processedHtml = htmlInput;
- if (!processedHtml.includes(cssLink)) {
- processedHtml = processedHtml.replace('', `${cssLink}`);
- }
- if (!processedHtml.includes(customStyles)) {
- processedHtml = processedHtml.replace('', `${customStyles}`);
- }
- if (!processedHtml.includes(jsInput)) {
- processedHtml = processedHtml.replace('
- ${htmlInput}
- ${scripts}
- ', `${scripts}`);
- }
- return processedHtml;
- } else {
- return `
-
-
-