Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { CONTROLS, OBJECTS } from "./tags";
import { Palette } from "../../../utils/colors";
 
export const EMPTY_CONFIG = "<View></View>";
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();
  }
}