asimex-visual-editor
Version:
A powerful visual page editor component for React applications
1,749 lines • 120 kB
JavaScript
'use client';
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
// src/hooks/useEditor.ts
var useEditor = function () {
var iframeRef = React.useRef(null);
var _a = React.useState(null), selectedElement = _a[0], setSelectedElement = _a[1];
var _b = React.useState({}), currentStyles = _b[0], setCurrentStyles = _b[1];
var _c = React.useState(false), showAssetManager = _c[0], setShowAssetManager = _c[1];
var _d = React.useState(false), isLoading = _d[0], setIsLoading = _d[1];
var _e = React.useState(false), hasContent = _e[0], setHasContent = _e[1];
var sendMessage = React.useCallback(function (message) {
var _a, _b;
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, '*');
}, []);
// Inject editor script into HTML
var injectEditorScript = React.useCallback(function (html) {
var editorScript = "\n <script data-editor-script=\"true\">\n class IframeHelper {\n constructor() {\n this.selectedElement = null;\n this.init();\n }\n\n init() {\n document.addEventListener('mouseover', this.handleMouseOver.bind(this));\n document.addEventListener('mouseout', this.handleMouseOut.bind(this));\n document.addEventListener('click', this.handleClick.bind(this));\n document.addEventListener('dblclick', this.handleDoubleClick.bind(this));\n window.addEventListener('message', this.handleMessage.bind(this));\n }\n\n handleMouseOver(e) {\n if (this.previewMode) return; // ADD THIS LINE\n \n if (e.target !== this.selectedElement && e.target !== document.body && e.target !== document.documentElement) {\n e.target.style.outline = '2px dashed #007bff';\n }\n}\n\nhandleMouseOut(e) {\n if (this.previewMode) return; // ADD THIS LINE\n \n if (e.target !== this.selectedElement && e.target !== document.body && e.target !== document.documentElement) {\n e.target.style.outline = '';\n }\n}\n\n\n \n\n handleClick(e) {\n if (this.previewMode) return;\n \n e.preventDefault();\n e.stopPropagation();\n\n if (this.selectedElement) {\n this.selectedElement.style.outline = '';\n this.selectedElement.removeAttribute('contenteditable');\n }\n\n if (e.target === document.body || e.target === document.documentElement) {\n return;\n }\n\n this.selectedElement = e.target;\n this.selectedElement.style.outline = '2px solid #007bff';\n\n // Make element editable immediately\n const editableTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN', 'DIV', 'A', 'BUTTON'];\n if (editableTags.includes(this.selectedElement.tagName)) {\n this.selectedElement.setAttribute('contenteditable', 'true');\n this.selectedElement.focus();\n }\n\n const elementData = this.getElementData(this.selectedElement);\n window.parent.postMessage({\n type: 'ELEMENT_SELECTED',\n payload: elementData,\n }, '*');\n}\n handleDoubleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n\n if (e.target.tagName === 'IMG') {\n window.parent.postMessage({\n type: 'OPEN_ASSET_MANAGER',\n payload: {}\n }, '*');\n }\n }\n\n handleMessage(e) {\n const { type, payload } = e.data;\n \n switch (type) {\n case 'APPLY_STYLE':\n this.applyStyle(payload.selector, payload.styles);\n break;\n case 'UPDATE_CLASSES':\n this.updateClasses(payload.selector, payload.classes);\n break;\n case 'DELETE_ELEMENT':\n this.deleteElement(payload.selector);\n break;\n case 'DUPLICATE_ELEMENT':\n this.duplicateElement(payload.selector);\n break;\n case 'GET_HTML':\n this.exportHTML();\n break;\n case 'ADD_BLOCK':\n this.addBlock(payload.content, payload.targetSelector);\n break;\n case 'TOGGLE_PREVIEW_MODE': // ADD THIS NEW CASE\n this.togglePreviewMode(payload.preview);\n break;\n case 'CLEAR_CANVAS':\n this.clearCanvas();\n break;\n }\n}\n\nclearCanvas() {\n try {\n // Clear the body content but keep the script\n const scripts = document.querySelectorAll('script[data-editor-script=\"true\"]');\n document.body.innerHTML = '';\n \n // Re-add the editor script\n scripts.forEach(script => {\n document.body.appendChild(script);\n });\n \n // Reset selected element\n this.selectedElement = null;\n \n // Notify parent\n window.parent.postMessage({\n type: 'CANVAS_CLEARED',\n payload: {}\n }, '*');\n } catch (error) {\n console.error('Error clearing canvas:', error);\n }\n}\n // IMPROVED: Better block addition to prevent duplicates\naddBlock(content, targetSelector) {\n const target = document.querySelector(targetSelector || 'body');\n if (target && content) {\n // Create unique ID for this addition\n const additionId = 'block-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);\n \n // Check if this exact content was just added (prevent duplicates)\n const recentAdditions = this.recentAdditions || [];\n const now = Date.now();\n \n // Clean old additions (older than 1 second)\n this.recentAdditions = recentAdditions.filter(a => now - a.timestamp < 1000);\n \n // Check for recent duplicate\n const isDuplicate = this.recentAdditions.some(a => a.content === content);\n if (isDuplicate) {\n console.log('Duplicate block addition prevented');\n return;\n }\n \n // Add to recent additions\n this.recentAdditions.push({ content, timestamp: now, id: additionId });\n \n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = content;\n \n // Add data attribute to track\n while (tempDiv.firstChild) {\n const element = tempDiv.firstChild;\n if (element.nodeType === Node.ELEMENT_NODE) {\n element.setAttribute('data-block-id', additionId);\n }\n target.appendChild(element);\n }\n \n // Notify parent that block was added\n window.parent.postMessage({\n type: 'BLOCK_ADDED_SUCCESS',\n payload: { \n target: target.tagName,\n additionId: additionId\n }\n }, '*');\n }\n}\n\ntogglePreviewMode(isPreview) {\n if (isPreview) {\n // Hide all editor outlines and make non-editable\n document.querySelectorAll('[style*=\"outline\"]').forEach(el => {\n el.style.outline = '';\n });\n document.querySelectorAll('[contenteditable]').forEach(el => {\n el.removeAttribute('contenteditable');\n });\n // Disable all editor event listeners\n this.previewMode = true;\n } else {\n // Re-enable editor functionality\n this.previewMode = false;\n }\n}\n\n\n duplicateElement(selector) {\n const el = document.querySelector(selector);\n if (el && el !== document.body && el !== document.documentElement) {\n const clone = el.cloneNode(true);\n \n clone.removeAttribute('id');\n const allElements = clone.querySelectorAll('*');\n allElements.forEach(child => child.removeAttribute('id'));\n \n el.parentNode.insertBefore(clone, el.nextSibling);\n \n setTimeout(() => {\n if (this.selectedElement) {\n this.selectedElement.style.outline = '';\n }\n this.selectedElement = clone;\n clone.style.outline = '2px solid #007bff';\n \n const elementData = this.getElementData(clone);\n window.parent.postMessage({\n type: 'ELEMENT_SELECTED',\n payload: elementData,\n }, '*');\n }, 100);\n }\n }\n\n exportHTML() {\n try {\n this.cleanupTempStyles();\n let htmlContent = document.documentElement.outerHTML;\n htmlContent = this.removeInjectedScript(htmlContent);\n const styles = this.extractStyles();\n \n window.parent.postMessage({\n type: 'EXPORT_DATA',\n payload: {\n html: htmlContent,\n css: styles\n }\n }, '*');\n } catch (error) {\n console.error('Export error:', error);\n }\n }\n\n cleanupTempStyles() {\n document.querySelectorAll('[style*=\"outline\"]').forEach(el => {\n const style = el.getAttribute('style');\n if (style) {\n const cleanedStyle = style\n .split(';')\n .filter(s => s.trim() && !s.toLowerCase().includes('outline'))\n .join(';');\n \n if (cleanedStyle.trim()) {\n el.setAttribute('style', cleanedStyle);\n } else {\n el.removeAttribute('style');\n }\n }\n });\n\n document.querySelectorAll('[contenteditable]').forEach(el => {\n el.removeAttribute('contenteditable');\n });\n }\n\n removeInjectedScript(html) {\n return html.replace(/<script[^>]*data-editor-script=[\"']true[\"'][^>]*>[\\s\\S]*?<\\/script>/gi, '').trim();\n }\n\n extractStyles() {\n let styles = '';\n \n const elementsWithStyles = document.querySelectorAll('[style]');\n elementsWithStyles.forEach(el => {\n const style = el.getAttribute('style');\n if (style && !style.toLowerCase().includes('outline')) {\n const selector = this.getUniqueSelector(el);\n if (selector) {\n styles += `${selector} { ${style} }\\n`;\n }\n }\n });\n\n return styles;\n }\n\n getUniqueSelector(el) {\n if (!el) return '';\n const path = [];\n \n while (el && el.nodeType === Node.ELEMENT_NODE) {\n let selector = el.nodeName.toLowerCase();\n \n if (el.id) {\n selector += '#' + el.id;\n path.unshift(selector);\n break;\n } else {\n let sibling = el;\n let nth = 1;\n while ((sibling = sibling.previousElementSibling)) {\n if (sibling.nodeName === el.nodeName) nth++;\n }\n if (nth > 1) {\n selector += ':nth-of-type(' + nth + ')';\n }\n }\n \n path.unshift(selector);\n el = el.parentElement;\n }\n \n return path.join(' > ');\n }\n\n getComputedStyles(el) {\n const computed = window.getComputedStyle(el);\n const relevantStyles = {};\n \n const styleProps = [\n 'display', 'position', 'top', 'bottom', 'left', 'right', 'z-index',\n 'width', 'height', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',\n 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',\n 'font-size', 'font-weight', 'color', 'background-color', 'border-width',\n 'border-style', 'border-color', 'border-radius', 'opacity'\n ];\n \n styleProps.forEach(prop => {\n relevantStyles[prop] = computed.getPropertyValue(prop);\n });\n \n return relevantStyles;\n }\n\n getElementData(el) {\n return {\n selector: this.getUniqueSelector(el),\n tagName: el.tagName,\n id: el.id || '',\n classes: Array.from(el.classList),\n textContent: el.textContent?.trim().substring(0, 50) || '',\n computedStyles: this.getComputedStyles(el)\n };\n }\n\n applyStyle(selector, styles) {\n const el = document.querySelector(selector);\n if (el) {\n if (styles.src && el.tagName === 'IMG') {\n el.src = styles.src;\n delete styles.src;\n }\n \n Object.assign(el.style, styles);\n }\n }\n\n updateClasses(selector, classes) {\n const el = document.querySelector(selector);\n if (el) {\n el.className = classes.join(' ');\n }\n }\n\n deleteElement(selector) {\n const el = document.querySelector(selector);\n if (el && el !== document.body && el !== document.documentElement) {\n el.remove();\n }\n }\n }\n\n new IframeHelper();\n </script>\n ";
// Clean HTML input
var cleanHtml = html.trim();
// If it's not a complete document, wrap it
if (!cleanHtml.toLowerCase().includes('<!doctype') && !cleanHtml.toLowerCase().includes('<html')) {
cleanHtml = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Visual Editor</title>\n</head>\n<body>\n".concat(cleanHtml, "\n</body>\n</html>");
}
// Inject script before closing body tag
if (cleanHtml.includes('</body>')) {
return cleanHtml.replace('</body>', "".concat(editorScript, "</body>"));
}
else {
return cleanHtml + editorScript;
}
}, []);
// Load HTML content and inject editor script
var loadHTMLContent = React.useCallback(function (htmlContent) {
if (!htmlContent.trim())
return;
setIsLoading(true);
try {
var modifiedHTML = injectEditorScript(htmlContent);
var blob = new Blob([modifiedHTML], { type: 'text/html; charset=utf-8' });
var blobUrl = URL.createObjectURL(blob);
if (iframeRef.current) {
iframeRef.current.src = blobUrl;
setHasContent(true);
}
}
catch (error) {
console.error('Error loading HTML content:', error);
}
finally {
setIsLoading(false);
}
}, [injectEditorScript]);
// Add block functionality
var addBlock = React.useCallback(function (content, targetSelector) {
sendMessage({
type: 'ADD_BLOCK',
payload: {
content: content,
targetSelector: targetSelector || 'body'
}
});
}, [sendMessage]);
// Export functionality
var exportHTML = React.useCallback(function () {
if (!hasContent)
return;
sendMessage({
type: 'GET_HTML',
payload: {}
});
}, [sendMessage, hasContent]);
// Handle element selection
var handleElementSelected = React.useCallback(function (payload) {
setSelectedElement(payload || null);
setCurrentStyles((payload === null || payload === void 0 ? void 0 : payload.computedStyles) || {});
}, []);
// Handle asset manager
var handleOpenAssetManager = React.useCallback(function () {
setShowAssetManager(true);
}, []);
// Update style
var updateStyle = React.useCallback(function (property, value) {
var _a;
setCurrentStyles(function (prev) {
var _a;
return (__assign(__assign({}, prev), (_a = {}, _a[property] = value, _a)));
});
if (selectedElement) {
sendMessage({
type: 'APPLY_STYLE',
payload: {
selector: selectedElement.selector,
styles: (_a = {}, _a[property] = value, _a),
},
});
}
}, [selectedElement, sendMessage]);
// Update classes
var updateClasses = React.useCallback(function (classes) {
if (selectedElement) {
var updatedElement = __assign(__assign({}, selectedElement), { classes: classes });
setSelectedElement(updatedElement);
sendMessage({
type: 'UPDATE_CLASSES',
payload: {
selector: selectedElement.selector,
classes: classes,
},
});
}
}, [selectedElement, sendMessage]);
// Delete element
var deleteElement = React.useCallback(function () {
if (selectedElement) {
sendMessage({
type: 'DELETE_ELEMENT',
payload: { selector: selectedElement.selector },
});
setSelectedElement(null);
}
}, [selectedElement, sendMessage]);
// Copy element
var copyElement = React.useCallback(function () {
if (selectedElement) {
sendMessage({
type: 'DUPLICATE_ELEMENT',
payload: { selector: selectedElement.selector },
});
}
}, [selectedElement, sendMessage]);
// Handle asset selection
var handleAssetSelect = React.useCallback(function (assetUrl) {
if (selectedElement && selectedElement.tagName === 'IMG') {
sendMessage({
type: 'APPLY_STYLE',
payload: {
selector: selectedElement.selector,
styles: { src: assetUrl },
},
});
}
}, [selectedElement, sendMessage]);
// Return all methods
return {
iframeRef: iframeRef,
selectedElement: selectedElement,
currentStyles: currentStyles,
showAssetManager: showAssetManager,
isLoading: isLoading,
hasContent: hasContent,
setShowAssetManager: setShowAssetManager,
handleAssetSelect: handleAssetSelect,
handleElementSelected: handleElementSelected,
handleOpenAssetManager: handleOpenAssetManager,
updateStyle: updateStyle,
updateClasses: updateClasses,
deleteElement: deleteElement,
copyElement: copyElement,
loadHTMLContent: loadHTMLContent,
exportHTML: exportHTML,
addBlock: addBlock,
};
};
// src/components/Editor/EditorCanvas.tsx (Complete Fix)
var EditorCanvas = React.forwardRef(function (_a, ref) {
var src = _a.src, style = _a.style, className = _a.className;
return (jsxRuntime.jsx("iframe", { ref: ref, src: src || "about:blank", className: className, style: __assign({ border: 'none', backgroundColor: '#fff' }, style), title: "Visual Editor Canvas" }));
});
EditorCanvas.displayName = 'EditorCanvas';
var AssetManager = function (_a) {
var isOpen = _a.isOpen, onClose = _a.onClose, onSelectAsset = _a.onSelectAsset; _a.selectedElement;
var _b = React.useState(''), searchTerm = _b[0], setSearchTerm = _b[1];
var _c = React.useState([]), uploadedAssets = _c[0], setUploadedAssets = _c[1];
var stockImages = [
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1508193638397-1c4234db14d8?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1501594907352-04cda38ebc29?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=400&h=300&fit=crop',
'https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=400&h=300&fit=crop',
];
var handleFileUpload = function (e) {
var files = e.target.files;
if (files) {
Array.from(files).forEach(function (file) {
var reader = new FileReader();
reader.onload = function (event) {
var _a;
if ((_a = event.target) === null || _a === void 0 ? void 0 : _a.result) {
setUploadedAssets(function (prev) { return __spreadArray(__spreadArray([], prev, true), [event.target.result], false); });
}
};
reader.readAsDataURL(file);
});
}
};
var allImages = __spreadArray(__spreadArray([], uploadedAssets, true), stockImages, true);
var filteredImages = searchTerm
? allImages.filter(function (_, index) { return index.toString().includes(searchTerm); })
: allImages;
if (!isOpen)
return null;
return (jsxRuntime.jsx("div", { style: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
zIndex: 10000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: jsxRuntime.jsxs("div", { style: {
backgroundColor: '#333',
borderRadius: '8px',
padding: '24px',
width: '90%',
maxWidth: '800px',
maxHeight: '80%',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}, children: [jsxRuntime.jsxs("div", { style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '20px',
color: '#fff'
}, children: [jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: '20px' }, children: "Asset Manager" }), jsxRuntime.jsx("button", { onClick: onClose, style: {
background: 'none',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer'
}, children: "\u2715" })] }), jsxRuntime.jsxs("div", { style: {
marginBottom: '20px',
padding: '16px',
backgroundColor: '#444',
borderRadius: '6px',
border: '2px dashed #666'
}, children: [jsxRuntime.jsx("input", { type: "file", multiple: true, accept: "image/*", onChange: handleFileUpload, style: { display: 'none' }, id: "file-upload" }), jsxRuntime.jsx("label", { htmlFor: "file-upload", style: {
display: 'block',
textAlign: 'center',
color: '#ccc',
cursor: 'pointer',
padding: '12px'
}, children: "\uD83D\uDCC1 Click to upload images or drag and drop" })] }), jsxRuntime.jsx("div", { style: { marginBottom: '20px' }, children: jsxRuntime.jsx("input", { type: "text", placeholder: "Search images...", value: searchTerm, onChange: function (e) { return setSearchTerm(e.target.value); }, style: {
width: '100%',
padding: '10px',
backgroundColor: '#555',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
fontSize: '14px'
} }) }), jsxRuntime.jsx("div", { style: {
flex: 1,
overflowY: 'auto',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
gap: '12px',
padding: '4px'
}, children: filteredImages.map(function (imageUrl, index) { return (jsxRuntime.jsx("div", { onClick: function () {
onSelectAsset(imageUrl);
onClose();
}, style: {
aspectRatio: '1',
backgroundImage: "url(".concat(imageUrl, ")"),
backgroundSize: 'cover',
backgroundPosition: 'center',
borderRadius: '6px',
cursor: 'pointer',
border: '2px solid transparent',
transition: 'all 0.2s ease'
}, onMouseEnter: function (e) {
e.currentTarget.style.borderColor = '#007bff';
e.currentTarget.style.transform = 'scale(1.05)';
}, onMouseLeave: function (e) {
e.currentTarget.style.borderColor = 'transparent';
e.currentTarget.style.transform = 'scale(1)';
} }, index)); }) }), jsxRuntime.jsxs("div", { style: {
marginTop: '20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
color: '#ccc',
fontSize: '12px'
}, children: [jsxRuntime.jsxs("span", { children: [filteredImages.length, " images available"] }), jsxRuntime.jsx("button", { onClick: onClose, style: {
padding: '8px 16px',
backgroundColor: '#666',
border: '1px solid #777',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer'
}, children: "Cancel" })] })] }) }));
};
// src/components/Editor/HTMLLoader.tsx
var HTMLLoader = function (_a) {
var isOpen = _a.isOpen, onClose = _a.onClose, onSubmit = _a.onSubmit;
var _b = React.useState(''), htmlContent = _b[0], setHtmlContent = _b[1];
var _c = React.useState('paste'), activeTab = _c[0], setActiveTab = _c[1];
if (!isOpen)
return null;
var handleSubmit = function (e) {
e.preventDefault();
if (!htmlContent.trim()) {
alert('Please enter HTML content');
return;
}
onSubmit(htmlContent);
setHtmlContent('');
onClose();
};
var handleFileUpload = function (e) {
var _a;
var file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
if (file && file.type === 'text/html') {
var reader = new FileReader();
reader.onload = function (event) {
var _a;
if ((_a = event.target) === null || _a === void 0 ? void 0 : _a.result) {
setHtmlContent(event.target.result);
}
};
reader.readAsText(file);
}
};
var sampleHTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Sample Page</title>\n <style>\n body { font-family: Arial, sans-serif; margin: 40px; }\n .container { max-width: 800px; margin: 0 auto; }\n .hero { background: #f0f0f0; padding: 30px; border-radius: 8px; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"hero\">\n <h1>Welcome to Visual Editor</h1>\n <p>This is a sample HTML page. Click on any element to start editing!</p>\n <img src=\"https://via.placeholder.com/400x200\" alt=\"Sample Image\" />\n <button style=\"padding: 10px 20px; margin: 10px 0;\">Click Me</button>\n </div>\n </div>\n</body>\n</html>";
return (jsxRuntime.jsx("div", { style: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
zIndex: 10000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: jsxRuntime.jsxs("div", { style: {
backgroundColor: '#333',
borderRadius: '8px',
padding: '24px',
width: '90%',
maxWidth: '700px',
maxHeight: '80%',
color: '#fff',
display: 'flex',
flexDirection: 'column'
}, children: [jsxRuntime.jsxs("div", { style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '20px'
}, children: [jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: '18px' }, children: "Load HTML Content" }), jsxRuntime.jsx("button", { onClick: onClose, style: {
background: 'none',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer'
}, children: "\u2715" })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', marginBottom: '20px', gap: '4px' }, children: [jsxRuntime.jsx("button", { onClick: function () { return setActiveTab('paste'); }, style: {
padding: '8px 16px',
backgroundColor: activeTab === 'paste' ? '#007bff' : '#555',
border: '1px solid #666',
borderRadius: '4px 4px 0 0',
color: '#fff',
cursor: 'pointer'
}, children: "Paste HTML" }), jsxRuntime.jsx("button", { onClick: function () { return setActiveTab('upload'); }, style: {
padding: '8px 16px',
backgroundColor: activeTab === 'upload' ? '#007bff' : '#555',
border: '1px solid #666',
borderRadius: '4px 4px 0 0',
color: '#fff',
cursor: 'pointer'
}, children: "Upload File" })] }), jsxRuntime.jsxs("form", { onSubmit: handleSubmit, style: { flex: 1, display: 'flex', flexDirection: 'column' }, children: [activeTab === 'paste' ? (jsxRuntime.jsxs("div", { style: { flex: 1, display: 'flex', flexDirection: 'column' }, children: [jsxRuntime.jsxs("div", { style: { marginBottom: '12px', display: 'flex', gap: '8px' }, children: [jsxRuntime.jsx("button", { type: "button", onClick: function () { return setHtmlContent(sampleHTML); }, style: {
padding: '6px 12px',
backgroundColor: '#666',
border: '1px solid #777',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px'
}, children: "Load Sample HTML" }), jsxRuntime.jsx("button", { type: "button", onClick: function () { return setHtmlContent(''); }, style: {
padding: '6px 12px',
backgroundColor: '#666',
border: '1px solid #777',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px'
}, children: "Clear" })] }), jsxRuntime.jsx("textarea", { value: htmlContent, onChange: function (e) { return setHtmlContent(e.target.value); }, placeholder: "Paste your HTML content here...", style: {
flex: 1,
minHeight: '300px',
padding: '12px',
backgroundColor: '#444',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
fontSize: '14px',
fontFamily: 'monospace',
resize: 'vertical'
} })] })) : (jsxRuntime.jsxs("div", { style: { marginBottom: '16px' }, children: [jsxRuntime.jsx("label", { style: {
display: 'block',
marginBottom: '8px',
fontSize: '14px',
color: '#ccc'
}, children: "Upload HTML File:" }), jsxRuntime.jsx("input", { type: "file", accept: ".html,.htm", onChange: handleFileUpload, style: {
width: '100%',
padding: '12px',
backgroundColor: '#444',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
fontSize: '14px'
} })] })), jsxRuntime.jsxs("div", { style: {
display: 'flex',
gap: '12px',
justifyContent: 'flex-end',
marginTop: '16px'
}, children: [jsxRuntime.jsx("button", { type: "button", onClick: onClose, style: {
padding: '10px 20px',
backgroundColor: '#666',
border: '1px solid #777',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer'
}, children: "Cancel" }), jsxRuntime.jsx("button", { type: "submit", style: {
padding: '10px 20px',
backgroundColor: '#007bff',
border: '1px solid #0066cc',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer'
}, children: "Load HTML" })] })] })] }) }));
};
var ExportModal = function (_a) {
var isOpen = _a.isOpen, onClose = _a.onClose, data = _a.data;
if (!isOpen || !data)
return null;
var downloadFile = function (content, filename, type) {
var blob = new Blob([content], { type: type });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
var handleDownloadHTML = function () {
downloadFile(data.html, 'edited-page.html', 'text/html');
};
var handleDownloadCSS = function () {
downloadFile(data.css, 'styles.css', 'text/css');
};
var handleDownloadBoth = function () {
// Create a combined HTML file with embedded styles
var combinedHTML = data.html.replace('</head>', "<style>\n".concat(data.css, "\n</style>\n</head>"));
downloadFile(combinedHTML, 'edited-page-with-styles.html', 'text/html');
};
return (jsxRuntime.jsx("div", { style: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
zIndex: 10000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: jsxRuntime.jsxs("div", { style: {
backgroundColor: '#333',
borderRadius: '8px',
padding: '24px',
width: '90%',
maxWidth: '800px',
maxHeight: '80%',
color: '#fff',
display: 'flex',
flexDirection: 'column'
}, children: [jsxRuntime.jsxs("div", { style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '20px'
}, children: [jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: '20px' }, children: "Export HTML & CSS" }), jsxRuntime.jsx("button", { onClick: onClose, style: {
background: 'none',
border: 'none',
color: '#fff',
fontSize: '24px',
cursor: 'pointer'
}, children: "\u2715" })] }), jsxRuntime.jsxs("div", { style: {
display: 'flex',
gap: '16px',
marginBottom: '20px'
}, children: [jsxRuntime.jsx("button", { onClick: handleDownloadHTML, style: {
padding: '12px 20px',
backgroundColor: '#4CAF50',
border: 'none',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '14px'
}, children: "\uD83D\uDCC4 Download HTML" }), jsxRuntime.jsx("button", { onClick: handleDownloadCSS, style: {
padding: '12px 20px',
backgroundColor: '#2196F3',
border: 'none',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '14px'
}, children: "\uD83C\uDFA8 Download CSS" }), jsxRuntime.jsx("button", { onClick: handleDownloadBoth, style: {
padding: '12px 20px',
backgroundColor: '#FF9800',
border: 'none',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '14px'
}, children: "\uD83D\uDCE6 Download Combined" })] }), jsxRuntime.jsxs("div", { style: { flex: 1, display: 'flex', gap: '16px' }, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("h3", { style: { fontSize: '16px', marginBottom: '8px' }, children: "HTML Content" }), jsxRuntime.jsx("textarea", { value: data.html, readOnly: true, style: {
width: '100%',
height: '300px',
backgroundColor: '#2a2a2a',
color: '#f8f8f2',
border: '1px solid #555',
borderRadius: '4px',
padding: '12px',
fontSize: '12px',
fontFamily: 'monospace',
resize: 'none'
} })] }), jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("h3", { style: { fontSize: '16px', marginBottom: '8px' }, children: "CSS Styles" }), jsxRuntime.jsx("textarea", { value: data.css, readOnly: true, style: {
width: '100%',
height: '300px',
backgroundColor: '#2a2a2a',
color: '#f8f8f2',
border: '1px solid #555',
borderRadius: '4px',
padding: '12px',
fontSize: '12px',
fontFamily: 'monospace',
resize: 'none'
} })] })] })] }) }));
};
// src/components/DeviceManager/index.tsx
// Update your defaultDevices in DeviceManager:
var defaultDevices = [
{
id: 'desktop',
name: 'Desktop',
width: '100%',
height: '100%',
icon: '🖥️'
},
{
id: 'tablet',
name: 'Tablet',
width: 768,
height: 1024,
icon: '📱'
},
{
id: 'mobile',
name: 'Mobile',
width: 375,
height: 667,
icon: '📱'
},
];
var DeviceManager = function (_a) {
var _b;
var _c = _a.devices, devices = _c === void 0 ? defaultDevices : _c, selectedDeviceId = _a.selectedDeviceId, onDeviceChange = _a.onDeviceChange, style = _a.style;
var _d = React.useState(selectedDeviceId || ((_b = devices[0]) === null || _b === void 0 ? void 0 : _b.id)), currentDeviceId = _d[0], setCurrentDeviceId = _d[1];
React.useEffect(function () {
var device = devices.find(function (d) { return d.id === currentDeviceId; });
if (device) {
onDeviceChange(device);
}
}, [currentDeviceId, devices, onDeviceChange]);
return (jsxRuntime.jsxs("div", { style: __assign({ display: 'flex', gap: '8px', padding: '8px 16px', backgroundColor: '#333', borderBottom: '1px solid #555', alignItems: 'center' }, style), children: [jsxRuntime.jsx("span", { style: { color: '#ccc', fontSize: '12px', marginRight: '8px' }, children: "Device:" }), devices.map(function (device) { return (jsxRuntime.jsxs("button", { onClick: function () { return setCurrentDeviceId(device.id); }, style: {
backgroundColor: currentDeviceId === device.id ? '#007bff' : '#555',
color: 'white',
border: 'none',
borderRadius: '4px',
padding: '6px 12px',
cursor: 'pointer',
fontSize: '12px',
display: 'flex',
alignItems: 'center',
gap: '4px',
transition: 'background-color 0.2s'
}, title: "".concat(device.name, " (").concat(device.width, " \u00D7 ").concat(device.height, ")"), children: [device.icon && jsxRuntime.jsx("span", { children: device.icon }), jsxRuntime.jsx("span", { children: device.name })] }, device.id)); })] }));
};
// src/components/BlockManager/DraggableBlockManager.tsx (Fixed duplicates)
var DraggableBlockManager = function (_a) {
var blocks = _a.blocks, onAddBlock = _a.onAddBlock, iframeRef = _a.iframeRef, style = _a.style;
var _b = React.useState('all'), selectedCategory = _b[0], setSelectedCategory = _b[1];
var _c = React.useState(false), isDragging = _c[0], setIsDragging = _c[1];
var filteredBlocks = selectedCategory === 'all'
? blocks
: blocks.filter(function (block) { return block.category === selectedCategory; });
var uniqueCategories = __spreadArray(['all'], Array.from(new Set(blocks.map(function (b) { return b.category; }).filter(Boolean))), true);
// FIXED: Single drag handler to prevent duplicates
var handleDragStart = React.useCallback(function (e, block) {
console.log('Drag start:', block.label);
setIsDragging(true);
e.dataTransfer.setData('text/html', block.content);
e.dataTransfer.setData('application/json', JSON.stringify(block));
e.dataTransfer.effectAllowed = 'copy';
// Visual feedback
e.currentTarget.style.opacity = '0.5';
e.currentTarget.style.transform = 'scale(0.95)';
}, []);
var handleDragEnd = React.useCallback(function (e) {
console.log('Drag end');
setIsDragging(false);
// Reset visual feedback
e.currentTarget.style.opacity = '1';
e.currentTarget.style.transform = 'scale(1)';
}, []);
// FIXED: Separate click handler with drag state check
var handleBlockClick = React.useCallback(function (block, e) {
e.preventDefault();
e.stopPropagation();
// Don't trigger click if we just finished dragging
if (isDragging) {
return;
}
console.log('Block clicked:', block.label);
onAddBlock(block.content, block);
}, [onAddBlock, isDragging]);
// Setup drop zone on iframe
React.useEffect(function () {
if (!(iframeRef === null || iframeRef === void 0 ? void 0 : iframeRef.current))
return;
var iframe = iframeRef.current;
var dragCounter = 0; // Track drag enter/leave
var setupDropZone = function () {
var _a;
try {
var iframeDoc_1 = iframe.contentDocument || ((_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document);
if (!iframeDoc_1)
return;
var handleDragEnter = function (e) {
e.preventDefault();
dragCounter++;
// Add visual feedback to iframe
if (dragCounter === 1) {
iframeDoc_1.body.style.backgroundColor = 'rgba(0, 123, 255, 0.1)';
iframeDoc_1.body.style.outline = '2px dashed #007bff';
}
};
var handleDragLeave = function (e) {
e.preventDefault();
dragCounter--;
// Remove visual feedback when completely leaving
if (dragCounter === 0) {
iframeDoc_1.body.style.backgroundColor = '';
iframeDoc_1.body.style.outline = '';
}
};
var handleDragOver = function (e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
};
var handleDrop = function (e) {
e.preventDefault();
dragCounter = 0;
// Reset visual feedback
iframeDoc_1.body.style.backgroundColor = '';
iframeDoc_1.body.style.outline = '';
var htmlContent = e.dataTransfer.getData('text/html');
var blockData = e.dataTransfer.getData('application/json');
if (htmlContent && blockData) {
try {
var block = JSON.parse(blockData);
console.log('Block dropped:', block.label);
// Find the appropriate drop target
var targetElement = e.target;
// Navigate up to find a suitable container
while (targetElement && targetElement !== iframeDoc_1.body) {
if (['DIV', 'SECTION', 'MAIN', 'ARTICLE', 'HEADER', 'FOOTER'].includes(targetElement.tagName)) {
break;
}
targetElement = targetElement.parentElement;
}
if (!targetElement) {
targetElement = iframeDoc_1.body;
}
// Create and insert the new element
var tempDiv = document.createElement('div');
tempDiv.innerHTML = htmlContent;
// Add each element from the block
while (tempDiv.firstChild) {
targetElement.appendChild(tempDiv.firstChild);
}
// Notify parent
window.parent.postMessage({
type: 'BLOCK_ADDED_SUCCESS',
payload: { block: block, target: targetElement.tagName }
}, '*');
}
catch (error) {
console.error('Error adding block via drop:', error);
}
}
};
// Clean up existing listeners
iframeDoc_1.removeEventListener('dragenter', handleDragEnter);
iframeDoc_1.removeEventListener('dragleave', handleDragLeave);
iframeDoc_1.removeEventListener('dragover', handleDragOver);
iframeDoc_1.removeEventListener('drop', handleDrop);
// Add new listeners
iframeDoc_1.addEventListener('dragenter', handleDragEnter);
iframeDoc_1.addEventListener('dragleave', handleDragLeave);
iframeDoc_1.addEventListener('dragover', handleDragOver);
iframeDoc_1.addEventListener('drop', handleDrop);
}
catch (error) {
console.warn('Cannot setup drop zone:', error);
}
};
iframe.addEventListener('load', setupDropZone);
if (iframe.contentDocument) {
setupDropZone();
}
return function () {
iframe.removeEventListener('load', setupDropZone);
};
}, [iframeRef, onAddBlock]);
return (jsxRuntime.jsxs("div", { style: __assign({ display: 'flex', flexDirection: 'column', height: '100%', backgroundColor: '#444', color: '#fff' }, style), children: [jsxRuntime.jsx("div", { style: {
padding: '12px 16px',
borderBottom: '1px solid #555',
backgroundColor: '#333'
}, children: jsxRuntime.jsx("h3", { style: { margin: 0, fontSize: '14px', fontWeight: 'bold' }, children: "\uD83D\uDCE6 Blocks" }) }), uniqueCategories.length > 1 && (jsxRuntime.jsx("div", { style: {
padding: '8px 16px',
borderBottom: '1px solid #555'
}, children: jsxRuntime.jsx("select", { value: selectedCategory, onChange: function (e) { return setSelectedCategory(e.target.value); }, style: {
width: '100%',
padding: '6px',
backgroundColor: '#555',
color: '#fff',
border: '1px solid #666',
borderRadius: '4px',
fontSize: '12px'
}, children: uniqueCategories.map(function (category) { return (jsxRuntime.jsx("option", { value: category, children: category ? category.charAt(0).toUpperCase() + category.slice(1) : 'All' }, category)); }) }) })), jsxRuntime.jsxs("div", { style: {
flex: 1,
overflowY: 'auto',
padding: '8px'
}, children: [jsxRuntime.jsx("div", { style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(80px, 1fr))',
gap: '8px'
}, children: filteredBlocks.map(function (block) { return (jsxRuntime.jsxs("div", { draggable: true, onDragStart: function (e) { return handleDragStart(e, block); }, onDragEnd: handleDragEnd, onClick: function (e) { return handleBlockClick(block, e); }, style: {
backgroundColor: '#555',
borderRadius: '6px',
padding: '8px',
cursor: 'grab',
textAlign: 'center',
transition: 'all 0.2s ease',
border: '2px solid transparent',
position: 'relative',
userSelect: 'none'
}, onMouseEnter: function (e) {
if (!isDragging) {
e.currentTarget.style.backgroundColor = '#666';
e.currentTarget.style.borderColor = '#007bff';
e.currentTarget.style.transform = 'scale(1.02)';
}
}, onMouseLeave: function (e) {
if (!isDragging) {
e.currentTarget.style.backgroundColor = '#555';
e.currentTarget.style.borderColor = 'transparent';
e.currentTarget.style.transform = 'scale(1)';
}
}, title: "".concat(block.label, " - Drag to canvas or click to add to body"), children: [jsxRuntime.jsx("div", { style: {
position: 'absolute',
top: '4px',
right: '4px',
fontSize: '8px',
color: '#aaa',
lineHeight: 1
}, children: "\u22EE\u22EE" }), block.thumbnailUrl ? (jsxRuntime.jsx("img", { src: block.thumbnailUrl, alt: block.label, style: {
width: '100%',
height: '50px',
objectFit: 'cover',
borderRadius: '4px',
marginBottom: '4px',
pointerEvents: 'none'
} })) : (jsxRuntime.jsx("div", { style: {
width: '100%',
height: '50px',
backgroundColor: '#666',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
marginBottom: '4px'
}, children: block.icon || '📄' })), jsxRuntime.jsx("div", { style: { fontSize: '10px', color: '#ccc', lineHeight: 1.2 }, children: block.label })] }, block.id)); }) }), filteredBlocks.length === 0 && (jsxRuntime.jsx("div", { style: {
textAlign: 'center',
color: '#666',
padding: '40px 20px',
fontSize: '14px'
}, children: "No blocks available" }))] }), jsxRuntime.jsx("div", { style: {
padding: '8px 12px',
borderTop: '1px solid #555',
backgroundColor: '#333',
fontSize: '11px',
color: '#aaa',
textAlign: 'center'
}, children: "\uD83D\uDCA1 Drag to canvas or click to add to body" })] }));
};
var ClassTag = function (_a) {
var className = _a.className, onRemove = _a.onRemove;
return (jsxRuntime.jsxs("div", { style: {
display: 'flex',
alignItems: 'center',
backgroundColor: '#8e44ad',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
margin: '2px'
}, children: [jsxRuntime.jsx("span", { children: className }), jsxRuntime.jsx("button", { onClick: function () { return onRemove(className); }, style: {
marginLeft: '6px',
background: 'none',
border: 'none',
color: '#fff',
cursor: 'pointer',
fontSize: '14px',
padding: '0'
}, children: "\u2715" })] }));
};
var STYLE_SECTIONS = [
{
name: 'General',
icon: '⚙️',
props: [
{ name: 'display', label: 'Display', type: 'select', options: ['block', 'inline', 'inline-block', 'flex', 'grid', 'none'] },
{ name: 'position', label: 'Position', type: 'select', options: ['static', 'relative', 'absolute', 'fixed', 'sticky'] },
{ name: 'top', label: 'Top', type: 'dimension' },
{ name: 'bottom', label: 'Bottom', type: 'dimension' },
{ name: 'left', label: 'Left', type: 'dimension' },
{ name: 'right', label: 'Right', type: 'dimension' },
{ name: 'z-index', label: 'Z Index', type: 'number' },
{ name: 'float', label: 'Float', type: 'select', options: ['none', 'left', 'right'] },
{ name: 'clear', label: 'Clear', type: 'select', options: ['none', 'left', 'right', 'both'] },
{ name: 'overflow', label: 'Overflow', type: 'select', options: ['visible', 'hidden', 'scroll', 'auto'] },
{ name: 'opacity', label: 'Opacity', type: 'slider', min: 0, max: 1, step: 0.1 },
{ name: 'visibility', label: 'Visibility', type: 'select', options: ['visible', 'hidden', 'collapse'] },
],
},
{
name: 'Dimension',
icon: '📏',
props: [
{ name: 'width', label: 'Width', type: 'dimension' },
{ name: 'height', label: 'Height', type: 'dimension' },
{ name: 'min-width', label: 'Min Width', type: 'dimension' },
{ name: 'min-height', label: 'Min Height', type: 'dimension' },
{ name: 'max-width', label: 'Max Width', type: 'dimension' },
{ name: 'max-height', label: 'Max Height', type: 'dimension' },
],
},
{
name: 'Spacing',
icon: '📐',
props: [
{ name: 'margin-top', label: 'Margin Top', type: 'dimension' },
{ name: 'margin-right', label: 'Margin Right', type: 'dimension' },
{ name: 'margin-bottom', label: 'Margin Bottom', type: 'dimension' },
{ name: 'margin-left', label: 'Margin Left', type: 'dimension' },
{ name: 'padding-top', label: 'Padding Top', type: 'dimension' },
{ name: 'padding-right', label: 'Padding Right', type: 'dimension' },
{ name: 'padding-bottom', label: 'Padding Bottom', type: 'dimension' },
{ name: 'padding-left', label: 'Padding Left', type: 'dimension' },
],
},
{
name: 'Typography',
icon: '🔤',
props: [
{ name: 'font-size', label: 'Font Size', type: 'dimension' },
{ name: 'font-weight', label: 'Font Weight', type: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] },
{ name: 'font-style', label: 'Font Style', type: 'select', options: ['normal', 'italic', 'oblique'] },
{ name: 'line-height', label: 'Line Height', type: 'dimension' },
{ name: 'letter-spacing', label: 'Letter Spacing', type: 'dimension' },
{ name: 'word-spacing', label: 'Word Spacing', type: 'dimension' },
{ name: 'text-align', label: 'Text Align', type: 'select', options: ['left', 'center', 'right', 'justify'] },
{ name: 'text-transform', label: 'Text Transform', type: 'select', options: ['none', 'capitalize', 'uppercase', 'lowercase'] },
{ name: 'text-decoration', label: 'Text Decoration', type: 'select', options: ['none', 'underline', 'line-through', 'overline'] },
{ name: 'white-space', label: 'White Space', type: 'select', options: ['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap'] },
{ name: 'color', label: 'Color', type: 'color' },
],
},
{
name: 'Background',
icon: '🎨',
props: [
{ name: 'background-color', label: 'Background Color', type: 'color' },
{ name: 'background-repeat', label: 'Background Repeat', type: 'select', options: ['repeat', 'no-repeat', 'repeat-x', 'repeat-y'] },
{ name: 'background-position', label: 'Background Position', type: 'select', options: ['left top', 'center', 'right bottom'] },
{ name: 'background-size', label: 'Background Size', type: 'select', options: ['auto', 'cover', 'contain'] },
{ name: 'background-attachment', label: 'Background Attachment', type: 'select', options: ['scroll', 'fixed', 'local'] },
],
},
{
name: 'Border',
icon: '⬜',
props: [
{ name: 'border-width', label: 'Border Width', type: 'dimension' },
{ name: 'border-style', label: 'Border Style', type: 'select', options: ['none', 'solid', 'dashed', 'dotted', 'double'] },
{ name: 'border-color', label: 'Border Color', type: 'color' },
{ name: 'border-radius', label: 'Border Radius', type: 'dimension' },
{ name: 'border-top-left-radius', label: 'Top Left Radius', type: 'dimension' },
{ name: 'border-top-right-radius', label: 'Top Right Radius', type: 'dimension' },
{ name: 'border-bottom-left-radius', label: 'Bottom Left Radius', type: 'dimension' },
{ name: 'border-bottom-right-radius', label: 'Bottom Right Radius', type: 'dimension' },
],
},
{
name: 'Flexbox',
icon: '📦',
props: [
{ name: 'flex-direction', label: 'Flex Direction', type: 'select', options: ['row', 'column', 'row-reverse', 'column-reverse'] },
{ name: 'flex-wrap', label: 'Flex Wrap', type: 'select', options: ['nowrap', 'wrap', 'wrap-reverse'] },
{ name: 'justify-content', label: 'Justify Content', type: 'select', options: ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'] },
{ name: 'align-items', label: 'Align Items', type: 'select', options: ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'] },
{ name: 'gap', label: 'Gap', type: 'dimension' },
],
},
];
var DEFAULT_CLASSES = [
'title', 'text-center', 'expert-css', 'btn', 'btn-primary',
'container', 'row', 'col', 'header', 'footer', 'sidebar',
'card', 'nav', 'section', 'article', 'aside'
];
var ClassManager = function (_a) {
var elementClasses = _a.elementClasses, onUpdateClasses = _a.onUpdateClasses;
var _b = React.useState(false), showAddModal = _b[0], setShowAddModal = _b[1];
var _c = React.useState(''), newClassName = _c[0], setNewClassName = _c[1];
var removeClass = function (className) {
var newClasses = elementClasses.filter(function (c) { return c !== className; });
onUpdateClasses(newClasses);
};
var addClass = function (className) {
if (className && !elementClasses.includes(className)) {
var newClasses = __spreadArray(__spreadArray([], elementClasses, true), [className], false);
onUpdateClasses(newClasses);
}
};
var handleAddCustomClass = function () {
if (newClassName.trim()) {
addClass(newClassName.trim());
setNewClassName('');
setShowAddModal(false);
}
};
return (jsxRuntime.jsxs("div", { style: { padding: '16px', borderBottom: '1px solid #555' }, children: [jsxRuntime.jsxs("div", { style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '12px'
}, children: [jsxRuntime.jsx("h3", { style: { margin: 0, fontSize: '14px', fontWeight: 'bold' }, children: "Classes" }), jsxRuntime.jsxs("select", { style: {
padding: '4px 8px',
backgroundColor: '#555',
color: '#fff',
border: '1px solid #666',
borderRadius: '4px',
fontSize: '12px'
}, children: [jsxRuntime.jsx("option", { value: "", children: "- State -" }), jsxRuntime.jsx("option", { value: "hover", children: ":hover" }), jsxRuntime.jsx("option", { value: "focus", children: ":focus" }), jsxRuntime.jsx("option", { value: "active", children: ":active" })] })] }), jsxRuntime.jsx("div", { style: {
display: 'flex',
flexWrap: 'wrap',
gap: '6px',
marginBottom: '12px',
minHeight: '40px'
}, children: elementClasses.map(function (className) { return (jsxRuntime.jsx(ClassTag, { className: className, onRemove: removeClass }, className)); }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: '8px', alignItems: 'center' }, children: [jsxRuntime.jsx("button", { onClick: function () { return setShowAddModal(!showAddModal); }, style: {
width: '40px',
height: '40px',
backgroundColor: '#555',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
fontSize: '18px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: "+" }), showAddModal && (jsxRuntime.jsx("div", { style: { flex: 1 }, children: jsxRuntime.jsx("input", { type: "text", value: newClassName, onChange: function (e) { return setNewClassName(e.target.value); }, placeholder: "Enter class name", style: {
width: '100%',
padding: '6px 8px',
backgroundColor: '#555',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
fontSize: '12px'
}, onKeyPress: function (e) {
if (e.key === 'Enter') {
handleAddCustomClass();
}
} }) }))] }), showAddModal && (jsxRuntime.jsxs("div", { style: { marginTop: '8px' }, children: [jsxRuntime.jsx("div", { style: { fontSize: '12px', color: '#ccc', marginBottom: '4px' }, children: "Quick Add:" }), jsxRuntime.jsx("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '4px' }, children: DEFAULT_CLASSES.map(function (className) { return (jsxRuntime.jsx("button", { onClick: function () { return addClass(className); }, style: {
padding: '2px 6px',
backgroundColor: '#666',
border: '1px solid #777',
borderRadius: '3px',
color: '#fff',
fontSize: '10px',
cursor: 'pointer'
}, disabled: elementClasses.includes(className), children: className }, className)); }) })] }))] }));
};
var ElementInfo = function (_a) {
var selectedElement = _a.selectedElement, onCopy = _a.onCopy, onDelete = _a.onDelete, onOpenAssets = _a.onOpenAssets;
return (jsxRuntime.jsx("div", { style: { padding: '16px', borderBottom: '1px solid #555' }, children: jsxRuntime.jsxs("div", { style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
gap: '12px'
}, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("div", { style: { fontSize: '12px', color: '#ccc', marginBottom: '4px' }, children: "Selected:" }), jsxRuntime.jsxs("div", { style: {
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '4px'
}, children: [selectedElement.tagName.toLowerCase(), selectedElement.id && (jsxRuntime.jsxs("span", { style: { color: '#4CAF50' }, children: ["#", selectedElement.id] }))] }), jsxRuntime.jsx("div", { style: {
fontSize: '11px',
color: '#888',
wordBreak: 'break-all',
marginBottom: '8px'
}, children: selectedElement.selector }), selectedElement.textContent && (jsxRuntime.jsxs("div", { style: {
fontSize: '11px',
color: '#999',
fontStyle: 'italic',
overflow: 'hidden',
textOverflow: 'ellipsis'
}, children: ["\"", selectedElement.textContent, "...\""] }))] }), jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '4px' }, children: [selectedElement.tagName === 'IMG' && onOpenAssets && (jsxRuntime.jsx("button", { onClick: onOpenAssets, title: "Change image", style: {
padding: '8px',
backgroundColor: '#4CAF50',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px',
minWidth: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: "\uD83D\uDDBC\uFE0F" })), jsxRuntime.jsx("button", { onClick: onCopy, title: "Duplicate element", style: {
padding: '8px',
backgroundColor: '#555',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px',
minWidth: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: "\uD83D\uDCD1" }), jsxRuntime.jsx("button", { onClick: onDelete, title: "Delete element", style: {
padding: '8px',
backgroundColor: '#d32f2f',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px',
minWidth: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}, children: "\uD83D\uDDD1\uFE0F" })] })] }) }));
};
// src/components/StyleManager/SearchBar.tsx (Compact version)
var SearchBar = function (_a) {
var searchTerm = _a.searchTerm, onSearchChange = _a.onSearchChange;
return (jsxRuntime.jsx("div", { style: {
padding: '8px 12px', // Reduced padding
borderBottom: '1px solid #555',
backgroundColor: '#333'
}, children: jsxRuntime.jsx("input", { type: "text", value: searchTerm, onChange: function (e) { return onSearchChange(e.target.value); }, placeholder: "Search styles...", style: {
width: '100%',
padding: '6px 8px', // Reduced padding
backgroundColor: '#555',
border: '1px solid #666',
borderRadius: '3px',
color: '#fff',
fontSize: '11px', // Smaller font
outline: 'none'
} }) }));
};
var StyleInput = function (_a) {
var property = _a.property, value = _a.value, onChange = _a.onChange;
var inputStyles = {
base: {
padding: '6px 8px',
border: '1px solid #555',
backgroundColor: '#444',
color: '#fff',
borderRadius: '4px',
fontSize: '12px',
}
};
var getNumericValue = function (val) {
if (!val)
return property.max || 1;
var match = val.match(/^(\d*\.?\d+)/);
return match ? parseFloat(match[1]) : (property.max || 1);
};
var adjustValue = function (increment) {
var current = getNumericValue(value);
var step = property.step || 1;
var newVal = increment ? current + step : current - step;
var constrainedVal = newVal;
if (property.min !== undefined)
constrainedVal = Math.max(property.min, constrainedVal);
if (property.max !== undefined)
constrainedVal = Math.min(property.max, constrainedVal);
var unit = value ? value.replace(/^[\d.]+/, '') : 'px';
onChange("".concat(constrainedVal).concat(unit || 'px'));
};
var renderInput = function () {
var _a;
switch (property.type) {
case 'select':
return (jsxRuntime.jsxs("select", { value: value, onChange: function (e) { return onChange(e.target.value); }, style: __assign(__assign({}, inputStyles.base), { flex: 1 }), children: [jsxRuntime.jsx("option", { value: "", children: "auto" }), (_a = property.options) === null || _a === void 0 ? void 0 : _a.map(function (option) { return (jsxRuntime.jsx("option", { value: option, children: option }, option)); })] }));
case 'color':
return (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [jsxRuntime.jsx("input", { type: "color", value: value || '#000000', onChange: function (e) { return onChange(e.target.value); }, style: { width: '30px', height: '30px', border: 'none', borderRadius: '4px' } }), jsxRuntime.jsx("input", { type: "text", value: value, onChange: function (e) { return onChange(e.target.value); }, placeholder: "auto", style: __assign(__assign({}, inputStyles.base), { flex: 1 }) })] }));
case 'dimension':
return (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '4px' }, children: [jsxRuntime.jsx("input", { type: "text", value: value, onChange: function (e) { return onChange(e.target.value); }, placeholder: "auto", style: __assign(__assign({}, inputStyles.base), { flex: 1, textAlign: 'center' }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [jsxRuntime.jsx("button", { onClick: function () { return adjustValue(true); }, style: {
padding: '1px 3px',
border: '1px solid #555',
backgroundColor: '#555',
color: '#fff',
borderRadius: '2px',
fontSize: '8px',
cursor: 'pointer',
lineHeight: '10px',
width: '16px',
height: '12px'
}, children: "\u25B2" }), jsxRuntime.jsx("button", { onClick: function () { return adjustValue(false); }, style: {
padding: '1px 3px',
border: '1px solid #555',
backgroundColor: '#555',
color: '#fff',
borderRadius: '2px',
fontSize: '8px',
cursor: 'pointer',
lineHeight: '10px',
width: '16px',
height: '12px',
marginTop: '1px'
}, children: "\u25BC" })] })] }));
case 'slider':
var numValue = getNumericValue(value);
var min = property.min || 0;
var max = property.max || 1;
var step = property.step || 0.1;
return (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [jsxRuntime.jsx("input", { type: "range", min: min, max: max, step: step, value: numValue, onChange: function (e) { return onChange(e.target.value); }, style: { flex: 1 } }), jsxRuntime.jsx("span", { style: { color: '#ccc', fontSize: '12px', minWidth: '30px' }, children: numValue.toFixed(1) })] }));
default:
return (jsxRuntime.jsx("input", { type: "text", value: value, onChange: function (e) { return onChange(e.target.value); }, placeholder: "auto", style: __assign(__assign({}, inputStyles.base), { width: '100%' }) }));
}
};
return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: {
display: 'block',
fontSize: '12px',
color: '#ccc',
marginBottom: '4px',
fontWeight: 'normal'
}, children: property.label }), renderInput()] }));
};
var StyleSection = function (_a) {
var section = _a.section, isOpen = _a.isOpen, currentStyles = _a.currentStyles, onToggle = _a.onToggle, onStyleChange = _a.onStyleChange;
return (jsxRuntime.jsxs("div", { style: { borderBottom: '1px solid #555' }, children: [jsxRuntime.jsxs("div", { onClick: onToggle, style: {
padding: '8px 12px', // Reduced from whatever you had
backgroundColor: '#333',
borderBottom: '1px solid #555',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
fontSize: '12px', // Smaller font
fontWeight: 'bold',
minHeight: '32px' // Fixed smaller height
}, children: [jsxRuntime.jsx("span", { children: section.name }), jsxRuntime.jsx("span", { style: { fontSize: '10px', color: '#aaa' }, children: isOpen ? '▼' : '▶' })] }), isOpen && (jsxRuntime.jsx("div", { style: { padding: '16px' }, children: jsxRuntime.jsx("div", { style: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '12px',
alignItems: 'start'
}, children: section.props.map(function (prop) { return (jsxRuntime.jsx(StyleInput, { property: prop, value: currentStyles[prop.name] || '', onChange: function (value) { return onStyleChange(prop.name, value); } }, prop.name)); }) }) }))] }));
};
// src/components/StyleManager/index.tsx (Improved scrolling)
var StyleManager = function (_a) {
var currentStyles = _a.currentStyles, onStyleChange = _a.onStyleChange;
var _b = React.useState(''), searchTerm = _b[0], setSearchTerm = _b[1];
var _c = React.useState({
General: true,
Dimension: false,
Spacing: false,
Typography: false,
Background: false,
Border: false,
}), openSections = _c[0], setOpenSections = _c[1];
var filteredSections = React.useMemo(function () {
if (!searchTerm)
return STYLE_SECTIONS;
return STYLE_SECTIONS.map(function (section) { return (__assign(__assign({}, section), { props: section.props.filter(function (prop) {
return prop.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
prop.name.toLowerCase().includes(searchTerm.toLowerCase());
}) })); }).filter(function (section) { return section.props.length > 0; });
}, [searchTerm]);
var toggleSection = function (sectionName) {
setOpenSections(function (prev) {
var _a;
return (__assign(__assign({}, prev), (_a = {}, _a[sectionName] = !prev[sectionName], _a)));
});
};
return (jsxRuntime.jsxs("div", { style: {
height: '100%',
display: 'flex',
flexDirection: 'column'
}, children: [jsxRuntime.jsx("div", { style: { flexShrink: 0 }, children: jsxRuntime.jsx(SearchBar, { searchTerm: searchTerm, onSearchChange: setSearchTerm }) }), jsxRuntime.jsx("div", { style: {
flex: 1,
overflowY: 'auto',
overflowX: 'hidden'
}, children: filteredSections.map(function (section) { return (jsxRuntime.jsx(StyleSection, { section: section, isOpen: openSections[section.name] || false, currentStyles: currentStyles, onToggle: function () { return toggleSection(section.name); }, onStyleChange: onStyleChange }, section.name)); }) })] }));
};
// src/components/Editor/EditorSidebar.tsx (Fixed scrolling)
var EditorSidebar = function (_a) {
var _b;
var editor = _a.editor;
return (jsxRuntime.jsxs("div", { style: {
height: '100%',
backgroundColor: '#444',
color: '#fff',
display: 'flex',
flexDirection: 'column',
borderLeft: '1px solid #555'
}, children: [jsxRuntime.jsxs("div", { style: {
flexShrink: 0, // Don't shrink
backgroundColor: '#333',
borderBottom: '1px solid #555'
}, children: [jsxRuntime.jsx(ClassManager, { elementClasses: ((_b = editor.selectedElement) === null || _b === void 0 ? void 0 : _b.classes) || [], onUpdateClasses: editor.updateClasses }), editor.selectedElement && (jsxRuntime.jsx(ElementInfo, { selectedElement: editor.selectedElement, onCopy: editor.copyElement, onDelete: editor.deleteElement, onOpenAssets: function () { return editor.setShowAssetManager(true); } }))] }), jsxRuntime.jsx("div", { style: {
flex: 1,
overflow: 'hidden', // This container should not scroll
display: 'flex',
flexDirection: 'column'
}, children: jsxRuntime.jsx("div", { style: {
flex: 1,
overflowY: 'auto', // Only this inner div scrolls
overflowX: 'hidden'
}, children: jsxRuntime.jsx(StyleManager, { currentStyles: editor.currentStyles, onStyleChange: editor.updateStyle }) }) })] }));
};
// src/components/TabbedRightPanel/index.tsx (Auto-switch functionality)
var TabbedRightPanel = function (_a) {
var blocks = _a.blocks, showBlockManager = _a.showBlockManager, onAddBlock = _a.onAddBlock, editor = _a.editor, iframeRef = _a.iframeRef;
var _b = React.useState('blocks'), activeTab = _b[0], setActiveTab = _b[1];
// AUTO-SWITCH: Switch to styles when element is selected
React.useEffect(function () {
if (editor === null || editor === void 0 ? void 0 : editor.selectedElement) {
setActiveTab('styles');
}
}, [editor === null || editor === void 0 ? void 0 : editor.selectedElement]);
var tabs = [
{
id: 'blocks',
label: 'Blocks',
icon: '📦',
show: showBlockManager && blocks.length > 0
},
{
id: 'styles',
label: 'Styles',
icon: '🎨',
show: true
}
].filter(function (tab) { return tab.show; });
return (jsxRuntime.jsxs("div", { style: {
width: '350px',
height: '100%',
backgroundColor: '#444',
color: '#fff',
display: 'flex',
flexDirection: 'column',
borderLeft: '1px solid #555'
}, children: [jsxRuntime.jsx("div", { style: {
display: 'flex',
borderBottom: '1px solid #555',
backgroundColor: '#333'
}, children: tabs.map(function (tab) { return (jsxRuntime.jsxs("button", { onClick: function () { return setActiveTab(tab.id); }, style: {
flex: 1,
padding: '12px 8px',
backgroundColor: activeTab === tab.id ? '#555' : 'transparent',
color: activeTab === tab.id ? '#fff' : '#aaa',
border: 'none',
borderBottom: activeTab === tab.id ? '2px solid #007bff' : '2px solid transparent',
cursor: 'pointer',
fontSize: '12px',
fontWeight: activeTab === tab.id ? 'bold' : 'normal',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '4px',
transition: 'all 0.2s ease'
}, onMouseEnter: function (e) {
if (activeTab !== tab.id) {
e.currentTarget.style.backgroundColor = '#555';
e.currentTarget.style.color = '#fff';
}
}, onMouseLeave: function (e) {
if (activeTab !== tab.id) {
e.currentTarget.style.backgroundColor = 'transparent';
e.currentTarget.style.color = '#aaa';
}
}, children: [jsxRuntime.jsx("span", { style: { fontSize: '14px' }, children: tab.icon }), jsxRuntime.jsx("span", { children: tab.label })] }, tab.id)); }) }), jsxRuntime.jsxs("div", { style: { flex: 1, overflow: 'hidden' }, children: [activeTab === 'blocks' && showBlockManager && (jsxRuntime.jsx(DraggableBlockManager, { blocks: blocks, onAddBlock: onAddBlock, iframeRef: iframeRef })), activeTab === 'styles' && (jsxRuntime.jsx(EditorSidebar, { editor: editor }))] }), jsxRuntime.jsxs("div", { style: {
height: '30px',
backgroundColor: '#333',
borderTop: '1px solid #555',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '11px',
color: '#666'
}, children: [activeTab === 'blocks' && "".concat(blocks.length, " blocks available"), activeTab === 'styles' && ((editor === null || editor === void 0 ? void 0 : editor.selectedElement) ? "".concat(editor.selectedElement.tagName, " selected") : 'No element selected')] })] }));
};
// src/components/PreviewButton/index.tsx
var PreviewButton = function (_a) {
var isPreview = _a.isPreview, onTogglePreview = _a.onTogglePreview, style = _a.style;
return (jsxRuntime.jsxs("button", { onClick: onTogglePreview, style: __assign({ display: 'flex', alignItems: 'center', gap: '6px', padding: '8px 12px', backgroundColor: isPreview ? '#4CAF50' : '#555', border: '1px solid #666', borderRadius: '4px', color: '#fff', fontSize: '12px', cursor: 'pointer', transition: 'background-color 0.2s' }, style), title: isPreview ? 'Exit Preview Mode' : 'Enter Preview Mode', children: [jsxRuntime.jsx("span", { style: { fontSize: '14px' }, children: isPreview ? '🚫' : '👁️' }), jsxRuntime.jsx("span", { children: isPreview ? 'Exit Preview' : 'Preview' })] }));
};
// src/components/Editor/index.tsx (Enhanced with single header + clear canvas)
var Editor = function (_a) {
var canvasUrl = _a.canvasUrl, _b = _a.devices, devices = _b === void 0 ? defaultDevices : _b, _c = _a.blocks, blocks = _c === void 0 ? [] : _c, _d = _a.showDeviceManager, showDeviceManager = _d === void 0 ? true : _d, _e = _a.showBlockManager, showBlockManager = _e === void 0 ? true : _e, _f = _a.showPreview, showPreview = _f === void 0 ? true : _f;
var editor = useEditor();
var _g = React.useState(!canvasUrl && !(editor === null || editor === void 0 ? void 0 : editor.hasContent)), showHTMLLoader = _g[0], setShowHTMLLoader = _g[1];
var _h = React.useState(false), showExportModal = _h[0], setShowExportModal = _h[1];
var _j = React.useState(null), exportData = _j[0], setExportData = _j[1];
var _k = React.useState(devices[0]), currentDevice = _k[0], setCurrentDevice = _k[1];
var _l = React.useState(false), isPreviewMode = _l[0], setIsPreviewMode = _l[1];
// Handle preview toggle
var handlePreviewToggle = function () {
var _a, _b, _c;
if (!((_a = editor === null || editor === void 0 ? void 0 : editor.iframeRef) === null || _a === void 0 ? void 0 : _a.current))
return;
if (!isPreviewMode) {
setIsPreviewMode(true);
(_b = editor.iframeRef.current.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage({
type: 'TOGGLE_PREVIEW_MODE',
payload: { preview: true }
}, '*');
}
else {
setIsPreviewMode(false);
(_c = editor.iframeRef.current.contentWindow) === null || _c === void 0 ? void 0 : _c.postMessage({
type: 'TOGGLE_PREVIEW_MODE',
payload: { preview: false }
}, '*');
}
};
// Clear canvas functionality
var handleClearCanvas = function () {
var _a, _b, _c, _d;
if (window.confirm('Are you sure you want to clear the canvas? This action cannot be undone.')) {
(_c = (_b = (_a = editor === null || editor === void 0 ? void 0 : editor.iframeRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.contentWindow) === null || _c === void 0 ? void 0 : _c.postMessage({
type: 'CLEAR_CANVAS',
payload: {}
}, '*');
// Reset editor state
(_d = editor === null || editor === void 0 ? void 0 : editor.handleElementSelected) === null || _d === void 0 ? void 0 : _d.call(editor, undefined);
}
};
React.useEffect(function () {
var handleMessage = function (e) {
var _a, _b;
var _c = e.data, type = _c.type, payload = _c.payload;
switch (type) {
case 'ELEMENT_SELECTED':
if (!isPreviewMode) {
(_a = editor === null || editor === void 0 ? void 0 : editor.handleElementSelected) === null || _a === void 0 ? void 0 : _a.call(editor, payload);
}
break;
case 'OPEN_ASSET_MANAGER':
if (!isPreviewMode) {
(_b = editor === null || editor === void 0 ? void 0 : editor.handleOpenAssetManager) === null || _b === void 0 ? void 0 : _b.call(editor);
}
break;
case 'EXPORT_DATA':
setExportData(payload);
setShowExportModal(true);
break;
case 'BLOCK_ADDED_SUCCESS':
console.log('Block successfully added');
break;
}
};
window.addEventListener('message', handleMessage);
return function () { return window.removeEventListener('message', handleMessage); };
}, [editor, isPreviewMode]);
React.useEffect(function () {
var _a;
if (canvasUrl && ((_a = editor === null || editor === void 0 ? void 0 : editor.iframeRef) === null || _a === void 0 ? void 0 : _a.current)) {
editor.iframeRef.current.src = canvasUrl;
}
}, [canvasUrl, editor === null || editor === void 0 ? void 0 : editor.iframeRef]);
var handleHTMLSubmit = function (html) {
var _a;
(_a = editor === null || editor === void 0 ? void 0 : editor.loadHTMLContent) === null || _a === void 0 ? void 0 : _a.call(editor, html);
setShowHTMLLoader(false);
};
var handleAddBlock = function (content, block) {
if (editor === null || editor === void 0 ? void 0 : editor.addBlock) {
editor.addBlock(content);
}
};
({
width: typeof currentDevice.width === 'number' ? "".concat(currentDevice.width, "px") : currentDevice.width,
height: typeof currentDevice.height === 'number' ? "".concat(currentDevice.height, "px") : currentDevice.height});
return (jsxRuntime.jsxs("div", { style: {
display: 'flex',
flexDirection: 'column',
height: '100vh',
backgroundColor: '#2d2d2d',
overflow: 'hidden'
}, children: [!isPreviewMode && (jsxRuntime.jsxs("div", { className: "editor-header", style: {
height: '60px',
backgroundColor: '#333',
borderBottom: '1px solid #555',
display: 'flex',
alignItems: 'center',
padding: '0 16px',
gap: '12px',
flexShrink: 0,
zIndex: 1000
}, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '12px' }, children: [jsxRuntime.jsx("button", { onClick: function () { return setShowHTMLLoader(true); }, style: {
padding: '8px 12px',
backgroundColor: '#555',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px'
}, children: "\uD83D\uDCDD Load HTML" }), jsxRuntime.jsx("button", { onClick: handleClearCanvas, disabled: !(editor === null || editor === void 0 ? void 0 : editor.hasContent), style: {
padding: '8px 12px',
backgroundColor: (editor === null || editor === void 0 ? void 0 : editor.hasContent) ? '#dc3545' : '#444',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
cursor: (editor === null || editor === void 0 ? void 0 : editor.hasContent) ? 'pointer' : 'not-allowed',
fontSize: '12px'
}, title: "Clear Canvas", children: "\uD83D\uDDD1\uFE0F Clear" }), jsxRuntime.jsx("button", { onClick: function () { var _a; return (_a = editor === null || editor === void 0 ? void 0 : editor.exportHTML) === null || _a === void 0 ? void 0 : _a.call(editor); }, disabled: !(editor === null || editor === void 0 ? void 0 : editor.hasContent), style: {
padding: '8px 12px',
backgroundColor: (editor === null || editor === void 0 ? void 0 : editor.hasContent) ? '#4CAF50' : '#444',
border: '1px solid #666',
borderRadius: '4px',
color: '#fff',
cursor: (editor === null || editor === void 0 ? void 0 : editor.hasContent) ? 'pointer' : 'not-allowed',
fontSize: '12px'
}, children: "\uD83D\uDCE5 Export" })] }), showDeviceManager && (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [jsxRuntime.jsx("span", { style: { color: '#ccc', fontSize: '12px' }, children: "Device:" }), jsxRuntime.jsx("select", { value: currentDevice.id, onChange: function (e) {
var device = devices.find(function (d) { return d.id === e.target.value; });
if (device)
setCurrentDevice(device);
}, style: {
padding: '6px 12px',
backgroundColor: '#555',
color: '#fff',
border: '1px solid #666',
borderRadius: '4px',
fontSize: '12px',
minWidth: '120px'
}, children: devices.map(function (device) { return (jsxRuntime.jsxs("option", { value: device.id, children: [device.icon, " ", device.name] }, device.id)); }) })] })), jsxRuntime.jsxs("div", { style: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: '12px' }, children: [showPreview && (jsxRuntime.jsx(PreviewButton, { isPreview: isPreviewMode, onTogglePreview: handlePreviewToggle })), (editor === null || editor === void 0 ? void 0 : editor.isLoading) && (jsxRuntime.jsx("div", { style: { color: '#fff', fontSize: '12px' }, children: "Loading..." })), !(editor === null || editor === void 0 ? void 0 : editor.hasContent) && (jsxRuntime.jsx("div", { style: { color: '#aaa', fontSize: '12px' }, children: "Load HTML to start editing" }))] })] })), isPreviewMode && (jsxRuntime.jsx("div", { style: {
position: 'fixed',
top: '20px',
right: '20px',
zIndex: 10000
}, children: jsxRuntime.jsx(PreviewButton, { isPreview: isPreviewMode, onTogglePreview: handlePreviewToggle }) })), jsxRuntime.jsxs("div", { style: {
display: 'flex',
flex: 1,
overflow: 'hidden',
backgroundColor: isPreviewMode ? '#000' : '#2d2d2d'
}, children: [jsxRuntime.jsx("div", { style: {
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
backgroundColor: isPreviewMode ? '#000' : '#f5f5f5',
padding: isPreviewMode ? '0' : '20px',
overflow: 'auto',
minHeight: 0
}, children: jsxRuntime.jsx(EditorCanvas, { ref: editor === null || editor === void 0 ? void 0 : editor.iframeRef, src: "about:blank", style: isPreviewMode ? {
width: '100%',
height: '100%'
} : {
width: typeof currentDevice.width === 'number' ? "".concat(currentDevice.width, "px") : currentDevice.width,
height: typeof currentDevice.height === 'number' ? "".concat(currentDevice.height, "px") : currentDevice.height,
border: '1px solid #ddd',
borderRadius: '4px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
backgroundColor: '#fff',
minWidth: '300px',
minHeight: '200px'
} }) }), !isPreviewMode && (jsxRuntime.jsx(TabbedRightPanel, { blocks: blocks, showBlockManager: showBlockManager, onAddBlock: handleAddBlock, editor: editor, iframeRef: editor === null || editor === void 0 ? void 0 : editor.iframeRef }))] }), jsxRuntime.jsx(HTMLLoader, { isOpen: showHTMLLoader, onClose: function () { return setShowHTMLLoader(false); }, onSubmit: handleHTMLSubmit }), jsxRuntime.jsx(ExportModal, { isOpen: showExportModal, onClose: function () { return setShowExportModal(false); }, data: exportData }), jsxRuntime.jsx(AssetManager, { isOpen: !!(editor === null || editor === void 0 ? void 0 : editor.showAssetManager), onClose: function () { var _a; return (_a = editor === null || editor === void 0 ? void 0 : editor.setShowAssetManager) === null || _a === void 0 ? void 0 : _a.call(editor, false); }, onSelectAsset: (editor === null || editor === void 0 ? void 0 : editor.handleAssetSelect) || (function () { }), selectedElement: (editor === null || editor === void 0 ? void 0 : editor.selectedElement) || null })] }));
};
// src/components/BlockManager/index.tsx (Fixed)
var BlockManager = function (_a) {
var blocks = _a.blocks, onAddBlock = _a.onAddBlock, style = _a.style, categories = _a.categories;
var _b = React.useState('all'), selectedCategory = _b[0], setSelectedCategory = _b[1];
var filteredBlocks = selectedCategory === 'all'
? blocks
: blocks.filter(function (block) { return block.category === selectedCategory; });
var uniqueCategories = categories || __spreadArray(['all'], Array.from(new Set(blocks.map(function (b) { return b.category; }).filter(Boolean))), true);
return (jsxRuntime.jsxs("div", { style: __assign({ display: 'flex', flexDirection: 'column', height: '100%', backgroundColor: '#444', color: '#fff' }, style), children: [jsxRuntime.jsx("div", { style: {
padding: '12px 16px',
borderBottom: '1px solid #555',
backgroundColor: '#333'
}, children: jsxRuntime.jsx("h3", { style: { margin: 0, fontSize: '14px', fontWeight: 'bold' }, children: "Blocks" }) }), uniqueCategories.length > 1 && (jsxRuntime.jsx("div", { style: {
padding: '8px 16px',
borderBottom: '1px solid #555'
}, children: jsxRuntime.jsx("select", { value: selectedCategory, onChange: function (e) { return setSelectedCategory(e.target.value); }, style: {
width: '100%',
padding: '6px',
backgroundColor: '#555',
color: '#fff',
border: '1px solid #666',
borderRadius: '4px',
fontSize: '12px'
}, children: uniqueCategories.map(function (category) { return (jsxRuntime.jsx("option", { value: category, children: category ? category.charAt(0).toUpperCase() + category.slice(1) : 'Unknown' }, category)); }) }) })), jsxRuntime.jsx("div", { style: {
flex: 1,
overflowY: 'auto',
padding: '8px'
}, children: jsxRuntime.jsx("div", { style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(80px, 1fr))',
gap: '8px'
}, children: filteredBlocks.map(function (block) { return (jsxRuntime.jsxs("div", { onClick: function () { return onAddBlock(block.content, block); }, style: {
backgroundColor: '#555',
borderRadius: '6px',
padding: '8px',
cursor: 'pointer',
textAlign: 'center',
transition: 'background-color 0.2s',
border: '1px solid transparent'
}, onMouseEnter: function (e) {
e.currentTarget.style.backgroundColor = '#666';
e.currentTarget.style.borderColor = '#007bff';
}, onMouseLeave: function (e) {
e.currentTarget.style.backgroundColor = '#555';
e.currentTarget.style.borderColor = 'transparent';
}, title: block.label, children: [block.thumbnailUrl ? (jsxRuntime.jsx("img", { src: block.thumbnailUrl, alt: block.label, style: {
width: '100%',
height: '60px',
objectFit: 'cover',
borderRadius: '4px',
marginBottom: '4px'
} })) : (jsxRuntime.jsx("div", { style: {
width: '100%',
height: '60px',
backgroundColor: '#666',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '24px',
marginBottom: '4px'
}, children: block.icon || '📄' })), jsxRuntime.jsx("div", { style: { fontSize: '10px', color: '#ccc' }, children: block.label })] }, block.id)); }) }) })] }));
};
// src/components/ResizablePanel/index.tsx
var ResizablePanel = function (_a) {
var _b = _a.initialWidth, initialWidth = _b === void 0 ? 300 : _b, _c = _a.minWidth, minWidth = _c === void 0 ? 200 : _c, _d = _a.maxWidth, maxWidth = _d === void 0 ? 600 : _d, _e = _a.direction, direction = _e === void 0 ? 'right' : _e, children = _a.children, style = _a.style, onResize = _a.onResize;
var _f = React.useState(initialWidth), width = _f[0], setWidth = _f[1];
var _g = React.useState(false), isResizing = _g[0], setIsResizing = _g[1];
var resizerRef = React.useRef(null);
var panelRef = React.useRef(null);
var handleMouseDown = React.useCallback(function (e) {
e.preventDefault();
setIsResizing(true);
var startX = e.clientX;
var startWidth = width;
var handleMouseMove = function (e) {
e.preventDefault();
var newWidth;
if (direction === 'right') {
newWidth = startWidth + (e.clientX - startX);
}
else {
newWidth = startWidth - (e.clientX - startX);
}
if (newWidth >= minWidth && newWidth <= maxWidth) {
setWidth(newWidth);
onResize === null || onResize === void 0 ? void 0 : onResize(newWidth);
}
};
var handleMouseUp = function () {
setIsResizing(false);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, [width, minWidth, maxWidth, direction, onResize]);
return (jsxRuntime.jsxs("div", { ref: panelRef, style: __assign({ width: width, height: '100%', position: 'relative', display: 'flex', flexDirection: 'column', backgroundColor: '#444', color: '#fff' }, style), children: [children, jsxRuntime.jsx("div", { ref: resizerRef, onMouseDown: handleMouseDown, style: {
width: '5px',
cursor: 'ew-resize',
position: 'absolute',
top: 0,
bottom: 0,
right: direction === 'right' ? 0 : undefined,
left: direction === 'left' ? 0 : undefined,
backgroundColor: isResizing ? '#007bff' : 'transparent',
transition: 'background-color 0.2s',
zIndex: 1000
}, onMouseEnter: function (e) {
if (!isResizing) {
e.currentTarget.style.backgroundColor = '#666';
}
}, onMouseLeave: function (e) {
if (!isResizing) {
e.currentTarget.style.backgroundColor = 'transparent';
}
} })] }));
};
// src/components/PanelManager/index.tsx
var PanelManager = function (_a) {
var panels = _a.panels, _b = _a.activePanelIds, activePanelIds = _b === void 0 ? [] : _b, onPanelToggle = _a.onPanelToggle, children = _a.children;
var _c = React.useState(activePanelIds), localActivePanels = _c[0], setLocalActivePanels = _c[1];
var togglePanel = function (panelId) {
var isActive = localActivePanels.includes(panelId);
var newActivePanels = isActive
? localActivePanels.filter(function (id) { return id !== panelId; })
: __spreadArray(__spreadArray([], localActivePanels, true), [panelId], false);
setLocalActivePanels(newActivePanels);
onPanelToggle === null || onPanelToggle === void 0 ? void 0 : onPanelToggle(panelId, !isActive);
};
var leftPanels = panels.filter(function (p) { return p.position === 'left' && localActivePanels.includes(p.id); });
var rightPanels = panels.filter(function (p) { return p.position === 'right' && localActivePanels.includes(p.id); });
return (jsxRuntime.jsxs("div", { style: { display: 'flex', height: '100%' }, children: [leftPanels.map(function (panel) {
var PanelComponent = panel.component;
return (jsxRuntime.jsx("div", { style: { width: panel.defaultWidth || 300 }, children: jsxRuntime.jsx(PanelComponent, __assign({}, panel.props)) }, panel.id));
}), jsxRuntime.jsx("div", { style: { flex: 1 }, children: children }), rightPanels.map(function (panel) {
var PanelComponent = panel.component;
return (jsxRuntime.jsx("div", { style: { width: panel.defaultWidth || 300 }, children: jsxRuntime.jsx(PanelComponent, __assign({}, panel.props)) }, panel.id));
}), jsxRuntime.jsx("div", { style: {
position: 'absolute',
top: '10px',
right: '10px',
display: 'flex',
gap: '4px',
zIndex: 1000
}, children: panels.map(function (panel) { return (jsxRuntime.jsx("button", { onClick: function () { return togglePanel(panel.id); }, style: {
padding: '6px',
backgroundColor: localActivePanels.includes(panel.id) ? '#007bff' : '#555',
border: 'none',
borderRadius: '4px',
color: '#fff',
cursor: 'pointer',
fontSize: '12px'
}, title: panel.title, children: panel.icon || panel.title.charAt(0) }, panel.id)); }) })] }));
};
var getIframeHelperScript = function () {
return "\n<script>\n class IframeHelper {\n constructor() {\n this.selectedElement = null;\n this.init();\n }\n\n init() {\n document.addEventListener('mouseover', this.handleMouseOver.bind(this));\n document.addEventListener('mouseout', this.handleMouseOut.bind(this));\n document.addEventListener('click', this.handleClick.bind(this));\n document.addEventListener('dblclick', this.handleDoubleClick.bind(this));\n window.addEventListener('message', this.handleMessage.bind(this));\n }\n\n handleMouseOver(e) {\n if (e.target !== this.selectedElement) {\n e.target.style.outline = '2px dashed #007bff';\n }\n }\n\n handleMouseOut(e) {\n if (e.target !== this.selectedElement) {\n e.target.style.outline = '';\n }\n }\n\n handleClick(e) {\n e.stopPropagation();\n\n if (this.selectedElement) {\n this.selectedElement.style.outline = '';\n }\n\n this.selectedElement = e.target;\n this.selectedElement.style.outline = '2px solid #007bff';\n\n const editableTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN', 'DIV', 'A', 'BUTTON'];\n if (editableTags.includes(this.selectedElement.tagName)) {\n this.selectedElement.setAttribute('contenteditable', 'true');\n this.selectedElement.focus();\n }\n\n const elementData = this.getElementData(this.selectedElement);\n window.parent.postMessage({\n type: 'ELEMENT_SELECTED',\n payload: elementData,\n }, '*');\n }\n\n handleDoubleClick(e) {\n e.preventDefault();\n e.stopPropagation();\n\n if (e.target.tagName === 'IMG') {\n window.parent.postMessage({\n type: 'OPEN_ASSET_MANAGER',\n payload: {}\n }, '*');\n }\n }\n\n handleMessage(e) {\n const { type, payload } = e.data;\n \n switch (type) {\n case 'APPLY_STYLE':\n this.applyStyle(payload.selector, payload.styles);\n break;\n case 'UPDATE_CLASSES':\n this.updateClasses(payload.selector, payload.classes);\n break;\n case 'DELETE_ELEMENT':\n this.deleteElement(payload.selector);\n break;\n case 'DUPLICATE_ELEMENT':\n this.duplicateElement(payload.selector);\n break;\n }\n }\n\n duplicateElement(selector) {\n const el = document.querySelector(selector);\n if (el && el !== document.body && el !== document.documentElement) {\n const clone = el.cloneNode(true);\n \n clone.removeAttribute('id');\n const allElements = clone.querySelectorAll('*');\n allElements.forEach(child => child.removeAttribute('id'));\n \n el.parentNode.insertBefore(clone, el.nextSibling);\n \n setTimeout(() => {\n if (this.selectedElement) {\n this.selectedElement.style.outline = '';\n }\n this.selectedElement = clone;\n clone.style.outline = '2px solid #007bff';\n \n const elementData = this.getElementData(clone);\n window.parent.postMessage({\n type: 'ELEMENT_SELECTED',\n payload: elementData,\n }, '*');\n }, 100);\n }\n }\n\n getUniqueSelector(el) {\n if (!el) return '';\n const path = [];\n \n while (el && el.nodeType === Node.ELEMENT_NODE) {\n let selector = el.nodeName.toLowerCase();\n \n if (el.id) {\n selector += '#' + el.id;\n path.unshift(selector);\n break;\n } else {\n let sibling = el;\n let nth = 1;\n while ((sibling = sibling.previousElementSibling)) {\n if (sibling.nodeName === el.nodeName) nth++;\n }\n selector += ':nth-of-type(' + nth + ')';\n }\n \n path.unshift(selector);\n el = el.parentElement;\n }\n \n return path.join(' > ');\n }\n\n getComputedStyles(el) {\n const computed = window.getComputedStyle(el);\n const relevantStyles = {};\n \n const styleProps = [\n 'display', 'position', 'top', 'bottom', 'left', 'right', 'z-index',\n 'width', 'height', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',\n 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',\n 'font-size', 'font-weight', 'color', 'background-color', 'border-width',\n 'border-style', 'border-color', 'border-radius', 'opacity'\n ];\n \n styleProps.forEach(prop => {\n relevantStyles[prop] = computed.getPropertyValue(prop);\n });\n \n return relevantStyles;\n }\n\n getElementData(el) {\n return {\n selector: this.getUniqueSelector(el),\n tagName: el.tagName,\n id: el.id || '',\n classes: Array.from(el.classList),\n textContent: el.textContent?.trim().substring(0, 50) || '',\n computedStyles: this.getComputedStyles(el)\n };\n }\n\n applyStyle(selector, styles) {\n const el = document.querySelector(selector);\n if (el) {\n if (styles.src && el.tagName === 'IMG') {\n el.src = styles.src;\n delete styles.src;\n }\n \n Object.assign(el.style, styles);\n }\n }\n\n updateClasses(selector, classes) {\n const el = document.querySelector(selector);\n if (el) {\n el.className = classes.join(' ');\n }\n }\n\n deleteElement(selector) {\n const el = document.querySelector(selector);\n if (el && el !== document.body && el !== document.documentElement) {\n el.remove();\n }\n }\n }\n\n new IframeHelper();\n</script>\n";
};
exports.AssetManager = AssetManager;
exports.BlockManager = BlockManager;
exports.ClassManager = ClassManager;
exports.DEFAULT_CLASSES = DEFAULT_CLASSES;
exports.DeviceManager = DeviceManager;
exports.DraggableBlockManager = DraggableBlockManager;
exports.ElementInfo = ElementInfo;
exports.PanelManager = PanelManager;
exports.PreviewButton = PreviewButton;
exports.ResizablePanel = ResizablePanel;
exports.STYLE_SECTIONS = STYLE_SECTIONS;
exports.StyleManager = StyleManager;
exports.TabbedRightPanel = TabbedRightPanel;
exports.VisualEditor = Editor;
exports.defaultDevices = defaultDevices;
exports.getIframeHelperScript = getIframeHelperScript;
exports.useEditor = useEditor;
//# sourceMappingURL=index.js.map