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;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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 {
|
.asset-info {
|
||||||
|
|
@ -303,7 +319,7 @@
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Asset detail popover */
|
/* Asset action buttons */
|
||||||
.asset-actions {
|
.asset-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--sp-2);
|
top: var(--sp-2);
|
||||||
|
|
@ -619,6 +635,7 @@
|
||||||
setupDrag();
|
setupDrag();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
setupFilters();
|
setupFilters();
|
||||||
|
setupRenameListener(document.getElementById('assetGrid'));
|
||||||
|
|
||||||
// Multi-select bulk actions
|
// Multi-select bulk actions
|
||||||
if (window.SelectionManager) {
|
if (window.SelectionManager) {
|
||||||
|
|
@ -806,7 +823,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="asset-meta">
|
<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">
|
<div class="asset-info">
|
||||||
<span class="asset-type">${escHtml(asset.media_type || '')}</span>
|
<span class="asset-type">${escHtml(asset.media_type || '')}</span>
|
||||||
<span class="text-tertiary text-xs">${asset.file_size ? formatFileSize(asset.file_size) : ''}</span>
|
<span class="text-tertiary text-xs">${asset.file_size ? formatFileSize(asset.file_size) : ''}</span>
|
||||||
|
|
@ -819,6 +836,7 @@
|
||||||
|
|
||||||
card.addEventListener('click', (e) => {
|
card.addEventListener('click', (e) => {
|
||||||
if (e.target.closest('.asset-action-btn')) return;
|
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);
|
if (window.openAssetPreview) window.openAssetPreview(asset.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -827,6 +845,61 @@
|
||||||
if (window.SelectionManager) SelectionManager.refreshUI();
|
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) {
|
function statusBadgeClass(s) {
|
||||||
const map = { live:'badge-live', ingesting:'badge-ingesting', processing:'badge-processing', ready:'badge-ready', error:'badge-error', archived:'badge-archived' };
|
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';
|
return map[s] || 'badge-idle';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue