import { app } from "../../scripts/app.js"; console.log("[SmartResolution] keywords_editor.js loaded"); const API = "/smart_resolution"; const STYLE = { overlay: ` position: fixed; inset: 0; background: rgba(0,0,0,0.75); display: flex; align-items: center; justify-content: center; z-index: 9999;`, dialog: ` background: #1a1a2e; color: #e0e0e0; border: 1px solid #444; border-radius: 8px; padding: 20px; width: 860px; max-width: 95vw; max-height: 82vh; display: flex; flex-direction: column; gap: 12px; font-family: monospace; font-size: 13px;`, input: ` background: #252535; color: #e0e0e0; border: 1px solid #555; border-radius: 3px; padding: 3px 6px; width: 100%; box-sizing: border-box; font-family: monospace; font-size: 12px;`, btnPrimary: ` background: #1a3a6a; color: #fff; border: 1px solid #3a6aaa; border-radius: 4px; padding: 6px 18px; cursor: pointer;`, btnSecondary: ` background: #2e2e2e; color: #ccc; border: 1px solid #555; border-radius: 4px; padding: 6px 18px; cursor: pointer;`, btnAdd: ` background: #1e3a1e; color: #8f8; border: 1px solid #3a6a3a; border-radius: 4px; padding: 5px 14px; cursor: pointer; align-self: flex-start;`, btnDel: ` background: #5a1a1a; color: #faa; border: 1px solid #8a3a3a; border-radius: 3px; padding: 2px 7px; cursor: pointer;`, }; function el(tag, style, attrs = {}) { const e = document.createElement(tag); if (style) e.style.cssText = style; Object.assign(e, attrs); return e; } function makeInput(value, placeholder = "") { const inp = el("input", STYLE.input, { value, placeholder }); return inp; } function makeSelect(selected) { const sel = el("select", STYLE.input); for (const opt of ["limited", "all"]) { const o = document.createElement("option"); o.value = o.textContent = opt; o.selected = opt === selected; sel.appendChild(o); } return sel; } function makeRow(tbody, entry = null) { const tr = document.createElement("tr"); tr.style.borderBottom = "1px solid #2a2a3a"; const ratioVal = Array.isArray(entry?.ratios) ? entry.ratios.join(", ") : (entry?.ratios ?? ""); const inputs = { label: makeInput(entry?.label ?? "", "label"), words: makeInput((entry?.words ?? []).join(", "), "word1, word2, …"), ratios: makeInput(ratioVal, "16:9, 21:9 or $LANDSCAPE"), weights: makeInput((entry?.weights ?? []).join(", "), "3, 2, 1 (optional)"), category: makeSelect(entry?.category ?? "all"), }; for (const [key, widget] of Object.entries(inputs)) { const td = el("td", "padding: 4px;"); widget.dataset.col = key; td.appendChild(widget); tr.appendChild(td); } const delTd = el("td", "padding: 4px; text-align: center;"); const delBtn = el("button", STYLE.btnDel, { textContent: "✕", title: "Remove row" }); delBtn.addEventListener("click", () => tr.remove()); delTd.appendChild(delBtn); tr.appendChild(delTd); tbody.appendChild(tr); return inputs; } function collectRows(tbody) { return [...tbody.querySelectorAll("tr")].map(tr => { const get = col => tr.querySelector(`[data-col="${col}"]`)?.value?.trim() ?? ""; const rawRatios = get("ratios"); const ratios = rawRatios.startsWith("$") ? rawRatios : rawRatios.split(",").map(s => s.trim()).filter(Boolean); const rawWeights = get("weights"); const weightArr = rawWeights ? rawWeights.split(",").map(s => parseFloat(s.trim())).filter(n => !isNaN(n)) : []; return { label: get("label"), words: get("words").split(",").map(s => s.trim()).filter(Boolean), ratios, weights: weightArr.length ? weightArr : null, category: get("category"), }; }); } async function openEditor() { let keywords; try { const resp = await fetch(`${API}/keywords`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); keywords = await resp.json(); } catch (err) { alert(`Could not load keywords: ${err.message}`); return; } // ── Overlay + dialog ────────────────────────────────────────────────────── const overlay = el("div", STYLE.overlay); const dialog = el("div", STYLE.dialog); overlay.appendChild(dialog); // Title const title = el("h3", "margin: 0; color: #a0c4ff; font-size: 15px;", { textContent: "Edit Keywords" }); const hint = el("p", "margin: 0; font-size: 11px; color: #777;", { textContent: "Limited keywords activate in \u201cLimited\u201d and \u201cAll\u201d modes; All keywords only in \u201cAll\u201d mode. " + "Use $LANDSCAPE or $PORTRAIT as ratio values to reference the full ratio sets.", }); // Table const tableWrap = el("div", "overflow-y: auto; flex: 1; min-height: 0;"); const table = el("table", "width: 100%; border-collapse: collapse;"); const thead = document.createElement("thead"); thead.innerHTML = `