Commit ·
fe9d17e
1
Parent(s): 21a54f1
feat: add state for form controls and iframe management
Browse filesIntroduce state variables for HTML input attributes and iframe configuration to support advanced element editing and persistent scroll positioning during reloads.
- src/App.tsx +273 -24
src/App.tsx
CHANGED
|
@@ -63,6 +63,19 @@ export default function App() {
|
|
| 63 |
const [htmlId, setHtmlId] = useState("");
|
| 64 |
const [htmlTitle, setHtmlTitle] = useState("");
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
// Styling Editor fields
|
| 67 |
const [fontSize, setFontSize] = useState("16");
|
| 68 |
const [fontWeight, setFontWeight] = useState("normal");
|
|
@@ -108,6 +121,12 @@ export default function App() {
|
|
| 108 |
// Component references
|
| 109 |
const canvasParentRef = useRef<HTMLDivElement>(null);
|
| 110 |
const canvasContentRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
// Initialize with landing page template on first run
|
| 113 |
useEffect(() => {
|
|
@@ -122,20 +141,38 @@ export default function App() {
|
|
| 122 |
pushHistoryState(defaultTemplate.html, "Template SaaS Carregado");
|
| 123 |
}, []);
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
// Sync canvas nodes inside our editing sandbox relative container when content changes
|
| 126 |
const visualHtml = useMemo(() => {
|
| 127 |
if (!htmlCode) return "";
|
| 128 |
return injectEditorIds(htmlCode);
|
| 129 |
}, [htmlCode]);
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
// Inject unique data-el-id to each node recursively (stable, deterministic naming system)
|
| 132 |
function injectEditorIds(htmlStr: string): string {
|
| 133 |
const parser = new DOMParser();
|
| 134 |
const doc = parser.parseFromString(htmlStr, "text/html");
|
| 135 |
let index = 0;
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
const traverse = (node: Element) => {
|
| 138 |
-
if (node.tagName && node.tagName !== "BODY" && node.tagName !== "HTML") {
|
| 139 |
if (!node.getAttribute("data-el-id")) {
|
| 140 |
node.setAttribute("data-el-id", `el_node_${index++}`);
|
| 141 |
} else {
|
|
@@ -155,9 +192,12 @@ export default function App() {
|
|
| 155 |
|
| 156 |
if (doc.body) {
|
| 157 |
Array.from(doc.body.children).forEach(traverse);
|
| 158 |
-
return doc.body.innerHTML;
|
| 159 |
}
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
}
|
| 162 |
|
| 163 |
// Remove editor identifiers before storing or exporting
|
|
@@ -170,19 +210,31 @@ export default function App() {
|
|
| 170 |
Array.from(node.children).forEach(traverse);
|
| 171 |
};
|
| 172 |
|
| 173 |
-
if (doc.
|
| 174 |
-
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
}
|
| 177 |
-
return htmlStr;
|
| 178 |
}
|
| 179 |
|
| 180 |
-
// Handle Canvas Operations directly
|
| 181 |
const editDOM = (currentHtml: string, action: (doc: Document) => void): string => {
|
| 182 |
const parser = new DOMParser();
|
| 183 |
const doc = parser.parseFromString(currentHtml, "text/html");
|
| 184 |
action(doc);
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
| 186 |
};
|
| 187 |
|
| 188 |
const pushHistoryState = (newHtml: string, description: string = "Edição") => {
|
|
@@ -321,8 +373,28 @@ export default function App() {
|
|
| 321 |
setImageAlt("");
|
| 322 |
}
|
| 323 |
|
| 324 |
-
//
|
| 325 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
const inline = el.style;
|
| 327 |
|
| 328 |
setTextColor(rgbToHex(inline.color || comp.color) || "#000000");
|
|
@@ -385,23 +457,31 @@ export default function App() {
|
|
| 385 |
// Run selection outlining effect calculations relative to canvas wrapper parent
|
| 386 |
useEffect(() => {
|
| 387 |
const updateOutlines = () => {
|
| 388 |
-
if (viewMode !== "visual" || !canvasParentRef.current) {
|
| 389 |
setSelectedRect(null);
|
| 390 |
setHoverRect(null);
|
| 391 |
return;
|
| 392 |
}
|
| 393 |
|
| 394 |
const canvasParent = canvasParentRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
// Update Selected Element bounding card
|
| 397 |
if (selectedId) {
|
| 398 |
-
const selectedEl =
|
|
|
|
|
|
|
|
|
|
| 399 |
if (selectedEl) {
|
| 400 |
const rect = selectedEl.getBoundingClientRect();
|
| 401 |
-
const parentRect = canvasParent.getBoundingClientRect();
|
| 402 |
setSelectedRect({
|
| 403 |
-
top: rect.top
|
| 404 |
-
left: rect.left
|
| 405 |
width: rect.width,
|
| 406 |
height: rect.height,
|
| 407 |
tagName: selectedEl.tagName.toLowerCase()
|
|
@@ -415,13 +495,15 @@ export default function App() {
|
|
| 415 |
|
| 416 |
// Update Hovered Element bounding card
|
| 417 |
if (hoverId && hoverId !== selectedId) {
|
| 418 |
-
const hoverEl =
|
|
|
|
|
|
|
|
|
|
| 419 |
if (hoverEl) {
|
| 420 |
const rect = hoverEl.getBoundingClientRect();
|
| 421 |
-
const parentRect = canvasParent.getBoundingClientRect();
|
| 422 |
setHoverRect({
|
| 423 |
-
top: rect.top
|
| 424 |
-
left: rect.left
|
| 425 |
width: rect.width,
|
| 426 |
height: rect.height,
|
| 427 |
tagName: hoverEl.tagName.toLowerCase()
|
|
@@ -449,7 +531,7 @@ export default function App() {
|
|
| 449 |
canvasObj.removeEventListener("scroll", updateOutlines);
|
| 450 |
}
|
| 451 |
};
|
| 452 |
-
}, [selectedId, hoverId, visualHtml, viewMode, responsiveMode]);
|
| 453 |
|
| 454 |
// Synchronize visual modifications back to code state
|
| 455 |
const handleApplyPropertyChange = (updates: {
|
|
@@ -480,8 +562,14 @@ export default function App() {
|
|
| 480 |
Object.entries(updates.attributes).forEach(([key, val]) => {
|
| 481 |
if (val === "" || val === undefined) {
|
| 482 |
el.removeAttribute(key);
|
|
|
|
|
|
|
|
|
|
| 483 |
} else {
|
| 484 |
el.setAttribute(key, val);
|
|
|
|
|
|
|
|
|
|
| 485 |
}
|
| 486 |
});
|
| 487 |
}
|
|
@@ -842,6 +930,18 @@ export default function App() {
|
|
| 842 |
/>
|
| 843 |
</label>
|
| 844 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 845 |
<button
|
| 846 |
onClick={handleCopyCode}
|
| 847 |
className="flex items-center gap-1.5 px-4 h-8 bg-[#212427] hover:bg-zinc-800 border border-zinc-700/60 rounded text-xs font-medium cursor-pointer transition-all"
|
|
@@ -1155,6 +1255,81 @@ export default function App() {
|
|
| 1155 |
</div>
|
| 1156 |
)}
|
| 1157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1158 |
{/* Custom classes builder - allows direct styling over tailwind classes context */}
|
| 1159 |
<div>
|
| 1160 |
<label className="block text-[10px] text-zinc-400 font-bold mb-1">Classes CSS (Tailwind)</label>
|
|
@@ -1767,11 +1942,29 @@ export default function App() {
|
|
| 1767 |
<span className="text-[9px] bg-[#0e0f10] border border-zinc-850 px-2 py-0.5 rounded text-pink-500 font-mono tracking-widest uppercase">Editável</span>
|
| 1768 |
</div>
|
| 1769 |
<textarea
|
| 1770 |
-
value={
|
| 1771 |
-
onChange={(e) =>
|
| 1772 |
className="w-full flex-1 bg-[#0e0f10] border border-zinc-800 focus:border-pink-500 rounded p-4 font-mono text-xs text-zinc-200 outline-none resize-none select-text scrollbar leading-relaxed selection:bg-pink-600/30 selection:text-white"
|
| 1773 |
-
placeholder="
|
| 1774 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1775 |
</div>
|
| 1776 |
)}
|
| 1777 |
|
|
@@ -1959,6 +2152,62 @@ export default function App() {
|
|
| 1959 |
|
| 1960 |
</main>
|
| 1961 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1962 |
</div>
|
| 1963 |
);
|
| 1964 |
}
|
|
|
|
| 63 |
const [htmlId, setHtmlId] = useState("");
|
| 64 |
const [htmlTitle, setHtmlTitle] = useState("");
|
| 65 |
|
| 66 |
+
// Form / Input fields state
|
| 67 |
+
const [inputPlaceholder, setInputPlaceholder] = useState("");
|
| 68 |
+
const [inputValue, setInputValue] = useState("");
|
| 69 |
+
const [inputName, setInputName] = useState("");
|
| 70 |
+
const [inputType, setInputType] = useState("");
|
| 71 |
+
const [inputChecked, setInputChecked] = useState(false);
|
| 72 |
+
const [inputDataPoints, setInputDataPoints] = useState("");
|
| 73 |
+
|
| 74 |
+
// Paste / Import modal and Code controls state
|
| 75 |
+
const [showPasteModal, setShowPasteModal] = useState(false);
|
| 76 |
+
const [pastedHtml, setPastedHtml] = useState("");
|
| 77 |
+
const [localCode, setLocalCode] = useState("");
|
| 78 |
+
|
| 79 |
// Styling Editor fields
|
| 80 |
const [fontSize, setFontSize] = useState("16");
|
| 81 |
const [fontWeight, setFontWeight] = useState("normal");
|
|
|
|
| 121 |
// Component references
|
| 122 |
const canvasParentRef = useRef<HTMLDivElement>(null);
|
| 123 |
const canvasContentRef = useRef<HTMLDivElement>(null);
|
| 124 |
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
| 125 |
+
|
| 126 |
+
// Scroll retention for iframe reloads
|
| 127 |
+
const lastScrollTopRef = useRef<number>(0);
|
| 128 |
+
const lastScrollLeftRef = useRef<number>(0);
|
| 129 |
+
const [outlineTrigger, setOutlineTrigger] = useState<number>(0);
|
| 130 |
|
| 131 |
// Initialize with landing page template on first run
|
| 132 |
useEffect(() => {
|
|
|
|
| 141 |
pushHistoryState(defaultTemplate.html, "Template SaaS Carregado");
|
| 142 |
}, []);
|
| 143 |
|
| 144 |
+
// Check if coding input contains full-page indicators wrapper
|
| 145 |
+
const hasFullHtmlWrapper = (htmlStr: string): boolean => {
|
| 146 |
+
const lower = htmlStr.toLowerCase();
|
| 147 |
+
return lower.includes("<doctype") || lower.includes("<html") || lower.includes("<body") || lower.includes("<head") || lower.includes("<style") || lower.includes("<script");
|
| 148 |
+
};
|
| 149 |
+
|
| 150 |
// Sync canvas nodes inside our editing sandbox relative container when content changes
|
| 151 |
const visualHtml = useMemo(() => {
|
| 152 |
if (!htmlCode) return "";
|
| 153 |
return injectEditorIds(htmlCode);
|
| 154 |
}, [htmlCode]);
|
| 155 |
|
| 156 |
+
// Sync manual local code value when html state changes
|
| 157 |
+
useEffect(() => {
|
| 158 |
+
setLocalCode(cleanHTMLForExport(htmlCode));
|
| 159 |
+
}, [htmlCode]);
|
| 160 |
+
|
| 161 |
// Inject unique data-el-id to each node recursively (stable, deterministic naming system)
|
| 162 |
function injectEditorIds(htmlStr: string): string {
|
| 163 |
const parser = new DOMParser();
|
| 164 |
const doc = parser.parseFromString(htmlStr, "text/html");
|
| 165 |
let index = 0;
|
| 166 |
|
| 167 |
+
// Auto-inject Tailwind CDN if missing inside head to support preset templates
|
| 168 |
+
if (doc.head && !doc.head.innerHTML.includes("tailwindcss.com")) {
|
| 169 |
+
const tailwindScript = doc.createElement("script");
|
| 170 |
+
tailwindScript.src = "https://cdn.tailwindcss.com";
|
| 171 |
+
doc.head.appendChild(tailwindScript);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
const traverse = (node: Element) => {
|
| 175 |
+
if (node.tagName && node.tagName !== "BODY" && node.tagName !== "HTML" && node.tagName !== "STYLE" && node.tagName !== "SCRIPT" && node.tagName !== "HEAD" && node.tagName !== "META" && node.tagName !== "TITLE" && node.tagName !== "LINK") {
|
| 176 |
if (!node.getAttribute("data-el-id")) {
|
| 177 |
node.setAttribute("data-el-id", `el_node_${index++}`);
|
| 178 |
} else {
|
|
|
|
| 192 |
|
| 193 |
if (doc.body) {
|
| 194 |
Array.from(doc.body.children).forEach(traverse);
|
|
|
|
| 195 |
}
|
| 196 |
+
|
| 197 |
+
if (hasFullHtmlWrapper(htmlStr)) {
|
| 198 |
+
return doc.documentElement.outerHTML;
|
| 199 |
+
}
|
| 200 |
+
return doc.body ? doc.body.innerHTML : htmlStr;
|
| 201 |
}
|
| 202 |
|
| 203 |
// Remove editor identifiers before storing or exporting
|
|
|
|
| 210 |
Array.from(node.children).forEach(traverse);
|
| 211 |
};
|
| 212 |
|
| 213 |
+
if (doc.documentElement) {
|
| 214 |
+
traverse(doc.documentElement);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
// Remove auto-injected Tailwind script block on export if requested
|
| 218 |
+
if (doc.head) {
|
| 219 |
+
const scripts = doc.head.querySelectorAll("script[src*='tailwindcss.com']");
|
| 220 |
+
scripts.forEach(s => s.remove());
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
if (hasFullHtmlWrapper(htmlStr)) {
|
| 224 |
+
return doc.documentElement.outerHTML;
|
| 225 |
}
|
| 226 |
+
return doc.body ? doc.body.innerHTML : htmlStr;
|
| 227 |
}
|
| 228 |
|
| 229 |
+
// Handle Canvas Operations directly over documents
|
| 230 |
const editDOM = (currentHtml: string, action: (doc: Document) => void): string => {
|
| 231 |
const parser = new DOMParser();
|
| 232 |
const doc = parser.parseFromString(currentHtml, "text/html");
|
| 233 |
action(doc);
|
| 234 |
+
if (hasFullHtmlWrapper(currentHtml)) {
|
| 235 |
+
return doc.documentElement.outerHTML;
|
| 236 |
+
}
|
| 237 |
+
return doc.body ? doc.body.innerHTML : currentHtml;
|
| 238 |
};
|
| 239 |
|
| 240 |
const pushHistoryState = (newHtml: string, description: string = "Edição") => {
|
|
|
|
| 373 |
setImageAlt("");
|
| 374 |
}
|
| 375 |
|
| 376 |
+
// Form Input options
|
| 377 |
+
const isFormTag = ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "OPTION"].includes(el.tagName);
|
| 378 |
+
if (isFormTag) {
|
| 379 |
+
setInputPlaceholder(el.getAttribute("placeholder") || "");
|
| 380 |
+
setInputValue(el.getAttribute("value") || "");
|
| 381 |
+
setInputName(el.getAttribute("name") || "");
|
| 382 |
+
setInputType(el.getAttribute("type") || "");
|
| 383 |
+
setInputChecked((el as any).checked || el.hasAttribute("checked"));
|
| 384 |
+
setInputDataPoints(el.getAttribute("data-points") || "");
|
| 385 |
+
} else {
|
| 386 |
+
setInputPlaceholder("");
|
| 387 |
+
setInputValue("");
|
| 388 |
+
setInputName("");
|
| 389 |
+
setInputType("");
|
| 390 |
+
setInputChecked(false);
|
| 391 |
+
setInputDataPoints("");
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
// Style Parsing helper, stripping typical px suffixes, compatible with iframe
|
| 395 |
+
const comp = el.ownerDocument?.defaultView
|
| 396 |
+
? el.ownerDocument.defaultView.getComputedStyle(el)
|
| 397 |
+
: window.getComputedStyle(el);
|
| 398 |
const inline = el.style;
|
| 399 |
|
| 400 |
setTextColor(rgbToHex(inline.color || comp.color) || "#000000");
|
|
|
|
| 457 |
// Run selection outlining effect calculations relative to canvas wrapper parent
|
| 458 |
useEffect(() => {
|
| 459 |
const updateOutlines = () => {
|
| 460 |
+
if (viewMode !== "visual" || !canvasParentRef.current || !canvasContentRef.current) {
|
| 461 |
setSelectedRect(null);
|
| 462 |
setHoverRect(null);
|
| 463 |
return;
|
| 464 |
}
|
| 465 |
|
| 466 |
const canvasParent = canvasParentRef.current;
|
| 467 |
+
const viewport = canvasContentRef.current;
|
| 468 |
+
const iframe = iframeRef.current || canvasParent.querySelector("iframe");
|
| 469 |
+
const doc = iframe?.contentDocument || iframe?.contentWindow?.document;
|
| 470 |
+
|
| 471 |
+
const viewportRect = viewport.getBoundingClientRect();
|
| 472 |
+
const iframeRect = iframe ? iframe.getBoundingClientRect() : { top: 0, left: 0 };
|
| 473 |
|
| 474 |
// Update Selected Element bounding card
|
| 475 |
if (selectedId) {
|
| 476 |
+
const selectedEl = doc
|
| 477 |
+
? doc.querySelector(`[data-el-id="${selectedId}"]`)
|
| 478 |
+
: canvasParent.querySelector(`[data-el-id="${selectedId}"]`);
|
| 479 |
+
|
| 480 |
if (selectedEl) {
|
| 481 |
const rect = selectedEl.getBoundingClientRect();
|
|
|
|
| 482 |
setSelectedRect({
|
| 483 |
+
top: rect.top + (iframeRect.top - viewportRect.top),
|
| 484 |
+
left: rect.left + (iframeRect.left - viewportRect.left),
|
| 485 |
width: rect.width,
|
| 486 |
height: rect.height,
|
| 487 |
tagName: selectedEl.tagName.toLowerCase()
|
|
|
|
| 495 |
|
| 496 |
// Update Hovered Element bounding card
|
| 497 |
if (hoverId && hoverId !== selectedId) {
|
| 498 |
+
const hoverEl = doc
|
| 499 |
+
? doc.querySelector(`[data-el-id="${hoverId}"]`)
|
| 500 |
+
: canvasParent.querySelector(`[data-el-id="${hoverId}"]`);
|
| 501 |
+
|
| 502 |
if (hoverEl) {
|
| 503 |
const rect = hoverEl.getBoundingClientRect();
|
|
|
|
| 504 |
setHoverRect({
|
| 505 |
+
top: rect.top + (iframeRect.top - viewportRect.top),
|
| 506 |
+
left: rect.left + (iframeRect.left - viewportRect.left),
|
| 507 |
width: rect.width,
|
| 508 |
height: rect.height,
|
| 509 |
tagName: hoverEl.tagName.toLowerCase()
|
|
|
|
| 531 |
canvasObj.removeEventListener("scroll", updateOutlines);
|
| 532 |
}
|
| 533 |
};
|
| 534 |
+
}, [selectedId, hoverId, visualHtml, viewMode, responsiveMode, outlineTrigger]);
|
| 535 |
|
| 536 |
// Synchronize visual modifications back to code state
|
| 537 |
const handleApplyPropertyChange = (updates: {
|
|
|
|
| 562 |
Object.entries(updates.attributes).forEach(([key, val]) => {
|
| 563 |
if (val === "" || val === undefined) {
|
| 564 |
el.removeAttribute(key);
|
| 565 |
+
if (key === "checked" && el.tagName === "INPUT") {
|
| 566 |
+
(el as HTMLInputElement).checked = false;
|
| 567 |
+
}
|
| 568 |
} else {
|
| 569 |
el.setAttribute(key, val);
|
| 570 |
+
if (key === "checked" && el.tagName === "INPUT") {
|
| 571 |
+
(el as HTMLInputElement).checked = true;
|
| 572 |
+
}
|
| 573 |
}
|
| 574 |
});
|
| 575 |
}
|
|
|
|
| 930 |
/>
|
| 931 |
</label>
|
| 932 |
|
| 933 |
+
<button
|
| 934 |
+
onClick={() => {
|
| 935 |
+
setPastedHtml(cleanHTMLForExport(htmlCode));
|
| 936 |
+
setShowPasteModal(true);
|
| 937 |
+
}}
|
| 938 |
+
className="flex items-center gap-1.5 px-4 h-8 bg-[#212427] hover:bg-zinc-800 border border-zinc-700/60 hover:border-zinc-500 rounded text-xs font-medium cursor-pointer transition-all active:scale-95 duration-100"
|
| 939 |
+
title="Colar ou editar código HTML completo"
|
| 940 |
+
>
|
| 941 |
+
<Code className="w-3.5 h-3.5 text-pink-500" />
|
| 942 |
+
<span>Colar HTML</span>
|
| 943 |
+
</button>
|
| 944 |
+
|
| 945 |
<button
|
| 946 |
onClick={handleCopyCode}
|
| 947 |
className="flex items-center gap-1.5 px-4 h-8 bg-[#212427] hover:bg-zinc-800 border border-zinc-700/60 rounded text-xs font-medium cursor-pointer transition-all"
|
|
|
|
| 1255 |
</div>
|
| 1256 |
)}
|
| 1257 |
|
| 1258 |
+
{/* Form / Input settings */}
|
| 1259 |
+
{["input", "textarea", "select", "button", "option"].includes(selectedRect?.tagName || "") && (
|
| 1260 |
+
<div className="space-y-3">
|
| 1261 |
+
<div>
|
| 1262 |
+
<label className="block text-[10px] text-zinc-400 font-bold mb-1">Nome do Campo (name)</label>
|
| 1263 |
+
<input
|
| 1264 |
+
type="text"
|
| 1265 |
+
value={inputName}
|
| 1266 |
+
onChange={(e) => {
|
| 1267 |
+
setInputName(e.target.value);
|
| 1268 |
+
handleApplyPropertyChange({ attributes: { name: e.target.value } }, "Alterar Nome Campo");
|
| 1269 |
+
}}
|
| 1270 |
+
className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
|
| 1271 |
+
/>
|
| 1272 |
+
</div>
|
| 1273 |
+
<div>
|
| 1274 |
+
<label className="block text-[10px] text-zinc-400 font-bold mb-1">Valor do Campo (value)</label>
|
| 1275 |
+
<input
|
| 1276 |
+
type="text"
|
| 1277 |
+
value={inputValue}
|
| 1278 |
+
onChange={(e) => {
|
| 1279 |
+
setInputValue(e.target.value);
|
| 1280 |
+
handleApplyPropertyChange({ attributes: { value: e.target.value } }, "Alterar Valor Campo");
|
| 1281 |
+
}}
|
| 1282 |
+
className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
|
| 1283 |
+
/>
|
| 1284 |
+
</div>
|
| 1285 |
+
{["input", "textarea"].includes(selectedRect?.tagName || "") && (
|
| 1286 |
+
<div>
|
| 1287 |
+
<label className="block text-[10px] text-zinc-400 font-bold mb-1">Inserir Placeholder</label>
|
| 1288 |
+
<input
|
| 1289 |
+
type="text"
|
| 1290 |
+
value={inputPlaceholder}
|
| 1291 |
+
onChange={(e) => {
|
| 1292 |
+
setInputPlaceholder(e.target.value);
|
| 1293 |
+
handleApplyPropertyChange({ attributes: { placeholder: e.target.value } }, "Alterar Placeholder");
|
| 1294 |
+
}}
|
| 1295 |
+
className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
|
| 1296 |
+
/>
|
| 1297 |
+
</div>
|
| 1298 |
+
)}
|
| 1299 |
+
{inputType === "checkbox" && (
|
| 1300 |
+
<div className="flex items-center gap-2 py-1">
|
| 1301 |
+
<input
|
| 1302 |
+
type="checkbox"
|
| 1303 |
+
checked={inputChecked}
|
| 1304 |
+
id="cb-checked-property"
|
| 1305 |
+
onChange={(e) => {
|
| 1306 |
+
setInputChecked(e.target.checked);
|
| 1307 |
+
handleApplyPropertyChange({
|
| 1308 |
+
attributes: {
|
| 1309 |
+
checked: e.target.checked ? "checked" : ""
|
| 1310 |
+
}
|
| 1311 |
+
}, "Marcar/Desmarcar Checkbox");
|
| 1312 |
+
}}
|
| 1313 |
+
className="w-4 h-4 rounded text-pink-600 focus:ring-pink-500 bg-zinc-800"
|
| 1314 |
+
/>
|
| 1315 |
+
<label htmlFor="cb-checked-property" className="text-[10px] text-zinc-400 font-bold cursor-pointer">Marcar por Padrão (Checked)</label>
|
| 1316 |
+
</div>
|
| 1317 |
+
)}
|
| 1318 |
+
<div>
|
| 1319 |
+
<label className="block text-[10px] text-zinc-400 font-bold mb-1">Pontos Personalizados (data-points)</label>
|
| 1320 |
+
<input
|
| 1321 |
+
type="text"
|
| 1322 |
+
value={inputDataPoints}
|
| 1323 |
+
onChange={(e) => {
|
| 1324 |
+
setInputDataPoints(e.target.value);
|
| 1325 |
+
handleApplyPropertyChange({ attributes: { "data-points": e.target.value } }, "Alterar data-points");
|
| 1326 |
+
}}
|
| 1327 |
+
className="w-full bg-[#181a1c] border border-zinc-800 focus:border-pink-500 rounded px-2 py-1 text-xs text-white outline-none"
|
| 1328 |
+
/>
|
| 1329 |
+
</div>
|
| 1330 |
+
</div>
|
| 1331 |
+
)}
|
| 1332 |
+
|
| 1333 |
{/* Custom classes builder - allows direct styling over tailwind classes context */}
|
| 1334 |
<div>
|
| 1335 |
<label className="block text-[10px] text-zinc-400 font-bold mb-1">Classes CSS (Tailwind)</label>
|
|
|
|
| 1942 |
<span className="text-[9px] bg-[#0e0f10] border border-zinc-850 px-2 py-0.5 rounded text-pink-500 font-mono tracking-widest uppercase">Editável</span>
|
| 1943 |
</div>
|
| 1944 |
<textarea
|
| 1945 |
+
value={localCode}
|
| 1946 |
+
onChange={(e) => setLocalCode(e.target.value)}
|
| 1947 |
className="w-full flex-1 bg-[#0e0f10] border border-zinc-800 focus:border-pink-500 rounded p-4 font-mono text-xs text-zinc-200 outline-none resize-none select-text scrollbar leading-relaxed selection:bg-pink-600/30 selection:text-white"
|
| 1948 |
+
placeholder="Cole ou digite o seu HTML completo aqui..."
|
| 1949 |
/>
|
| 1950 |
+
<div className="flex items-center justify-end gap-3 mt-3 select-none">
|
| 1951 |
+
{localCode !== cleanHTMLForExport(htmlCode) && (
|
| 1952 |
+
<button
|
| 1953 |
+
onClick={() => setLocalCode(cleanHTMLForExport(htmlCode))}
|
| 1954 |
+
className="px-4 py-1.5 bg-zinc-800 hover:bg-zinc-750 text-zinc-350 rounded text-xs font-semibold cursor-pointer transition-all"
|
| 1955 |
+
>
|
| 1956 |
+
Descartar
|
| 1957 |
+
</button>
|
| 1958 |
+
)}
|
| 1959 |
+
<button
|
| 1960 |
+
onClick={() => pushHistoryState(localCode, "Editar Código Manual")}
|
| 1961 |
+
disabled={localCode === cleanHTMLForExport(htmlCode)}
|
| 1962 |
+
className="px-5 py-1.5 bg-pink-600 hover:bg-pink-550 text-white rounded text-xs font-bold disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer transition-all flex items-center gap-1.5"
|
| 1963 |
+
>
|
| 1964 |
+
<Check className="w-3.5 h-3.5" />
|
| 1965 |
+
Aplicar Código Fonte
|
| 1966 |
+
</button>
|
| 1967 |
+
</div>
|
| 1968 |
</div>
|
| 1969 |
)}
|
| 1970 |
|
|
|
|
| 2152 |
|
| 2153 |
</main>
|
| 2154 |
|
| 2155 |
+
{/* Dynamic Paste HTML Modal Dialog */}
|
| 2156 |
+
{showPasteModal && (
|
| 2157 |
+
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4 animate-fade-in backdrop-blur-sm select-none">
|
| 2158 |
+
<div className="bg-[#181a1c] border border-zinc-800 rounded-lg max-w-2xl w-full flex flex-col shadow-2xl overflow-hidden animate-scale-up">
|
| 2159 |
+
<div className="flex items-center justify-between px-5 py-4 border-b border-zinc-850">
|
| 2160 |
+
<div className="flex items-center gap-2">
|
| 2161 |
+
<Code className="w-4 h-4 text-pink-500" />
|
| 2162 |
+
<h3 className="text-sm font-bold text-white uppercase tracking-wider font-sans">
|
| 2163 |
+
Colar ou Editar Código HTML
|
| 2164 |
+
</h3>
|
| 2165 |
+
</div>
|
| 2166 |
+
<button
|
| 2167 |
+
onClick={() => setShowPasteModal(false)}
|
| 2168 |
+
className="text-zinc-500 hover:text-white transition-colors cursor-pointer text-sm font-bold p-1 hover:bg-zinc-800 rounded font-sans"
|
| 2169 |
+
>
|
| 2170 |
+
✕
|
| 2171 |
+
</button>
|
| 2172 |
+
</div>
|
| 2173 |
+
|
| 2174 |
+
<div className="p-5 space-y-4">
|
| 2175 |
+
<span className="block text-[11px] text-zinc-400 font-sans leading-relaxed">
|
| 2176 |
+
Insira o código HTML completo da página abaixo (incluindo estilos, CDN links e scripts). O editor irá carregar, interpretar e sandboxear o documento automaticamente.
|
| 2177 |
+
</span>
|
| 2178 |
+
|
| 2179 |
+
<textarea
|
| 2180 |
+
value={pastedHtml}
|
| 2181 |
+
onChange={(e) => setPastedHtml(e.target.value)}
|
| 2182 |
+
rows={12}
|
| 2183 |
+
placeholder="Cole seu código HTML aqui..."
|
| 2184 |
+
className="w-full bg-[#0e0f10] border border-zinc-800 focus:border-pink-500 rounded p-4 font-mono text-xs text-zinc-200 outline-none select-text resize-none scrollbar leading-relaxed"
|
| 2185 |
+
/>
|
| 2186 |
+
</div>
|
| 2187 |
+
|
| 2188 |
+
<div className="flex items-center justify-end gap-3 px-5 py-4 bg-[#0e0f10] border-t border-zinc-850">
|
| 2189 |
+
<button
|
| 2190 |
+
onClick={() => setShowPasteModal(false)}
|
| 2191 |
+
className="px-4 py-2 bg-zinc-800 hover:bg-zinc-750 text-zinc-350 rounded text-xs font-semibold cursor-pointer transition-all uppercase tracking-wider h-9"
|
| 2192 |
+
>
|
| 2193 |
+
Cancelar
|
| 2194 |
+
</button>
|
| 2195 |
+
<button
|
| 2196 |
+
onClick={() => {
|
| 2197 |
+
parseAndLoadHTML(pastedHtml);
|
| 2198 |
+
setShowPasteModal(false);
|
| 2199 |
+
}}
|
| 2200 |
+
disabled={!pastedHtml.trim()}
|
| 2201 |
+
className="flex items-center gap-2 px-5 py-2 bg-pink-600 hover:bg-pink-550 text-white rounded text-xs font-bold disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer transition-all uppercase tracking-wider h-9"
|
| 2202 |
+
>
|
| 2203 |
+
<Check className="w-4 h-4" />
|
| 2204 |
+
Carregar HTML
|
| 2205 |
+
</button>
|
| 2206 |
+
</div>
|
| 2207 |
+
</div>
|
| 2208 |
+
</div>
|
| 2209 |
+
)}
|
| 2210 |
+
|
| 2211 |
</div>
|
| 2212 |
);
|
| 2213 |
}
|