- Move graph options from left panel to floating sidebar in GraphView - Add zoom controls (fit, +, -) in top-right corner - Add dynamic legend showing active categories - Add cleanup system to View base class for event listeners - Update delegateEvent to return cleanup function - Filter nodes by category before rendering - Fix text color variable (--text-primary to --text) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
79 lines
2.2 KiB
TypeScript
79 lines
2.2 KiB
TypeScript
import { View } from '../View.ts';
|
|
import { filterTags, getName, getImg, delegateEvent } from '@/utils/index.ts';
|
|
import type { Store } from '@/state/store.ts';
|
|
import type { AppState } from '@/types/index.ts';
|
|
|
|
export class GridView extends View {
|
|
private showDetail: (mrf: string) => void;
|
|
|
|
constructor(
|
|
container: HTMLElement,
|
|
store: Store<AppState>,
|
|
showDetail: (mrf: string) => void
|
|
) {
|
|
super(container, store);
|
|
this.showDetail = showDetail;
|
|
}
|
|
|
|
render(): void {
|
|
const state = this.getState();
|
|
const filtered = filterTags(state.tags, {
|
|
search: state.search,
|
|
group: state.group,
|
|
library: state.library,
|
|
libraryMembers: state.libraryMembers,
|
|
lang: state.lang
|
|
});
|
|
|
|
if (filtered.length === 0) {
|
|
this.container.innerHTML = '<div class="empty">Sin resultados</div>';
|
|
return;
|
|
}
|
|
|
|
this.container.innerHTML = filtered.map(tag => {
|
|
const img = getImg(tag);
|
|
const name = getName(tag, state.lang);
|
|
const isSelected = state.selected.has(tag.mrf);
|
|
|
|
return `
|
|
<div class="card ${isSelected ? 'selected' : ''}" data-mrf="${tag.mrf}">
|
|
${state.selectionMode ? `
|
|
<input type="checkbox" class="card-checkbox" ${isSelected ? 'checked' : ''}>
|
|
` : ''}
|
|
${img
|
|
? `<img class="card-img" src="${img}" alt="${tag.ref}" loading="lazy">`
|
|
: `<div class="card-placeholder">${tag.ref?.slice(0, 2) || 'T'}</div>`
|
|
}
|
|
<div class="card-name">${name}</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
this.bindEvents();
|
|
}
|
|
|
|
private bindEvents(): void {
|
|
this.cleanupListeners();
|
|
const cleanup = delegateEvent<MouseEvent>(this.container, '.card', 'click', (_, target) => {
|
|
const mrf = target.dataset.mrf;
|
|
if (!mrf) return;
|
|
|
|
// Obtener estado FRESCO en cada click (no del closure)
|
|
const state = this.getState();
|
|
|
|
if (state.selectionMode) {
|
|
const newSelected = new Set(state.selected);
|
|
if (newSelected.has(mrf)) {
|
|
newSelected.delete(mrf);
|
|
} else {
|
|
newSelected.add(mrf);
|
|
}
|
|
this.setState({ selected: newSelected });
|
|
} else {
|
|
this.showDetail(mrf);
|
|
}
|
|
});
|
|
this.addCleanup(cleanup);
|
|
}
|
|
}
|