v0.2.3: visible cross-panel drag ghost, front-image fallback, shorter hint
- Fix invisible device while dragging from front to rear: the block being dragged stayed structurally inside its own source <svg>, so it rendered underneath the other panel once it visually overlapped it. Replaced the in-SVG transform with a free-floating HTML ghost fixed to <body> that tracks the pointer, unaffected by either SVG's own stacking - the original block just dims in place during the drag, as before. - Fix rear elevation showing nothing for a device type with no rear_image: only full-depth devices (which have a genuinely different back) are restricted to their own rear image now; a shallow device (e.g. a PDU) falls back to its front image on the rear view, since that's usually the only picture that exists and is still recognisable either way. - Shorten the drag-hint badge text; the German translation of the previous, longer wording didn't fit well next to the other controls above the elevation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,6 @@
|
||||
height: auto;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
/* A device being dragged toward the other face's panel shouldn't just vanish the
|
||||
moment it crosses this SVG's own edge - let it render past the boundary while
|
||||
it's mid-drag, into whatever sits next to it (the other panel, side by side). */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.concept-elevation .unit-slot {
|
||||
@@ -108,6 +104,26 @@
|
||||
}
|
||||
|
||||
.concept-elevation .device-block.dragging {
|
||||
opacity: 0.6;
|
||||
opacity: 0.3;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Follows the pointer during a drag - a plain HTML element fixed to <body>, not
|
||||
scoped to either elevation's <svg>, so it stays visible the whole way from the
|
||||
front panel to the rear one and back. */
|
||||
.concept-drag-ghost {
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
padding-left: 6px;
|
||||
font-size: 11px;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 0 2px #000000, 0 0 2px #000000, 0 0 2px #000000;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,31 @@
|
||||
return el ? el.closest('.concept-elevation') : null;
|
||||
}
|
||||
|
||||
/* A free-floating copy of the device block, fixed to <body> and tracking the
|
||||
pointer - not scoped to either SVG, so it stays visible while crossing from one
|
||||
panel to the other. */
|
||||
function createGhost(block, rectEl, clientX, clientY) {
|
||||
const screenRect = rectEl.getBoundingClientRect();
|
||||
const label = block.querySelector('.device-label');
|
||||
|
||||
const ghost = document.createElement('div');
|
||||
ghost.className = 'concept-drag-ghost';
|
||||
ghost.style.width = screenRect.width + 'px';
|
||||
ghost.style.height = screenRect.height + 'px';
|
||||
ghost.style.backgroundColor = rectEl.getAttribute('fill') || '#c0c0c0';
|
||||
ghost.textContent = label ? label.textContent.trim() : '';
|
||||
ghost.dataset.offsetX = clientX - screenRect.left;
|
||||
ghost.dataset.offsetY = clientY - screenRect.top;
|
||||
document.body.appendChild(ghost);
|
||||
moveGhost(ghost, clientX, clientY);
|
||||
return ghost;
|
||||
}
|
||||
|
||||
function moveGhost(ghost, clientX, clientY) {
|
||||
ghost.style.left = (clientX - ghost.dataset.offsetX) + 'px';
|
||||
ghost.style.top = (clientY - ghost.dataset.offsetY) + 'px';
|
||||
}
|
||||
|
||||
container.querySelectorAll('.concept-elevation').forEach(function (panel) {
|
||||
const svg = panel.querySelector('svg');
|
||||
const geo = geometry(svg);
|
||||
@@ -154,6 +179,13 @@
|
||||
block.style.pointerEvents = 'none';
|
||||
block.setPointerCapture(event.pointerId);
|
||||
|
||||
// A device dragged toward the other panel is still, structurally,
|
||||
// content of its own source <svg> - it would render underneath the
|
||||
// other panel's own markup once it overlapped it, effectively
|
||||
// vanishing. A free-floating HTML ghost fixed to <body> sidesteps that
|
||||
// entirely: it's not inside either SVG, so it stays visible over both.
|
||||
const ghost = createGhost(block, rect, event.clientX, event.clientY);
|
||||
|
||||
function onMove(moveEvent) {
|
||||
const now = svgPoint(svg, moveEvent);
|
||||
dx = now.x - start.x;
|
||||
@@ -161,8 +193,7 @@
|
||||
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
|
||||
moved = true;
|
||||
}
|
||||
const snappedY = Math.round(dy / geo.unitHeight) * geo.unitHeight;
|
||||
block.setAttribute('transform', 'translate(' + dx + ' ' + snappedY + ')');
|
||||
moveGhost(ghost, moveEvent.clientX, moveEvent.clientY);
|
||||
}
|
||||
|
||||
function onUp(upEvent) {
|
||||
@@ -171,10 +202,10 @@
|
||||
block.removeEventListener('pointercancel', onUp);
|
||||
block.classList.remove('dragging');
|
||||
block.style.pointerEvents = '';
|
||||
ghost.remove();
|
||||
|
||||
if (!moved) {
|
||||
// Treat it as a click: follow the device link.
|
||||
block.removeAttribute('transform');
|
||||
const link = block.querySelector('a');
|
||||
if (link) window.location = link.getAttribute('href');
|
||||
return;
|
||||
@@ -245,12 +276,10 @@
|
||||
if (result.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
block.removeAttribute('transform');
|
||||
setStatus(result.body.error || 'The move was rejected.', true);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
block.removeAttribute('transform');
|
||||
setStatus('The move could not be saved.', true);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user