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.
This commit is contained in:
parent
f39d086bc8
commit
9c83698b81
1 changed files with 75 additions and 2 deletions
|
|
@ -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 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="asset-meta">
|
||||
<div class="asset-name">${escHtml(asset.display_name || asset.filename)}</div>
|
||||
<div class="asset-name" data-rename-id="${asset.id}" title="Double-click to rename">${escHtml(asset.display_name || asset.filename)}</div>
|
||||
<div class="asset-info">
|
||||
<span class="asset-type">${escHtml(asset.media_type || '')}</span>
|
||||
<span class="text-tertiary text-xs">${asset.file_size ? formatFileSize(asset.file_size) : ''}</span>
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Reference in a new issue