diff --git a/src/components/editor/faultTree/Editor.tsx b/src/components/editor/faultTree/Editor.tsx index d5f6cb84..39e9f4a5 100644 --- a/src/components/editor/faultTree/Editor.tsx +++ b/src/components/editor/faultTree/Editor.tsx @@ -119,6 +119,33 @@ const Editor = () => { } }; + const handleAutomaticLayout = async (graph, jointPaper) => { + for (const el of graph.getElements()) { + const faultEventIri = el.get(JOINTJS_NODE_MODEL.faultEventIri); + const movedEvent = findEventByIri(faultEventIri, getRootEvent()); + + if (movedEvent) { + const elementView = el.findView(jointPaper); + const size = elementView.model.attributes.size; + const position = elementView.model.attributes.position; + const rect: Rectangle = movedEvent.rectangle; + + if (rect.x != position.x || rect.y != position.y || rect.width != size.width || rect.height != size.height) { + rect.x = Math.round(position.x); + rect.y = Math.round(position.y); // The server only supports integers + rect.width = size.width; + rect.height = size.height; + + try { + await faultEventService.updateEventRectangle(faultEventIri, rect.iri, rect); + } catch (err) { + console.error("Failed to persist node position", err); + } + } + } + } + }; + const highlightBorders = (elementView) => { const tools = new joint.dia.ToolsView({ tools: [FTABoundary.factory()], @@ -219,6 +246,7 @@ const Editor = () => { onCutSetAnalysis={handleCutSetAnalysis} onConvertToTable={() => setFailureModesTableOpen(true)} onNodeMove={handleMoveEvent} + onApplyAutomaticLayout={handleAutomaticLayout} setHighlightedElement={setHighlightedElementView} refreshTree={refreshTree} faultEventScenarios={faultEventScenarios} diff --git a/src/components/editor/faultTree/canvas/EditorCanvas.tsx b/src/components/editor/faultTree/canvas/EditorCanvas.tsx index 5d070ed3..2b766a7f 100644 --- a/src/components/editor/faultTree/canvas/EditorCanvas.tsx +++ b/src/components/editor/faultTree/canvas/EditorCanvas.tsx @@ -51,6 +51,7 @@ interface Props { showTable: boolean; possibleFaultEventScenarios: FaultEventScenario[]; onScenarioSelect: (scenario: FaultEventScenario) => void; + onApplyAutomaticLayout: (container: joint.dia.Graph, jointPaper: joint.dia.Paper) => void; } const EditorCanvas = ({ @@ -71,6 +72,7 @@ const EditorCanvas = ({ showTable, possibleFaultEventScenarios, onScenarioSelect, + onApplyAutomaticLayout, }: Props) => { const { classes } = useStyles(); const theme = useTheme(); @@ -210,14 +212,10 @@ const EditorCanvas = ({ } }, [isExportingImage]); - const addSelf = (shape: any) => { - shape.addTo(container); - layout(container); - }; - const layout = (graph) => { const autoLayoutElements = []; - const manualLayoutElements = []; + const conditionalEventLayoutElements = []; + graph.getElements().forEach((el) => { const faultEventIri = el.get(JOINTJS_NODE_MODEL.faultEventIri); if (faultEventIri && faultEventIri === sidebarSelectedEvent?.iri) { @@ -226,12 +224,13 @@ const EditorCanvas = ({ } if (el.get("type") === "fta.ConditioningEvent") { - manualLayoutElements.push(el); - } else if (!el.get(JOINTJS_NODE_MODEL.hasPersistentPosition)) { + conditionalEventLayoutElements.push(el); + } else { autoLayoutElements.push(el); } }); - // Automatic Layout + + // Apply the automatic layout joint.layout.DirectedGraph.layout(graph.getSubgraph(autoLayoutElements), { dagre: dagre, graphlib: graphlib, @@ -241,15 +240,17 @@ const EditorCanvas = ({ rankSep: 100, // Vertical separation between ranks marginX: 20, marginY: 20, - preserveNodeGeometry: true, }); - // Manual Layout - manualLayoutElements.forEach((el) => { + + // Conditioning Events Manual Layout + conditionalEventLayoutElements.forEach((el) => { const neighbor = graph.getNeighbors(el, { inbound: true })[0]; if (!neighbor) return; const neighborPosition = neighbor.getBBox().bottomRight(); el.position(neighborPosition.x + 20, neighborPosition.y - el.size().height / 2 - 20); }); + + onApplyAutomaticLayout(graph, jointPaper); }; const handleDiagramExport = () => { @@ -258,7 +259,7 @@ const EditorCanvas = ({ }; useEffect(() => { - const renderAndLayout = async () => { + const render = async () => { if (container && rootEvent) { setRendering(true); container.removeCells(container.getCells()); @@ -270,12 +271,11 @@ const EditorCanvas = ({ } await renderTree(container, rootEvent, null, listOfPaths, faultTree?.status); - layout(container); setRendering(false); } }; - renderAndLayout(); + render(); }, [container, rootEvent, faultEventScenarios]); useEffect(() => { @@ -331,7 +331,7 @@ const EditorCanvas = ({ layout(container)} + onApplyAutomaticLayout={() => layout(container)} onCutSetAnalysis={onCutSetAnalysis} rendering={rendering} /> diff --git a/src/components/editor/faultTree/menu/SidebarMenuHeader.tsx b/src/components/editor/faultTree/menu/SidebarMenuHeader.tsx index c2d969a4..8664c1cb 100644 --- a/src/components/editor/faultTree/menu/SidebarMenuHeader.tsx +++ b/src/components/editor/faultTree/menu/SidebarMenuHeader.tsx @@ -23,7 +23,7 @@ const useActionCall = (isModified: boolean, setShowUnsavedChangesDialog: (show: const SidebarMenuHeader: React.FC = ({ onConvertToTable, onExportDiagram, - onRestoreLayout, + onApplyAutomaticLayout, onCutSetAnalysis, rendering, }) => { @@ -37,7 +37,7 @@ const SidebarMenuHeader: React.FC = ({ actionCall(onExportDiagram)} onConvertToTable={() => actionCall(onConvertToTable)} - onRestoreLayout={() => actionCall(onRestoreLayout)} + onApplyAutomaticLayout={() => actionCall(onApplyAutomaticLayout)} onCutSetAnalysis={() => actionCall(onCutSetAnalysis)} tableConversionAllowed={!table} rendering={rendering} diff --git a/src/components/editor/menu/DiagramOptions.tsx b/src/components/editor/menu/DiagramOptions.tsx index f5feccfe..58484596 100644 --- a/src/components/editor/menu/DiagramOptions.tsx +++ b/src/components/editor/menu/DiagramOptions.tsx @@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next"; import CircularProgress from "@mui/material/CircularProgress"; export interface Props { - onRestoreLayout: () => void; + onApplyAutomaticLayout: () => void; onExportDiagram: () => void; onConvertToTable?: () => void; onCutSetAnalysis?: () => void; @@ -18,7 +18,7 @@ export interface Props { } const DiagramOptions = ({ - onRestoreLayout, + onApplyAutomaticLayout, onExportDiagram, onConvertToTable, onCutSetAnalysis, @@ -33,7 +33,7 @@ const DiagramOptions = ({ Diagram Options
- + diff --git a/src/components/editor/system/canvas/EditorCanvas.tsx b/src/components/editor/system/canvas/EditorCanvas.tsx index f43e488a..c96c06ce 100644 --- a/src/components/editor/system/canvas/EditorCanvas.tsx +++ b/src/components/editor/system/canvas/EditorCanvas.tsx @@ -255,7 +255,7 @@ const EditorCanvas = ({ ))}
- layout(container)} onExportDiagram={handleDiagramExport} /> + layout(container)} onExportDiagram={handleDiagramExport} /> {t("diagramSidePanel.minimumOperationalHours")}