diff --git a/deck-frontend/index.html b/deck-frontend/index.html index 8d66720..ae18091 100644 --- a/deck-frontend/index.html +++ b/deck-frontend/index.html @@ -55,11 +55,15 @@ -
- - - -
+ + + + +
+
+ + +
diff --git a/deck-frontend/src/main.ts b/deck-frontend/src/main.ts index 45c4f6a..d08978e 100644 --- a/deck-frontend/src/main.ts +++ b/deck-frontend/src/main.ts @@ -3,7 +3,8 @@ import { Router } from '@/router/index.ts'; import { fetchTags, fetchHstTags, fetchGroups, fetchLibraries, fetchLibraryMembers } from '@/api/index.ts'; import { GridView, TreeView, GraphView, DetailPanel } from '@/views/index.ts'; import { $, $$, delegateEvent, toast, createNameMap, resolveGroupName } from '@/utils/index.ts'; -import type { BaseType, ViewType } from '@/types/index.ts'; +import { CATS, EDGE_COLORS } from '@/config/index.ts'; +import type { BaseType, ViewType, CategoryKey, EdgeType } from '@/types/index.ts'; import './styles/main.css'; class App { @@ -135,6 +136,14 @@ class App { const state = store.getState(); + // Show graph options when in graph view + if (state.view === 'graph') { + container.classList.add('graph-mode'); + this.renderGraphOptions(container); + return; + } + + container.classList.remove('graph-mode'); container.innerHTML = `
ALL @@ -167,6 +176,169 @@ class App { }); } + private renderGraphOptions(container: HTMLElement): void { + const state = store.getState(); + const { graphFilters, graphSettings, tags, graphEdges } = state; + + // Count nodes and edges + const nodeCount = tags.length; + const edgeCount = graphEdges.length; + + container.innerHTML = ` +
+ +
+
+ Nodos + ${nodeCount} +
+
+ Edges + ${edgeCount} +
+
+ + +
+
Categorias
+ ${Object.entries(CATS).map(([key, config]) => ` + + `).join('')} +
+ + +
+
Relaciones
+ ${Object.entries(EDGE_COLORS).map(([key, color]) => ` + + `).join('')} +
+ + +
+
Visualizacion
+ + +
+
+ Nodo + ${graphSettings.nodeSize}px +
+ +
+
+
+ Distancia + ${graphSettings.linkDist}px +
+ +
+
+
+ `; + + // Bind events + this.bindGraphOptionEvents(container); + } + + private bindGraphOptionEvents(container: HTMLElement): void { + // Category checkboxes + container.querySelectorAll('[data-cat]').forEach(cb => { + cb.addEventListener('change', () => { + const cat = cb.dataset.cat as CategoryKey; + const state = store.getState(); + const newCats = new Set(state.graphFilters.cats); + if (cb.checked) { + newCats.add(cat); + } else { + newCats.delete(cat); + } + store.setState({ + graphFilters: { ...state.graphFilters, cats: newCats } + }); + this.renderView(); + }); + }); + + // Edge checkboxes + container.querySelectorAll('[data-edge]').forEach(cb => { + cb.addEventListener('change', () => { + const edge = cb.dataset.edge as EdgeType; + const state = store.getState(); + const newEdges = new Set(state.graphFilters.edges); + if (cb.checked) { + newEdges.add(edge); + } else { + newEdges.delete(edge); + } + store.setState({ + graphFilters: { ...state.graphFilters, edges: newEdges } + }); + this.renderView(); + }); + }); + + // Show images checkbox + const showImgCb = container.querySelector('#graph-show-img'); + showImgCb?.addEventListener('change', () => { + const state = store.getState(); + store.setState({ + graphSettings: { ...state.graphSettings, showImg: showImgCb.checked } + }); + this.renderView(); + }); + + // Show labels checkbox + const showLblCb = container.querySelector('#graph-show-lbl'); + showLblCb?.addEventListener('change', () => { + const state = store.getState(); + store.setState({ + graphSettings: { ...state.graphSettings, showLbl: showLblCb.checked } + }); + this.renderView(); + }); + + // 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 = store.getState(); + store.setState({ + graphSettings: { ...state.graphSettings, nodeSize: size } + }); + this.renderView(); + }); + + // 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 = store.getState(); + store.setState({ + graphSettings: { ...state.graphSettings, linkDist: dist } + }); + this.renderView(); + }); + } + private updateBaseButtons(): void { const state = store.getState(); $$('.base-btn').forEach(btn => { @@ -212,6 +384,7 @@ class App { this.router.updateHash(); this.detailPanel?.close(); this.updateViewTabs(); + this.renderLibraries(); // Update left panel (graph options vs libraries) this.renderView(); }); diff --git a/deck-frontend/src/styles/main.css b/deck-frontend/src/styles/main.css index 9d41657..12fed62 100644 --- a/deck-frontend/src/styles/main.css +++ b/deck-frontend/src/styles/main.css @@ -87,6 +87,18 @@ body { .base-btn:hover { color: var(--text); } .base-btn.active { background: var(--accent); color: #fff; } +/* === VIEW BAR === */ +.view-bar { + height: 40px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + padding: 0 16px; + gap: 8px; +} + /* === GROUPS BAR === */ .groups-bar { height: 44px; @@ -573,3 +585,89 @@ select { cursor: pointer; } select:focus { outline: none; border-color: var(--accent); } + +/* === GRAPH OPTIONS PANEL === */ +.graph-options { + padding: 10px; + overflow-y: auto; + width: 180px; +} +.graph-section { + margin-bottom: 16px; +} +.graph-section-title { + font-size: 0.7em; + font-weight: 600; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 1px solid var(--border); +} +.graph-stat { + display: flex; + justify-content: space-between; + font-size: 0.75em; + color: var(--text-muted); + margin-bottom: 4px; +} +.graph-stat-value { + color: var(--text); + font-weight: 600; +} +.graph-checkbox { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.75em; + color: var(--text-muted); + margin-bottom: 6px; + cursor: pointer; +} +.graph-checkbox:hover { color: var(--text); } +.graph-checkbox input[type="checkbox"] { + width: 14px; + height: 14px; + accent-color: var(--accent); + cursor: pointer; +} +.graph-checkbox .color-dot { + width: 10px; + height: 10px; + border-radius: 50%; + margin-right: 4px; +} +.graph-slider { + margin-bottom: 12px; +} +.graph-slider-label { + display: flex; + justify-content: space-between; + font-size: 0.7em; + color: var(--text-muted); + margin-bottom: 4px; +} +.graph-slider-value { + color: var(--text); + font-weight: 600; +} +.graph-slider input[type="range"] { + width: 100%; + height: 4px; + background: var(--border); + border-radius: 2px; + -webkit-appearance: none; + cursor: pointer; +} +.graph-slider input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 14px; + height: 14px; + background: var(--accent); + border-radius: 50%; + cursor: pointer; +} +.left-panel.graph-mode { + width: 180px; +}