import { CONTROLS, OBJECTS } from "./tags"; import { Palette } from "../../../utils/colors"; export const EMPTY_CONFIG = ""; export const DEFAULT_COLUMN = "$undefined$"; export const isEmptyConfig = (config) => ["", EMPTY_CONFIG].includes(config.replace(/\s+/g, "")); export class Template { objects = []; controls = []; details = false; palette = Palette(); constructor(tpl) { this.tpl = tpl; this.config = tpl.config; const parser = new DOMParser(); this.$root = parser.parseFromString(this.config, "application/xml"); this.serializer = new XMLSerializer(); this.initRoot(); } flatten(el) { const tags = []; for (const tag of el.children) { tags.push(tag); if (tag.children.length) tags.push(...this.flatten(tag)); } return tags; } onConfigUpdate() { // should be overwritten } render() { const config = this.serializer.serializeToString(this.$root); this.onConfigUpdate(config); } initRoot() { const tags = this.flatten(this.$root); this.objects = tags.filter( ($tag) => $tag.tagName in OBJECTS && ($tag.getAttribute("value") || $tag.getAttribute("valueList")), ); const names = this.objects.map(($tag) => $tag.getAttribute("name")); this.controls = tags.filter(($tag) => names.includes($tag.getAttribute("toName"))); for (const $object of this.objects) { const object = OBJECTS[$object.tagName]; $object.$controls = this.controls.filter(($tag) => $tag.getAttribute("toName") === $object.getAttribute("name")); $object.$controls.forEach(($control) => ($control.$object = $object)); for (const item in object.settings) { object.settings[item].object = $object; } let settings = { ...object.settings }; $object.$controls.forEach(($control) => { const control = CONTROLS[$control.tagName]; if (control) { for (const item in control.settings) { control.settings[item].control = $control; control.settings[item].object = $object; } settings = { ...settings, ...control.settings }; } }); this.settings = settings; } } // fix `value` of object tags according to current columns from data fixColumns(columns) { if (columns.length === 1 && columns[0] === DEFAULT_COLUMN) return; const existing = this.objects.map((obj) => obj.getAttribute("value").replace(/^\$/, "")); const free = columns.filter((c) => !existing.includes(c)); for (const obj of this.objects) { if (!columns.includes(obj.getAttribute("value").replace(/^\$/, ""))) { obj.setAttribute("value", `$${free.shift() ?? columns[0]}`); } } this.render(); } addLabels(control, labels) { if (!labels) return; if (!Array.isArray(labels)) { labels = labels .split("\n") .map((s) => s.trim()) .filter(Boolean); } if (!labels.length) return; const existing = [...control.children].map((ch) => ch.getAttribute("value")); const isChoices = control.tagName === "Choices"; labels.forEach((label) => { if (existing.includes(label)) return; existing.push(label); const $label = this.$root.createElement(isChoices ? "Choice" : "Label"); $label.setAttribute("value", label); if (!isChoices) $label.setAttribute("background", this.palette.next().value); control.appendChild($label); }); this.render(); } removeLabel($label) { $label.parentNode.removeChild($label); this.render(); } changeLabel($label, attrs) { for (const attr of Object.keys(attrs)) { $label.setAttribute(attr, attrs[attr]); } this.render(); } }