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:
@@ -7,13 +7,13 @@
|
||||
<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]
|
||||
-->
|
||||
|
||||
<style>
|
||||
/* =============================================================================
|
||||
* DECK STYLES v4.5
|
||||
* DECK STYLES v4.6
|
||||
* ============================================================================= */
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -726,8 +726,8 @@ body {
|
||||
|
||||
<script>
|
||||
/**
|
||||
* DECK Frontend v4.5
|
||||
* All 12 bases: HST,FLG,ITM,LOC,PLY,MST,BCK,MTH,ATC,Oracle,MAIL,CHAT
|
||||
* DECK Frontend v4.6
|
||||
* 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 = {
|
||||
$(selector) { return document.querySelector(selector); },
|
||||
@@ -881,7 +894,7 @@ const Utils = {
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 4. API
|
||||
// 5. API
|
||||
// =============================================================================
|
||||
const API = {
|
||||
async fetch(endpoint, schema, options = {}) {
|
||||
@@ -950,7 +963,7 @@ const API = {
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 5. FILTER
|
||||
// 6. FILTER
|
||||
// =============================================================================
|
||||
const Filter = {
|
||||
apply(tags) {
|
||||
@@ -972,7 +985,7 @@ const Filter = {
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 6. COMPONENTS
|
||||
// 7. COMPONENTS
|
||||
// =============================================================================
|
||||
const GroupsBar = {
|
||||
el: null,
|
||||
@@ -984,7 +997,7 @@ const GroupsBar = {
|
||||
if (!btn) return;
|
||||
State.set({ group: btn.dataset.group });
|
||||
this.render();
|
||||
App.renderView();
|
||||
Events.emit('render');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1022,7 +1035,7 @@ const LibrariesPanel = {
|
||||
State.set({ library: mrf, libraryMembers: new Set(members) });
|
||||
}
|
||||
this.render();
|
||||
App.renderView();
|
||||
Events.emit('render');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1105,7 +1118,7 @@ const DetailPanel = {
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 7. VIEWS
|
||||
// 8. VIEWS
|
||||
// =============================================================================
|
||||
const GridView = {
|
||||
render(tags) {
|
||||
@@ -1318,7 +1331,7 @@ const GraphView = {
|
||||
.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", () => {
|
||||
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);
|
||||
cb.checked ? cats.add(cb.dataset.category) : cats.delete(cb.dataset.category);
|
||||
State.set({ graphFilters: { ...graphFilters, categories: cats } });
|
||||
App.renderView();
|
||||
Events.emit('render');
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1387,20 +1400,20 @@ const GraphView = {
|
||||
const edges = new Set(graphFilters.edgeTypes);
|
||||
cb.checked ? edges.add(cb.dataset.edge) : edges.delete(cb.dataset.edge);
|
||||
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-labels").onchange = (e) => { State.set({ graphSettings: { ...graphSettings, showLabels: 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 } }); Events.emit('render'); };
|
||||
|
||||
const sizeSlider = sidebar.querySelector("#gs-size");
|
||||
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");
|
||||
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) {
|
||||
@@ -1431,7 +1444,7 @@ const GraphView = {
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// 8. APP
|
||||
// 9. APP
|
||||
// =============================================================================
|
||||
const App = {
|
||||
async init() {
|
||||
@@ -1439,6 +1452,10 @@ const App = {
|
||||
if (hashState.base) State.set({ base: hashState.base });
|
||||
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();
|
||||
LibrariesPanel.init();
|
||||
DetailPanel.init();
|
||||
Reference in New Issue
Block a user