Restore circuit drag placement

This commit is contained in:
Julian Appel 2026-07-31 08:33:41 +02:00
parent 7c8d4a7ddf
commit 2d97fafce9
6 changed files with 194 additions and 25 deletions

View file

@ -10,6 +10,7 @@ import {
resolveProjectVoltage,
} from "../../domain/services/project-voltage.service";
import {
getAdjacentInsertionSortOrder,
getInsertionSortOrder,
isGridInsertionRowType,
resolveGridInsertionIntent,
@ -175,7 +176,13 @@ type ProjectDeviceDropIntent =
type DeviceRowMoveDropIntent =
| { kind: "move-to-circuit"; circuitId: string; sectionId: string; requiresConfirmation: boolean }
| { kind: "move-to-new-circuit"; sectionId: string; requiresConfirmation: boolean };
| {
kind: "move-to-new-circuit";
sectionId: string;
requiresConfirmation: boolean;
targetCircuitId?: string;
placement?: "before" | "after";
};
type CircuitReorderDropIntent =
| { kind: "before-circuit"; sectionId: string; targetCircuitId: string; valid: boolean }
@ -2329,6 +2336,34 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
);
}
function resolveDeviceRowCircuitDropIntent(
event: DragEvent<HTMLElement>,
circuitId: string,
sectionId: string,
requiresConfirmation: boolean
): DeviceRowMoveDropIntent {
const rect = (
event.currentTarget as HTMLTableRowElement
).getBoundingClientRect();
const relativeY =
rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;
if (relativeY <= 0.25 || relativeY >= 0.75) {
return {
kind: "move-to-new-circuit",
sectionId,
requiresConfirmation,
targetCircuitId: circuitId,
placement: relativeY <= 0.25 ? "before" : "after",
};
}
return {
kind: "move-to-circuit",
circuitId,
sectionId,
requiresConfirmation,
};
}
// Applies same-section circuit reorder as explicit id ordering.
// No implicit renumbering is performed here.
function resolveCircuitReorderOrder(intent: CircuitReorderDropIntent, sourceCircuitIds: string[]) {
@ -2512,13 +2547,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
redo: async () => {
const next = await getNextCircuitIdentifier(intent.sectionId);
const sortOrder =
targetSection.circuits.length > 0
? Math.max(
...targetSection.circuits.map(
(circuit) => circuit.sortOrder
)
) + 10
: 10;
intent.targetCircuitId && intent.placement
? getAdjacentInsertionSortOrder(
targetSection.circuits,
intent.targetCircuitId,
intent.placement
)
: getInsertionSortOrder(targetSection.circuits);
const targetCircuit = createCircuitSnapshot({
sectionId: intent.sectionId,
equipmentIdentifier: next.nextIdentifier,
@ -3841,6 +3876,28 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
deviceMoveIntent.requiresConfirmation
? "drop-target-confirm"
: ""
} ${
row.circuit &&
deviceMoveIntent?.kind === "move-to-new-circuit" &&
deviceMoveIntent.targetCircuitId === row.circuit.id
? deviceMoveIntent.requiresConfirmation
? "drop-target-confirm"
: "drop-target-active"
: ""
} ${
row.circuit &&
deviceMoveIntent?.kind === "move-to-new-circuit" &&
deviceMoveIntent.targetCircuitId === row.circuit.id &&
deviceMoveIntent.placement === "before"
? "circuit-insert-before"
: ""
} ${
row.circuit &&
deviceMoveIntent?.kind === "move-to-new-circuit" &&
deviceMoveIntent.targetCircuitId === row.circuit.id &&
deviceMoveIntent.placement === "after"
? "circuit-insert-after"
: ""
} ${
row.circuit &&
deviceMoveIntent?.kind === "move-to-circuit" &&
@ -3985,15 +4042,25 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
const sourceCircuitIds = activeDraggedDeviceRowIds
.map((id) => findDeviceRowCircuitId(id))
.filter((id): id is string => Boolean(id));
if (sourceCircuitIds.some((sourceCircuitId) => sourceCircuitId !== row.circuit!.id)) {
const intent =
resolveDeviceRowCircuitDropIntent(
event,
row.circuit.id,
row.sectionId,
requiresConfirmation
);
if (
intent.kind === "move-to-new-circuit" ||
sourceCircuitIds.some(
(sourceCircuitId) =>
sourceCircuitId !== row.circuit!.id
)
) {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
setDeviceMoveIntent({
kind: "move-to-circuit",
circuitId: row.circuit.id,
sectionId: row.sectionId,
requiresConfirmation,
});
setDeviceMoveIntent(intent);
} else {
setDeviceMoveIntent(null);
}
}
}
@ -4018,6 +4085,12 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
if (current.kind === "move-to-new-circuit" && row.rowType === "placeholder" && current.sectionId === row.sectionId) {
return null;
}
if (
current.kind === "move-to-new-circuit" &&
current.targetCircuitId === row.circuit?.id
) {
return null;
}
if (current.kind === "move-to-circuit" && current.circuitId === row.circuit?.id) {
return null;
}
@ -4096,12 +4169,15 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
return;
}
if (row.circuit && row.rowType !== "deviceRow") {
void handleDeviceRowDropWithIntent(event, {
kind: "move-to-circuit",
circuitId: row.circuit.id,
sectionId: row.sectionId,
requiresConfirmation,
});
void handleDeviceRowDropWithIntent(
event,
resolveDeviceRowCircuitDropIntent(
event,
row.circuit.id,
row.sectionId,
requiresConfirmation
)
);
}
}
}}
@ -4140,9 +4216,21 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
row.device?.id &&
((draggingDeviceRowIds.length > 0 && draggingDeviceRowIds.includes(row.device.id)) ||
(draggingDeviceRowIds.length === 0 && draggingDeviceRowId === row.device.id))
? "device-dragging"
: ""
}`}
? "device-dragging"
: ""
}`}
title={
column.key === "equipmentIdentifier" &&
row.circuit &&
(row.rowType === "circuitCompact" ||
row.rowType === "circuitSummary" ||
row.rowType === "reserveCircuit")
? "Stromkreis verschieben: am BMK ziehen und zwischen Stromkreisen ablegen"
: column.key === "displayName" &&
row.device
? "Gerät verschieben: in der Zeilenmitte zuordnen oder am oberen/unteren Rand einen neuen Stromkreis bilden"
: undefined
}
draggable={
!isEditing &&
((Boolean(row.device) &&
@ -4384,6 +4472,21 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
{`${draggingDeviceCount || 1} Gerätezeile(n) in diesen Stromkreis verschieben${deviceMoveIntent.requiresConfirmation ? " (Bestätigung erforderlich)" : ""}`}
</span>
) : null}
{deviceMoveIntent?.kind === "move-to-new-circuit" &&
deviceMoveIntent.targetCircuitId ===
row.circuit?.id ? (
<span className="drop-hint">
{`${draggingDeviceCount || 1} Gerätezeile(n) in einen neuen Stromkreis ${
deviceMoveIntent.placement === "before"
? "davor"
: "danach"
} verschieben${
deviceMoveIntent.requiresConfirmation
? " (Bestätigung erforderlich)"
: ""
}`}
</span>
) : null}
{circuitReorderIntent?.kind === "section-end" &&
row.rowType === "placeholder" &&
circuitReorderIntent.sectionId === row.sectionId ? (