UNPKG

on-codemerge

Version:

A WYSIWYG editor for on-codemerge is a user-friendly interface that allows users to edit and view their code in real time, exactly as it will appear in the final product

398 lines (397 loc) 18.4 kB
/*! on-codemerge v1.3.1 @author Pavel Kuzmin @license MIT @homepage https://s00d.github.io/on-codemerge/ @repository git+https://github.com/s00d/on-codemerge.git Copyright (c) 2026 Pavel Kuzmin - Built on 2026-07-02T13:39:17.947Z */ var E = Object.defineProperty; var T = (x, e, i) => e in x ? E(x, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : x[e] = i; var l = (x, e, i) => T(x, typeof e != "symbol" ? e + "" : e, i); import { PopupManager as L } from "../../../core/ui/PopupManager.mjs"; import { ChartRenderer as S } from "../services/ChartRenderer.mjs"; import { MultiSeriesDataEditor as A } from "./MultiSeriesDataEditor.mjs"; import { ChartDataEditor as v } from "./ChartDataEditor.mjs"; import { CHART_TYPE_CONFIGS as f } from "../constants/chartTypes.mjs"; import { createContainer as o, createSpan as m, createButton as P } from "../../../utils/helpers.mjs"; const w = [ { key: "bar-sales", name: "Bar: Sales", type: "bar", data: [ { label: "Jan", value: 120 }, { label: "Feb", value: 90 }, { label: "Mar", value: 150 }, { label: "Apr", value: 80 }, { label: "May", value: 200 } ] }, { key: "pie-expenses", name: "Pie: Expenses", type: "pie", data: [ { label: "Rent", value: 40 }, { label: "Salary", value: 30 }, { label: "Marketing", value: 15 }, { label: "Other", value: 15 } ] }, { key: "line-visitors", name: "Line: Visitors", type: "line", data: [ { label: "Mon", value: 100 }, { label: "Tue", value: 120 }, { label: "Wed", value: 90 }, { label: "Thu", value: 140 }, { label: "Fri", value: 180 }, { label: "Sat", value: 220 }, { label: "Sun", value: 160 } ] }, { key: "doughnut-browsers", name: "Doughnut: Browsers", type: "doughnut", data: [ { label: "Chrome", value: 65 }, { label: "Firefox", value: 15 }, { label: "Safari", value: 10 }, { label: "Edge", value: 7 }, { label: "Other", value: 3 } ] } ]; class U { constructor(e) { l(this, "editor"); l(this, "popup"); l(this, "renderer"); l(this, "currentEditor"); l(this, "selectedType", "bar"); l(this, "onInsert", null); l(this, "previewTimeout", null); l(this, "editingChart", null); l(this, "chartTitle", ""); l(this, "xAxisLabel", ""); l(this, "yAxisLabel", ""); l(this, "showLegend", !0); l(this, "chartWidth", 800); l(this, "chartHeight", 400); l(this, "showGrid", !0); l(this, "chartMode", "default"); l(this, "chartOrientation", "vertical"); this.editor = e, this.renderer = new S(e), this.popup = new L(e, { title: e.t("Insert"), className: "chart-menu", closeOnClickOutside: !0, buttons: [ { label: e.t("Cancel"), variant: "secondary", onClick: () => this.popup.hide() }, { label: e.t("Insert"), variant: "primary", onClick: () => this.handleSubmit() } ], items: this.createPopupItems() // Динамически создаем элементы }), this.currentEditor = new v( e, (i) => this.schedulePreviewUpdate([{ name: "Series 1", data: i }]) ); } createPopupItems() { return [ // 1. Chart type selector { type: "custom", id: "chart-type-selector", content: () => this.createChartTypeSelector() }, // 2. Заголовки (title, x/y axis) { type: "custom", id: "meta-fields", content: () => this.createMetaFields() }, // 3. Размеры графика { type: "custom", id: "chart-dimensions", content: () => this.createDimensionsSelector() }, // 4. Настройки отображения { type: "custom", id: "display-settings", content: () => this.createDisplaySettings() }, // 5. Templates (над редактором данных) { type: "custom", id: "template-selector", content: () => this.createTemplateSelector() }, // 6. Data editor (теперь над предпросмотром) { type: "custom", id: "data-editor-container", content: () => this.createDataEditorContainer() }, // 7. Preview (график) { type: "custom", id: "preview-container", content: () => this.createPreviewContainer() }, // 8. Export (под графиком) { type: "custom", id: "export-btn", content: () => this.createExportButton() } ]; } createMetaFields() { const e = o("meta-fields flex flex-col gap-3 mb-4"), i = o("flex flex-col gap-1"), r = m("text-sm font-medium text-gray-700"); r.textContent = this.editor.t("Chart Title"); const t = document.createElement("input"); t.type = "text", t.placeholder = this.editor.t("Enter chart title..."), t.value = this.chartTitle, t.className = "meta-input px-3 py-2 rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all", t.oninput = (u) => { this.chartTitle = u.target.value, this.schedulePreviewUpdate(this.currentEditor.getData()); }, i.appendChild(r), i.appendChild(t); const s = o("flex flex-col gap-1"), a = m("text-sm font-medium text-gray-700"); a.textContent = this.editor.t("X Axis Label"); const n = document.createElement("input"); n.type = "text", n.placeholder = this.editor.t("Enter X axis label..."), n.value = this.xAxisLabel, n.className = "meta-input px-3 py-2 rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all", n.oninput = (u) => { this.xAxisLabel = u.target.value, this.schedulePreviewUpdate(this.currentEditor.getData()); }, s.appendChild(a), s.appendChild(n); const d = o("flex flex-col gap-1"), c = m("text-sm font-medium text-gray-700"); c.textContent = this.editor.t("Y Axis Label"); const h = document.createElement("input"); return h.type = "text", h.placeholder = this.editor.t("Enter Y axis label..."), h.value = this.yAxisLabel, h.className = "meta-input px-3 py-2 rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all", h.oninput = (u) => { this.yAxisLabel = u.target.value, this.schedulePreviewUpdate(this.currentEditor.getData()); }, d.appendChild(c), d.appendChild(h), e.appendChild(i), e.appendChild(s), e.appendChild(d), e; } createTemplateSelector() { const e = o("template-selector flex flex-col gap-2 mb-4"), i = m("text-sm font-medium text-gray-700"); i.textContent = this.editor.t("Templates"), e.appendChild(i); const r = document.createElement("select"); r.className = "template-select px-3 py-2 rounded-lg border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all bg-white"; const t = document.createElement("option"); return t.value = "", t.textContent = this.editor.t("Select template..."), r.appendChild(t), w.forEach((s) => { const a = document.createElement("option"); a.value = s.key, a.textContent = s.name, r.appendChild(a); }), r.onchange = (s) => { var n, d; const a = s.target.value; if (a) { const c = w.find((h) => h.key === a); if (c) { this.selectedType = c.type, this.updateEditor(c.type); const h = (n = this.popup.getElement()) == null ? void 0 : n.querySelectorAll("[data-type]"); h == null || h.forEach((y) => y.classList.remove("selected")); const u = (d = this.popup.getElement()) == null ? void 0 : d.querySelector(`[data-type="${c.type}"]`); u && u.classList.add("selected"), setTimeout(() => { this.currentEditor && ("setData" in this.currentEditor && this.currentEditor.setData(c.data), this.updatePreview(c.data)); }, 100); } } }, e.appendChild(r), e; } createChartTypeSelector() { const e = o("chart-type-selector"), i = m("text-sm font-medium text-gray-700 mb-4 block"); i.textContent = this.editor.t("Chart Type"), e.appendChild(i); const r = o("grid"); return Object.entries(f).forEach(([t, s]) => { const a = o( `chart-type-option ${t === this.selectedType ? "selected" : ""}` ); a.setAttribute("data-type", t); const n = o("w-10 h-10 mb-3 mx-auto flex items-center justify-center"); n.innerHTML = s.icon; const d = m(); d.textContent = this.editor.t(s.name), a.appendChild(n), a.appendChild(d), a.onclick = () => { r.querySelectorAll(".chart-type-option").forEach((c) => { c.classList.remove("selected"); }), a.classList.add("selected"), this.selectedType = t, this.updateEditor(t); }, r.appendChild(a); }), e.appendChild(r), e; } createDataEditorContainer() { const e = o("data-editor-container mb-6"); return this.updateEditor("bar", e), e; } createPreviewContainer() { const e = o( "preview-container h-64 bg-gray-50 rounded-lg flex items-center justify-center" ); return e.innerHTML = '<div class="text-gray-400">Chart preview will appear here</div>', e; } createExportButton() { const e = o("export-container flex justify-center mt-4"), i = P("", () => this.exportPreviewAsPNG()); i.className = "export-btn px-4 py-2 rounded-lg bg-gradient-to-r from-green-500 to-green-600 text-white hover:from-green-600 hover:to-green-700 transition-all duration-200 shadow-md hover:shadow-lg flex items-center gap-2 font-medium"; const r = o("w-4 h-4"); r.innerHTML = '<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>'; const t = m(); return t.textContent = this.editor.t("Export as PNG"), i.appendChild(r), i.appendChild(t), e.appendChild(i), e; } exportPreviewAsPNG() { var r, t, s; const e = (r = this.popup.getElement()) == null ? void 0 : r.querySelector(".preview-container"); if (!e) return; const i = e.querySelector("img.svg-chart"); if (i && i.src) { const a = document.createElement("a"); a.href = i.src, a.download = "chart.png", (this.editor.getInnerContainer() || document.body).appendChild(a), a.click(), (t = a.parentNode) == null || t.removeChild(a); } else { const a = e.querySelector("canvas"); if (a) { const n = document.createElement("a"); n.href = a.toDataURL("image/png"), n.download = "chart.png", (this.editor.getInnerContainer() || document.body).appendChild(n), n.click(), (s = n.parentNode) == null || s.removeChild(n); } } } updateEditor(e, i) { const r = f[e], t = i ?? this.popup.getElement().querySelector(".data-editor-container"); t && (t.innerHTML = "", r.supportsMultipleSeries ? this.currentEditor = new A( this.editor, (s) => this.schedulePreviewUpdate(s) ) : this.currentEditor = new v( this.editor, (s) => this.schedulePreviewUpdate([ { name: this.editor.t("Series 1"), data: s } ]), r.requiresXY, e === "scatter" ), t.appendChild(this.currentEditor.getElement())); } schedulePreviewUpdate(e) { this.previewTimeout && window.clearTimeout(this.previewTimeout), this.previewTimeout = window.setTimeout(() => { this.updatePreview(e); }, 100); } updatePreview(e) { var s; const i = (s = this.popup.getElement()) == null ? void 0 : s.querySelector(".preview-container"); if (!i) return; const r = { width: 350, height: 250, title: this.chartTitle, xAxis: { title: this.xAxisLabel }, yAxis: { title: this.yAxisLabel }, legend: { show: this.showLegend }, grid: { show: this.showGrid }, mode: this.chartMode, orientation: this.chartOrientation }, t = this.renderer.createChart(this.selectedType, e, r); t.style.position = "absolute", t.style.top = "50%", t.style.left = "50%", t.style.transform = "translate(-50%, -50%)", i.innerHTML = "", i.appendChild(t); } handleSubmit() { const e = this.currentEditor.getData(); if (!e || Array.isArray(e) && e.length === 0) return; const i = { width: this.chartWidth, height: this.chartHeight, title: this.chartTitle, xAxis: { title: this.xAxisLabel }, yAxis: { title: this.yAxisLabel }, legend: { show: this.showLegend }, grid: { show: this.showGrid }, mode: this.chartMode, orientation: this.chartOrientation }; if (this.editingChart) { this.editingChart.setAttribute("data-chart-type", this.selectedType), this.editingChart.setAttribute("data-chart-data", JSON.stringify(e)); const r = this.renderer.createChart(this.selectedType, e, i); this.editingChart.innerHTML = "", this.editingChart.appendChild(r); } else { const r = o("chart-container"); r.style.width = i.width + "px", r.style.height = i.height + "px", r.setAttribute("data-chart-type", this.selectedType), r.setAttribute("data-chart-data", JSON.stringify(e)); const t = this.renderer.createChart(this.selectedType, e, i); r.appendChild(t), this.onInsert && this.onInsert(r); } this.popup.hide(); } show(e) { this.editingChart = null, this.onInsert = e, this.popup.show(); } edit(e, i = !1) { var s; this.editingChart = e; const r = e.getAttribute("data-chart-type"), t = e.getAttribute("data-chart-data"); if (r && t) try { const a = JSON.parse(t), n = (s = this.popup.getElement()) == null ? void 0 : s.querySelector(`[data-type="${r}"]`); n && n.classList.add("selected"), this.selectedType = r, this.updateEditor(r), setTimeout(() => { this.currentEditor.setData(a), this.updatePreview(a); }, 100), i || this.popup.show(); } catch (a) { console.error("Failed to parse chart data:", a); } } redrawChart(e, i, r, t) { const s = this.renderer.createChart(i, r, t); e.innerHTML = "", e.appendChild(s); } createDimensionsSelector() { const e = o("dimensions-selector flex gap-4 mb-4"), i = o("flex flex-col gap-1"), r = m("text-sm font-medium text-gray-700"); r.textContent = this.editor.t("Width"); const t = document.createElement("input"); t.type = "number", t.min = "200", t.max = "1200", t.step = "50", t.value = this.chartWidth.toString(), t.className = "dimension-input px-2 py-1 rounded border border-gray-300 text-sm", t.onchange = (d) => { this.chartWidth = parseInt(d.target.value), this.schedulePreviewUpdate(this.currentEditor.getData()); }, i.appendChild(r), i.appendChild(t); const s = o("flex flex-col gap-1"), a = m("text-sm font-medium text-gray-700"); a.textContent = this.editor.t("Height"); const n = document.createElement("input"); return n.type = "number", n.min = "150", n.max = "800", n.step = "50", n.value = this.chartHeight.toString(), n.className = "dimension-input px-2 py-1 rounded border border-gray-300 text-sm", n.onchange = (d) => { this.chartHeight = parseInt(d.target.value), this.schedulePreviewUpdate(this.currentEditor.getData()); }, s.appendChild(a), s.appendChild(n), e.appendChild(i), e.appendChild(s), e; } createDisplaySettings() { const e = o( "display-settings flex flex-col gap-3 mb-4 p-3 bg-gray-50 rounded-lg" ), i = m("text-sm font-medium text-gray-700 mb-2"); i.textContent = this.editor.t("Display Settings"), e.appendChild(i); const r = o("flex items-center gap-2"), t = document.createElement("input"); t.type = "checkbox", t.checked = this.showLegend, t.className = "setting-checkbox", t.onchange = (p) => { this.showLegend = p.target.checked, this.schedulePreviewUpdate(this.currentEditor.getData()); }; const s = m("text-sm"); s.textContent = this.editor.t("Show Legend"), r.appendChild(t), r.appendChild(s); const a = o("flex items-center gap-2"), n = document.createElement("input"); n.type = "checkbox", n.checked = this.showGrid, n.className = "setting-checkbox", n.onchange = (p) => { this.showGrid = p.target.checked, this.schedulePreviewUpdate(this.currentEditor.getData()); }; const d = m("text-sm"); d.textContent = this.editor.t("Show Grid"), a.appendChild(n), a.appendChild(d); const c = o("flex items-center gap-2"), h = m("text-sm"); h.textContent = this.editor.t("Bar/Area Mode"); const u = document.createElement("select"); u.className = "mode-select px-2 py-1 rounded border border-gray-300 text-sm", ["default", "stacked", "grouped"].forEach((p) => { const g = document.createElement("option"); g.value = p, g.textContent = this.editor.t(p.charAt(0).toUpperCase() + p.slice(1)), u.appendChild(g); }), u.value = this.chartMode, u.onchange = (p) => { this.chartMode = p.target.value, this.schedulePreviewUpdate(this.currentEditor.getData()); }, c.appendChild(h), c.appendChild(u); const y = o("flex items-center gap-2"), C = m("text-sm"); C.textContent = this.editor.t("Orientation"); const b = document.createElement("select"); return b.className = "orientation-select px-2 py-1 rounded border border-gray-300 text-sm", ["vertical", "horizontal"].forEach((p) => { const g = document.createElement("option"); g.value = p, g.textContent = this.editor.t(p.charAt(0).toUpperCase() + p.slice(1)), b.appendChild(g); }), b.value = this.chartOrientation, b.onchange = (p) => { this.chartOrientation = p.target.value, this.schedulePreviewUpdate(this.currentEditor.getData()); }, y.appendChild(C), y.appendChild(b), e.appendChild(r), e.appendChild(a), e.appendChild(c), e.appendChild(y), e; } destroy() { this.previewTimeout && (window.clearTimeout(this.previewTimeout), this.previewTimeout = null), this.popup.destroy(), this.currentEditor && this.currentEditor.destroy(), this.editor = null, this.popup = null, this.renderer = null, this.currentEditor = null, this.onInsert = null, this.editingChart = null; } } export { U as ChartMenu };