zauberflote 1.0.0 → 1.0.2
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/ui.js +268 -14
- package/.env +0 -1
package/package.json
CHANGED
package/src/ui.js
CHANGED
|
@@ -2,11 +2,77 @@ const ui = (() => {
|
|
|
2
2
|
const pendingApps = [];
|
|
3
3
|
let autoMountScheduled = false;
|
|
4
4
|
|
|
5
|
+
// Helper to wrap builders with Proxy for better error messages
|
|
6
|
+
function wrapBuilder(instance, className, chainHistory = []) {
|
|
7
|
+
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(instance))
|
|
8
|
+
.filter(m => m !== 'constructor' && typeof instance[m] === 'function');
|
|
9
|
+
|
|
10
|
+
return new Proxy(instance, {
|
|
11
|
+
get(target, prop) {
|
|
12
|
+
// Track chain for error messages
|
|
13
|
+
target._chainHistory = chainHistory;
|
|
14
|
+
|
|
15
|
+
if (prop in target) {
|
|
16
|
+
const value = target[prop];
|
|
17
|
+
if (typeof value === 'function') {
|
|
18
|
+
return function(...args) {
|
|
19
|
+
const result = value.apply(target, args);
|
|
20
|
+
// If result is a builder object, wrap it too
|
|
21
|
+
if (result && typeof result === 'object' && result.constructor &&
|
|
22
|
+
result.constructor.name.includes('Builder')) {
|
|
23
|
+
// Format arguments nicely for the chain
|
|
24
|
+
const formatArg = (a) => {
|
|
25
|
+
if (typeof a === 'string') {
|
|
26
|
+
const short = a.length > 20 ? a.slice(0, 20) + '...' : a;
|
|
27
|
+
return `"${short.replace(/\n/g, ' ')}"`;
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(a)) return `[${a.length} items]`;
|
|
30
|
+
if (typeof a === 'object' && a !== null) return '{...}';
|
|
31
|
+
return String(a);
|
|
32
|
+
};
|
|
33
|
+
const argStr = args.length > 0 ? `(${args.map(formatArg).join(', ')})` : '()';
|
|
34
|
+
const newChain = [...chainHistory, `.${prop}${argStr}`];
|
|
35
|
+
return wrapBuilder(result, result.constructor.name, newChain);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Property doesn't exist - provide helpful error
|
|
44
|
+
if (typeof prop === 'string' && !prop.startsWith('_')) {
|
|
45
|
+
const suggestion = methods.find(m => m.toLowerCase().includes(prop.toLowerCase()));
|
|
46
|
+
|
|
47
|
+
// Format chain nicely - show last 5 calls max
|
|
48
|
+
const recentChain = chainHistory.slice(-5);
|
|
49
|
+
const chainStr = recentChain.length > 0
|
|
50
|
+
? (chainHistory.length > 5 ? ' ...\n' : '') + recentChain.map(c => ` ${c}`).join('\n')
|
|
51
|
+
: ' (start of chain)';
|
|
52
|
+
|
|
53
|
+
let errorMsg = `\n[ui.js] ❌ ${className} has no method "${prop}"\n\n`;
|
|
54
|
+
errorMsg += `Available methods:\n ${methods.join(', ')}\n\n`;
|
|
55
|
+
errorMsg += `Chain (last ${Math.min(chainHistory.length, 5)} calls):\n${chainStr}\n .${prop}() ← ERROR HERE\n`;
|
|
56
|
+
if (suggestion) {
|
|
57
|
+
errorMsg += `\n💡 Did you mean: "${suggestion}"?\n`;
|
|
58
|
+
}
|
|
59
|
+
console.error(errorMsg);
|
|
60
|
+
|
|
61
|
+
// Return a function that throws to give a cleaner stack trace
|
|
62
|
+
return function() {
|
|
63
|
+
throw new Error(`[ui.js] ${className}.${prop} is not a function. See console for available methods.`);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
5
71
|
function app(title) {
|
|
6
72
|
const builder = new AppBuilder(title);
|
|
7
73
|
pendingApps.push(builder);
|
|
8
74
|
scheduleAutoMount();
|
|
9
|
-
return builder;
|
|
75
|
+
return wrapBuilder(builder, 'AppBuilder', [`ui.app("${title || 'App'}")`]);
|
|
10
76
|
}
|
|
11
77
|
|
|
12
78
|
class AppBuilder {
|
|
@@ -82,7 +148,7 @@ const ui = (() => {
|
|
|
82
148
|
this.app = app;
|
|
83
149
|
this.parent = parent || null;
|
|
84
150
|
this.title = title || 'Section';
|
|
85
|
-
this.
|
|
151
|
+
this.sectionId = slugify(this.title);
|
|
86
152
|
this.readConfig = null;
|
|
87
153
|
this.listTemplate = '';
|
|
88
154
|
this.actions = [];
|
|
@@ -100,14 +166,28 @@ const ui = (() => {
|
|
|
100
166
|
this.jsonConfig = null;
|
|
101
167
|
this.textConfig = null;
|
|
102
168
|
this.markdownConfig = null;
|
|
169
|
+
this.htmlContent = null;
|
|
103
170
|
this.customRenderer = null;
|
|
171
|
+
this.renderHook = null;
|
|
104
172
|
this.autoConfig = null;
|
|
173
|
+
this.mockConfig = null;
|
|
105
174
|
this.suppressOutput = false;
|
|
106
175
|
this.noAutoRefresh = false;
|
|
107
176
|
this.layoutConfig = null;
|
|
177
|
+
this.templateMode = null;
|
|
108
178
|
}
|
|
109
179
|
id(value) {
|
|
110
|
-
this.
|
|
180
|
+
this.sectionId = value;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
mock(generator) {
|
|
184
|
+
// Signature: Smart Mocking System - show placeholder data when backend is offline.
|
|
185
|
+
this.mockConfig = generator;
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
onRender(callback) {
|
|
189
|
+
// Signature: Lifecycle hook for custom library initialization (Charts, Maps).
|
|
190
|
+
this.renderHook = callback;
|
|
111
191
|
return this;
|
|
112
192
|
}
|
|
113
193
|
read(path) {
|
|
@@ -127,6 +207,11 @@ const ui = (() => {
|
|
|
127
207
|
this.listTemplate = template || '';
|
|
128
208
|
return this;
|
|
129
209
|
}
|
|
210
|
+
template(name) {
|
|
211
|
+
// Signature: chapter-9 geo-search, live-poll (table display mode).
|
|
212
|
+
this.templateMode = name || 'default';
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
130
215
|
listFrom(path) {
|
|
131
216
|
// Signature: chapter-4/4-4-pagination (items list from query results).
|
|
132
217
|
this.listFromPath = path;
|
|
@@ -151,6 +236,11 @@ const ui = (() => {
|
|
|
151
236
|
this.storeMap = Object.assign({}, this.storeMap, map);
|
|
152
237
|
return this;
|
|
153
238
|
}
|
|
239
|
+
hidden() {
|
|
240
|
+
// Signature: hide section output (useful for store-only reads).
|
|
241
|
+
this.suppressOutput = true;
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
154
244
|
storeView(key, template) {
|
|
155
245
|
// Signature: chapter-3 JWT/CSRF token display.
|
|
156
246
|
this.storeViewConfig = { key, template: template || `{{${key}}}` };
|
|
@@ -171,6 +261,11 @@ const ui = (() => {
|
|
|
171
261
|
this.textConfig = { text: text || '', path };
|
|
172
262
|
return this;
|
|
173
263
|
}
|
|
264
|
+
html(content) {
|
|
265
|
+
// Signature: static HTML content block.
|
|
266
|
+
this.htmlContent = content || '';
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
174
269
|
markdown(text, path) {
|
|
175
270
|
// Signature: chapter-4/4-1-validation, chapter-0 UI language docs.
|
|
176
271
|
this.markdownConfig = { text: text || '', path };
|
|
@@ -274,6 +369,15 @@ const ui = (() => {
|
|
|
274
369
|
this.path = path;
|
|
275
370
|
return this;
|
|
276
371
|
}
|
|
372
|
+
delete(path) {
|
|
373
|
+
// Alias for del() - more intuitive name.
|
|
374
|
+
return this.del(path);
|
|
375
|
+
}
|
|
376
|
+
confirm(message) {
|
|
377
|
+
// Signature: show confirmation dialog before action.
|
|
378
|
+
this.confirmMessage = message || 'Are you sure?';
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
277
381
|
upload(path) {
|
|
278
382
|
// Signature: chapter-4/4-8-uploads.
|
|
279
383
|
this.method = 'UPLOAD';
|
|
@@ -501,7 +605,7 @@ const ui = (() => {
|
|
|
501
605
|
|
|
502
606
|
if (section.actions.length > 0) {
|
|
503
607
|
section.actions.forEach((action) => {
|
|
504
|
-
actionWrap.appendChild(renderActionForm(action, output, section.appStore, sectionInputs, section));
|
|
608
|
+
actionWrap.appendChild(renderActionForm(action, output, section.appStore, sectionInputs, section, selectBindings));
|
|
505
609
|
});
|
|
506
610
|
root.appendChild(actionWrap);
|
|
507
611
|
}
|
|
@@ -546,6 +650,13 @@ const ui = (() => {
|
|
|
546
650
|
root.appendChild(markdownWrap);
|
|
547
651
|
}
|
|
548
652
|
|
|
653
|
+
// Static HTML content
|
|
654
|
+
if (section.htmlContent) {
|
|
655
|
+
const htmlWrap = document.createElement('div');
|
|
656
|
+
htmlWrap.innerHTML = section.htmlContent;
|
|
657
|
+
root.appendChild(htmlWrap);
|
|
658
|
+
}
|
|
659
|
+
|
|
549
660
|
let customWrap = null;
|
|
550
661
|
if (section.customRenderer) {
|
|
551
662
|
customWrap = document.createElement('div');
|
|
@@ -592,6 +703,9 @@ const ui = (() => {
|
|
|
592
703
|
customWrap.appendChild(result);
|
|
593
704
|
}
|
|
594
705
|
}
|
|
706
|
+
if (section.renderHook) {
|
|
707
|
+
section.renderHook({ data, store: section.appStore, element: root });
|
|
708
|
+
}
|
|
595
709
|
};
|
|
596
710
|
|
|
597
711
|
const refresh = async () => {
|
|
@@ -599,16 +713,65 @@ const ui = (() => {
|
|
|
599
713
|
if (section.readConfig) {
|
|
600
714
|
const payload = buildQueryPayload(section, sectionInputs, section.appStore);
|
|
601
715
|
const path = buildQueryPath(section.readConfig.path, payload);
|
|
602
|
-
|
|
716
|
+
let data;
|
|
717
|
+
try {
|
|
718
|
+
data = await request(section.readConfig, null, false, payload, section.appStore, path);
|
|
719
|
+
if (!data.ok && section.mockConfig) {
|
|
720
|
+
throw new Error("Backend error");
|
|
721
|
+
}
|
|
722
|
+
root.classList.remove('is-mocked');
|
|
723
|
+
root.style.borderStyle = '';
|
|
724
|
+
} catch (e) {
|
|
725
|
+
if (section.mockConfig || section.readConfig) {
|
|
726
|
+
console.warn(`⚠️ Mocking data for section "${section.title}"`);
|
|
727
|
+
let mockData;
|
|
728
|
+
if (section.mockConfig) {
|
|
729
|
+
mockData = typeof section.mockConfig === 'function' ? section.mockConfig(section.appStore) : section.mockConfig;
|
|
730
|
+
} else {
|
|
731
|
+
// Auto-inference logic
|
|
732
|
+
if (section.listTemplate) {
|
|
733
|
+
mockData = [
|
|
734
|
+
{ id: 1, name: "Sample Item 1", amount: 100, description: "Mocked data" },
|
|
735
|
+
{ id: 2, name: "Sample Item 2", amount: 200, description: "Mocked data" }
|
|
736
|
+
];
|
|
737
|
+
} else if (section.kpiConfig) {
|
|
738
|
+
mockData = {};
|
|
739
|
+
section.kpiConfig.items.forEach(item => { if (item.key) mockData[item.key] = 0; });
|
|
740
|
+
} else {
|
|
741
|
+
mockData = { message: "Mocked response" };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
data = { ok: true, status: 200, json: { data: mockData }, data: mockData };
|
|
745
|
+
root.classList.add('is-mocked');
|
|
746
|
+
root.style.borderStyle = 'dashed';
|
|
747
|
+
root.style.borderColor = '#cbd5e1'; // slate-300
|
|
748
|
+
if (!root.querySelector('.mock-badge')) {
|
|
749
|
+
const badge = document.createElement('span');
|
|
750
|
+
badge.className = 'mock-badge ml-2 inline-flex items-center rounded-md bg-amber-50 px-2 py-1 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-600/20';
|
|
751
|
+
badge.textContent = 'MOCKED';
|
|
752
|
+
root.querySelector('h2').appendChild(badge);
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
root.classList.remove('is-mocked');
|
|
756
|
+
root.style.borderStyle = '';
|
|
757
|
+
const badge = root.querySelector('.mock-badge');
|
|
758
|
+
if (badge) badge.remove();
|
|
759
|
+
throw e;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (!root.classList.contains('is-mocked')) {
|
|
763
|
+
const badge = root.querySelector('.mock-badge');
|
|
764
|
+
if (badge) badge.remove();
|
|
765
|
+
}
|
|
603
766
|
section.lastData = data.json || null;
|
|
604
767
|
applyStore(section.appStore, section.storeMap, data.json);
|
|
605
|
-
const hasExplicitView = section.listFromPath || section.listTemplate || section.kpiConfig || section.jsonConfig || section.textConfig || section.markdownConfig || section.customRenderer;
|
|
768
|
+
const hasExplicitView = section.listFromPath || section.listTemplate || section.templateMode || section.kpiConfig || section.jsonConfig || section.textConfig || section.markdownConfig || section.customRenderer;
|
|
606
769
|
if (!hasExplicitView && !section.autoConfig) {
|
|
607
770
|
section.autoConfig = { path: section.listFromPath || 'data' };
|
|
608
771
|
}
|
|
609
772
|
if (section.listFromPath) {
|
|
610
773
|
renderList(listWrap, section, { data: getPath(section.appStore, section.listFromPath) });
|
|
611
|
-
} else if (section.listTemplate) {
|
|
774
|
+
} else if (section.listTemplate || section.templateMode) {
|
|
612
775
|
renderList(listWrap, section, data);
|
|
613
776
|
}
|
|
614
777
|
if (metaEl && section.metaConfig) {
|
|
@@ -649,6 +812,8 @@ const ui = (() => {
|
|
|
649
812
|
|
|
650
813
|
section.refresh = refresh;
|
|
651
814
|
|
|
815
|
+
window.addEventListener(`ui:refresh:${section.sectionId}`, refresh);
|
|
816
|
+
|
|
652
817
|
return { root, refresh };
|
|
653
818
|
}
|
|
654
819
|
|
|
@@ -830,12 +995,37 @@ const ui = (() => {
|
|
|
830
995
|
target.appendChild(empty);
|
|
831
996
|
return;
|
|
832
997
|
}
|
|
998
|
+
// Table template mode
|
|
999
|
+
if (section.templateMode === 'table' && rows.length > 0) {
|
|
1000
|
+
const table = document.createElement('table');
|
|
1001
|
+
table.className = 'w-full text-sm border-collapse';
|
|
1002
|
+
const keys = Object.keys(rows[0]);
|
|
1003
|
+
const thead = document.createElement('thead');
|
|
1004
|
+
thead.innerHTML = '<tr class="border-b border-slate-200">' + keys.map(k => `<th class="text-left py-2 px-3 font-medium text-slate-600">${k}</th>`).join('') + '</tr>';
|
|
1005
|
+
table.appendChild(thead);
|
|
1006
|
+
const tbody = document.createElement('tbody');
|
|
1007
|
+
rows.forEach(row => {
|
|
1008
|
+
const tr = document.createElement('tr');
|
|
1009
|
+
tr.className = 'border-b border-slate-100 hover:bg-slate-50';
|
|
1010
|
+
tr.innerHTML = keys.map(k => `<td class="py-2 px-3 text-slate-700">${row[k] ?? ''}</td>`).join('');
|
|
1011
|
+
tbody.appendChild(tr);
|
|
1012
|
+
});
|
|
1013
|
+
table.appendChild(tbody);
|
|
1014
|
+
target.appendChild(table);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
833
1017
|
rows.forEach((row) => {
|
|
834
1018
|
const card = document.createElement('div');
|
|
835
1019
|
card.className = 'rounded border border-slate-200 px-3 py-2 space-y-2';
|
|
836
1020
|
if (section.listTemplate) {
|
|
837
1021
|
const label = document.createElement('div');
|
|
838
|
-
|
|
1022
|
+
const rendered = renderTemplate(section.listTemplate, row);
|
|
1023
|
+
// Use innerHTML if template contains HTML tags, otherwise textContent
|
|
1024
|
+
if (/<[^>]+>/.test(rendered)) {
|
|
1025
|
+
label.innerHTML = rendered;
|
|
1026
|
+
} else {
|
|
1027
|
+
label.textContent = rendered;
|
|
1028
|
+
}
|
|
839
1029
|
card.appendChild(label);
|
|
840
1030
|
} else {
|
|
841
1031
|
const label = document.createElement('div');
|
|
@@ -857,6 +1047,10 @@ const ui = (() => {
|
|
|
857
1047
|
btn.className = 'rounded border border-slate-300 px-3 py-2 text-sm';
|
|
858
1048
|
btn.textContent = action.label;
|
|
859
1049
|
btn.addEventListener('click', async () => {
|
|
1050
|
+
// Confirmation dialog if configured
|
|
1051
|
+
if (action.confirmMessage && !window.confirm(action.confirmMessage)) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
860
1054
|
const fields = action.fieldList.length > 0 ? action.fieldList : section.rowFields;
|
|
861
1055
|
const body = collectFields(fields, rowInputs);
|
|
862
1056
|
const path = renderTemplate(action.path, row, section.appStore);
|
|
@@ -878,19 +1072,26 @@ const ui = (() => {
|
|
|
878
1072
|
});
|
|
879
1073
|
}
|
|
880
1074
|
|
|
881
|
-
function renderActionForm(action, output, appStore, sectionInputs, section) {
|
|
1075
|
+
function renderActionForm(action, output, appStore, sectionInputs, section, selectBindings) {
|
|
882
1076
|
const wrap = document.createElement('div');
|
|
883
1077
|
wrap.className = 'grid gap-2 sm:grid-cols-4';
|
|
884
1078
|
const fieldInputs = {};
|
|
885
1079
|
action.fieldList.forEach((field) => {
|
|
886
1080
|
const input = renderField(field, appStore);
|
|
887
1081
|
fieldInputs[field.key] = input;
|
|
1082
|
+
if (field.optionsFrom && selectBindings) {
|
|
1083
|
+
selectBindings.push({ field, input });
|
|
1084
|
+
}
|
|
888
1085
|
wrap.appendChild(input);
|
|
889
1086
|
});
|
|
890
1087
|
const btn = document.createElement('button');
|
|
891
1088
|
btn.className = 'rounded border border-slate-300 px-3 py-2 text-sm';
|
|
892
1089
|
btn.textContent = action.label;
|
|
893
1090
|
btn.addEventListener('click', async () => {
|
|
1091
|
+
// Confirmation dialog if configured
|
|
1092
|
+
if (action.confirmMessage && !window.confirm(action.confirmMessage)) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
894
1095
|
const body = Object.assign({}, action.bodyMap, collectFields(action.fieldList, fieldInputs, sectionInputs));
|
|
895
1096
|
applySetMap(appStore, action.setMap, body, sectionInputs);
|
|
896
1097
|
applyAdjustments(appStore, action.adjustments, sectionInputs);
|
|
@@ -1269,6 +1470,10 @@ const ui = (() => {
|
|
|
1269
1470
|
|
|
1270
1471
|
function getPath(obj, path) {
|
|
1271
1472
|
if (!obj || !path) return undefined;
|
|
1473
|
+
if (typeof path !== 'string') {
|
|
1474
|
+
console.error(`[ui.js] getPath expected a string path, got ${typeof path}:`, path);
|
|
1475
|
+
return undefined;
|
|
1476
|
+
}
|
|
1272
1477
|
return path.split('.').reduce((acc, part) => (acc ? acc[part] : undefined), obj);
|
|
1273
1478
|
}
|
|
1274
1479
|
|
|
@@ -1276,15 +1481,37 @@ const ui = (() => {
|
|
|
1276
1481
|
if (!bindings || bindings.length === 0) return;
|
|
1277
1482
|
bindings.forEach(({ field, input }) => {
|
|
1278
1483
|
if (!field.optionsFrom) return;
|
|
1279
|
-
|
|
1280
|
-
|
|
1484
|
+
|
|
1485
|
+
// Handle optionsFrom as object { store: "key", value: "id", label: "name" } or string "key"
|
|
1486
|
+
let storePath, valueKey, labelKey;
|
|
1487
|
+
if (typeof field.optionsFrom === 'string') {
|
|
1488
|
+
storePath = field.optionsFrom;
|
|
1489
|
+
valueKey = field.optionValue || 'id';
|
|
1490
|
+
labelKey = field.optionLabel || 'name';
|
|
1491
|
+
} else if (typeof field.optionsFrom === 'object') {
|
|
1492
|
+
storePath = field.optionsFrom.store;
|
|
1493
|
+
valueKey = field.optionsFrom.value || field.optionValue || 'id';
|
|
1494
|
+
labelKey = field.optionsFrom.label || field.optionLabel || 'name';
|
|
1495
|
+
} else {
|
|
1496
|
+
console.error(`[ui.js] optionsFrom must be a string or object, got:`, field.optionsFrom);
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const list = getPath(store, storePath);
|
|
1501
|
+
if (!Array.isArray(list)) {
|
|
1502
|
+
if (list !== undefined) {
|
|
1503
|
+
console.warn(`[ui.js] optionsFrom "${storePath}" is not an array:`, list);
|
|
1504
|
+
}
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1281
1508
|
const current = input.value;
|
|
1282
1509
|
input.innerHTML = '';
|
|
1283
1510
|
list.forEach((row) => {
|
|
1284
1511
|
const option = document.createElement('option');
|
|
1285
|
-
const value =
|
|
1512
|
+
const value = getPath(row, valueKey) ?? row.id ?? row.name ?? row.value ?? '';
|
|
1286
1513
|
option.value = value;
|
|
1287
|
-
const label =
|
|
1514
|
+
const label = getPath(row, labelKey) ?? String(value);
|
|
1288
1515
|
option.textContent = label;
|
|
1289
1516
|
input.appendChild(option);
|
|
1290
1517
|
});
|
|
@@ -1477,9 +1704,36 @@ const ui = (() => {
|
|
|
1477
1704
|
}
|
|
1478
1705
|
}
|
|
1479
1706
|
|
|
1707
|
+
function loadScript(url) {
|
|
1708
|
+
return new Promise((resolve, reject) => {
|
|
1709
|
+
if (document.querySelector(`script[src="${url}"]`)) return resolve();
|
|
1710
|
+
const s = document.createElement('script');
|
|
1711
|
+
s.src = url;
|
|
1712
|
+
s.onload = resolve;
|
|
1713
|
+
s.onerror = reject;
|
|
1714
|
+
document.head.appendChild(s);
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
function loadCSS(url) {
|
|
1719
|
+
if (document.querySelector(`link[href="${url}"]`)) return;
|
|
1720
|
+
const l = document.createElement('link');
|
|
1721
|
+
l.rel = 'stylesheet';
|
|
1722
|
+
l.href = url;
|
|
1723
|
+
document.head.appendChild(l);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1480
1726
|
autoMountPending();
|
|
1481
1727
|
|
|
1482
|
-
|
|
1728
|
+
// Global listener to bridge ui-refresh events to section-specific events
|
|
1729
|
+
window.addEventListener('ui-refresh', (e) => {
|
|
1730
|
+
const id = e.detail && e.detail.id;
|
|
1731
|
+
if (id) {
|
|
1732
|
+
window.dispatchEvent(new CustomEvent(`ui:refresh:${id}`));
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
return { app, mount, loadScript, loadCSS };
|
|
1483
1737
|
})();
|
|
1484
1738
|
|
|
1485
1739
|
export default ui;
|
package/.env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
TOKEN=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
|