From 9c83698b819d8d881138b82cb57a4cc130e886c6 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Tue, 19 May 2026 00:41:43 -0400 Subject: [PATCH] feat: inline rename on double-click in library asset cards Double-clicking a clip name in the library shows an in-place text input. Enter/blur commits the new display_name via PATCH; Escape cancels. Clicking the card body or action buttons still work normally. --- services/web-ui/public/index.html | 77 ++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/services/web-ui/public/index.html b/services/web-ui/public/index.html index c9ebd60..f9770fe 100644 --- a/services/web-ui/public/index.html +++ b/services/web-ui/public/index.html @@ -257,6 +257,22 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + cursor: text; + } + + /* Inline rename input */ + .asset-name-input { + width: 100%; + font-size: var(--text-sm); + font-weight: 500; + font-family: inherit; + padding: 1px 4px; + border-radius: 3px; + border: 1px solid var(--accent-border); + background: var(--bg-raised); + color: var(--text-primary); + outline: none; + box-sizing: border-box; } .asset-info { @@ -303,7 +319,7 @@ min-width: 160px; } - /* Asset detail popover */ + /* Asset action buttons */ .asset-actions { position: absolute; top: var(--sp-2); @@ -619,6 +635,7 @@ setupDrag(); setupSearch(); setupFilters(); + setupRenameListener(document.getElementById('assetGrid')); // Multi-select bulk actions if (window.SelectionManager) { @@ -806,7 +823,7 @@
-
${escHtml(asset.display_name || asset.filename)}
+
${escHtml(asset.display_name || asset.filename)}
${escHtml(asset.media_type || '')} ${asset.file_size ? formatFileSize(asset.file_size) : ''} @@ -819,6 +836,7 @@ card.addEventListener('click', (e) => { if (e.target.closest('.asset-action-btn')) return; + if (e.target.closest('.asset-name[data-rename-id]')) return; // let rename handle it if (window.openAssetPreview) window.openAssetPreview(asset.id); }); @@ -827,6 +845,61 @@ if (window.SelectionManager) SelectionManager.refreshUI(); } + // ── Inline rename ───────────────────────── + function setupRenameListener(grid) { + grid.addEventListener('dblclick', async e => { + const nameEl = e.target.closest('.asset-name[data-rename-id]'); + if (!nameEl) return; + e.stopPropagation(); + + const assetId = nameEl.dataset.renameId; + const current = nameEl.textContent; + + const input = document.createElement('input'); + input.type = 'text'; + input.value = current; + input.className = 'asset-name-input'; + nameEl.style.display = 'none'; + nameEl.parentNode.insertBefore(input, nameEl.nextSibling); + input.focus(); + input.select(); + + let saved = false; + + const save = async () => { + if (saved) return; + saved = true; + const newName = input.value.trim(); + input.remove(); + nameEl.style.display = ''; + if (!newName || newName === current) return; + const r = await updateAsset(assetId, { display_name: newName }); + if (r.success) { + nameEl.textContent = newName; + // Update state cache so re-renders don't revert + const a = state.assets.find(x => x.id === assetId); + if (a) a.display_name = newName; + toast('Renamed', newName, 'success'); + } else { + toast('Rename failed', r.error, 'error'); + } + }; + + const cancel = () => { + if (saved) return; + saved = true; + input.remove(); + nameEl.style.display = ''; + }; + + input.addEventListener('blur', save); + input.addEventListener('keydown', e => { + if (e.key === 'Enter') { e.preventDefault(); input.blur(); } + if (e.key === 'Escape') { e.preventDefault(); cancel(); } + }); + }); + } + function statusBadgeClass(s) { const map = { live:'badge-live', ingesting:'badge-ingesting', processing:'badge-processing', ready:'badge-ready', error:'badge-error', archived:'badge-archived' }; return map[s] || 'badge-idle';