feat(uxp-panel): fix hover tooltips + add compact list view toggle

Tooltips: the floating .tip-bubble used position:fixed, which UXP's
engine does not render reliably, so tooltips never appeared. Switch to
position:absolute (body never scrolls, so viewport coords still map),
add scroll-offset compensation, and guard against UXP returning 0 for
window.innerWidth/innerHeight.

List view: add a toolbar toggle (grid vs compact list). List view shows
a small 50x28 thumbnail with name/meta and an inline status chip per
row, fitting many more clips on screen. Defaults to list, persists via
localStorage when the host allows it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-05-28 23:05:52 -04:00
parent 3430ef823e
commit 9d8adbbbc1
4 changed files with 77 additions and 5 deletions

View file

@ -77,6 +77,9 @@
<select id="project-filter" class="filter-select" title="Filter by project"> <select id="project-filter" class="filter-select" title="Filter by project">
<option value="all">All Projects</option> <option value="all">All Projects</option>
</select> </select>
<div id="view-toggle-btn" role="button" tabindex="0" class="iconbtn iconbtn--sm" data-tip="Grid view" data-tip-pos="down-left" aria-label="Toggle layout">
<svg width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M4 11h5V5H4v6zm0 7h5v-6H4v6zm6 0h5v-6h-5v6zm6 0h5v-6h-5v6zm-6-7h5V5h-5v6zm6-6v6h5V5h-5z"/></svg>
</div>
</div> </div>
<!-- Active sequence info bar --> <!-- Active sequence info bar -->

View file

