@quandis/qbo4.configuration 4.0.1-CI-20241007-232230 → 4.0.1-CI-20241009-155903
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Configuration.d.ts +35 -35
- package/src/Configuration.js +104 -104
- package/src/IConfiguration.d.ts +34 -34
- package/src/IConfiguration.js +37 -37
- package/src/Program.d.ts +24 -24
- package/src/Program.js +8 -8
- package/src/Services.d.ts +9 -9
- package/src/Services.js +18 -18
- package/src/qbo-config-editor.d.ts +18 -18
- package/src/qbo-config-editor.js +84 -84
- package/src/qbo-css.d.ts +35 -35
- package/src/qbo-css.js +226 -226
- package/src/qbo-template.d.ts +22 -22
- package/src/qbo-template.js +417 -417
- package/src/styles.d.ts +1 -1
- package/src/styles.js +6 -6
- package/src/styles.js.map +1 -1
- package/src/styles.ts +5 -5
- package/wwwroot/css/qbo-config-editor.css +2 -2
- package/wwwroot/css/qbo-configuration.css +1730 -1730
- package/wwwroot/css/qbo-configuration.css.map +1 -1
- package/wwwroot/css/qbo-configuration.min.css +5 -5
- package/wwwroot/js/esm/qbo4.configuration.js +39074 -39565
- package/wwwroot/js/esm/qbo4.configuration.min.js +76 -76
- package/wwwroot/js/esm/qbo4.configuration.min.js.LICENSE.txt +41 -41
- package/wwwroot/js/esm/qbo4.configuration.min.js.map +1 -1
- package/wwwroot/js/qbo4.configuration.js +39150 -39641
- package/wwwroot/js/qbo4.configuration.min.js +76 -76
- package/wwwroot/js/qbo4.configuration.min.js.LICENSE.txt +41 -41
- package/wwwroot/js/qbo4.configuration.min.js.map +1 -1
package/src/qbo-template.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
};
|
|
10
|
-
import { indentWithTab } from "@codemirror/commands";
|
|
11
|
-
import { html as htmlcode } from "@codemirror/lang-html";
|
|
12
|
-
import { EditorView, keymap } from "@codemirror/view";
|
|
13
|
-
import { basicSetup } from "codemirror";
|
|
14
|
-
import { LitElement, css, html } from 'lit';
|
|
15
|
-
import { property } from 'lit/decorators.js';
|
|
16
|
-
import { configurationCss } from './styles.js';
|
|
17
|
-
const scriptCache = {};
|
|
18
|
-
export const templates = new Map();
|
|
19
|
-
export function template(prefix) {
|
|
20
|
-
return (target, propertyKey) => {
|
|
21
|
-
const key = target.prefix ?? target.constructor?.name;
|
|
22
|
-
if (!key)
|
|
23
|
-
return console.error('Template prefix and constructor name not defined.', target, propertyKey);
|
|
24
|
-
const map = templates.get(key) || new Map();
|
|
25
|
-
map.set(prefix ?? propertyKey, target[propertyKey]);
|
|
26
|
-
templates.set(key, map);
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
export const QboTemplateMixin = (superClass) => {
|
|
30
|
-
class QboTemplateClass extends superClass {
|
|
31
|
-
static { this.styles = [
|
|
32
|
-
configurationCss,
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { indentWithTab } from "@codemirror/commands";
|
|
11
|
+
import { html as htmlcode } from "@codemirror/lang-html";
|
|
12
|
+
import { EditorView, keymap } from "@codemirror/view";
|
|
13
|
+
import { basicSetup } from "codemirror";
|
|
14
|
+
import { LitElement, css, html } from 'lit';
|
|
15
|
+
import { property } from 'lit/decorators.js';
|
|
16
|
+
import { configurationCss } from './styles.js';
|
|
17
|
+
const scriptCache = {};
|
|
18
|
+
export const templates = new Map();
|
|
19
|
+
export function template(prefix) {
|
|
20
|
+
return (target, propertyKey) => {
|
|
21
|
+
const key = target.prefix ?? target.constructor?.name;
|
|
22
|
+
if (!key)
|
|
23
|
+
return console.error('Template prefix and constructor name not defined.', target, propertyKey);
|
|
24
|
+
const map = templates.get(key) || new Map();
|
|
25
|
+
map.set(prefix ?? propertyKey, target[propertyKey]);
|
|
26
|
+
templates.set(key, map);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const QboTemplateMixin = (superClass) => {
|
|
30
|
+
class QboTemplateClass extends superClass {
|
|
31
|
+
static { this.styles = [
|
|
32
|
+
configurationCss,
|
|
33
33
|
css `
|
|
34
34
|
:host {
|
|
35
35
|
display: block;
|
|
@@ -45,267 +45,267 @@ input.working {
|
|
|
45
45
|
background-color: #f3f3f3;
|
|
46
46
|
cursor: wait;
|
|
47
47
|
}
|
|
48
|
-
`
|
|
49
|
-
]; }
|
|
50
|
-
constructor(...args) {
|
|
51
|
-
super(...args);
|
|
52
|
-
this.dragging = false;
|
|
53
|
-
this.resizing = false;
|
|
54
|
-
this.offsetX = 0;
|
|
55
|
-
this.offsetY = 0;
|
|
56
|
-
this.startWidth = 0;
|
|
57
|
-
this.startHeight = 0;
|
|
58
|
-
this.regex = /`(.*?)`/s;
|
|
59
|
-
// when true, the editor will show the error class
|
|
60
|
-
this.editorError = null;
|
|
61
|
-
this.type = null;
|
|
62
|
-
this.editing = false;
|
|
63
|
-
this.defaultTemplate = (component) => html `<span>Template matching ${component.type} not defined</span>`;
|
|
64
|
-
this.templateEndpoint = '/configuration/template';
|
|
65
|
-
this.prefix = null;
|
|
66
|
-
this.map = undefined;
|
|
67
|
-
this.editor = null;
|
|
68
|
-
this.editorType = this.type;
|
|
69
|
-
this.editorCoding = false;
|
|
70
|
-
}
|
|
71
|
-
async connectedCallback() {
|
|
72
|
-
super.connectedCallback();
|
|
73
|
-
this.prefix ??= this.constructor.name;
|
|
74
|
-
// Ensure we have a map for this class
|
|
75
|
-
if (!templates.has(this.prefix)) {
|
|
76
|
-
templates.set(this.prefix, new Map());
|
|
77
|
-
}
|
|
78
|
-
;
|
|
79
|
-
this.map = templates.get(this.prefix);
|
|
80
|
-
if (this.map === undefined)
|
|
81
|
-
console.error(`The prefix ${this.prefix} is not defined in the templates map.`);
|
|
82
|
-
// Don't bother with the API call if we already have the template.
|
|
83
|
-
if (this.map && this.type && !this.map.has(this.type)) {
|
|
84
|
-
await this.loadScript(this.prefix);
|
|
85
|
-
}
|
|
86
|
-
this.addEventListener('dblclick', this.requestEdit.bind(this));
|
|
87
|
-
this.shadowRoot?.addEventListener('mousedown', this._onMouseDown.bind(this));
|
|
88
|
-
this.shadowRoot?.addEventListener('mousedown', this._onResizeMouseDown.bind(this), true);
|
|
89
|
-
window.addEventListener('mousemove', this._onMouseMove.bind(this));
|
|
90
|
-
window.addEventListener('mouseup', this._onMouseUp.bind(this));
|
|
91
|
-
}
|
|
92
|
-
async disconnectedCallback() {
|
|
93
|
-
this.removeEventListener('dblclick', this.requestEdit.bind(this));
|
|
94
|
-
this.shadowRoot?.removeEventListener('mousedown', this._onMouseDown.bind(this));
|
|
95
|
-
this.shadowRoot?.removeEventListener('mousedown', this._onResizeMouseDown.bind(this), true);
|
|
96
|
-
window.removeEventListener('mousemove', this._onMouseMove.bind(this));
|
|
97
|
-
window.removeEventListener('mouseup', this._onMouseUp.bind(this));
|
|
98
|
-
}
|
|
99
|
-
_onMouseDown(event) {
|
|
100
|
-
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
101
|
-
if (!editor || !event.target.closest('.qbo-code header'))
|
|
102
|
-
return;
|
|
103
|
-
// Ensure that the editor has a defined position
|
|
104
|
-
const computedStyle = window.getComputedStyle(editor);
|
|
105
|
-
editor.style.left = computedStyle.left === 'auto' ? `${editor.getBoundingClientRect().left}px` : computedStyle.left;
|
|
106
|
-
editor.style.top = computedStyle.top === 'auto' ? `${editor.getBoundingClientRect().top}px` : computedStyle.top;
|
|
107
|
-
this.dragging = true;
|
|
108
|
-
this.offsetX = event.clientX - parseFloat(computedStyle.left || '0');
|
|
109
|
-
this.offsetY = event.clientY - parseFloat(computedStyle.top || '0');
|
|
110
|
-
}
|
|
111
|
-
_onResizeMouseDown(event) {
|
|
112
|
-
if (!event.target.closest('.qbo-code .resize-handle'))
|
|
113
|
-
return;
|
|
114
|
-
this.resizing = true;
|
|
115
|
-
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
116
|
-
if (editor) {
|
|
117
|
-
this.startWidth = editor.offsetWidth;
|
|
118
|
-
this.startHeight = editor.offsetHeight;
|
|
119
|
-
this.offsetX = event.clientX;
|
|
120
|
-
this.offsetY = event.clientY;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
_onMouseMove(event) {
|
|
124
|
-
if (this.dragging) {
|
|
125
|
-
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
126
|
-
if (editor) {
|
|
127
|
-
const x = event.clientX - this.offsetX;
|
|
128
|
-
const y = event.clientY - this.offsetY;
|
|
129
|
-
editor.style.left = `${x}px`;
|
|
130
|
-
editor.style.top = `${y}px`;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else if (this.resizing) {
|
|
134
|
-
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
135
|
-
if (editor) {
|
|
136
|
-
editor.style.width = `${this.startWidth + (event.clientX - this.offsetX)}px`;
|
|
137
|
-
editor.style.height = `${this.startHeight + (event.clientY - this.offsetY)}px`;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
_onMouseUp() {
|
|
142
|
-
this.dragging = false;
|
|
143
|
-
this.resizing = false;
|
|
144
|
-
}
|
|
145
|
-
// Function to flatten JSON object keys for use in autocomplete
|
|
146
|
-
flattenObjectKeys(obj, prefix = '') {
|
|
147
|
-
return Object.keys(obj).reduce((res, k) => {
|
|
148
|
-
const pre = prefix.length ? `${prefix}.` : '';
|
|
149
|
-
if (typeof obj[k] === 'object' && obj[k] !== null) {
|
|
150
|
-
res = res.concat(this.flattenObjectKeys(obj[k], `${pre}${k}`));
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
res.push(`${pre}${k}`);
|
|
154
|
-
}
|
|
155
|
-
return res;
|
|
156
|
-
}, []);
|
|
157
|
-
}
|
|
158
|
-
// Function to flatten properties of a LitElement-derived component for use in autocomplete
|
|
159
|
-
flattenComponentKeys(component) {
|
|
160
|
-
const keys = [];
|
|
161
|
-
const proto = Object.getPrototypeOf(component);
|
|
162
|
-
// Use Reflect API to get metadata if available
|
|
163
|
-
const propertyKeys = Object.getOwnPropertyNames(proto);
|
|
164
|
-
propertyKeys.forEach((key) => {
|
|
165
|
-
if (key === 'constructor' || key.startsWith('_'))
|
|
166
|
-
return; // Skip constructor and private fields
|
|
167
|
-
const metadata = Reflect.getMetadata('design:type', proto, key);
|
|
168
|
-
if (metadata) {
|
|
169
|
-
if (typeof component[key] === 'object' && component[key] !== null) {
|
|
170
|
-
keys.push(...this.flattenObjectKeys(component[key], key));
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
keys.push(key);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
return keys.map(key => ({ label: key, type: 'variable' }));
|
|
178
|
-
}
|
|
179
|
-
_isSerializable(value) {
|
|
180
|
-
if (typeof value === 'function' || typeof value === 'undefined') {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
JSON.stringify(value);
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
getComponent() {
|
|
192
|
-
let json = {};
|
|
193
|
-
const ownProperties = new Set(Object.keys(this));
|
|
194
|
-
// Filter properties to exclude those defined by the base class
|
|
195
|
-
ownProperties.forEach(prop => {
|
|
196
|
-
if (prop.startsWith('_'))
|
|
197
|
-
return;
|
|
198
|
-
if (prop === 'constructor'
|
|
199
|
-
|| prop === 'shadowRoot'
|
|
200
|
-
|| prop === 'renderOptions'
|
|
201
|
-
|| prop === 'renderRoot'
|
|
202
|
-
|| prop === 'editor'
|
|
203
|
-
|| typeof this[prop] === 'function')
|
|
204
|
-
return;
|
|
205
|
-
json[prop] = this[prop];
|
|
206
|
-
});
|
|
207
|
-
return json;
|
|
208
|
-
}
|
|
209
|
-
async loadScript(prefix) {
|
|
210
|
-
if (!scriptCache[prefix]) {
|
|
211
|
-
scriptCache[prefix] = this.fetchScript(prefix);
|
|
212
|
-
}
|
|
213
|
-
try {
|
|
214
|
-
const scriptText = await scriptCache[prefix];
|
|
215
|
-
this.appendScriptToHead(scriptText);
|
|
216
|
-
console.log(`Template script for ${this.prefix} loaded and executed.`);
|
|
217
|
-
this.requestUpdate();
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
console.error(`Template script for ${this.prefix} failed to load:`, error);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
async fetchScript(prefix) {
|
|
224
|
-
const response = await fetch(`${this.templateEndpoint}/search/${prefix}`);
|
|
225
|
-
if (!response.ok) {
|
|
226
|
-
throw new Error(`Failed to fetch templates from: ${this.templateEndpoint}/search/${prefix}`);
|
|
227
|
-
}
|
|
228
|
-
return response.text();
|
|
229
|
-
}
|
|
230
|
-
appendScriptToHead(scriptText) {
|
|
231
|
-
const scriptElement = document.createElement('script');
|
|
232
|
-
scriptElement.type = 'text/javascript';
|
|
233
|
-
scriptElement.text = scriptText;
|
|
234
|
-
document.head.appendChild(scriptElement);
|
|
235
|
-
}
|
|
236
|
-
requestEdit(event) {
|
|
237
|
-
if (event.ctrlKey && this.closest('.qbo-design')) {
|
|
238
|
-
this.toggleEdit(!this.editing);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
toggleEdit(editing) {
|
|
242
|
-
this.editing = editing;
|
|
243
|
-
if (!this.editing) {
|
|
244
|
-
this.testTemplate = undefined;
|
|
245
|
-
this.editor?.destroy();
|
|
246
|
-
this.editor = null;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// Creates a function from the user's input.
|
|
250
|
-
setTemplate(content) {
|
|
251
|
-
const test = new Function('component', `return qbo4.configuration.html\`${content}\``);
|
|
252
|
-
try {
|
|
253
|
-
test(this);
|
|
254
|
-
this.editorError = undefined;
|
|
255
|
-
this.testTemplate = test;
|
|
256
|
-
}
|
|
257
|
-
catch (e) {
|
|
258
|
-
this.editorError = e;
|
|
259
|
-
console.error(`Error rendering ${content}`, e);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
// resets the template, ignoring the user's input.
|
|
263
|
-
resetTemplate(event) {
|
|
264
|
-
this.editorType = event.target.value;
|
|
265
|
-
if (this.map?.has(event.target.value))
|
|
266
|
-
this.testTemplate = this.map.get(event.target.value) ?? this.defaultTemplate;
|
|
267
|
-
let expression = '<span>Template not defined</span>';
|
|
268
|
-
if (this.testTemplate !== undefined) {
|
|
269
|
-
const matches = this.testTemplate.toString().match(this.regex);
|
|
270
|
-
if (matches) {
|
|
271
|
-
expression = matches[1];
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
this.editor?.dispatch(this.editor.state.update({
|
|
275
|
-
changes: { from: 0, to: this.editor.state.doc.length, insert: expression }
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
updated(changedProperties) {
|
|
279
|
-
super.update(changedProperties);
|
|
280
|
-
let expression = '<span>Template not defined</span>';
|
|
281
|
-
if (this.testTemplate !== undefined) {
|
|
282
|
-
const matches = this.testTemplate.toString().match(this.regex);
|
|
283
|
-
if (matches) {
|
|
284
|
-
expression = matches[1];
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (this.editing) {
|
|
288
|
-
// const componentKeys = this.flattenComponentKeys(this);
|
|
289
|
-
// todo: create a custom autocomplete source
|
|
290
|
-
this.editor ??= new EditorView({
|
|
291
|
-
doc: expression,
|
|
292
|
-
extensions: [
|
|
293
|
-
basicSetup,
|
|
294
|
-
htmlcode(),
|
|
295
|
-
keymap.of([indentWithTab]),
|
|
296
|
-
EditorView.updateListener.of((e) => {
|
|
297
|
-
this.setTemplate(e.state.doc.toString());
|
|
298
|
-
// this.dispatchEvent(new CustomEvent('change', { detail: { code: this.value } }));
|
|
299
|
-
})
|
|
300
|
-
],
|
|
301
|
-
parent: this.shadowRoot?.querySelector('div.editor')
|
|
302
|
-
// parent: <DocumentFragment>this.shadowRoot,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
edit() {
|
|
307
|
-
this.testTemplate ??= this.map?.get(this.type) ?? this.defaultTemplate;
|
|
308
|
-
const id = Math.random().toString(36).substring(2, 15);
|
|
48
|
+
`
|
|
49
|
+
]; }
|
|
50
|
+
constructor(...args) {
|
|
51
|
+
super(...args);
|
|
52
|
+
this.dragging = false;
|
|
53
|
+
this.resizing = false;
|
|
54
|
+
this.offsetX = 0;
|
|
55
|
+
this.offsetY = 0;
|
|
56
|
+
this.startWidth = 0;
|
|
57
|
+
this.startHeight = 0;
|
|
58
|
+
this.regex = /`(.*?)`/s;
|
|
59
|
+
// when true, the editor will show the error class
|
|
60
|
+
this.editorError = null;
|
|
61
|
+
this.type = null;
|
|
62
|
+
this.editing = false;
|
|
63
|
+
this.defaultTemplate = (component) => html `<span>Template matching ${component.type} not defined</span>`;
|
|
64
|
+
this.templateEndpoint = '/configuration/template';
|
|
65
|
+
this.prefix = null;
|
|
66
|
+
this.map = undefined;
|
|
67
|
+
this.editor = null;
|
|
68
|
+
this.editorType = this.type;
|
|
69
|
+
this.editorCoding = false;
|
|
70
|
+
}
|
|
71
|
+
async connectedCallback() {
|
|
72
|
+
super.connectedCallback();
|
|
73
|
+
this.prefix ??= this.constructor.name;
|
|
74
|
+
// Ensure we have a map for this class
|
|
75
|
+
if (!templates.has(this.prefix)) {
|
|
76
|
+
templates.set(this.prefix, new Map());
|
|
77
|
+
}
|
|
78
|
+
;
|
|
79
|
+
this.map = templates.get(this.prefix);
|
|
80
|
+
if (this.map === undefined)
|
|
81
|
+
console.error(`The prefix ${this.prefix} is not defined in the templates map.`);
|
|
82
|
+
// Don't bother with the API call if we already have the template.
|
|
83
|
+
if (this.map && this.type && !this.map.has(this.type)) {
|
|
84
|
+
await this.loadScript(this.prefix);
|
|
85
|
+
}
|
|
86
|
+
this.addEventListener('dblclick', this.requestEdit.bind(this));
|
|
87
|
+
this.shadowRoot?.addEventListener('mousedown', this._onMouseDown.bind(this));
|
|
88
|
+
this.shadowRoot?.addEventListener('mousedown', this._onResizeMouseDown.bind(this), true);
|
|
89
|
+
window.addEventListener('mousemove', this._onMouseMove.bind(this));
|
|
90
|
+
window.addEventListener('mouseup', this._onMouseUp.bind(this));
|
|
91
|
+
}
|
|
92
|
+
async disconnectedCallback() {
|
|
93
|
+
this.removeEventListener('dblclick', this.requestEdit.bind(this));
|
|
94
|
+
this.shadowRoot?.removeEventListener('mousedown', this._onMouseDown.bind(this));
|
|
95
|
+
this.shadowRoot?.removeEventListener('mousedown', this._onResizeMouseDown.bind(this), true);
|
|
96
|
+
window.removeEventListener('mousemove', this._onMouseMove.bind(this));
|
|
97
|
+
window.removeEventListener('mouseup', this._onMouseUp.bind(this));
|
|
98
|
+
}
|
|
99
|
+
_onMouseDown(event) {
|
|
100
|
+
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
101
|
+
if (!editor || !event.target.closest('.qbo-code header'))
|
|
102
|
+
return;
|
|
103
|
+
// Ensure that the editor has a defined position
|
|
104
|
+
const computedStyle = window.getComputedStyle(editor);
|
|
105
|
+
editor.style.left = computedStyle.left === 'auto' ? `${editor.getBoundingClientRect().left}px` : computedStyle.left;
|
|
106
|
+
editor.style.top = computedStyle.top === 'auto' ? `${editor.getBoundingClientRect().top}px` : computedStyle.top;
|
|
107
|
+
this.dragging = true;
|
|
108
|
+
this.offsetX = event.clientX - parseFloat(computedStyle.left || '0');
|
|
109
|
+
this.offsetY = event.clientY - parseFloat(computedStyle.top || '0');
|
|
110
|
+
}
|
|
111
|
+
_onResizeMouseDown(event) {
|
|
112
|
+
if (!event.target.closest('.qbo-code .resize-handle'))
|
|
113
|
+
return;
|
|
114
|
+
this.resizing = true;
|
|
115
|
+
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
116
|
+
if (editor) {
|
|
117
|
+
this.startWidth = editor.offsetWidth;
|
|
118
|
+
this.startHeight = editor.offsetHeight;
|
|
119
|
+
this.offsetX = event.clientX;
|
|
120
|
+
this.offsetY = event.clientY;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
_onMouseMove(event) {
|
|
124
|
+
if (this.dragging) {
|
|
125
|
+
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
126
|
+
if (editor) {
|
|
127
|
+
const x = event.clientX - this.offsetX;
|
|
128
|
+
const y = event.clientY - this.offsetY;
|
|
129
|
+
editor.style.left = `${x}px`;
|
|
130
|
+
editor.style.top = `${y}px`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (this.resizing) {
|
|
134
|
+
const editor = this.shadowRoot?.querySelector('.qbo-code');
|
|
135
|
+
if (editor) {
|
|
136
|
+
editor.style.width = `${this.startWidth + (event.clientX - this.offsetX)}px`;
|
|
137
|
+
editor.style.height = `${this.startHeight + (event.clientY - this.offsetY)}px`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
_onMouseUp() {
|
|
142
|
+
this.dragging = false;
|
|
143
|
+
this.resizing = false;
|
|
144
|
+
}
|
|
145
|
+
// Function to flatten JSON object keys for use in autocomplete
|
|
146
|
+
flattenObjectKeys(obj, prefix = '') {
|
|
147
|
+
return Object.keys(obj).reduce((res, k) => {
|
|
148
|
+
const pre = prefix.length ? `${prefix}.` : '';
|
|
149
|
+
if (typeof obj[k] === 'object' && obj[k] !== null) {
|
|
150
|
+
res = res.concat(this.flattenObjectKeys(obj[k], `${pre}${k}`));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
res.push(`${pre}${k}`);
|
|
154
|
+
}
|
|
155
|
+
return res;
|
|
156
|
+
}, []);
|
|
157
|
+
}
|
|
158
|
+
// Function to flatten properties of a LitElement-derived component for use in autocomplete
|
|
159
|
+
flattenComponentKeys(component) {
|
|
160
|
+
const keys = [];
|
|
161
|
+
const proto = Object.getPrototypeOf(component);
|
|
162
|
+
// Use Reflect API to get metadata if available
|
|
163
|
+
const propertyKeys = Object.getOwnPropertyNames(proto);
|
|
164
|
+
propertyKeys.forEach((key) => {
|
|
165
|
+
if (key === 'constructor' || key.startsWith('_'))
|
|
166
|
+
return; // Skip constructor and private fields
|
|
167
|
+
const metadata = Reflect.getMetadata('design:type', proto, key);
|
|
168
|
+
if (metadata) {
|
|
169
|
+
if (typeof component[key] === 'object' && component[key] !== null) {
|
|
170
|
+
keys.push(...this.flattenObjectKeys(component[key], key));
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
keys.push(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return keys.map(key => ({ label: key, type: 'variable' }));
|
|
178
|
+
}
|
|
179
|
+
_isSerializable(value) {
|
|
180
|
+
if (typeof value === 'function' || typeof value === 'undefined') {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
JSON.stringify(value);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
getComponent() {
|
|
192
|
+
let json = {};
|
|
193
|
+
const ownProperties = new Set(Object.keys(this));
|
|
194
|
+
// Filter properties to exclude those defined by the base class
|
|
195
|
+
ownProperties.forEach(prop => {
|
|
196
|
+
if (prop.startsWith('_'))
|
|
197
|
+
return;
|
|
198
|
+
if (prop === 'constructor'
|
|
199
|
+
|| prop === 'shadowRoot'
|
|
200
|
+
|| prop === 'renderOptions'
|
|
201
|
+
|| prop === 'renderRoot'
|
|
202
|
+
|| prop === 'editor'
|
|
203
|
+
|| typeof this[prop] === 'function')
|
|
204
|
+
return;
|
|
205
|
+
json[prop] = this[prop];
|
|
206
|
+
});
|
|
207
|
+
return json;
|
|
208
|
+
}
|
|
209
|
+
async loadScript(prefix) {
|
|
210
|
+
if (!scriptCache[prefix]) {
|
|
211
|
+
scriptCache[prefix] = this.fetchScript(prefix);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const scriptText = await scriptCache[prefix];
|
|
215
|
+
this.appendScriptToHead(scriptText);
|
|
216
|
+
console.log(`Template script for ${this.prefix} loaded and executed.`);
|
|
217
|
+
this.requestUpdate();
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
console.error(`Template script for ${this.prefix} failed to load:`, error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async fetchScript(prefix) {
|
|
224
|
+
const response = await fetch(`${this.templateEndpoint}/search/${prefix}`);
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
throw new Error(`Failed to fetch templates from: ${this.templateEndpoint}/search/${prefix}`);
|
|
227
|
+
}
|
|
228
|
+
return response.text();
|
|
229
|
+
}
|
|
230
|
+
appendScriptToHead(scriptText) {
|
|
231
|
+
const scriptElement = document.createElement('script');
|
|
232
|
+
scriptElement.type = 'text/javascript';
|
|
233
|
+
scriptElement.text = scriptText;
|
|
234
|
+
document.head.appendChild(scriptElement);
|
|
235
|
+
}
|
|
236
|
+
requestEdit(event) {
|
|
237
|
+
if (event.ctrlKey && this.closest('.qbo-design')) {
|
|
238
|
+
this.toggleEdit(!this.editing);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
toggleEdit(editing) {
|
|
242
|
+
this.editing = editing;
|
|
243
|
+
if (!this.editing) {
|
|
244
|
+
this.testTemplate = undefined;
|
|
245
|
+
this.editor?.destroy();
|
|
246
|
+
this.editor = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Creates a function from the user's input.
|
|
250
|
+
setTemplate(content) {
|
|
251
|
+
const test = new Function('component', `return qbo4.configuration.html\`${content}\``);
|
|
252
|
+
try {
|
|
253
|
+
test(this);
|
|
254
|
+
this.editorError = undefined;
|
|
255
|
+
this.testTemplate = test;
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
this.editorError = e;
|
|
259
|
+
console.error(`Error rendering ${content}`, e);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// resets the template, ignoring the user's input.
|
|
263
|
+
resetTemplate(event) {
|
|
264
|
+
this.editorType = event.target.value;
|
|
265
|
+
if (this.map?.has(event.target.value))
|
|
266
|
+
this.testTemplate = this.map.get(event.target.value) ?? this.defaultTemplate;
|
|
267
|
+
let expression = '<span>Template not defined</span>';
|
|
268
|
+
if (this.testTemplate !== undefined) {
|
|
269
|
+
const matches = this.testTemplate.toString().match(this.regex);
|
|
270
|
+
if (matches) {
|
|
271
|
+
expression = matches[1];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this.editor?.dispatch(this.editor.state.update({
|
|
275
|
+
changes: { from: 0, to: this.editor.state.doc.length, insert: expression }
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
updated(changedProperties) {
|
|
279
|
+
super.update(changedProperties);
|
|
280
|
+
let expression = '<span>Template not defined</span>';
|
|
281
|
+
if (this.testTemplate !== undefined) {
|
|
282
|
+
const matches = this.testTemplate.toString().match(this.regex);
|
|
283
|
+
if (matches) {
|
|
284
|
+
expression = matches[1];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (this.editing) {
|
|
288
|
+
// const componentKeys = this.flattenComponentKeys(this);
|
|
289
|
+
// todo: create a custom autocomplete source
|
|
290
|
+
this.editor ??= new EditorView({
|
|
291
|
+
doc: expression,
|
|
292
|
+
extensions: [
|
|
293
|
+
basicSetup,
|
|
294
|
+
htmlcode(),
|
|
295
|
+
keymap.of([indentWithTab]),
|
|
296
|
+
EditorView.updateListener.of((e) => {
|
|
297
|
+
this.setTemplate(e.state.doc.toString());
|
|
298
|
+
// this.dispatchEvent(new CustomEvent('change', { detail: { code: this.value } }));
|
|
299
|
+
})
|
|
300
|
+
],
|
|
301
|
+
parent: this.shadowRoot?.querySelector('div.editor')
|
|
302
|
+
// parent: <DocumentFragment>this.shadowRoot,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
edit() {
|
|
307
|
+
this.testTemplate ??= this.map?.get(this.type) ?? this.defaultTemplate;
|
|
308
|
+
const id = Math.random().toString(36).substring(2, 15);
|
|
309
309
|
return html `
|
|
310
310
|
<dialog class="qbo-code" open>
|
|
311
311
|
<header>
|
|
@@ -325,128 +325,128 @@ input.working {
|
|
|
325
325
|
<button type="submit" @click=${this.save} ?disabled="${this.editorCoding}">Save</button>
|
|
326
326
|
<button type="reset" @click=${this.cancel} ?disabled="${this.editorCoding}">Cancel</button>
|
|
327
327
|
</footer>
|
|
328
|
-
</dialog>`;
|
|
329
|
-
}
|
|
330
|
-
cancel() {
|
|
331
|
-
this.toggleEdit(false);
|
|
332
|
-
}
|
|
333
|
-
async save() {
|
|
334
|
-
try {
|
|
335
|
-
if (this.editor == null)
|
|
336
|
-
return;
|
|
337
|
-
const formData = new FormData();
|
|
338
|
-
formData.append('htmlTemplate', this.editor.state.doc.toString());
|
|
339
|
-
const response = await fetch(`${this.templateEndpoint}/save/${this.prefix}/${this.editorType}`, {
|
|
340
|
-
method: 'POST',
|
|
341
|
-
body: formData
|
|
342
|
-
});
|
|
343
|
-
if (response.ok) {
|
|
344
|
-
this.map?.set(this.editorType, this.testTemplate);
|
|
345
|
-
this.type = this.editorType;
|
|
346
|
-
this.toggleEdit(false);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
throw new Error(`Failed to save template: ${response.statusText}`);
|
|
350
|
-
}
|
|
351
|
-
catch (e) {
|
|
352
|
-
throw e;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
async code(event) {
|
|
356
|
-
try {
|
|
357
|
-
if (this.editor == null)
|
|
358
|
-
return;
|
|
359
|
-
this.editorCoding = true;
|
|
360
|
-
const prompt = event.target?.value;
|
|
361
|
-
const response = await fetch(`${this.templateEndpoint}/code`, {
|
|
362
|
-
method: 'POST',
|
|
363
|
-
headers: { 'Content-Type': 'application/json' },
|
|
364
|
-
body: JSON.stringify({
|
|
365
|
-
'HtmlTemplate': this.editor.state.doc.toString(),
|
|
366
|
-
'Component': this.getComponent(),
|
|
367
|
-
'Prompt': prompt
|
|
368
|
-
})
|
|
369
|
-
});
|
|
370
|
-
this.editorCoding = false;
|
|
371
|
-
if (response.ok) {
|
|
372
|
-
const html = await response.text();
|
|
373
|
-
this.editor.dispatch({
|
|
374
|
-
changes: { from: 0, to: this.editor.state.doc.length, insert: html }
|
|
375
|
-
});
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
else
|
|
379
|
-
throw new Error(`Failed to generate code: ${response.statusText}`);
|
|
380
|
-
}
|
|
381
|
-
catch (e) {
|
|
382
|
-
this.editorCoding = false;
|
|
383
|
-
throw e;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
render() {
|
|
387
|
-
let template = this.defaultTemplate;
|
|
388
|
-
if (this.type && this.prefix && !this.map?.has(this.type))
|
|
389
|
-
this.map = templates.get(this.prefix);
|
|
390
|
-
if (this.type && this.map?.has(this.type))
|
|
391
|
-
template = this.map.get(this.type);
|
|
392
|
-
return (this.editing)
|
|
393
|
-
? html `${this.edit()}${this.testTemplate(this)}`
|
|
394
|
-
: template(this);
|
|
395
|
-
// return html`${this.editing ? this.edit() : html``}${this.editing ? this.testTemplate(this) : template(this)}`
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
__decorate([
|
|
399
|
-
property({ attribute: false }),
|
|
400
|
-
__metadata("design:type", Object)
|
|
401
|
-
], QboTemplateClass.prototype, "editorError", void 0);
|
|
402
|
-
__decorate([
|
|
403
|
-
property(),
|
|
404
|
-
__metadata("design:type", Object)
|
|
405
|
-
], QboTemplateClass.prototype, "type", void 0);
|
|
406
|
-
__decorate([
|
|
407
|
-
property(),
|
|
408
|
-
__metadata("design:type", Object)
|
|
409
|
-
], QboTemplateClass.prototype, "editing", void 0);
|
|
410
|
-
__decorate([
|
|
411
|
-
property({ attribute: false }),
|
|
412
|
-
__metadata("design:type", Function)
|
|
413
|
-
], QboTemplateClass.prototype, "defaultTemplate", void 0);
|
|
414
|
-
__decorate([
|
|
415
|
-
property({ attribute: false }),
|
|
416
|
-
__metadata("design:type", Object)
|
|
417
|
-
], QboTemplateClass.prototype, "testTemplate", void 0);
|
|
418
|
-
__decorate([
|
|
419
|
-
property(),
|
|
420
|
-
__metadata("design:type", Object)
|
|
421
|
-
], QboTemplateClass.prototype, "templateEndpoint", void 0);
|
|
422
|
-
__decorate([
|
|
423
|
-
property(),
|
|
424
|
-
__metadata("design:type", Object)
|
|
425
|
-
], QboTemplateClass.prototype, "prefix", void 0);
|
|
426
|
-
__decorate([
|
|
427
|
-
property({ attribute: false }),
|
|
428
|
-
__metadata("design:type", Object)
|
|
429
|
-
], QboTemplateClass.prototype, "map", void 0);
|
|
430
|
-
__decorate([
|
|
431
|
-
property({ attribute: false }),
|
|
432
|
-
__metadata("design:type", Boolean)
|
|
433
|
-
], QboTemplateClass.prototype, "editorCoding", void 0);
|
|
434
|
-
;
|
|
435
|
-
return QboTemplateClass;
|
|
436
|
-
};
|
|
437
|
-
/**
|
|
438
|
-
* Renders a template defined in configuration.
|
|
439
|
-
*
|
|
440
|
-
* @remarks
|
|
441
|
-
* @param apiEndpoint - The API endpoint to fetch data from.
|
|
442
|
-
* @param method - The HTTP method to use when fetching data.
|
|
443
|
-
* @param error - Indicates whether an error occurred while fetching data.
|
|
444
|
-
*/
|
|
445
|
-
export const QboTemplate = QboTemplateMixin(LitElement);
|
|
446
|
-
if (typeof window !== 'undefined') {
|
|
447
|
-
window.qbo4 = window.qbo4 || {};
|
|
448
|
-
window.qbo4.configuration = window.qbo4.configuration || {};
|
|
449
|
-
window.qbo4.configuration.templates = templates;
|
|
450
|
-
window.qbo4.configuration.html = html;
|
|
451
|
-
}
|
|
328
|
+
</dialog>`;
|
|
329
|
+
}
|
|
330
|
+
cancel() {
|
|
331
|
+
this.toggleEdit(false);
|
|
332
|
+
}
|
|
333
|
+
async save() {
|
|
334
|
+
try {
|
|
335
|
+
if (this.editor == null)
|
|
336
|
+
return;
|
|
337
|
+
const formData = new FormData();
|
|
338
|
+
formData.append('htmlTemplate', this.editor.state.doc.toString());
|
|
339
|
+
const response = await fetch(`${this.templateEndpoint}/save/${this.prefix}/${this.editorType}`, {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
body: formData
|
|
342
|
+
});
|
|
343
|
+
if (response.ok) {
|
|
344
|
+
this.map?.set(this.editorType, this.testTemplate);
|
|
345
|
+
this.type = this.editorType;
|
|
346
|
+
this.toggleEdit(false);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
throw new Error(`Failed to save template: ${response.statusText}`);
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
throw e;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async code(event) {
|
|
356
|
+
try {
|
|
357
|
+
if (this.editor == null)
|
|
358
|
+
return;
|
|
359
|
+
this.editorCoding = true;
|
|
360
|
+
const prompt = event.target?.value;
|
|
361
|
+
const response = await fetch(`${this.templateEndpoint}/code`, {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
headers: { 'Content-Type': 'application/json' },
|
|
364
|
+
body: JSON.stringify({
|
|
365
|
+
'HtmlTemplate': this.editor.state.doc.toString(),
|
|
366
|
+
'Component': this.getComponent(),
|
|
367
|
+
'Prompt': prompt
|
|
368
|
+
})
|
|
369
|
+
});
|
|
370
|
+
this.editorCoding = false;
|
|
371
|
+
if (response.ok) {
|
|
372
|
+
const html = await response.text();
|
|
373
|
+
this.editor.dispatch({
|
|
374
|
+
changes: { from: 0, to: this.editor.state.doc.length, insert: html }
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
else
|
|
379
|
+
throw new Error(`Failed to generate code: ${response.statusText}`);
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
this.editorCoding = false;
|
|
383
|
+
throw e;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
render() {
|
|
387
|
+
let template = this.defaultTemplate;
|
|
388
|
+
if (this.type && this.prefix && !this.map?.has(this.type))
|
|
389
|
+
this.map = templates.get(this.prefix);
|
|
390
|
+
if (this.type && this.map?.has(this.type))
|
|
391
|
+
template = this.map.get(this.type);
|
|
392
|
+
return (this.editing)
|
|
393
|
+
? html `${this.edit()}${this.testTemplate(this)}`
|
|
394
|
+
: template(this);
|
|
395
|
+
// return html`${this.editing ? this.edit() : html``}${this.editing ? this.testTemplate(this) : template(this)}`
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
__decorate([
|
|
399
|
+
property({ attribute: false }),
|
|
400
|
+
__metadata("design:type", Object)
|
|
401
|
+
], QboTemplateClass.prototype, "editorError", void 0);
|
|
402
|
+
__decorate([
|
|
403
|
+
property(),
|
|
404
|
+
__metadata("design:type", Object)
|
|
405
|
+
], QboTemplateClass.prototype, "type", void 0);
|
|
406
|
+
__decorate([
|
|
407
|
+
property(),
|
|
408
|
+
__metadata("design:type", Object)
|
|
409
|
+
], QboTemplateClass.prototype, "editing", void 0);
|
|
410
|
+
__decorate([
|
|
411
|
+
property({ attribute: false }),
|
|
412
|
+
__metadata("design:type", Function)
|
|
413
|
+
], QboTemplateClass.prototype, "defaultTemplate", void 0);
|
|
414
|
+
__decorate([
|
|
415
|
+
property({ attribute: false }),
|
|
416
|
+
__metadata("design:type", Object)
|
|
417
|
+
], QboTemplateClass.prototype, "testTemplate", void 0);
|
|
418
|
+
__decorate([
|
|
419
|
+
property(),
|
|
420
|
+
__metadata("design:type", Object)
|
|
421
|
+
], QboTemplateClass.prototype, "templateEndpoint", void 0);
|
|
422
|
+
__decorate([
|
|
423
|
+
property(),
|
|
424
|
+
__metadata("design:type", Object)
|
|
425
|
+
], QboTemplateClass.prototype, "prefix", void 0);
|
|
426
|
+
__decorate([
|
|
427
|
+
property({ attribute: false }),
|
|
428
|
+
__metadata("design:type", Object)
|
|
429
|
+
], QboTemplateClass.prototype, "map", void 0);
|
|
430
|
+
__decorate([
|
|
431
|
+
property({ attribute: false }),
|
|
432
|
+
__metadata("design:type", Boolean)
|
|
433
|
+
], QboTemplateClass.prototype, "editorCoding", void 0);
|
|
434
|
+
;
|
|
435
|
+
return QboTemplateClass;
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* Renders a template defined in configuration.
|
|
439
|
+
*
|
|
440
|
+
* @remarks
|
|
441
|
+
* @param apiEndpoint - The API endpoint to fetch data from.
|
|
442
|
+
* @param method - The HTTP method to use when fetching data.
|
|
443
|
+
* @param error - Indicates whether an error occurred while fetching data.
|
|
444
|
+
*/
|
|
445
|
+
export const QboTemplate = QboTemplateMixin(LitElement);
|
|
446
|
+
if (typeof window !== 'undefined') {
|
|
447
|
+
window.qbo4 = window.qbo4 || {};
|
|
448
|
+
window.qbo4.configuration = window.qbo4.configuration || {};
|
|
449
|
+
window.qbo4.configuration.templates = templates;
|
|
450
|
+
window.qbo4.configuration.html = html;
|
|
451
|
+
}
|
|
452
452
|
//# sourceMappingURL=qbo-template.js.map
|