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">
|
<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();
|
||||||
Reference in New Issue
Block a user