Make Ctrl+Plus context aware
This commit is contained in:
@@ -5,6 +5,10 @@ import {
|
||||
inferProjectDeviceSectionKey,
|
||||
isProjectDevicePlacementValid,
|
||||
} from "../../domain/services/project-device-placement.service";
|
||||
import {
|
||||
getInsertionSortOrder,
|
||||
resolveGridInsertionIntent,
|
||||
} from "../utils/circuit-grid-insertion";
|
||||
import {
|
||||
createCircuit,
|
||||
createCircuitDeviceRow,
|
||||
@@ -1616,7 +1620,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
requestAnimationFrame(() => containerRef.current?.focus());
|
||||
}
|
||||
|
||||
async function handleAddReserveCircuit(sectionId: string) {
|
||||
async function handleAddReserveCircuit(sectionId: string, afterCircuitId?: string) {
|
||||
const section = data?.sections.find((entry) => entry.id === sectionId);
|
||||
if (!section) {
|
||||
return;
|
||||
@@ -1626,8 +1630,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
label: "Add circuit",
|
||||
redo: async () => {
|
||||
const next = await getNextCircuitIdentifier(sectionId);
|
||||
const sortOrder =
|
||||
section.circuits.length > 0 ? Math.max(...section.circuits.map((circuit) => circuit.sortOrder)) + 10 : 10;
|
||||
const sortOrder = getInsertionSortOrder(section.circuits, afterCircuitId);
|
||||
const created = (await createCircuit(projectId, circuitListId, {
|
||||
sectionId,
|
||||
equipmentIdentifier: next.nextIdentifier,
|
||||
@@ -1659,7 +1662,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAddManualDevice(circuit: CircuitTreeCircuitDto, sectionId: string) {
|
||||
async function handleAddManualDevice(
|
||||
circuit: CircuitTreeCircuitDto,
|
||||
sectionId: string,
|
||||
afterDeviceRowId?: string
|
||||
) {
|
||||
const originalDeviceCount = circuit.deviceRows.length;
|
||||
const section = data?.sections.find((entry) => entry.id === sectionId);
|
||||
let createdRowId: string | null = null;
|
||||
await runCommand({
|
||||
label: "Add manual device",
|
||||
@@ -1667,18 +1676,19 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
const created = (await createCircuitDeviceRow(circuit.id, {
|
||||
name: "Manual device",
|
||||
displayName: "Manual device",
|
||||
phaseType: "single_phase",
|
||||
phaseType: section?.key === "three_phase" ? "three_phase" : "single_phase",
|
||||
quantity: 1,
|
||||
powerPerUnit: 0,
|
||||
simultaneityFactor: 1,
|
||||
cosPhi: 1,
|
||||
sortOrder: getInsertionSortOrder(circuit.deviceRows, afterDeviceRowId),
|
||||
})) as { id: string };
|
||||
createdRowId = created.id;
|
||||
setActiveSectionId(sectionId);
|
||||
return {
|
||||
rowKey: `device:${created.id}`,
|
||||
rowKey: originalDeviceCount === 0 ? `circuitCompact:${circuit.id}` : `device:${created.id}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "deviceRow",
|
||||
rowType: originalDeviceCount === 0 ? "circuitCompact" : "deviceRow",
|
||||
sectionId,
|
||||
circuitId: circuit.id,
|
||||
deviceId: created.id,
|
||||
@@ -1688,17 +1698,68 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
if (createdRowId) {
|
||||
await deleteCircuitDeviceRowById(createdRowId);
|
||||
}
|
||||
const rowKey =
|
||||
originalDeviceCount === 0
|
||||
? `reserveCircuit:${circuit.id}`
|
||||
: originalDeviceCount === 1
|
||||
? `circuitCompact:${circuit.id}`
|
||||
: afterDeviceRowId
|
||||
? `device:${afterDeviceRowId}`
|
||||
: `circuitSummary:${circuit.id}`;
|
||||
return {
|
||||
rowKey: `reserveCircuit:${circuit.id}`,
|
||||
rowKey,
|
||||
cellKey: "displayName",
|
||||
rowType: "reserveCircuit",
|
||||
rowType:
|
||||
originalDeviceCount === 0
|
||||
? "reserveCircuit"
|
||||
: originalDeviceCount === 1
|
||||
? "circuitCompact"
|
||||
: afterDeviceRowId
|
||||
? "deviceRow"
|
||||
: "circuitSummary",
|
||||
sectionId,
|
||||
circuitId: circuit.id,
|
||||
deviceId: afterDeviceRowId,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleInsertBelowSelection() {
|
||||
const row = selectedCell ? findRow(selectedCell.rowKey) : null;
|
||||
const cell = row?.cells.find((entry) => entry.cellKey === selectedCell?.cellKey);
|
||||
const selection =
|
||||
row && cell && row.rowType !== "section"
|
||||
? {
|
||||
rowType: row.rowType,
|
||||
cellKind: cell.kind,
|
||||
sectionId: row.sectionId,
|
||||
circuitId: row.circuit?.id,
|
||||
deviceId: row.device?.id,
|
||||
}
|
||||
: null;
|
||||
const intent = resolveGridInsertionIntent(
|
||||
selection,
|
||||
activeSectionId ?? data?.sections[0]?.id
|
||||
);
|
||||
if (!intent) {
|
||||
setError("No section is available for insertion.");
|
||||
return;
|
||||
}
|
||||
if (intent.kind === "circuit") {
|
||||
await handleAddReserveCircuit(intent.sectionId, intent.afterCircuitId);
|
||||
return;
|
||||
}
|
||||
const circuit = data?.sections
|
||||
.flatMap((sectionEntry) => sectionEntry.circuits)
|
||||
.find((entry) => entry.id === intent.circuitId);
|
||||
if (!circuit) {
|
||||
setError("Invalid circuit id.");
|
||||
return;
|
||||
}
|
||||
await handleAddManualDevice(circuit, intent.sectionId, intent.afterDeviceRowId);
|
||||
}
|
||||
|
||||
function resolvePhaseType(device: ProjectDeviceDto): string {
|
||||
return device.phaseType;
|
||||
}
|
||||
@@ -2472,10 +2533,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
(event.key === "+" || (event.shiftKey && event.key === "=") || event.code === "NumpadAdd");
|
||||
if (isCtrlPlus) {
|
||||
event.preventDefault();
|
||||
const sectionId = activeSectionId ?? data?.sections[0]?.id;
|
||||
if (sectionId) {
|
||||
void handleAddReserveCircuit(sectionId);
|
||||
}
|
||||
void handleInsertBelowSelection();
|
||||
return;
|
||||
}
|
||||
if (event.key === "Tab" && selectedCell) {
|
||||
@@ -3486,7 +3544,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p className="todo-hint">TODO Phase: Ctrl+Plus currently adds circuit at end of active section.</p>
|
||||
<p className="todo-hint">
|
||||
Ctrl+Plus inserts below the selected circuit or device context. Use Undo to revert the insertion.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
export type GridInsertionRowType =
|
||||
| "circuitCompact"
|
||||
| "circuitSummary"
|
||||
| "deviceRow"
|
||||
| "reserveCircuit"
|
||||
| "placeholder";
|
||||
|
||||
export type GridInsertionCellKind = "circuitField" | "deviceField" | "readonly" | "computed";
|
||||
|
||||
export interface GridInsertionSelection {
|
||||
rowType: GridInsertionRowType;
|
||||
cellKind: GridInsertionCellKind;
|
||||
sectionId: string;
|
||||
circuitId?: string;
|
||||
deviceId?: string;
|
||||
}
|
||||
|
||||
export type GridInsertionIntent =
|
||||
| { kind: "circuit"; sectionId: string; afterCircuitId?: string }
|
||||
| { kind: "device"; sectionId: string; circuitId: string; afterDeviceRowId?: string };
|
||||
|
||||
export function resolveGridInsertionIntent(
|
||||
selection: GridInsertionSelection | null,
|
||||
fallbackSectionId?: string
|
||||
): GridInsertionIntent | null {
|
||||
if (!selection) {
|
||||
return fallbackSectionId ? { kind: "circuit", sectionId: fallbackSectionId } : null;
|
||||
}
|
||||
|
||||
if (selection.rowType === "placeholder") {
|
||||
return { kind: "circuit", sectionId: selection.sectionId };
|
||||
}
|
||||
|
||||
const targetsDevice =
|
||||
selection.rowType === "deviceRow" ||
|
||||
((selection.rowType === "circuitCompact" || selection.rowType === "reserveCircuit") &&
|
||||
selection.cellKind === "deviceField");
|
||||
|
||||
if (targetsDevice && selection.circuitId) {
|
||||
return {
|
||||
kind: "device",
|
||||
sectionId: selection.sectionId,
|
||||
circuitId: selection.circuitId,
|
||||
afterDeviceRowId: selection.deviceId,
|
||||
};
|
||||
}
|
||||
|
||||
if (selection.circuitId) {
|
||||
return {
|
||||
kind: "circuit",
|
||||
sectionId: selection.sectionId,
|
||||
afterCircuitId: selection.circuitId,
|
||||
};
|
||||
}
|
||||
|
||||
return { kind: "circuit", sectionId: selection.sectionId };
|
||||
}
|
||||
|
||||
export function getInsertionSortOrder(
|
||||
orderedEntries: Array<{ id: string; sortOrder: number }>,
|
||||
afterId?: string
|
||||
): number {
|
||||
if (orderedEntries.length === 0) {
|
||||
return 10;
|
||||
}
|
||||
if (!afterId) {
|
||||
return Math.max(...orderedEntries.map((entry) => entry.sortOrder)) + 10;
|
||||
}
|
||||
|
||||
const entries = [...orderedEntries].sort((left, right) => left.sortOrder - right.sortOrder);
|
||||
const index = entries.findIndex((entry) => entry.id === afterId);
|
||||
if (index < 0) {
|
||||
return entries[entries.length - 1].sortOrder + 10;
|
||||
}
|
||||
|
||||
const current = entries[index].sortOrder;
|
||||
const next = entries[index + 1]?.sortOrder;
|
||||
return next === undefined ? current + 10 : current + (next - current) / 2;
|
||||
}
|
||||
Reference in New Issue
Block a user