aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-09 17:12:03 -0400
committerhistoria <[not public]>2026-07-09 17:12:03 -0400
commitb888414c63c0e5fae0d33b3c5faea8a604d195fa (patch)
tree1c5a54101c774d6126c3d08fa5b6e9b44d0c3c04 /internal/admin/static
parentb8c90886ef1f3afb8d908aac89028bd177836cae (diff)
downloadthehouseoficarus-b888414c63c0e5fae0d33b3c5faea8a604d195fa.tar.gz
feat(admin): multi-select and delete rooms
Diffstat (limited to 'internal/admin/static')
-rw-r--r--internal/admin/static/map.js87
1 files changed, 76 insertions, 11 deletions
diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js
index 1f67275..e053dda 100644
--- a/internal/admin/static/map.js
+++ b/internal/admin/static/map.js
@@ -42,7 +42,7 @@ function onMapMouseMove(e) {
if (!dragging) return;
var moved = Math.abs(e.clientX - startX) >= 4 || Math.abs(e.clientY - startY) >= 4;
- if (delFromId && moved && !delDrag) { delDrag = true; }
+ if (delFromId && moved && !delDrag && multiSelectedIds.size === 0) { delDrag = true; }
if (delDrag && moved) {
delOneWay = e.ctrlKey;
updateDelDragTarget(e);
@@ -81,7 +81,11 @@ function onMapMouseUp(e) {
var moved = Math.abs(e.clientX - startX) >= 4 || Math.abs(e.clientY - startY) >= 4;
if (delFromId && !delDrag && !moved) {
- showRoomContextMenu(startX, startY, delFromId);
+ if (multiSelectedIds.size > 0) {
+ showBulkDeleteContextMenu(startX, startY, delFromId);
+ } else {
+ showRoomContextMenu(startX, startY, delFromId);
+ }
clearDelHighlight();
dragging = false; dragRoom = false; delFromId = null; delTargetId = null; delDrag = false;
if (document.activeElement && document.activeElement.blur) document.activeElement.blur();
@@ -368,7 +372,7 @@ function renderMap(data) {
var onUdBtn = e.target.classList.contains('ud-btn');
var onNavBtn = e.target.classList.contains('nav-btn');
- if (!e.shiftKey && multiSelectedIds.size > 0) {
+ if (e.button === 0 && !e.shiftKey && multiSelectedIds.size > 0) {
clearMultiSelect();
}
@@ -2263,6 +2267,58 @@ function showRoomContextMenu(x, y, id) {
clampMenuToViewport(menu);
}
+function showBulkDeleteContextMenu(x, y, clickedId) {
+ var overlay = document.createElement('div');
+ overlay.className = 'cm-overlay';
+ var closeAll = function() {
+ if (overlay._subHideTimer) { clearTimeout(overlay._subHideTimer); overlay._subHideTimer = null; }
+ overlay.remove(); menu.remove();
+ };
+ overlay.onclick = closeAll;
+ overlay.addEventListener('contextmenu', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ closeAll();
+ });
+
+ var menu = document.createElement('div');
+ menu.className = 'cm-menu';
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+ menu.oncontextmenu = function(e) { e.preventDefault(); };
+
+ var n = multiSelectedIds.size;
+ var delBtn = document.createElement('button');
+ delBtn.textContent = 'Delete ' + n + ' room' + (n !== 1 ? 's' : '');
+ delBtn.className = 'danger';
+ delBtn.onclick = function() {
+ closeAll();
+ deleteMultiRooms();
+ };
+ menu.appendChild(delBtn);
+
+ document.body.appendChild(overlay);
+ document.body.appendChild(menu);
+
+ clampMenuToViewport(menu);
+}
+
+function deleteMultiRooms() {
+ var n = multiSelectedIds.size;
+ var cb = document.getElementById('confirmDelete');
+ if (!cb || cb.checked) {
+ if (!confirm('Delete ' + n + ' room' + (n !== 1 ? 's' : '') + '?')) return;
+ }
+ var ids = Array.from(multiSelectedIds);
+ API.post('/api/rooms/bulk-delete', {ids: ids}).then(function(res) {
+ var msg = 'Deleted ' + n + ' room' + (n !== 1 ? 's' : '');
+ notify(msg, 'success');
+ updateUndoBar();
+ clearMultiSelect();
+ loadMap();
+ }).catch(function(e) { notify('Delete failed: ' + extractError(e), 'error'); });
+}
+
// buildSubmenuItem constructs a parent menu row ("Insert Room"/"Remove Room")
// that carries a nested submenu of the room's exits. The submenu is
// revealed by both hover (Windows-style: mouseenter with a small dismiss delay
@@ -2572,6 +2628,19 @@ function applyLinkHighlight(fromId, toId) {
g.appendChild(line);
}
+function restoreRoomStroke(el, id) {
+ if (multiSelectedIds.has(id)) {
+ el.setAttribute('stroke', '#5cf');
+ el.setAttribute('stroke-width', '3.5');
+ } else if (selectedRoom === id) {
+ el.setAttribute('stroke', '#fff');
+ el.setAttribute('stroke-width', '3');
+ } else {
+ el.setAttribute('stroke', el.getAttribute('fill') || '#3a3a6a');
+ el.setAttribute('stroke-width', '1.5');
+ }
+}
+
function clearLinkHighlight() {
var line = document.getElementById('__hl_line');
if (line) line.remove();
@@ -2579,15 +2648,13 @@ function clearLinkHighlight() {
if (linkFromId) {
var el = document.querySelector('.rm[data-id="'+linkFromId+'"]');
if (el) {
- el.setAttribute('stroke', selectedRoom === linkFromId ? '#fff' : (el.getAttribute('fill') || '#3a3a6a'));
- el.setAttribute('stroke-width', selectedRoom === linkFromId ? '3' : '1.5');
+ restoreRoomStroke(el, linkFromId);
}
}
if (linkTargetId) {
var el = document.querySelector('.rm[data-id="'+linkTargetId+'"]');
if (el) {
- el.setAttribute('stroke', selectedRoom === linkTargetId ? '#fff' : (el.getAttribute('fill') || '#3a3a6a'));
- el.setAttribute('stroke-width', selectedRoom === linkTargetId ? '3' : '1.5');
+ restoreRoomStroke(el, linkTargetId);
}
}
}
@@ -2666,15 +2733,13 @@ function clearDelHighlight() {
if (delFromId) {
var el = document.querySelector('.rm[data-id="'+delFromId+'"]');
if (el) {
- el.setAttribute('stroke', selectedRoom === delFromId ? '#fff' : (el.getAttribute('fill') || '#3a3a6a'));
- el.setAttribute('stroke-width', selectedRoom === delFromId ? '3' : '1.5');
+ restoreRoomStroke(el, delFromId);
}
}
if (delTargetId) {
var el = document.querySelector('.rm[data-id="'+delTargetId+'"]');
if (el) {
- el.setAttribute('stroke', selectedRoom === delTargetId ? '#fff' : (el.getAttribute('fill') || '#3a3a6a'));
- el.setAttribute('stroke-width', selectedRoom === delTargetId ? '3' : '1.5');
+ restoreRoomStroke(el, delTargetId);
}
}
}