zauberflote 1.0.0 → 1.0.1
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 +136 -9
- package/.env +0 -1
package/package.json
CHANGED
package/src/ui.js
CHANGED
|
@@ -82,7 +82,7 @@ const ui = (() => {
|
|
|
82
82
|
this.app = app;
|
|
83
83
|
this.parent = parent || null;
|
|
84
84
|
this.title = title || 'Section';
|
|
85
|
-
this.
|
|
85
|
+
this.sectionId = slugify(this.title);
|
|
86
86
|
this.readConfig = null;
|
|
87
87
|
this.listTemplate = '';
|
|
88
88
|
this.actions = [];
|
|
@@ -101,13 +101,26 @@ const ui = (() => {
|
|
|
101
101
|
this.textConfig = null;
|
|
102
102
|
this.markdownConfig = null;
|
|
103
103
|
this.customRenderer = null;
|
|
104
|
+
this.renderHook = null;
|
|
104
105
|
this.autoConfig = null;
|
|
106
|
+
this.mockConfig = null;
|
|
105
107
|
this.suppressOutput = false;
|
|
106
108
|
this.noAutoRefresh = false;
|
|
107
109
|
this.layoutConfig = null;
|
|
110
|
+
this.templateMode = null;
|
|
108
111
|
}
|
|
109
112
|
id(value) {
|
|
110
|
-
this.
|
|
113
|
+
this.sectionId = value;
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
mock(generator) {
|
|
117
|
+
// Signature: Smart Mocking System - show placeholder data when backend is offline.
|
|
118
|
+
this.mockConfig = generator;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
onRender(callback) {
|
|
122
|
+
// Signature: Lifecycle hook for custom library initialization (Charts, Maps).
|
|
123
|
+
this.renderHook = callback;
|
|
111
124
|
return this;
|
|
112
125
|
}
|
|
113
126
|
read(path) {
|
|
@@ -127,6 +140,11 @@ const ui = (() => {
|
|
|
127
140
|
this.listTemplate = template || '';
|
|
128
141
|
return this;
|
|
129
142
|
}
|
|
143
|
+
template(name) {
|
|
144
|
+
// Signature: chapter-9 geo-search, live-poll (table display mode).
|
|
145
|
+
this.templateMode = name || 'default';
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
130
148
|
listFrom(path) {
|
|
131
149
|
// Signature: chapter-4/4-4-pagination (items list from query results).
|
|
132
150
|
this.listFromPath = path;
|
|
@@ -501,7 +519,7 @@ const ui = (() => {
|
|
|
501
519
|
|
|
502
520
|
if (section.actions.length > 0) {
|
|
503
521
|
section.actions.forEach((action) => {
|
|
504
|
-
actionWrap.appendChild(renderActionForm(action, output, section.appStore, sectionInputs, section));
|
|
522
|
+
actionWrap.appendChild(renderActionForm(action, output, section.appStore, sectionInputs, section, selectBindings));
|
|
505
523
|
});
|
|
506
524
|
root.appendChild(actionWrap);
|
|
507
525
|
}
|
|
@@ -592,6 +610,9 @@ const ui = (() => {
|
|
|
592
610
|
customWrap.appendChild(result);
|
|
593
611
|
}
|
|
594
612
|
}
|
|
613
|
+
if (section.renderHook) {
|
|
614
|
+
section.renderHook({ data, store: section.appStore, element: root });
|
|
615
|
+
}
|
|
595
616
|
};
|
|
596
617
|
|
|
597
618
|
const refresh = async () => {
|
|
@@ -599,16 +620,65 @@ const ui = (() => {
|
|
|
599
620
|
if (section.readConfig) {
|
|
600
621
|
const payload = buildQueryPayload(section, sectionInputs, section.appStore);
|
|
601
622
|
const path = buildQueryPath(section.readConfig.path, payload);
|
|
602
|
-
|
|
623
|
+
let data;
|
|
624
|
+
try {
|
|
625
|
+
data = await request(section.readConfig, null, false, payload, section.appStore, path);
|
|
626
|
+
if (!data.ok && section.mockConfig) {
|
|
627
|
+
throw new Error("Backend error");
|
|
628
|
+
}
|
|
629
|
+
root.classList.remove('is-mocked');
|
|
630
|
+
root.style.borderStyle = '';
|
|
631
|
+
} catch (e) {
|
|
632
|
+
if (section.mockConfig || section.readConfig) {
|
|
633
|
+
console.warn(`⚠️ Mocking data for section "${section.title}"`);
|
|
634
|
+
let mockData;
|
|
635
|
+
if (section.mockConfig) {
|
|
636
|
+
mockData = typeof section.mockConfig === 'function' ? section.mockConfig(section.appStore) : section.mockConfig;
|
|
637
|
+
} else {
|
|
638
|
+
// Auto-inference logic
|
|
639
|
+
if (section.listTemplate) {
|
|
640
|
+
mockData = [
|
|
641
|
+
{ id: 1, name: "Sample Item 1", amount: 100, description: "Mocked data" },
|
|
642
|
+
{ id: 2, name: "Sample Item 2", amount: 200, description: "Mocked data" }
|
|
643
|
+
];
|
|
644
|
+
} else if (section.kpiConfig) {
|
|
645
|
+
mockData = {};
|
|
646
|
+
section.kpiConfig.items.forEach(item => { if (item.key) mockData[item.key] = 0; });
|
|
647
|
+
} else {
|
|
648
|
+
mockData = { message: "Mocked response" };
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
data = { ok: true, status: 200, json: { data: mockData }, data: mockData };
|
|
652
|
+
root.classList.add('is-mocked');
|
|
653
|
+
root.style.borderStyle = 'dashed';
|
|
654
|
+
root.style.borderColor = '#cbd5e1'; // slate-300
|
|
655
|
+
if (!root.querySelector('.mock-badge')) {
|
|
656
|
+
const badge = document.createElement('span');
|
|
657
|
+
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';
|
|
658
|
+
badge.textContent = 'MOCKED';
|
|
659
|
+
root.querySelector('h2').appendChild(badge);
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
root.classList.remove('is-mocked');
|
|
663
|
+
root.style.borderStyle = '';
|
|
664
|
+
const badge = root.querySelector('.mock-badge');
|
|
665
|
+
if (badge) badge.remove();
|
|
666
|
+
throw e;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (!root.classList.contains('is-mocked')) {
|
|
670
|
+
const badge = root.querySelector('.mock-badge');
|
|
671
|
+
if (badge) badge.remove();
|
|
672
|
+
}
|
|
603
673
|
section.lastData = data.json || null;
|
|
604
674
|
applyStore(section.appStore, section.storeMap, data.json);
|
|
605
|
-
const hasExplicitView = section.listFromPath || section.listTemplate || section.kpiConfig || section.jsonConfig || section.textConfig || section.markdownConfig || section.customRenderer;
|
|
675
|
+
const hasExplicitView = section.listFromPath || section.listTemplate || section.templateMode || section.kpiConfig || section.jsonConfig || section.textConfig || section.markdownConfig || section.customRenderer;
|
|
606
676
|
if (!hasExplicitView && !section.autoConfig) {
|
|
607
677
|
section.autoConfig = { path: section.listFromPath || 'data' };
|
|
608
678
|
}
|
|
609
679
|
if (section.listFromPath) {
|
|
610
680
|
renderList(listWrap, section, { data: getPath(section.appStore, section.listFromPath) });
|
|
611
|
-
} else if (section.listTemplate) {
|
|
681
|
+
} else if (section.listTemplate || section.templateMode) {
|
|
612
682
|
renderList(listWrap, section, data);
|
|
613
683
|
}
|
|
614
684
|
if (metaEl && section.metaConfig) {
|
|
@@ -649,6 +719,8 @@ const ui = (() => {
|
|
|
649
719
|
|
|
650
720
|
section.refresh = refresh;
|
|
651
721
|
|
|
722
|
+
window.addEventListener(`ui:refresh:${section.sectionId}`, refresh);
|
|
723
|
+
|
|
652
724
|
return { root, refresh };
|
|
653
725
|
}
|
|
654
726
|
|
|
@@ -830,12 +902,37 @@ const ui = (() => {
|
|
|
830
902
|
target.appendChild(empty);
|
|
831
903
|
return;
|
|
832
904
|
}
|
|
905
|
+
// Table template mode
|
|
906
|
+
if (section.templateMode === 'table' && rows.length > 0) {
|
|
907
|
+
const table = document.createElement('table');
|
|
908
|
+
table.className = 'w-full text-sm border-collapse';
|
|
909
|
+
const keys = Object.keys(rows[0]);
|
|
910
|
+
const thead = document.createElement('thead');
|
|
911
|
+
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>';
|
|
912
|
+
table.appendChild(thead);
|
|
913
|
+
const tbody = document.createElement('tbody');
|
|
914
|
+
rows.forEach(row => {
|
|
915
|
+
const tr = document.createElement('tr');
|
|
916
|
+
tr.className = 'border-b border-slate-100 hover:bg-slate-50';
|
|
917
|
+
tr.innerHTML = keys.map(k => `<td class="py-2 px-3 text-slate-700">${row[k] ?? ''}</td>`).join('');
|
|
918
|
+
tbody.appendChild(tr);
|
|
919
|
+
});
|
|
920
|
+
table.appendChild(tbody);
|
|
921
|
+
target.appendChild(table);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
833
924
|
rows.forEach((row) => {
|
|
834
925
|
const card = document.createElement('div');
|
|
835
926
|
card.className = 'rounded border border-slate-200 px-3 py-2 space-y-2';
|
|
836
927
|
if (section.listTemplate) {
|
|
837
928
|
const label = document.createElement('div');
|
|
838
|
-
|
|
929
|
+
const rendered = renderTemplate(section.listTemplate, row);
|
|
930
|
+
// Use innerHTML if template contains HTML tags, otherwise textContent
|
|
931
|
+
if (/<[^>]+>/.test(rendered)) {
|
|
932
|
+
label.innerHTML = rendered;
|
|
933
|
+
} else {
|
|
934
|
+
label.textContent = rendered;
|
|
935
|
+
}
|
|
839
936
|
card.appendChild(label);
|
|
840
937
|
} else {
|
|
841
938
|
const label = document.createElement('div');
|
|
@@ -878,13 +975,16 @@ const ui = (() => {
|
|
|
878
975
|
});
|
|
879
976
|
}
|
|
880
977
|
|
|
881
|
-
function renderActionForm(action, output, appStore, sectionInputs, section) {
|
|
978
|
+
function renderActionForm(action, output, appStore, sectionInputs, section, selectBindings) {
|
|
882
979
|
const wrap = document.createElement('div');
|
|
883
980
|
wrap.className = 'grid gap-2 sm:grid-cols-4';
|
|
884
981
|
const fieldInputs = {};
|
|
885
982
|
action.fieldList.forEach((field) => {
|
|
886
983
|
const input = renderField(field, appStore);
|
|
887
984
|
fieldInputs[field.key] = input;
|
|
985
|
+
if (field.optionsFrom && selectBindings) {
|
|
986
|
+
selectBindings.push({ field, input });
|
|
987
|
+
}
|
|
888
988
|
wrap.appendChild(input);
|
|
889
989
|
});
|
|
890
990
|
const btn = document.createElement('button');
|
|
@@ -1477,9 +1577,36 @@ const ui = (() => {
|
|
|
1477
1577
|
}
|
|
1478
1578
|
}
|
|
1479
1579
|
|
|
1580
|
+
function loadScript(url) {
|
|
1581
|
+
return new Promise((resolve, reject) => {
|
|
1582
|
+
if (document.querySelector(`script[src="${url}"]`)) return resolve();
|
|
1583
|
+
const s = document.createElement('script');
|
|
1584
|
+
s.src = url;
|
|
1585
|
+
s.onload = resolve;
|
|
1586
|
+
s.onerror = reject;
|
|
1587
|
+
document.head.appendChild(s);
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function loadCSS(url) {
|
|
1592
|
+
if (document.querySelector(`link[href="${url}"]`)) return;
|
|
1593
|
+
const l = document.createElement('link');
|
|
1594
|
+
l.rel = 'stylesheet';
|
|
1595
|
+
l.href = url;
|
|
1596
|
+
document.head.appendChild(l);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1480
1599
|
autoMountPending();
|
|
1481
1600
|
|
|
1482
|
-
|
|
1601
|
+
// Global listener to bridge ui-refresh events to section-specific events
|
|
1602
|
+
window.addEventListener('ui-refresh', (e) => {
|
|
1603
|
+
const id = e.detail && e.detail.id;
|
|
1604
|
+
if (id) {
|
|
1605
|
+
window.dispatchEvent(new CustomEvent(`ui:refresh:${id}`));
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
return { app, mount, loadScript, loadCSS };
|
|
1483
1610
|
})();
|
|
1484
1611
|
|
|
1485
1612
|
export default ui;
|
package/.env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
TOKEN=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
|