fix(database): column resize follows mouse, inline rename in context menu
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped

- Column resize: use getBoundingClientRect() + setPointerCapture for proper tracking
- Context menu 'Name' field: click to edit, Enter to save, Escape to cancel
- Rename persists to server via PUT /db/properties/{id}/api
This commit is contained in:
2026-07-22 16:23:02 -04:00
parent 461492eede
commit 9c4de01fd2
+81 -9
View File
@@ -164,30 +164,29 @@
thead.querySelectorAll('.db-col-resize-handle').forEach(handle => {
handle.onpointerdown = function(e) {
e.preventDefault();
e.stopPropagation();
this.setPointerCapture(e.pointerId);
const propId = parseInt(this.dataset.propId);
const th = this.parentElement;
const startX = e.clientX;
const startW = th.offsetWidth;
const startW = th.getBoundingClientRect().width;
function onMove(ev) {
const diff = ev.clientX - startX;
const newW = Math.max(60, startW + diff);
state.colWidths[propId] = newW;
// Update header
th.style.width = newW + 'px';
th.style.minWidth = newW + 'px';
// Update all cells in this column
const colIdx = state.properties.findIndex(p => p.id === propId);
if (colIdx >= 0) {
document.querySelectorAll('#db-tbody .db-row').forEach(row => {
const cells = row.querySelectorAll('.db-td-cell');
if (cells[colIdx]) {
cells[colIdx].style.minWidth = newW + 'px';
}
if (cells[colIdx]) cells[colIdx].style.minWidth = newW + 'px';
});
}
}
function onUp() {
function onUp(ev) {
handle.releasePointerCapture(ev.pointerId);
document.removeEventListener('pointermove', onMove);
document.removeEventListener('pointerup', onUp);
document.body.style.cursor = '';
@@ -242,7 +241,38 @@
// Set header
document.getElementById('db-col-menu-icon').innerHTML = propTypeSvg(propType);
document.getElementById('db-col-menu-name').textContent = propName;
const nameEl = document.getElementById('db-col-menu-name');
nameEl.textContent = propName;
nameEl.contentEditable = 'false';
nameEl.style.cursor = 'text';
nameEl.style.border = 'none';
nameEl.style.outline = 'none';
nameEl.style.padding = '0';
nameEl.style.background = 'transparent';
nameEl.title = 'Click to rename';
// Click on name to edit
nameEl.onclick = function(e) {
e.stopPropagation();
if (nameEl.contentEditable === 'true') return;
nameEl.contentEditable = 'true';
nameEl.style.border = '1px solid var(--accent, #2383E2)';
nameEl.style.borderRadius = '3px';
nameEl.style.padding = '2px 4px';
nameEl.style.outline = 'none';
nameEl.focus();
// Select all text
const range = document.createRange();
range.selectNodeContents(nameEl);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
};
// Blur/Enter → save rename
nameEl.onblur = function() { commitColRename(propId); };
nameEl.onkeydown = function(e) {
if (e.key === 'Enter') { e.preventDefault(); commitColRename(propId); }
else if (e.key === 'Escape') { cancelColRename(propId, propName); }
};
document.getElementById('db-col-menu-info').textContent = 'ⓘ';
// Position near the anchor
@@ -268,13 +298,55 @@
const menu = document.getElementById('db-col-menu');
if (menu) {
menu.style.display = 'none';
// Hide all submenus
menu.querySelectorAll('.db-col-menu-sub').forEach(s => s.style.display = 'none');
}
state.colMenuPropId = null;
state.colMenuSubOpen = null;
}
async function commitColRename(propId) {
const nameEl = document.getElementById('db-col-menu-name');
if (!nameEl) return;
const newName = nameEl.textContent.trim();
if (!newName) return;
nameEl.contentEditable = 'false';
nameEl.style.border = 'none';
nameEl.style.borderRadius = '0';
nameEl.style.padding = '0';
const prop = state.properties.find(p => p.id === propId);
if (!prop || prop.name === newName) return;
// Update local state
prop.name = newName;
// Update header in table
renderTable();
// If menu still open, update its display
if (state.colMenuPropId === propId) {
nameEl.textContent = newName;
}
// Persist to server
const csrf = document.cookie.match(/csrf_token=([^;]+)/);
try {
await fetch('/db/properties/' + propId + '/api', {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf ? csrf[1] : '' },
body: JSON.stringify({ name: newName })
});
} catch(e) { console.error('[DB] rename property error:', e); }
}
function cancelColRename(propId, originalName) {
const nameEl = document.getElementById('db-col-menu-name');
if (!nameEl) return;
nameEl.contentEditable = 'false';
nameEl.textContent = originalName;
nameEl.style.border = 'none';
nameEl.style.borderRadius = '0';
nameEl.style.padding = '0';
}
function toggleColSubmenu(subId) {
const sub = document.getElementById(subId);
if (!sub) return;