DECK Frontend v4.6 - EventBus decoupling

Added Events module for inter-component communication:
- Events.on(event, handler) - subscribe
- Events.off(event, handler) - unsubscribe
- Events.emit(event, data) - publish

Decoupled:
- GroupsBar → Events.emit('render') instead of App.renderView()
- LibrariesPanel → Events.emit('render')
- GraphView sidebar → Events.emit('render')
- GraphView node click → Events.emit('detail:show', mrf)

App subscribes to events in init():
- Events.on('render', () => this.renderView())
- Events.on('detail:show', (mrf) => DetailPanel.show(mrf))

Now modules don't know about each other - changes are isolated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ARCHITECT
2026-01-16 19:37:34 +00:00
parent daea7753b6
commit 7672d5582f

View File

@@ -7,13 +7,13 @@
<meta name="description" content="DECK Tag Management System"> <meta name="description" content="DECK Tag Management System">
<!-- <!--
DECK FRONTEND v4.5 - All bases + generic structure DECK FRONTEND v4.6 - EventBus decoupling
Extract: ./extract.sh deck.html [output_dir] Extract: ./extract.sh deck.html [output_dir]
--> -->
<style> <style>
/* ============================================================================= /* =============================================================================
* DECK STYLES v4.5 * DECK STYLES v4.6
* ============================================================================= */ * ============================================================================= */
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
@@ -726,8 +726,8 @@ body {
<script> <script>
/** /**
* DECK Frontend v4.5 * DECK Frontend v4.6
* All 12 bases: HST,FLG,ITM,LOC,PLY,MST,BCK,MTH,ATC,Oracle,MAIL,CHAT * EventBus pattern for module decoupling
*/ */
// ============================================================================= // =============================================================================
@@ -804,7 +804,20 @@ const State = {
}; };
// ============================================================================= // =============================================================================
// 3. UTILS // 3. EVENTS (desacoplamiento entre módulos)
// =============================================================================
const Events = {
_handlers: {},
on(event, fn) { (this._handlers[event] ||= []).push(fn); },
off(event, fn) {
if (!fn) delete this._handlers[event];
else this._handlers[event] = (this._handlers[event] || []).filter(h => h !== fn);
},
emit(event, data) { (this._handlers[event] || []).forEach(fn => fn(data)); }
};
// =============================================================================
// 4. UTILS
// ============================================================================= // =============================================================================
const Utils = { const Utils = {
$(selector) { return document.querySelector(selector); }, $(selector) { return document.querySelector(selector); },
@@ -881,7 +894,7 @@ const Utils = {
}; };
// ============================================================================= // =============================================================================
// 4. API // 5. API
// ============================================================================= // =============================================================================
const API = { const API = {
async fetch(endpoint, schema, options = {}) { async fetch(endpoint, schema, options = {}) {
@@ -950,7 +963,7 @@ const API = {
}; };
// ============================================================================= // =============================================================================
// 5. FILTER // 6. FILTER
// ============================================================================= // =============================================================================
const Filter = { const Filter = {
apply(tags) { apply(tags) {
@@ -972,7 +985,7 @@ const Filter = {
}; };
// ============================================================================= // =============================================================================
// 6. COMPONENTS // 7. COMPONENTS
// ============================================================================= // =============================================================================
const GroupsBar = { const GroupsBar = {
el: null, el: null,
@@ -984,7 +997,7 @@ const GroupsBar = {
if (!btn) return; if (!btn) return;
State.set({ group: btn.dataset.group }); State.set({ group: btn.dataset.group });
this.render(); this.render();
App.renderView(); Events.emit('render');
}); });
}, },
@@ -1022,7 +1035,7 @@ const LibrariesPanel = {
State.set({ library: mrf, libraryMembers: new Set(members) }); State.set({ library: mrf, libraryMembers: new Set(members) });
} }
this.render(); this.render();
App.renderView(); Events.emit('render');
}); });
}, },
@@ -1105,7 +1118,7 @@ const DetailPanel = {
}; };
// ============================================================================= // =============================================================================
// 7. VIEWS // 8. VIEWS
// ============================================================================= // =============================================================================
const GridView = { const GridView = {
render(tags) { render(tags) {
@@ -1318,7 +1331,7 @@ const GraphView = {
.attr("fill", "#e0e0e0").attr("font-size", "10px"); .attr("fill", "#e0e0e0").attr("font-size", "10px");
} }
node.on("click", (e, d) => { e.stopPropagation(); DetailPanel.show(d.id); }); node.on("click", (e, d) => { e.stopPropagation(); Events.emit('detail:show', d.id); });
this.simulation.on("tick", () => { this.simulation.on("tick", () => {
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y); link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y);
@@ -1378,7 +1391,7 @@ const GraphView = {
const cats = new Set(graphFilters.categories); const cats = new Set(graphFilters.categories);
cb.checked ? cats.add(cb.dataset.category) : cats.delete(cb.dataset.category); cb.checked ? cats.add(cb.dataset.category) : cats.delete(cb.dataset.category);
State.set({ graphFilters: { ...graphFilters, categories: cats } }); State.set({ graphFilters: { ...graphFilters, categories: cats } });
App.renderView(); Events.emit('render');
}; };
}); });
@@ -1387,20 +1400,20 @@ const GraphView = {
const edges = new Set(graphFilters.edgeTypes); const edges = new Set(graphFilters.edgeTypes);
cb.checked ? edges.add(cb.dataset.edge) : edges.delete(cb.dataset.edge); cb.checked ? edges.add(cb.dataset.edge) : edges.delete(cb.dataset.edge);
State.set({ graphFilters: { ...graphFilters, edgeTypes: edges } }); State.set({ graphFilters: { ...graphFilters, edgeTypes: edges } });
App.renderView(); Events.emit('render');
}; };
}); });
sidebar.querySelector("#gs-images").onchange = (e) => { State.set({ graphSettings: { ...graphSettings, showImages: e.target.checked } }); App.renderView(); }; sidebar.querySelector("#gs-images").onchange = (e) => { State.set({ graphSettings: { ...graphSettings, showImages: e.target.checked } }); Events.emit('render'); };
sidebar.querySelector("#gs-labels").onchange = (e) => { State.set({ graphSettings: { ...graphSettings, showLabels: e.target.checked } }); App.renderView(); }; sidebar.querySelector("#gs-labels").onchange = (e) => { State.set({ graphSettings: { ...graphSettings, showLabels: e.target.checked } }); Events.emit('render'); };
const sizeSlider = sidebar.querySelector("#gs-size"); const sizeSlider = sidebar.querySelector("#gs-size");
sizeSlider.oninput = (e) => { sidebar.querySelector("#gs-size-val").textContent = e.target.value + "px"; }; sizeSlider.oninput = (e) => { sidebar.querySelector("#gs-size-val").textContent = e.target.value + "px"; };
sizeSlider.onchange = (e) => { State.set({ graphSettings: { ...graphSettings, nodeSize: parseInt(e.target.value) } }); App.renderView(); }; sizeSlider.onchange = (e) => { State.set({ graphSettings: { ...graphSettings, nodeSize: parseInt(e.target.value) } }); Events.emit('render'); };
const distSlider = sidebar.querySelector("#gs-dist"); const distSlider = sidebar.querySelector("#gs-dist");
distSlider.oninput = (e) => { sidebar.querySelector("#gs-dist-val").textContent = e.target.value + "px"; }; distSlider.oninput = (e) => { sidebar.querySelector("#gs-dist-val").textContent = e.target.value + "px"; };
distSlider.onchange = (e) => { State.set({ graphSettings: { ...graphSettings, linkDist: parseInt(e.target.value) } }); App.renderView(); }; distSlider.onchange = (e) => { State.set({ graphSettings: { ...graphSettings, linkDist: parseInt(e.target.value) } }); Events.emit('render'); };
}, },
renderControls(container) { renderControls(container) {
@@ -1431,7 +1444,7 @@ const GraphView = {
}; };
// ============================================================================= // =============================================================================
// 8. APP // 9. APP
// ============================================================================= // =============================================================================
const App = { const App = {
async init() { async init() {
@@ -1439,6 +1452,10 @@ const App = {
if (hashState.base) State.set({ base: hashState.base }); if (hashState.base) State.set({ base: hashState.base });
if (hashState.view) State.set({ view: hashState.view }); if (hashState.view) State.set({ view: hashState.view });
// Event subscriptions (desacoplamiento)
Events.on('render', () => this.renderView());
Events.on('detail:show', (mrf) => DetailPanel.show(mrf));
GroupsBar.init(); GroupsBar.init();
LibrariesPanel.init(); LibrariesPanel.init();
DetailPanel.init(); DetailPanel.init();