From 8e44b5fbd565e2ca7e9c6b69079f9e1eb8333672 Mon Sep 17 00:00:00 2001 From: janis steiner Date: Tue, 12 May 2026 10:48:46 +0200 Subject: [PATCH] making shapes selectable --- frontend/assets/js/app.js | 105 ++++++++++---------------------------- 1 file changed, 28 insertions(+), 77 deletions(-) diff --git a/frontend/assets/js/app.js b/frontend/assets/js/app.js index 9806df0..3e505e4 100644 --- a/frontend/assets/js/app.js +++ b/frontend/assets/js/app.js @@ -732,7 +732,7 @@ async function loadNetworkData() { renderNodeList(); renderShapeList(); renderNodeToolbar(); - setupCanvasDrop(); + setupCanvasClickCreate(); startSync(); if (shapes.length) nextShapeZ = Math.max(...shapes.map(x => x.z_index)) + 1; buildCanvasGraph(); @@ -1167,39 +1167,37 @@ function renderNodeToolbar() { { type: 'shape:rectangle', icon: 'fa-vector-square', color: '#3b82f6', label: 'Box', isShape: true }, { type: 'shape:ellipse', icon: 'fa-circle', color: '#3b82f6', label: 'Ellipse', isShape: true }, ]; - bar.innerHTML = 'Drag to canvas:'; + bar.innerHTML = 'Click item then click canvas:'; iconMap.forEach(t => { const el = document.createElement('span'); el.className = 'd-inline-flex align-items-center gap-1 px-2 py-1 rounded'; - el.draggable = true; - el.style.cssText = 'cursor:grab;font-size:.75rem;color:' + t.color + ';background:' + t.color + '12;border:1px solid ' + t.color + '30;'; + el.style.cssText = 'cursor:pointer;font-size:.75rem;color:' + t.color + ';background:' + t.color + '12;border:1px solid ' + t.color + '30;'; el.innerHTML = '' + t.label + ''; - el.addEventListener('dragstart', (e) => { - e.dataTransfer.setData('text/plain', t.type); - e.dataTransfer.effectAllowed = 'copy'; + el.addEventListener('click', () => { + bar.querySelectorAll('.d-inline-flex').forEach(s => s.style.outline = 'none'); + el.style.outline = '2px solid ' + t.color; + pendingCanvasCreate = t.type; }); bar.appendChild(el); }); } -function setupCanvasDrop() { +let pendingCanvasCreate = null; + +function setupCanvasClickCreate() { const wrapper = document.getElementById('networkCanvasWrapper'); - let dropPending = false; - wrapper.addEventListener('dragover', (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }); - wrapper.addEventListener('drop', async (e) => { - e.preventDefault(); - e.stopPropagation(); - if (dropPending) return; - dropPending = true; - suppressNextMouse = Date.now() + 500; - const data = e.dataTransfer.getData('text/plain'); - if (!data) { dropPending = false; return; } + const bar = document.getElementById('nodeToolbar'); + wrapper.addEventListener('click', async (e) => { + if (!pendingCanvasCreate) return; const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left - panX; const my = e.clientY - rect.top - panY; + const type = pendingCanvasCreate; + pendingCanvasCreate = null; + bar.querySelectorAll('.d-inline-flex').forEach(s => s.style.outline = 'none'); - if (data.startsWith('shape:')) { - const shapeType = data.split(':')[1]; + if (type.startsWith('shape:')) { + const shapeType = type.split(':')[1]; await apiFetch('shapes', { method: 'POST', body: JSON.stringify({ label: shapeType === 'rectangle' ? 'Box' : 'Ellipse', shape_type: shapeType, @@ -1210,67 +1208,16 @@ function setupCanvasDrop() { })}); } else { await apiFetch('nodes', { method: 'POST', body: JSON.stringify({ - label: data.charAt(0).toUpperCase() + data.slice(1), - ip_address: '', node_type: data || 'host', status: 'unknown', + label: type.charAt(0).toUpperCase() + type.slice(1), + ip_address: '', node_type: type || 'host', status: 'unknown', group_name: 'default', notes: '', pos_x: mx, pos_y: my })}); } await loadNetworkData(); - dropPending = false; }); } -// Real-time sync: poll backend every 5 seconds for all data -let syncInterval = null; -let syncHashes = { events: null, documents: null, network: null }; - -function startSync() { - if (syncInterval) return; - syncInterval = setInterval(async () => { - if (dragType || dragTarget || isPanning) return; - const openModals = document.querySelectorAll('.modal.show'); - try { - const [eventsData, docsData, nodesData, linksData, shapesData] = await Promise.all([ - apiFetch('events'), - apiFetch('documents'), - apiFetch('nodes'), - apiFetch('links'), - apiFetch('shapes'), - ]); - - const eventsHash = JSON.stringify(eventsData); - if (eventsHash !== syncHashes.events) { - syncHashes.events = eventsHash; - events = Array.isArray(eventsData) ? eventsData : []; - loadAllTags(); - renderTimeline(); - } - - const docsHash = JSON.stringify(docsData); - if (docsHash !== syncHashes.documents) { - syncHashes.documents = docsHash; - documents = Array.isArray(docsData) ? docsData : []; - renderDocuments(); - } - - const netHash = JSON.stringify({ n: nodesData, l: linksData, s: shapesData }); - if (netHash !== syncHashes.network) { - syncHashes.network = netHash; - nodes = Array.isArray(nodesData) ? nodesData : []; - links = Array.isArray(linksData) ? linksData : []; - shapes = Array.isArray(shapesData) ? shapesData : []; - populateNodeSelects(); - renderNodeList(); - renderShapeList(); - if (shapes.length) nextShapeZ = Math.max(...shapes.map(x => x.z_index)) + 1; - buildCanvasGraph(); - renderNetwork(); - } - } catch (e) {} - }, 5000); -} - function getCanvasNodeAt(mx, my) { return canvasNodes.find(n => Math.hypot(mx - n.x, my - n.y) < 28); } @@ -1416,10 +1363,14 @@ function onMouseUp(e) { const rw = Math.abs(mx - selectStartX); const rh = Math.abs(my - selectStartY); if (rw > 5 || rh > 5) { - const found = canvasNodes.filter(n => n.x >= rx && n.x <= rx + rw && n.y >= ry && n.y <= ry + rh); - selectedNodeIds = found.map(n => n.id); - selectedNodeId = found.length > 0 ? found[0].id : null; - selectedShapeId = null; + const foundNodes = canvasNodes.filter(n => n.x >= rx && n.x <= rx + rw && n.y >= ry && n.y <= ry + rh); + const foundShapes = canvasShapes.filter(s => { + const sx = s.x, sy = s.y, ex = s.x + s.w, ey = s.y + s.h; + return sx < rx + rw && ex > rx && sy < ry + rh && ey > ry; + }); + selectedNodeIds = foundNodes.map(n => n.id); + selectedNodeId = foundNodes.length > 0 ? foundNodes[0].id : null; + selectedShapeId = foundShapes.length > 0 ? foundShapes[foundShapes.length - 1].id : null; renderNodeList(); renderShapeList(); renderNetwork(); } }