fix(database): column resize follows mouse, inline rename in context menu
- 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:
@@ -164,30 +164,29 @@
|
|||||||
thead.querySelectorAll('.db-col-resize-handle').forEach(handle => {
|
thead.querySelectorAll('.db-col-resize-handle').forEach(handle => {
|
||||||
handle.onpointerdown = function(e) {
|
handle.onpointerdown = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.setPointerCapture(e.pointerId);
|
||||||
const propId = parseInt(this.dataset.propId);
|
const propId = parseInt(this.dataset.propId);
|
||||||
const th = this.parentElement;
|
const th = this.parentElement;
|
||||||
const startX = e.clientX;
|
const startX = e.clientX;
|
||||||
const startW = th.offsetWidth;
|
const startW = th.getBoundingClientRect().width;
|
||||||
|
|
||||||
function onMove(ev) {
|
function onMove(ev) {
|
||||||
const diff = ev.clientX - startX;
|
const diff = ev.clientX - startX;
|
||||||
const newW = Math.max(60, startW + diff);
|
const newW = Math.max(60, startW + diff);
|
||||||
state.colWidths[propId] = newW;
|
state.colWidths[propId] = newW;
|
||||||
// Update header
|
|
||||||
th.style.width = newW + 'px';
|
th.style.width = newW + 'px';
|
||||||
th.style.minWidth = newW + 'px';
|
th.style.minWidth = newW + 'px';
|
||||||
// Update all cells in this column
|
|
||||||
const colIdx = state.properties.findIndex(p => p.id === propId);
|
const colIdx = state.properties.findIndex(p => p.id === propId);
|
||||||
if (colIdx >= 0) {
|
if (colIdx >= 0) {
|
||||||
document.querySelectorAll('#db-tbody .db-row').forEach(row => {
|
document.querySelectorAll('#db-tbody .db-row').forEach(row => {
|
||||||
const cells = row.querySelectorAll('.db-td-cell');
|
const cells = row.querySelectorAll('.db-td-cell');
|
||||||
if (cells[colIdx]) {
|
if (cells[colIdx]) cells[colIdx].style.minWidth = newW + 'px';
|
||||||
cells[colIdx].style.minWidth = newW + 'px';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onUp() {
|
function onUp(ev) {
|
||||||
|
handle.releasePointerCapture(ev.pointerId);
|
||||||
document.removeEventListener('pointermove', onMove);
|
document.removeEventListener('pointermove', onMove);
|
||||||
document.removeEventListener('pointerup', onUp);
|
document.removeEventListener('pointerup', onUp);
|
||||||
document.body.style.cursor = '';
|
document.body.style.cursor = '';
|
||||||
@@ -242,7 +241,38 @@
|
|||||||
|
|
||||||
// Set header
|
// Set header
|
||||||
document.getElementById('db-col-menu-icon').innerHTML = propTypeSvg(propType);
|
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 = 'ⓘ';
|
document.getElementById('db-col-menu-info').textContent = 'ⓘ';
|
||||||
|
|
||||||
// Position near the anchor
|
// Position near the anchor
|
||||||
@@ -268,13 +298,55 @@
|
|||||||
const menu = document.getElementById('db-col-menu');
|
const menu = document.getElementById('db-col-menu');
|
||||||
if (menu) {
|
if (menu) {
|
||||||
menu.style.display = 'none';
|
menu.style.display = 'none';
|
||||||
// Hide all submenus
|
|
||||||
menu.querySelectorAll('.db-col-menu-sub').forEach(s => s.style.display = 'none');
|
menu.querySelectorAll('.db-col-menu-sub').forEach(s => s.style.display = 'none');
|
||||||
}
|
}
|
||||||
state.colMenuPropId = null;
|
state.colMenuPropId = null;
|
||||||
state.colMenuSubOpen = 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) {
|
function toggleColSubmenu(subId) {
|
||||||
const sub = document.getElementById(subId);
|
const sub = document.getElementById(subId);
|
||||||
if (!sub) return;
|
if (!sub) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user