diff --git a/src/components/editor/faultTree/Editor.tsx b/src/components/editor/faultTree/Editor.tsx index 9feab36a..30094df9 100644 --- a/src/components/editor/faultTree/Editor.tsx +++ b/src/components/editor/faultTree/Editor.tsx @@ -18,6 +18,8 @@ import {ROUTES} from "@utils/constants"; import {extractFragment} from "@services/utils/uriIdentifierUtils"; import {FTABoundary} from "@components/editor/faultTree/shapes/shapesDefinitions"; import * as joint from "jointjs"; +import {Rectangle} from "@models/utils/Rectangle"; +import {JOINTJS_NODE_MODEL} from "@components/editor/faultTree/shapes/constants"; const Editor = ({setAppBarName}: DashboardTitleProps) => { const history = useNavigate(); @@ -30,6 +32,16 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { const [highlightedElementView, setHighlightedElementView] = useState(null) const _localContext = useLocalContext({rootEvent: rootEvent, highlightedElementView: highlightedElementView}) + const getRootEvent = () : FaultEvent => { + // @ts-ignore + return _localContext.rootEvent; + } + + const getHighlightedElementView = () => { + // @ts-ignore + return _localContext.highlightedElementView + } + useEffect(() => { if (faultTree) { setAppBarName(faultTree.name) @@ -58,17 +70,16 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { const [contextMenuAnchor, setContextMenuAnchor] = useState(contextMenuDefaultAnchor) const handleContextMenu = (elementView, evt) => { - const elementIri = elementView.model.get('custom/faultEventIri'); - // @ts-ignore - const foundEvent = findEventByIri(elementIri, _localContext.rootEvent); + const elementIri = elementView.model.get(JOINTJS_NODE_MODEL.faultEventIri); + + const foundEvent = findEventByIri(elementIri, getRootEvent()); setContextMenuSelectedEvent(foundEvent); setContextMenuAnchor({mouseX: evt.pageX, mouseY: evt.pageY,}) } const handleElementPointerClick = (elementView) => { - const elementIri = elementView.model.get('custom/faultEventIri'); - // @ts-ignore - const foundEvent = findEventByIri(elementIri, _localContext.rootEvent); + const elementIri = elementView.model.get(JOINTJS_NODE_MODEL.faultEventIri); + const foundEvent = findEventByIri(elementIri, getRootEvent()); setSidebarSelectedEvent(foundEvent); setHighlightedElementView(elementView); @@ -79,6 +90,22 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { hideHighlightedBorders(); } + const handleMoveEvent = (elementView, evt) => { + const faultEventIri = elementView.model.get(JOINTJS_NODE_MODEL.faultEventIri); + + const movedEvent = findEventByIri(faultEventIri, getRootEvent()); + const rect :Rectangle = movedEvent.rectangle; + const size = elementView.model.attributes.size; + const position = elementView.model.attributes.position; + if(rect.x != position.x || rect.y != position.y || rect.width != size.width || rect.height != size.height) { + rect.x = position.x; + rect.y = position.y; + rect.width = size.width; + rect.height = size.height; + faultEventService.updateEventRectangle(faultEventIri, rect.iri, rect); + } + } + const highlightBorders = (elementView) => { const tools = new joint.dia.ToolsView({ tools: [FTABoundary.factory()] @@ -86,8 +113,7 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { elementView.addTools(tools); } const hideHighlightedBorders = () => { - // @ts-ignore - _localContext.highlightedElementView?.removeTools(); + getHighlightedElementView()?.removeTools(); setHighlightedElementView(null); } @@ -134,6 +160,7 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { onElementPointerClick={handleElementPointerClick} onBlankPointerClick={handleBlankPointerClick} onConvertToTable={() => setFailureModesTableOpen(true)} + onNodeMove={handleMoveEvent} setHighlightedElement={setHighlightedElementView} refreshTree={refreshTree} /> diff --git a/src/components/editor/faultTree/canvas/EditorCanvas.tsx b/src/components/editor/faultTree/canvas/EditorCanvas.tsx index 27f38793..2aec953a 100644 --- a/src/components/editor/faultTree/canvas/EditorCanvas.tsx +++ b/src/components/editor/faultTree/canvas/EditorCanvas.tsx @@ -14,6 +14,13 @@ import * as svgPanZoom from "svg-pan-zoom"; import {SVG_PAN_ZOOM_OPTIONS} from "@utils/constants"; import {saveSvgAsPng} from "save-svg-as-png"; import renderTree from "@components/editor/faultTree/shapes/RenderTree"; +import {JOINTJS_NODE_MODEL} from "@components/editor/faultTree/shapes/constants"; + +enum MOVE_NODE { + DRAGGING = 0, + RELEASING =1 +} + interface Props { treeName: string, @@ -24,6 +31,7 @@ interface Props { onBlankPointerClick: () => void, onEventUpdated: (faultEvent: FaultEvent) => void, onConvertToTable: () => void, + onNodeMove: (element: any, evt: any) => void, refreshTree: () => void, setHighlightedElement: (element: any) => void, } @@ -37,6 +45,7 @@ const EditorCanvas = ({ onBlankPointerClick, onEventUpdated, onConvertToTable, + onNodeMove, refreshTree, setHighlightedElement }: Props) => { @@ -51,6 +60,8 @@ const EditorCanvas = ({ const [currentZoom, setCurrentZoom] = useState(1); const [isExportingImage, setIsExportingImage] = useState(false); + let dragStartPosition = null; + useEffect(() => { const canvasWidth = containerRef.current.clientWidth; const canvasHeight = containerRef.current.clientHeight; @@ -58,7 +69,7 @@ const EditorCanvas = ({ const graph = new joint.dia.Graph; const divContainer = document.getElementById("jointjs-container"); const paper = new joint.dia.Paper({ - // @ts-ignore + el: divContainer, model: graph, width: canvasWidth, @@ -77,7 +88,6 @@ const EditorCanvas = ({ }); setSvgZoom(diagramZoom); - // @ts-ignore paper.on({ 'element:contextmenu': (elementView, evt) => { onElementContextMenu(elementView, evt) @@ -85,6 +95,13 @@ const EditorCanvas = ({ 'element:pointerclick': (elementView, evt) => { onElementPointerClick(elementView, evt) }, + 'element:pointermove': (elementView: joint.dia.ElementView, evt: joint.dia.Event, x: number, y: number) => + { + handleNodeMove(MOVE_NODE.DRAGGING, elementView, evt, x, y); + }, + 'element:pointerup': (elementView: joint.dia.ElementView, evt: joint.dia.Event, x: number, y: number) => { + handleNodeMove(MOVE_NODE.RELEASING, elementView, evt, x, y); + }, 'blank:pointerclick': () => onBlankPointerClick(), 'blank:pointerdown': () => diagramZoom.enablePan(), 'blank:pointerup': () => diagramZoom.disablePan(), @@ -94,6 +111,15 @@ const EditorCanvas = ({ setJointPaper(paper); }, []); + const handleNodeMove = (move: MOVE_NODE, elementView: joint.dia.ElementView, evt: joint.dia.Event, x: number, y: number) => { + if(!dragStartPosition && move === MOVE_NODE.DRAGGING) + dragStartPosition = [x, y]; + else if(dragStartPosition && (dragStartPosition[0] != x || dragStartPosition[1] != y) && move === MOVE_NODE.RELEASING){ + dragStartPosition = null; + onNodeMove(elementView, evt); + } + } + useEffect(() => { if (isExportingImage) { const svgPaper = document.querySelector('#jointjs-container > svg'); @@ -118,7 +144,7 @@ const EditorCanvas = ({ const autoLayoutElements = []; const manualLayoutElements = []; graph.getElements().forEach((el) => { - const faultEventIri = el.get('custom/faultEventIri'); + const faultEventIri = el.get(JOINTJS_NODE_MODEL.faultEventIri); if(faultEventIri && faultEventIri === sidebarSelectedEvent?.iri) { const elementView = el.findView(jointPaper); setHighlightedElement(elementView) @@ -126,7 +152,7 @@ const EditorCanvas = ({ if (el.get('type') === 'fta.ConditioningEvent') { manualLayoutElements.push(el); - } else { + } else if(!el.get(JOINTJS_NODE_MODEL.hasPersistentPosition)) { autoLayoutElements.push(el); } }); diff --git a/src/components/editor/faultTree/shapes/FaultEventShape.tsx b/src/components/editor/faultTree/shapes/FaultEventShape.tsx index b64e856a..30e203b1 100644 --- a/src/components/editor/faultTree/shapes/FaultEventShape.tsx +++ b/src/components/editor/faultTree/shapes/FaultEventShape.tsx @@ -8,6 +8,7 @@ import {createShape} from "@services/jointService"; import {sequenceListToArray} from "@services/faultEventService"; import * as faultEventService from "@services/faultEventService"; import {has} from "lodash"; +import {JOINTJS_NODE_MODEL} from "@components/editor/faultTree/shapes/constants"; const FaultEventShape = ({addSelf, treeEvent, parentShape}: JointEventShapeProps) => { const [currentShape, setCurrentShape] = useState(undefined) @@ -28,7 +29,7 @@ const FaultEventShape = ({addSelf, treeEvent, parentShape}: JointEventShapeProps } // @ts-ignore - eventShape.set('custom/faultEventIri', treeEvent.iri) + eventShape.set(JOINTJS_NODE_MODEL.faultEventIri, treeEvent.iri) setCurrentShape(eventShape) diff --git a/src/components/editor/faultTree/shapes/RenderTree.tsx b/src/components/editor/faultTree/shapes/RenderTree.tsx index 30d93546..9b0e43c3 100644 --- a/src/components/editor/faultTree/shapes/RenderTree.tsx +++ b/src/components/editor/faultTree/shapes/RenderTree.tsx @@ -5,6 +5,7 @@ import * as faultEventService from "../../../../services/faultEventService"; import {Link} from "./shapesDefinitions"; import {flatten} from "lodash"; import {has} from "lodash"; +import {JOINTJS_NODE_MODEL} from "@components/editor/faultTree/shapes/constants"; const renderLink = (container, source, target) => { // @ts-ignore @@ -26,8 +27,13 @@ const renderTree = (container, node, parentShape = null) => { nodeShape.attr(['probabilityLabel', 'text'], node.probability.toExponential(2)); } // @ts-ignore - nodeShape.set('custom/faultEventIri', node.iri) - + nodeShape.set(JOINTJS_NODE_MODEL.faultEventIri, node.iri) + const r = node.rectangle; + if(r && r.x && r.y && r.width && r.height){ + nodeShape.position(node.rectangle.x, node.rectangle.y); + // @ts-ignore + nodeShape.set(JOINTJS_NODE_MODEL.hasPersistentPosition, true); + } // Render link if(parentShape){ renderLink(container, parentShape, nodeShape) diff --git a/src/components/editor/faultTree/shapes/constants.tsx b/src/components/editor/faultTree/shapes/constants.tsx new file mode 100644 index 00000000..ce80c833 --- /dev/null +++ b/src/components/editor/faultTree/shapes/constants.tsx @@ -0,0 +1,4 @@ +export const JOINTJS_NODE_MODEL = { + faultEventIri: 'custom/faultEventIri', + hasPersistentPosition: 'custom/has-persistent-position' +} \ No newline at end of file diff --git a/src/components/editor/system/Editor.tsx b/src/components/editor/system/Editor.tsx index 70f442fb..68c8b452 100644 --- a/src/components/editor/system/Editor.tsx +++ b/src/components/editor/system/Editor.tsx @@ -45,11 +45,20 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { setContextMenuAnchor({mouseX: evt.pageX, mouseY: evt.pageY,}) } + const getSystem = () => { + // @ts-ignore + return _localContext.system; + } + + const getHighlightedElementView = () => { + // @ts-ignore + return _localContext.highlightedElementView + } + const handleContextMenu = (elementView, evt) => { const componentIri = elementView.model.get('custom/componentIri'); - // @ts-ignore - const flattenedComponents = flatten([_localContext.system.components]); + const flattenedComponents = flatten([getSystem().components]); const index = findIndex(flattenedComponents, el => el.iri === componentIri); if (index > -1) { setContextMenuSelectedComponent(flattenedComponents[index]); @@ -60,8 +69,7 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { const handleElementPointerClick = (elementView) => { const componentIri = elementView.model.get('custom/componentIri'); - // @ts-ignore - const flattenedComponents = flatten([_localContext.system.components]); + const flattenedComponents = flatten([getSystem().components]); const index = findIndex(flattenedComponents, el => el.iri === componentIri); if (index > -1) { setSidebarSelectedComponent(flattenedComponents[index]); @@ -82,8 +90,7 @@ const Editor = ({setAppBarName}: DashboardTitleProps) => { elementView.addTools(tools); } const hideHighlightedBorders = () => { - // @ts-ignore - _localContext.highlightedElementView?.removeTools(); + getHighlightedElementView()?.removeTools(); setHighlightedElementView(null); } diff --git a/src/components/editor/system/canvas/EditorCanvas.tsx b/src/components/editor/system/canvas/EditorCanvas.tsx index c4bcfe47..332ff7a8 100644 --- a/src/components/editor/system/canvas/EditorCanvas.tsx +++ b/src/components/editor/system/canvas/EditorCanvas.tsx @@ -55,7 +55,6 @@ const EditorCanvas = ({ const graph = new joint.dia.Graph; const divContainer = document.getElementById("jointjs-system-container"); const paper = new joint.dia.Paper({ - // @ts-ignore el: divContainer, model: graph, width: canvasWidth, @@ -73,7 +72,6 @@ const EditorCanvas = ({ }); setSvgZoom(diagramZoom); - // @ts-ignore paper.on({ 'blank:contextmenu': (evt) => { onBlankContextMenu(evt); diff --git a/src/components/editor/system/menu/component/ComponentEditMenu.tsx b/src/components/editor/system/menu/component/ComponentEditMenu.tsx index 9416d0a0..cd854df8 100644 --- a/src/components/editor/system/menu/component/ComponentEditMenu.tsx +++ b/src/components/editor/system/menu/component/ComponentEditMenu.tsx @@ -38,7 +38,6 @@ const ComponentEditMenu = ({component, onComponentUpdated}: Props) => { useEffect(() => { - // @ts-ignore reset(defaultValues) }, [component]) diff --git a/src/components/editor/system/menu/function/ComponentFunctionsList.tsx b/src/components/editor/system/menu/function/ComponentFunctionsList.tsx index 797e9041..4384d68a 100644 --- a/src/components/editor/system/menu/function/ComponentFunctionsList.tsx +++ b/src/components/editor/system/menu/function/ComponentFunctionsList.tsx @@ -148,7 +148,6 @@ const ComponentFunctionsList = ({ component }) => { }) } - // @ts-ignore return ( diff --git a/src/models/eventModel.tsx b/src/models/eventModel.tsx index 1e355f26..e5724ff1 100644 --- a/src/models/eventModel.tsx +++ b/src/models/eventModel.tsx @@ -1,6 +1,7 @@ import VocabularyUtils from "@utils/VocabularyUtils"; import {AbstractModel, CONTEXT as ABSTRACT_CONTEXT} from "@models/abstractModel"; import {FailureMode, CONTEXT as FAILURE_MODE_CONTEXT} from "@models/failureModeModel"; +import {Rectangle, CONTEXT as RECTANGLE_CONTEXT, PREFIX as DIAGRAM_PREFIX} from "@models/utils/Rectangle"; const ctx = { "name": VocabularyUtils.PREFIX + "hasName", @@ -14,9 +15,10 @@ const ctx = { "failureMode": VocabularyUtils.PREFIX + "hasFailureMode", "sequenceProbability": VocabularyUtils.PREFIX + "hasSequenceProbability", "childrenSequence": VocabularyUtils.PREFIX + "hasChildrenSequence", + "rectangle": DIAGRAM_PREFIX + "has-rectangle", }; -export const CONTEXT = Object.assign({}, ctx, ABSTRACT_CONTEXT, FAILURE_MODE_CONTEXT); +export const CONTEXT = Object.assign({}, ctx, ABSTRACT_CONTEXT, FAILURE_MODE_CONTEXT, RECTANGLE_CONTEXT); export enum EventType { BASIC = "BASIC", @@ -36,6 +38,7 @@ export interface FaultEvent extends AbstractModel { failureMode?: FailureMode, sequenceProbability?: number, childrenSequence?: any, + rectangle?: Rectangle, } export enum GateType { diff --git a/src/models/utils/Rectangle.tsx b/src/models/utils/Rectangle.tsx new file mode 100644 index 00000000..d8d507ff --- /dev/null +++ b/src/models/utils/Rectangle.tsx @@ -0,0 +1,20 @@ +export const PREFIX = "http://onto.fel.cvut.cz/ontologies/diagram/"; + +const ctx = { + "iri": "@id", + "types": "@type", + "x": PREFIX + "x", + "y": PREFIX + "y", + "width": PREFIX + "width", + "height": PREFIX + "height" +}; + +export const CONTEXT = Object.assign({}, ctx); + +export interface Rectangle { + iri?: string, + x?: number, + y?: number, + width?: number, + height?: number +} \ No newline at end of file diff --git a/src/services/faultEventService.tsx b/src/services/faultEventService.tsx index 2d740f85..41304bed 100644 --- a/src/services/faultEventService.tsx +++ b/src/services/faultEventService.tsx @@ -9,6 +9,7 @@ import {findIndex, flatten, sortBy} from "lodash"; import {CONTEXT as FAILURE_MODE_CONTEXT, FailureMode} from "@models/failureModeModel"; import {handleServerError} from "@services/utils/responseUtils"; import {simplifyReferencesOfReferences} from "@utils/utils"; +import {Rectangle, CONTEXT as RECTANGLE_CONTEXT} from "@models/utils/Rectangle"; export const findAll = async (): Promise => { try { @@ -91,6 +92,26 @@ export const addEvent = async (faultEventIri: string, event: FaultEvent): Promis } } +export const updateEventRectangle = async (faultEventIri: string, rectangleIri: string, rect: Rectangle) :Promise => { + try { + const fragment = extractFragment(faultEventIri); + const updateRequest = Object.assign({}, rect, {"@context": RECTANGLE_CONTEXT}); + + await axiosClient.put( + `/faultEvents/${fragment}/rectangle`, + updateRequest, + { + headers: authHeaders() + } + ) + return new Promise((resolve) => resolve()); + } catch (e) { + console.log('Event Service - Failed to call /updateRectangle') + const defaultMessage = "Failed to update rectangle"; + return new Promise((resolve, reject) => reject(handleServerError(e, defaultMessage))); + } +} + export const eventFromHookFormValues = (values: any): FaultEvent => { let faultEvent if (values.existingEvent) {