llm-semantic-visualizer / src /ui /panels /BookmarksPanel.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
5.52 kB
import { STATE, removeBookmark } from '../../core/State.js';
let updateCallback = null;
/**
* Build the Bookmarks list card.
* @param {import('../types.js').PanelContext} ctx
* @returns {HTMLDivElement}
*/
export function buildBookmarksPanel(ctx) {
const card = document.createElement('div');
card.className = 'section-card';
card.innerHTML = `
<div class="section-title" style="color: var(--color-brand-primary);" title="List of saved relief coordinates for quick jump to Mode C">📌 Navigation Bookmarks</div>
<ul id="bookmarks-list-ul" class="list-container" style="max-height: 180px; overflow-y: auto;"></ul>
`;
updateCallback = () => {
const ul = card.querySelector('#bookmarks-list-ul');
if (!ul) return;
ul.innerHTML = '';
if (!STATE.bookmarks || STATE.bookmarks.length === 0) {
ul.innerHTML = `<li class="list-item" style="color: var(--color-neutral-400); justify-content: center; font-size: var(--font-size-small); font-style: italic; text-align: center; padding: var(--spacing-sm) 0;">
No bookmarks yet. Point at a relief and press 'B' to save
</li>`;
return;
}
STATE.bookmarks.forEach(bm => {
const li = document.createElement('li');
li.className = 'list-item';
li.style.cssText = 'cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: var(--spacing-xs);';
// User name, metadata badges (chunk index, dimension), and a delete icon.
li.innerHTML = `
<div class="bookmark-info" style="display: flex; flex-direction: column; gap: 2px; overflow: hidden; flex: 1; padding-right: var(--spacing-xs);">
<span style="font-weight: 700; color: var(--color-neutral-50); font-size: var(--font-size-small); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${bm.name}">${bm.name}</span>
<span style="font-size: 10px; color: var(--color-neutral-400); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${bm.docId}">${bm.docId}</span>
<div style="display: flex; gap: 4px; flex-wrap: wrap; margin-top: 2px;">
<span class="badge" style="background: oklch(from var(--color-neutral-50) l c h / 0.05); border: 1px solid oklch(from var(--color-neutral-50) l c h / 0.1); padding: 1px 4px; border-radius: 4px; font-size: 9px; font-family: var(--font-mono); color: var(--color-brand-secondary);" title="Fragment index (Chunk Z)">Chunk: ${bm.chunkIndex}</span>
<span class="badge" style="background: oklch(from var(--color-neutral-50) l c h / 0.05); border: 1px solid oklch(from var(--color-neutral-50) l c h / 0.1); padding: 1px 4px; border-radius: 4px; font-size: 9px; font-family: var(--font-mono); color: var(--color-brand-primary);" title="Latent dimension index (Dim X)">Dim: ${bm.dimIndex}</span>
<span class="badge" style="background: oklch(from var(--color-neutral-50) l c h / 0.05); border: 1px solid oklch(from var(--color-neutral-50) l c h / 0.1); padding: 1px 4px; border-radius: 4px; font-size: 9px; font-family: var(--font-mono); color: var(--color-semantic-success);" title="Numeric activation value (Y)">Val: ${bm.value.toFixed(4)}</span>
</div>
</div>
<button class="btn-delete btn btn-danger" style="width: 22px; height: 22px; min-height: 22px; padding: 0; font-size: 10px; border-radius: 4px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;" title="Remove bookmark">🗑️</button>
`;
// Click handlers
li.onclick = (e) => {
// If clicked on delete button, do not navigate
if (e.target.closest('.btn-delete')) return;
// On click on the item body: Set STATE.comparisonMode = 'C', set STATE.reliefInspectDim = dimIndex,
// and open the fullscreen Mode C inspector, focusing directly on the marked dimension.
STATE.comparisonMode = 'C';
STATE.reliefInspectDim = bm.dimIndex;
// Add the dimension to comparison dims if not already there
if (!STATE.reliefInspectDims) STATE.reliefInspectDims = [];
if (!STATE.reliefInspectDims.some(d => d.dim === bm.dimIndex)) {
STATE.reliefInspectDims.push({ dim: bm.dimIndex, visible: true });
}
// Call applyComparison
if (typeof ctx.applyComparison === 'function') {
ctx.applyComparison();
} else if (typeof window.applyComparison === 'function') {
window.applyComparison();
}
};
const btnDelete = li.querySelector('.btn-delete');
if (btnDelete) {
btnDelete.onclick = (e) => {
e.stopPropagation();
removeBookmark(bm.id);
updateBookmarksUI();
};
}
ul.appendChild(li);
});
};
// Register refresh
ctx.refresh.bookmarks = updateCallback;
// Render initially
setTimeout(() => {
updateBookmarksUI();
}, 0);
return card;
}
export function updateBookmarksUI() {
if (typeof updateCallback === 'function') {
updateCallback();
}
}