+34
-15
@@ -49,10 +49,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
window.addEventListener('resize', () => { resizeCanvas(); renderNetwork(); });
|
window.addEventListener('resize', () => { resizeCanvas(); renderNetwork(); });
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
if (e.key === 'Delete') {
|
||||||
if (!document.activeElement || document.activeElement.tagName !== 'INPUT') {
|
if (!document.activeElement || document.activeElement.tagName !== 'INPUT') {
|
||||||
if (selectedNodeId) deleteSelectedNode();
|
if (selectedNodeId) deleteSelectedNode(selectedNodeId);
|
||||||
else if (selectedShapeId) deleteSelectedShape();
|
else if (selectedShapeId) deleteSelectedShape(selectedShapeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
@@ -204,9 +204,9 @@ async function loadNetworkData() {
|
|||||||
apiFetch('links'),
|
apiFetch('links'),
|
||||||
apiFetch('shapes')
|
apiFetch('shapes')
|
||||||
]);
|
]);
|
||||||
nodes = n;
|
nodes = Array.isArray(n) ? n : [];
|
||||||
links = l;
|
links = Array.isArray(l) ? l : [];
|
||||||
shapes = s;
|
shapes = Array.isArray(s) ? s : [];
|
||||||
populateNodeSelects();
|
populateNodeSelects();
|
||||||
renderNodeList();
|
renderNodeList();
|
||||||
renderShapeList();
|
renderShapeList();
|
||||||
@@ -265,7 +265,7 @@ function selectNode(id) {
|
|||||||
<div><span class="text-secondary">Type:</span> ${n.node_type}</div>
|
<div><span class="text-secondary">Type:</span> ${n.node_type}</div>
|
||||||
<div><span class="text-secondary">Status:</span> ${n.status}</div>
|
<div><span class="text-secondary">Status:</span> ${n.status}</div>
|
||||||
<div><span class="text-secondary">Group:</span> ${n.group_name}</div>
|
<div><span class="text-secondary">Group:</span> ${n.group_name}</div>
|
||||||
<button class="btn btn-outline-danger btn-sm mt-2" onclick="deleteSelectedNode()"><i class="fas fa-trash me-1"></i>Delete</button>
|
<button class="btn btn-outline-danger btn-sm mt-2" onclick="deleteSelectedNode(${n.id})"><i class="fas fa-trash me-1"></i>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -282,24 +282,43 @@ function selectShape(id) {
|
|||||||
renderNetwork();
|
renderNetwork();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelectedNode() {
|
async function deleteSelectedNode(id) {
|
||||||
if (!selectedNodeId) return;
|
if (!id) return;
|
||||||
if (!confirm('Delete this node and its connections?')) return;
|
const ok = await showConfirm('Delete this node and its connections?');
|
||||||
const id = selectedNodeId;
|
if (!ok) return;
|
||||||
selectedNodeId = null;
|
selectedNodeId = null;
|
||||||
await apiFetch(`nodes/${id}`, { method: 'DELETE' });
|
await apiFetch(`nodes/${id}`, { method: 'DELETE' });
|
||||||
loadNetworkData();
|
loadNetworkData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelectedShape() {
|
async function deleteSelectedShape(id) {
|
||||||
if (!selectedShapeId) return;
|
if (!id) return;
|
||||||
if (!confirm('Delete this shape?')) return;
|
const ok = await showConfirm('Delete this shape?');
|
||||||
const id = selectedShapeId;
|
if (!ok) return;
|
||||||
selectedShapeId = null;
|
selectedShapeId = null;
|
||||||
await apiFetch(`shapes/${id}`, { method: 'DELETE' });
|
await apiFetch(`shapes/${id}`, { method: 'DELETE' });
|
||||||
loadNetworkData();
|
loadNetworkData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showConfirm(msg) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const modalEl = document.getElementById('confirmModal');
|
||||||
|
const modal = new bootstrap.Modal(modalEl);
|
||||||
|
document.getElementById('confirmMsg').textContent = msg;
|
||||||
|
const btn = document.getElementById('confirmBtn');
|
||||||
|
let resolved = false;
|
||||||
|
const cleanup = () => {
|
||||||
|
btn.removeEventListener('click', onClick);
|
||||||
|
modalEl.removeEventListener('hidden.bs.modal', onHidden);
|
||||||
|
};
|
||||||
|
const onClick = () => { resolved = true; cleanup(); modal.hide(); resolve(true); };
|
||||||
|
const onHidden = () => { if (!resolved) { cleanup(); resolve(false); } };
|
||||||
|
btn.addEventListener('click', onClick);
|
||||||
|
modalEl.addEventListener('hidden.bs.modal', onHidden);
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function saveNode() {
|
async function saveNode() {
|
||||||
const data = {
|
const data = {
|
||||||
label: document.getElementById('nodeLabel').value,
|
label: document.getElementById('nodeLabel').value,
|
||||||
|
|||||||
@@ -329,6 +329,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ==================== CONFIRM MODAL ==================== -->
|
||||||
|
<div class="modal fade" id="confirmModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-sm modal-dialog-centered">
|
||||||
|
<div class="modal-content bg-dark">
|
||||||
|
<div class="modal-body text-center py-4">
|
||||||
|
<i class="fas fa-exclamation-triangle text-warning fs-3 mb-2"></i>
|
||||||
|
<p id="confirmMsg" class="mb-3">Are you sure?</p>
|
||||||
|
<div class="d-flex justify-content-center gap-2">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" id="confirmBtn">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="assets/js/app.js"></script>
|
<script src="assets/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user