diff --git a/app/templates/_database_table_scripts.html b/app/templates/_database_table_scripts.html index a5b282b..a852af5 100644 --- a/app/templates/_database_table_scripts.html +++ b/app/templates/_database_table_scripts.html @@ -232,31 +232,72 @@ } catch(e) { console.error('[DB] addNewPage error:', e); } } - // ── Property modal (uses global modal from page) ── + // ── Property modal (inline, self-contained) ── function showPropModal() { - const modal = document.getElementById('db-prop-modal'); - if (!modal) return; - modal._dbInstance = this; - modal.style.display = 'flex'; - document.getElementById('db-prop-name-input').value = ''; - document.getElementById('db-prop-name-input').focus(); - // Store ref to this instance for the create handler - modal._collectionId = collectionId; - modal._onPropCreated = function(newProp) { state.properties.push(newProp); render(); }; + // Remove any existing inline modal + var old = containerEl.querySelector('.db-prop-modal-inline'); + if (old) old.remove(); + + var modal = document.createElement('div'); + modal.className = 'db-prop-modal-inline'; + modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.3);z-index:1000;display:flex;align-items:center;justify-content:center'; + modal.innerHTML = '
'+ + '

Add a property

'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'; + document.body.appendChild(modal); + + var nameInput = modal.querySelector('.db-prop-name-inline'); + nameInput.focus(); + + modal.querySelector('.db-prop-modal-close-btn').onclick = function(){ modal.remove(); }; + modal.querySelector('.db-prop-cancel-inline').onclick = function(){ modal.remove(); }; + modal.onclick = function(e){ if(e.target===modal) modal.remove(); }; + + modal.querySelector('.db-prop-create-inline').onclick = async function(){ + var name = nameInput.value.trim(); + if(!name) return; + var type = modal.querySelector('.db-prop-type-inline').value; + try { + var r = await fetch('/db/'+collectionId+'/properties/api',{ + method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':getCsrf()}, + body:JSON.stringify({name:name,prop_type:type}) + }); + var d = await r.json(); + if(d.id){ state.properties.push(d); render(); modal.remove(); } + } catch(e){ console.error('[DB] createProperty error:',e); } + }; + nameInput.onkeydown = function(e){ if(e.key==='Enter') modal.querySelector('.db-prop-create-inline').click(); }; } - // ── Column context menu ── + // ── Column context menu (inline) ── function showColMenu(propId, propType, propName, anchor) { - const menu = document.getElementById('db-col-menu'); - if (!menu) return; - state.colMenuPropId = propId; - document.getElementById('db-col-menu-icon').innerHTML = propTypeSvg(propType); - const nameEl = document.getElementById('db-col-menu-name'); - nameEl.textContent = propName; - const rect = anchor.getBoundingClientRect(); - menu.style.top = (rect.bottom+4)+'px'; menu.style.left = rect.left+'px'; menu.style.display = 'block'; + // Remove any existing + var old = document.querySelector('.db-col-menu-inline'); + if (old) old.remove(); + + var menu = document.createElement('div'); + menu.className = 'db-col-menu-inline'; + menu.style.cssText = 'position:fixed;min-width:240px;max-width:320px;background:var(--bg-modal);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.14);z-index:800;padding:4px 0;font-size:13px;color:var(--text-primary)'; + var rect = anchor.getBoundingClientRect(); + menu.style.top = (rect.bottom+4)+'px'; menu.style.left = rect.left+'px'; + + menu.innerHTML = '
'+ + ''+propTypeSvg(propType)+''+escHtml(propName)+'ⓘ
'+ + '
'+ + '
≡ Filter
'+ + '
↕ Sort
'+ + '
▦ Group
'+ + '
'+ + '
├← Insert left
'+ + '
→┤ Insert right
'; + + document.body.appendChild(menu); setTimeout(() => document.addEventListener('click', function onDoc(e) { - if(!menu.contains(e.target)) { menu.style.display='none'; document.removeEventListener('click',onDoc); } + if(!menu.contains(e.target)) { menu.remove(); document.removeEventListener('click',onDoc); } }, {once:true}), 0); } @@ -292,13 +333,40 @@ } } - // ── Global Side Peek for Database Pages ── + // ── Global Side Peek for Database Pages (auto-creates panel) ── function openPageInSidePeek(pageId) { var panel = document.getElementById('db-side-peek'); + if (!panel) { + // Create panel dynamically + panel = document.createElement('div'); + panel.id = 'db-side-peek'; + panel.style.cssText = 'position:fixed;top:0;right:0;width:560px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 20px rgba(0,0,0,0.12);z-index:900;display:flex;flex-direction:column'; + panel.innerHTML = '
'+ + '
'+ + ''+ + ''+ + '
'+ + ''; + document.body.appendChild(panel); + + document.getElementById('db-side-peek-close').onclick = function(){ panel.style.display='none'; document.getElementById('db-side-peek-iframe').src=''; }; + document.getElementById('db-side-peek-full').onclick = function(){ + var ifr = document.getElementById('db-side-peek-iframe'); + if(ifr&&ifr.src) window.open(ifr.src.replace('?embed=1',''),'_blank'); + }; + // Resize + var rh = document.getElementById('db-side-peek-resize'); + rh.onpointerdown = function(e) { + e.preventDefault(); e.stopPropagation(); + var sx = e.clientX, sw = panel.offsetWidth; + function mv(ev) { var w = Math.max(300, Math.min(window.innerWidth*0.9, sw-(ev.clientX-sx))); panel.style.width = w+'px'; } + function up() { document.removeEventListener('pointermove',mv); document.removeEventListener('pointerup',up); document.body.style.cursor=''; } + document.body.style.cursor='col-resize'; + document.addEventListener('pointermove',mv); document.addEventListener('pointerup',up); + }; + } var iframe = document.getElementById('db-side-peek-iframe'); - if (!panel || !iframe) return; - iframe.src = '/pages/' + pageId + '?embed=1'; - panel.style.display = 'flex'; + if (iframe) { iframe.src = '/pages/'+pageId+'?embed=1'; panel.style.display = 'flex'; } } function closeSidePeek() { var panel = document.getElementById('db-side-peek'); @@ -306,37 +374,5 @@ var iframe = document.getElementById('db-side-peek-iframe'); if (iframe) { iframe.src = ''; } } - // Bind close button on DOM ready - setTimeout(function() { - var closeBtn = document.getElementById('db-side-peek-close'); - if (closeBtn) closeBtn.onclick = closeSidePeek; - var fullBtn = document.getElementById('db-side-peek-full'); - if (fullBtn) fullBtn.onclick = function() { - var iframe = document.getElementById('db-side-peek-iframe'); - if (iframe && iframe.src) window.open(iframe.src.replace('?embed=1',''), '_blank'); - }; - // Resize handle - var resizeHandle = document.getElementById('db-side-peek-resize'); - var panel = document.getElementById('db-side-peek'); - if (resizeHandle && panel) { - resizeHandle.onpointerdown = function(e) { - e.preventDefault(); e.stopPropagation(); - var startX = e.clientX; - var startW = panel.offsetWidth; - function onMove(ev) { - var newW = Math.max(300, Math.min(window.innerWidth * 0.9, startW - (ev.clientX - startX))); - panel.style.width = newW + 'px'; - } - function onUp() { - document.removeEventListener('pointermove', onMove); - document.removeEventListener('pointerup', onUp); - document.body.style.cursor = ''; - } - document.body.style.cursor = 'col-resize'; - document.addEventListener('pointermove', onMove); - document.addEventListener('pointerup', onUp); - }; - } - }, 100); })();