@ -28,6 +28,29 @@
}); });
} }
// Asset layout toggle: compact list (default) vs thumbnail grid. Persisted
// in localStorage when available (UXP host permitting), else session-only.
const GRID_ICON = '<svg width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M4 11h5V5H4v6zm0 7h5v-6H4v6zm6 0h5v-6h-5v6zm6 0h5v-6h-5v6zm-6-7h5V5h-5v6zm6-6v6h5V5h-5z"/></svg>';
const LIST_ICON = '<svg width="15" height="15" viewBox="0 0 24 24"><path fill="currentColor" d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/></svg>';
let _viewMode = null;
function getViewMode() {
if (_viewMode) return _viewMode;
try { _viewMode = localStorage.getItem('df_view_mode'); } catch (e) {}
return _viewMode || 'list';
}
function applyViewMode(mode) {
_viewMode = mode === 'grid' ? 'grid' : 'list';
try { localStorage.setItem('df_view_mode', _viewMode); } catch (e) {}
const isList = _viewMode === 'list';
document.querySelectorAll('.asset-grid').forEach(g => g.classList.toggle('list-view', isList));
const btn = $('view-toggle-btn');
if (btn) {
// Show the icon for the layout a click switches TO.
btn.innerHTML = isList ? GRID_ICON : LIST_ICON;
btn.setAttribute('data-tip', isList ? 'Grid view' : 'List view');
}
}
function syncConnectBtn() { function syncConnectBtn() {
$('connect-btn').disabled = !$('server-url').value.trim() || !$('api-token').value.trim(); $('connect-btn').disabled = !$('server-url').value.trim() || !$('api-token').value.trim();
} }
@ -96,6 +119,11 @@
$('tab-library').addEventListener('click', () => Library.switchTab('library')); $('tab-library').addEventListener('click', () => Library.switchTab('library'));
$('tab-growing').addEventListener('click', () => Library.switchTab('growing')); $('tab-growing').addEventListener('click', () => Library.switchTab('growing'));
const vt = $('view-toggle-btn');
if (vt) vt.addEventListener('click', () => {
applyViewMode(getViewMode() === 'list' ? 'grid' : 'list');
});
let searchTimer; let searchTimer;
$('search-input').addEventListener('input', e => { $('search-input').addEventListener('input', e => {
clearTimeout(searchTimer); clearTimeout(searchTimer);
@ -488,6 +516,7 @@
function init() { function init() {
enableDivDisabled(); enableDivDisabled();
applyViewMode(getViewMode());
wireConnectPane(); wireLibraryPane(); wireConnectPane(); wireLibraryPane();
wireExportPanel(); wireConformPanel(); wireRelinkPanel(); wireExportPanel(); wireConformPanel(); wireRelinkPanel();
showVersion(); showVersion();

View file

@ -26,6 +26,10 @@
const r = el.getBoundingClientRect(); const r = el.getBoundingClientRect();
const t = tip.getBoundingClientRect(); const t = tip.getBoundingClientRect();
// position:absolute on a body-level node is offset from the document
// origin; add scroll offset (0 in practice, body doesn't scroll) for safety.
const sx = window.pageXOffset || document.documentElement.scrollLeft || 0;
const sy = window.pageYOffset || document.documentElement.scrollTop || 0;
const gap = 7; const gap = 7;
const pos = el.getAttribute('data-tip-pos') || 'down'; const pos = el.getAttribute('data-tip-pos') || 'down';
let x, y; let x, y;
@ -42,11 +46,12 @@
x = r.left + (r.width - t.width) / 2; y = r.bottom + gap; x = r.left + (r.width - t.width) / 2; y = r.bottom + gap;
} }
const vw = window.innerWidth, vh = window.innerHeight; const vw = window.innerWidth || document.documentElement.clientWidth || 99999;
const vh = window.innerHeight || document.documentElement.clientHeight || 99999;
x = Math.max(4, Math.min(x, vw - t.width - 4)); x = Math.max(4, Math.min(x, vw - t.width - 4));
y = Math.max(4, Math.min(y, vh - t.height - 4)); y = Math.max(4, Math.min(y, vh - t.height - 4));
tip.style.left = x + 'px'; tip.style.left = (x + sx) + 'px';
tip.style.top = y + 'px'; tip.style.top = (y + sy) + 'px';
tip.style.opacity = '1'; tip.style.opacity = '1';
} }

View file

@ -454,6 +454,38 @@ input[type="search"]::-webkit-search-cancel-button { display: none; }
.status-ready { background: var(--success); color: #06120d; } .status-ready { background: var(--success); color: #06120d; }
.status-error { background: var(--danger); color: #fff; } .status-error { background: var(--danger); color: #fff; }
/* ── compact list view (toolbar toggle) ─────────────────────────── */
.asset-grid.list-view { gap: 2px; padding: 6px 8px; }
.asset-grid.list-view .asset-card {
flex: 1 1 100%;
max-width: 100%;
flex-direction: row;
align-items: center;
gap: 9px;
padding: 3px 8px 3px 3px;
border-radius: 6px;
}
.asset-grid.list-view .asset-thumb,
.asset-grid.list-view .asset-thumb-placeholder {
width: 50px;
height: 28px;
border-radius: 4px;
}
.asset-grid.list-view .asset-thumb-placeholder { font-size: 0; }
.asset-grid.list-view .asset-info {
order: 1;
flex: 1 1 auto;
min-width: 0;
padding: 0;
gap: 1px;
}
.asset-grid.list-view .asset-status {
order: 2;
position: static;
margin-left: auto;
flex-shrink: 0;
}
/* ── action dock ────────────────────────────────────────────────── */ /* ── action dock ────────────────────────────────────────────────── */
.dock { .dock {
flex-shrink: 0; flex-shrink: 0;
@ -606,8 +638,11 @@ input[type="search"]::-webkit-search-cancel-button { display: none; }
/* ── hover tooltip bubble (driven by src/tooltip.js) ────────────── */ /* ── hover tooltip bubble (driven by src/tooltip.js) ────────────── */
.tip-bubble { .tip-bubble {
position: fixed; /* UXP's engine does not reliably support position:fixed; absolute on a
z-index: 100; body-level node maps to the same coords because the panel body never
scrolls (only .asset-grid scrolls internally). */
position: absolute;
z-index: 1000;
display: none; display: none;
pointer-events: none; pointer-events: none;
white-space: nowrap; white-space: nowrap;