/** * StandardModule - Módulo estándar para bases con vistas Grid/Tree/Graph * * Usado por: taxonomía (hst, flg, itm, loc, ply), maestros (mst, bck), * registro (atc, mth) */ import { BaseModule } from '../registry.ts'; import { GridView, TreeView, GraphView } from '@/views/index.ts'; import { fetchTags, fetchHstTags, fetchGroups, fetchLibraries, fetchLibraryMembers } from '@/api/index.ts'; import { createNameMap, resolveGroupName, delegateEvent } from '@/utils/index.ts'; import type { ViewType } from '@/types/index.ts'; export class StandardModule extends BaseModule { private currentView: GridView | TreeView | GraphView | null = null; async mount(): Promise { // Show loading this.ctx.container.innerHTML = '
Cargando...
'; // Load data await this.loadData(); // Render sidebar and groups this.renderSidebar(); this.renderGroupsBar(); // Render main view this.render(); this.mounted = true; } unmount(): void { this.currentView?.unmount(); this.currentView = null; this.unsubscribe?.(); this.unsubscribe = null; this.mounted = false; } async loadData(): Promise { const config = this.getConfig(); // Fetch tags para esta base const tags = await fetchTags(config.id); // Fetch HST tags para resolución de nombres de grupos (si tiene grupos) const hstTags = config.api.hasGroups ? await fetchHstTags() : []; // Fetch grupos (solo si esta base los soporta) const groups = config.api.hasGroups ? await fetchGroups() : []; // Fetch bibliotecas (solo si esta base las soporta) const libraries = config.api.hasLibraries ? await fetchLibraries(config.id) : []; this.setState({ tags, hstTags, groups, libraries, library: 'all', libraryMembers: new Set(), group: 'all' }); } render(): void { const state = this.getState(); const config = this.getConfig(); // Verificar que la vista está soportada if (!this.isViewSupported(state.view)) { // Cambiar a vista por defecto const defaultView = config.defaultView as ViewType; this.setState({ view: defaultView }); return; } // Unmount current view this.currentView?.unmount(); // Clear container this.ctx.container.innerHTML = ''; this.ctx.container.className = `content-area ${state.view}-view`; // Mount new view switch (state.view) { case 'grid': this.currentView = new GridView(this.ctx.container, this.ctx.store, this.ctx.showDetail); this.currentView.mount(); break; case 'tree': if (config.views.tree) { this.currentView = new TreeView(this.ctx.container, this.ctx.store, this.ctx.showDetail); this.currentView.mount(); } break; case 'graph': if (config.views.graph) { this.currentView = new GraphView(this.ctx.container, this.ctx.store, this.ctx.showDetail); (this.currentView as GraphView).mount(); } break; } } renderSidebar(): void { const container = this.ctx.leftPanel; const state = this.getState(); const config = this.getConfig(); // Si es vista de grafo, mostrar opciones de grafo if (state.view === 'graph' && config.views.graph) { container.classList.add('graph-mode'); this.renderGraphOptions(container); return; } container.classList.remove('graph-mode'); // Si no tiene bibliotecas, vaciar sidebar if (!config.api.hasLibraries) { container.innerHTML = ''; return; } // Ordenar bibliotecas alfabéticamente const sortedLibs = [...state.libraries].sort((a, b) => { const nameA = a.name || a.name_es || a.alias || a.ref || ''; const nameB = b.name || b.name_es || b.alias || b.ref || ''; return nameA.localeCompare(nameB); }); // Renderizar bibliotecas (simple - sin config por ahora) container.innerHTML = `
ALL
${sortedLibs.map(lib => { const icon = lib.img_thumb_url || lib.icon_url || ''; const name = lib.name || lib.name_es || lib.alias || lib.ref || lib.mrf.slice(0, 6); return `
${icon ? `` : ''} ${name.slice(0, 8)}
`; }).join('')} `; // Bind library clicks delegateEvent(container, '.lib-icon', 'click', async (_, target) => { const library = target.dataset.lib || 'all'; if (library === 'all') { this.setState({ library: 'all', libraryMembers: new Set() }); } else { const currentBase = this.getState().base; const members = await fetchLibraryMembers(library, currentBase); this.setState({ library, libraryMembers: new Set(members) }); } this.renderSidebar(); this.render(); }); } renderGroupsBar(): void { const container = this.ctx.groupsBar; const state = this.getState(); const config = this.getConfig(); // Si no tiene grupos, vaciar if (!config.api.hasGroups) { container.innerHTML = ''; return; } // Usar hstTags para resolución de nombres const nameMap = createNameMap(state.hstTags, state.lang); // Contar tags por grupo const counts = new Map(); state.tags.forEach(tag => { const group = tag.set_hst || 'sin-grupo'; counts.set(group, (counts.get(group) || 0) + 1); }); // Ordenar por count y tomar top 20 const sorted = Array.from(counts.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 20); container.innerHTML = ` ${sorted.map(([groupMrf, count]) => { const groupName = resolveGroupName(groupMrf === 'sin-grupo' ? undefined : groupMrf, nameMap); return ` `; }).join('')} `; // Bind group clicks delegateEvent(container, '.group-btn', 'click', (_, target) => { const group = target.dataset.group || 'all'; this.setState({ group }); this.renderGroupsBar(); this.render(); }); } private renderGraphOptions(container: HTMLElement): void { // TODO: Extraer a componente separado const state = this.getState(); const { graphSettings, tags, graphEdges } = state; container.innerHTML = `
Nodos ${tags.length}
Edges ${graphEdges.length}
Visualización
Nodo ${graphSettings.nodeSize}px
Distancia ${graphSettings.linkDist}px
`; this.bindGraphOptionEvents(container); } private bindGraphOptionEvents(container: HTMLElement): void { // Show images checkbox const showImgCb = container.querySelector('#graph-show-img'); showImgCb?.addEventListener('change', () => { const state = this.getState(); this.setState({ graphSettings: { ...state.graphSettings, showImg: showImgCb.checked } }); this.render(); }); // Show labels checkbox const showLblCb = container.querySelector('#graph-show-lbl'); showLblCb?.addEventListener('change', () => { const state = this.getState(); this.setState({ graphSettings: { ...state.graphSettings, showLbl: showLblCb.checked } }); this.render(); }); // Node size slider const nodeSizeSlider = container.querySelector('#graph-node-size'); const nodeSizeVal = container.querySelector('#node-size-val'); nodeSizeSlider?.addEventListener('input', () => { const size = parseInt(nodeSizeSlider.value, 10); if (nodeSizeVal) nodeSizeVal.textContent = `${size}px`; const state = this.getState(); this.setState({ graphSettings: { ...state.graphSettings, nodeSize: size } }); this.render(); }); // Link distance slider const linkDistSlider = container.querySelector('#graph-link-dist'); const linkDistVal = container.querySelector('#link-dist-val'); linkDistSlider?.addEventListener('input', () => { const dist = parseInt(linkDistSlider.value, 10); if (linkDistVal) linkDistVal.textContent = `${dist}px`; const state = this.getState(); this.setState({ graphSettings: { ...state.graphSettings, linkDist: dist } }); this.render(); }); } } export default StandardModule;