555 lines
21 KiB
React
555 lines
21 KiB
React
|
|
import { useEffect, useState } from "react";
|
|||
|
|
import useEditorStore from "./useEditorStore.js";
|
|||
|
|
import CanvasStage from "./CanvasStage";
|
|||
|
|
import Toolbox from "./Toolbox";
|
|||
|
|
import PropertiesPanel from "./PropertiesPanel";
|
|||
|
|
import LayersPanel from "./LayersPanel";
|
|||
|
|
import { PAGE_SIZES } from "./utils/pageSizes";
|
|||
|
|
import "./Editor.scss";
|
|||
|
|
import { generateHtml } from "./utils/exportToHtml.js";
|
|||
|
|
import { printHtml } from "./utils/printHtml.js";
|
|||
|
|
|
|||
|
|
export default function Editor() {
|
|||
|
|
const {
|
|||
|
|
undo,
|
|||
|
|
redo,
|
|||
|
|
deleteSelected,
|
|||
|
|
copyElement,
|
|||
|
|
pasteElement,
|
|||
|
|
exportTemplate,
|
|||
|
|
selectedId,
|
|||
|
|
setPageSize,
|
|||
|
|
page,
|
|||
|
|
toggleLayers,
|
|||
|
|
showLayers,
|
|||
|
|
} = useEditorStore();
|
|||
|
|
|
|||
|
|
const [zoom, setZoom] = useState(0.9);
|
|||
|
|
const [selectedPageSize, setSelectedPageSize] = useState("A4");
|
|||
|
|
|
|||
|
|
// Keyboard shortcuts
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleKeyDown = (e) => {
|
|||
|
|
// Ignore shortcuts when typing in input/textarea
|
|||
|
|
const isTyping =
|
|||
|
|
e.target.tagName === "INPUT" ||
|
|||
|
|
e.target.tagName === "TEXTAREA" ||
|
|||
|
|
e.target.isContentEditable;
|
|||
|
|
|
|||
|
|
if (isTyping) return;
|
|||
|
|
|
|||
|
|
// Ctrl/Cmd + Z = Undo
|
|||
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
undo();
|
|||
|
|
}
|
|||
|
|
// Ctrl/Cmd + Shift + Z = Redo
|
|||
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "z" && e.shiftKey) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
redo();
|
|||
|
|
}
|
|||
|
|
// Ctrl/Cmd + Y = Redo (alternative)
|
|||
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "y") {
|
|||
|
|
e.preventDefault();
|
|||
|
|
redo();
|
|||
|
|
}
|
|||
|
|
// Delete/Backspace = Delete selected (only when not typing)
|
|||
|
|
if ((e.key === "Delete" || e.key === "Backspace") && selectedId) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
deleteSelected();
|
|||
|
|
}
|
|||
|
|
// Ctrl/Cmd + C = Copy
|
|||
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "c" && selectedId) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
copyElement();
|
|||
|
|
}
|
|||
|
|
// Ctrl/Cmd + V = Paste
|
|||
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
|
|||
|
|
e.preventDefault();
|
|||
|
|
pasteElement();
|
|||
|
|
}
|
|||
|
|
// Ctrl/Cmd + D = Duplicate
|
|||
|
|
if ((e.ctrlKey || e.metaKey) && e.key === "d" && selectedId) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
const { duplicateElement } = useEditorStore.getState();
|
|||
|
|
duplicateElement(selectedId);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.addEventListener("keydown", handleKeyDown);
|
|||
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|||
|
|
}, [undo, redo, deleteSelected, copyElement, pasteElement, selectedId]);
|
|||
|
|
|
|||
|
|
const handleSave = () => {
|
|||
|
|
debugger
|
|||
|
|
const json = exportTemplate();
|
|||
|
|
const blob = new Blob([json], { type: "application/json" });
|
|||
|
|
const url = URL.createObjectURL(blob);
|
|||
|
|
const a = document.createElement("a");
|
|||
|
|
a.href = url;
|
|||
|
|
a.download = "invoice-template.json";
|
|||
|
|
a.click();
|
|||
|
|
URL.revokeObjectURL(url);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handlePageSizeChange = (e) => {
|
|||
|
|
const sizeName = e.target.value;
|
|||
|
|
setSelectedPageSize(sizeName);
|
|||
|
|
const size = PAGE_SIZES[sizeName];
|
|||
|
|
setPageSize({ ...size, name: sizeName });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleZoomIn = () => {
|
|||
|
|
setZoom((prev) => Math.min(prev + 0.1, 2));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleZoomOut = () => {
|
|||
|
|
setZoom((prev) => Math.max(prev - 0.1, 0.3));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleLoadTestTemplate = async () => {
|
|||
|
|
const testTemplate = {
|
|||
|
|
"elements": [
|
|||
|
|
{
|
|||
|
|
"id": "73079a52-87ac-4bd8-a10e-b901e316a911",
|
|||
|
|
"type": "text",
|
|||
|
|
"x": 30,
|
|||
|
|
"y": 40,
|
|||
|
|
"text": "Pozomind Technologies",
|
|||
|
|
"fontSize": 25,
|
|||
|
|
"fontFamily": "Arial",
|
|||
|
|
"fontWeight": "normal",
|
|||
|
|
"color": "#ff0000",
|
|||
|
|
"align": "left",
|
|||
|
|
"bold": true,
|
|||
|
|
"italic": false,
|
|||
|
|
"underline": true,
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "af702c6a-03b0-498b-8511-3737b754c15d",
|
|||
|
|
"type": "image",
|
|||
|
|
"x": 630,
|
|||
|
|
"y": 20,
|
|||
|
|
"width": 150,
|
|||
|
|
"height": 100,
|
|||
|
|
"src": "http://192.168.1.38/upload/getfile?fileId=69cbba1bdb04001678b283b8.png",
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "58ae6bb8-039c-46f3-b5de-6000553a89cb",
|
|||
|
|
"type": "text",
|
|||
|
|
"x": 30,
|
|||
|
|
"y": 80,
|
|||
|
|
"text": "51, Aahhaa Restaurant Back Side, Amirtha Nagar,\nStep Colony, Dharga, Hosur, Krishnagiri,\nTamil Nadu - 635126",
|
|||
|
|
"fontSize": 18,
|
|||
|
|
"fontFamily": "Arial",
|
|||
|
|
"fontWeight": "normal",
|
|||
|
|
"color": "#000000",
|
|||
|
|
"align": "left",
|
|||
|
|
"bold": false,
|
|||
|
|
"italic": false,
|
|||
|
|
"underline": false,
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "969bda69-023b-461e-9c65-7842004d1289",
|
|||
|
|
"type": "table",
|
|||
|
|
"x": 30,
|
|||
|
|
"y": 210,
|
|||
|
|
"width": 480,
|
|||
|
|
"columns": [
|
|||
|
|
{
|
|||
|
|
"key": "sno",
|
|||
|
|
"label": "S.No",
|
|||
|
|
"width": 50,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "item",
|
|||
|
|
"label": "Item",
|
|||
|
|
"width": 150,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "hsn",
|
|||
|
|
"label": "HSN",
|
|||
|
|
"width": 60,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "qty",
|
|||
|
|
"label": "Qty",
|
|||
|
|
"width": 50,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "mrp",
|
|||
|
|
"label": "MRP",
|
|||
|
|
"width": 60,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "rate",
|
|||
|
|
"label": "Rate",
|
|||
|
|
"width": 60,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "dis",
|
|||
|
|
"label": "Dis",
|
|||
|
|
"width": 50,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "tax",
|
|||
|
|
"label": "Tax %",
|
|||
|
|
"width": 60,
|
|||
|
|
"visible": true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "amt",
|
|||
|
|
"label": "Amt",
|
|||
|
|
"width": 70,
|
|||
|
|
"visible": true
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"sampleData": [
|
|||
|
|
{
|
|||
|
|
"sno": "1",
|
|||
|
|
"item": "ITEM 1",
|
|||
|
|
"hsn": "01",
|
|||
|
|
"qty": "2",
|
|||
|
|
"mrp": "100",
|
|||
|
|
"rate": "88",
|
|||
|
|
"dis": "0",
|
|||
|
|
"tax": "5",
|
|||
|
|
"amt": "176"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"sno": "2",
|
|||
|
|
"item": "ITEM 2",
|
|||
|
|
"hsn": "02",
|
|||
|
|
"qty": "1",
|
|||
|
|
"mrp": "350",
|
|||
|
|
"rate": "300",
|
|||
|
|
"dis": "0",
|
|||
|
|
"tax": "5",
|
|||
|
|
"amt": "300"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"sno": "3",
|
|||
|
|
"item": "ITEM 3",
|
|||
|
|
"hsn": "03",
|
|||
|
|
"qty": "1",
|
|||
|
|
"mrp": "250",
|
|||
|
|
"rate": "200",
|
|||
|
|
"dis": "0",
|
|||
|
|
"tax": "0",
|
|||
|
|
"amt": "200"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"sno": "4",
|
|||
|
|
"item": "ITEM 4",
|
|||
|
|
"hsn": "04",
|
|||
|
|
"qty": "1",
|
|||
|
|
"mrp": "50",
|
|||
|
|
"rate": "30",
|
|||
|
|
"dis": "0",
|
|||
|
|
"tax": "18",
|
|||
|
|
"amt": "30"
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"sno": "5",
|
|||
|
|
"item": "ITEM 5",
|
|||
|
|
"hsn": "05",
|
|||
|
|
"qty": "1",
|
|||
|
|
"mrp": "50",
|
|||
|
|
"rate": "30",
|
|||
|
|
"dis": "0",
|
|||
|
|
"tax": "40",
|
|||
|
|
"amt": "30"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"rowHeight": 30,
|
|||
|
|
"headerBg": "#f0f0f0",
|
|||
|
|
"borderColor": "#000000",
|
|||
|
|
"showSampleData": true,
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "098a7eac-5660-40d7-91b2-d7df56fccfd4",
|
|||
|
|
"type": "text",
|
|||
|
|
"x": 360,
|
|||
|
|
"y": 170,
|
|||
|
|
"text": "Takeaway",
|
|||
|
|
"fontSize": 18,
|
|||
|
|
"fontFamily": "Arial",
|
|||
|
|
"fontWeight": "normal",
|
|||
|
|
"color": "#000000",
|
|||
|
|
"align": "left",
|
|||
|
|
"bold": false,
|
|||
|
|
"italic": false,
|
|||
|
|
"underline": false,
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "dcfcd7b5-965c-4aa7-bfa6-9bba2dd1f147",
|
|||
|
|
"type": "qrcode",
|
|||
|
|
"x": 670,
|
|||
|
|
"y": 450,
|
|||
|
|
"size": 100,
|
|||
|
|
"data": "https://example.com",
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "a2812eeb-f2fa-44df-a4a9-cd70b69a0115",
|
|||
|
|
"type": "summary",
|
|||
|
|
"x": 30,
|
|||
|
|
"y": 460,
|
|||
|
|
"fields": [
|
|||
|
|
{
|
|||
|
|
"key": "items",
|
|||
|
|
"label": "Items",
|
|||
|
|
"visible": true,
|
|||
|
|
"width": 150
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "qty",
|
|||
|
|
"label": "Qty",
|
|||
|
|
"visible": true,
|
|||
|
|
"width": 100
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"key": "amount",
|
|||
|
|
"label": "Amount",
|
|||
|
|
"visible": true,
|
|||
|
|
"width": 150
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"fontSize": 14,
|
|||
|
|
"labelColor": "#000000",
|
|||
|
|
"valueColor": "#000000",
|
|||
|
|
"spacing": 10,
|
|||
|
|
"rotation": 0
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "6a4de268-9839-4ee6-b612-c6b4669830ea",
|
|||
|
|
"type": "line",
|
|||
|
|
"x": 40.82365943816583,
|
|||
|
|
"y": 150.00000000000014,
|
|||
|
|
"width": 729.4637752663348,
|
|||
|
|
"thickness": 2,
|
|||
|
|
"color": "#000000",
|
|||
|
|
"rotation": 0
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"page": {
|
|||
|
|
"width": 794,
|
|||
|
|
"height": 1123,
|
|||
|
|
"name": "A4"
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const { importTemplate } = useEditorStore.getState();
|
|||
|
|
importTemplate(JSON.stringify(testTemplate));
|
|||
|
|
|
|||
|
|
const data = {
|
|||
|
|
companyName: "Pozomind Technologies",
|
|||
|
|
address: "123 Main St",
|
|||
|
|
items: "10",
|
|||
|
|
qty: "25",
|
|||
|
|
amount: "₹5000",
|
|||
|
|
tableData: [
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
{ sno: "1", item: "Product A", qty: "2", amt: "200" },
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const html = generateHtml(testTemplate, data);
|
|||
|
|
await printHtml(html);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div style={{ height: "100vh", display: "flex", flexDirection: "column", width: '83vw', fontFamily: 'Poppins, sans-serif' }}>
|
|||
|
|
|
|||
|
|
{/* TOP BAR */}
|
|||
|
|
|
|||
|
|
|
|||
|
|
{/* BODY */}
|
|||
|
|
<div style={{ flex: 1, display: "flex" }}>
|
|||
|
|
|
|||
|
|
{/* LAYERS PANEL */}
|
|||
|
|
{showLayers && <LayersPanel />}
|
|||
|
|
|
|||
|
|
{/* LEFT TOOLBAR */}
|
|||
|
|
<Toolbox />
|
|||
|
|
|
|||
|
|
{/* CANVAS */}
|
|||
|
|
<div style={{
|
|||
|
|
flex: 1,
|
|||
|
|
background: "#f3f3f3",
|
|||
|
|
display: "flex",
|
|||
|
|
flexDirection: "column",
|
|||
|
|
overflow: "auto"
|
|||
|
|
}}>
|
|||
|
|
{/* Page Size Selector */}
|
|||
|
|
<div style={{
|
|||
|
|
padding: "12px 16px",
|
|||
|
|
background: "#fff",
|
|||
|
|
borderBottom: "1px solid #ddd",
|
|||
|
|
display: "flex",
|
|||
|
|
alignItems: "center",
|
|||
|
|
gap: "12px"
|
|||
|
|
}}>
|
|||
|
|
<label style={{ fontSize: 13, fontWeight: 600 }}>Page Size:</label>
|
|||
|
|
<select
|
|||
|
|
value={selectedPageSize}
|
|||
|
|
onChange={handlePageSizeChange}
|
|||
|
|
style={{
|
|||
|
|
padding: "6px 12px",
|
|||
|
|
border: "1px solid #ddd",
|
|||
|
|
borderRadius: 4,
|
|||
|
|
fontSize: 13
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<option value="A4">A4 (210 × 297 mm)</option>
|
|||
|
|
<option value="A5">A5 (148 × 210 mm)</option>
|
|||
|
|
<option value="THERMAL_2IN">2 inch Thermal</option>
|
|||
|
|
<option value="THERMAL_3IN">3 inch Thermal</option>
|
|||
|
|
</select>
|
|||
|
|
<div style={{ marginLeft: "auto", display: "flex", gap: 8, alignItems: "center" }}>
|
|||
|
|
<button
|
|||
|
|
onClick={handleZoomOut}
|
|||
|
|
style={{
|
|||
|
|
padding: "6px 12px",
|
|||
|
|
border: "1px solid #ddd",
|
|||
|
|
borderRadius: 4,
|
|||
|
|
background: "#fff",
|
|||
|
|
cursor: "pointer",
|
|||
|
|
fontSize: 16
|
|||
|
|
}}
|
|||
|
|
title="Zoom Out"
|
|||
|
|
>
|
|||
|
|
−
|
|||
|
|
</button>
|
|||
|
|
{/* <span
|
|||
|
|
onClick={handleZoomReset}
|
|||
|
|
style={{
|
|||
|
|
fontSize: 13,
|
|||
|
|
fontWeight: 600,
|
|||
|
|
minWidth: 50,
|
|||
|
|
textAlign: "center",
|
|||
|
|
cursor: "pointer",
|
|||
|
|
userSelect: "none"
|
|||
|
|
}}
|
|||
|
|
title="Reset Zoom"
|
|||
|
|
>
|
|||
|
|
{Math.round(zoom * 100)}%
|
|||
|
|
</span> */}
|
|||
|
|
<button
|
|||
|
|
onClick={handleZoomIn}
|
|||
|
|
style={{
|
|||
|
|
padding: "6px 12px",
|
|||
|
|
border: "1px solid #ddd",
|
|||
|
|
borderRadius: 4,
|
|||
|
|
background: "#fff",
|
|||
|
|
cursor: "pointer",
|
|||
|
|
fontSize: 16
|
|||
|
|
}}
|
|||
|
|
title="Zoom In"
|
|||
|
|
>
|
|||
|
|
+
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Canvas Area */}
|
|||
|
|
<div style={{
|
|||
|
|
flex: 1,
|
|||
|
|
display: "flex",
|
|||
|
|
justifyContent: "center",
|
|||
|
|
alignItems: "center",
|
|||
|
|
padding: 20
|
|||
|
|
}}>
|
|||
|
|
<div style={{
|
|||
|
|
transform: `scale(${zoom})`,
|
|||
|
|
transformOrigin: "center center",
|
|||
|
|
transition: "transform 0.2s ease"
|
|||
|
|
}}>
|
|||
|
|
<div style={{
|
|||
|
|
background: "#fff",
|
|||
|
|
boxShadow: "0 4px 20px rgba(0,0,0,0.1)"
|
|||
|
|
}}>
|
|||
|
|
<CanvasStage />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* RIGHT PANEL */}
|
|||
|
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '35px' }}>
|
|||
|
|
<PropertiesPanel />
|
|||
|
|
<div style={{
|
|||
|
|
background: "#fff",
|
|||
|
|
borderBottom: "1px solid #ddd",
|
|||
|
|
display: "flex",
|
|||
|
|
flexWrap: "wrap",
|
|||
|
|
alignItems: "center",
|
|||
|
|
justifyContent: "space-between",
|
|||
|
|
padding: "16px",
|
|||
|
|
width: '280px'
|
|||
|
|
}}>
|
|||
|
|
<div style={{
|
|||
|
|
display: "flex", gap: "8px", flexWrap: "wrap", alignItems: "center", justifyContent: "center", fontFamily: 'Poppins, sans-serif'
|
|||
|
|
}}>
|
|||
|
|
<div className="editor-action-btn secondary" onClick={toggleLayers}>
|
|||
|
|
{showLayers ? "Hide" : "Show"} Layers
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="editor-action-btn secondary" onClick={undo}>
|
|||
|
|
↶ Undo
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="editor-action-btn secondary" onClick={redo}>
|
|||
|
|
↷ Redo
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="editor-action-btn" onClick={handleLoadTestTemplate} style={{ background: "#ff9800", color: "#fff" }}>
|
|||
|
|
🧪 Load Test
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div
|
|||
|
|
className={`editor-action-btn danger ${!selectedId ? "disabled" : ""}`}
|
|||
|
|
onClick={selectedId ? deleteSelected : undefined}
|
|||
|
|
>
|
|||
|
|
🗑️ Delete
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="editor-action-btn">
|
|||
|
|
👁️ Preview
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="editor-action-btn primary" onClick={handleSave}>
|
|||
|
|
💾 Save
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|