Skip to content

Commit

Permalink
[2504] Use tool position to place created node
Browse files Browse the repository at this point in the history
Bug: #2504
Signed-off-by: Michaël Charfadi <michael.charfadi@obeosoft.com>
  • Loading branch information
mcharfadi authored and gcoutable committed Oct 27, 2023
1 parent 48a6e82 commit 67a7194
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 53 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.adoc
Expand Up @@ -71,7 +71,8 @@ This coupling will be removed in the near future as a consequence, the dependenc
- https://github.com/eclipse-sirius/sirius-web/issues/2268[#2268] [diagram] Create child nodes where the user has clicked.
- https://github.com/eclipse-sirius/sirius-web/issues/2475[#2475] [diagram] Prevent nodes to move each diagram refresh event. Only the size is still updated.
- https://github.com/eclipse-sirius/sirius-web/issues/2485[#2485] [view] Change the default size of View Node Description from 1x1 to 150x70.
- https://github.com/eclipse-sirius/sirius-web/issues/2493[2493] [diagram] Disable double click to zoom in with ReactFlow.
- https://github.com/eclipse-sirius/sirius-web/issues/2493[#2493] [diagram] Disable double click to zoom in with ReactFlow.
- https://github.com/eclipse-sirius/sirius-web/issues/2504[#2504] [diagram] Create nodes at the cursor position when using create instance or drop tools.

== v2023.10.0

Expand Down
Expand Up @@ -69,8 +69,7 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe
fitviewLifecycle: 'neverRendered',
});

const { layout } = useLayout();

const { layout, setNextNodePosition } = useLayout();
const { onDiagramBackgroundClick, hideDiagramPalette } = useDiagramPalette();
const { onDiagramElementClick } = useDiagramElementPalette();

Expand All @@ -90,7 +89,6 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe
nodes: nodes as Node<NodeData, DiagramNodeType>[],
edges,
};

layout(previousDiagram, convertedDiagram, (laidOutDiagram) => {
setNodes(laidOutDiagram.nodes);
setEdges(laidOutDiagram.edges);
Expand Down Expand Up @@ -224,7 +222,7 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe
},
],
};

setNextNodePosition(event, '');
setSelection(selection);
onDiagramBackgroundClick(event);
};
Expand All @@ -240,6 +238,11 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe

const { backgroundColor, smallGridColor, largeGridColor } = dropFeedbackStyleProvider.getDiagramBackgroundStyle();

const handleNodeClick = (event, node: Node<NodeData>) => {
onDiagramElementClick(event);
setNextNodePosition(event, node.id);
};

return (
<ReactFlow
nodes={nodes}
Expand All @@ -255,7 +258,7 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe
onEdgeUpdate={reconnectEdge}
onPaneClick={handlePaneClick}
onEdgeClick={onDiagramElementClick}
onNodeClick={onDiagramElementClick}
onNodeClick={handleNodeClick}
onMove={() => hideDiagramPalette()}
onDrop={onDrop}
onDragOver={onDragOver}
Expand Down
Expand Up @@ -15,6 +15,7 @@ import { DRAG_SOURCES_TYPE, useMultiToast } from '@eclipse-sirius/sirius-compone
import { useContext, useEffect } from 'react';
import { DiagramContext } from '../../contexts/DiagramContext';
import { DiagramContextValue } from '../../contexts/DiagramContext.types';
import { useLayout } from '../layout/useLayout';
import {
GQLDropOnDiagramData,
GQLDropOnDiagramInput,
Expand Down Expand Up @@ -46,6 +47,7 @@ const isErrorPayload = (payload: GQLDropOnDiagramPayload): payload is GQLErrorPa
export const useDrop = (): UseDropValue => {
const { addErrorMessage, addMessages } = useMultiToast();
const { diagramId, editingContextId } = useContext<DiagramContextValue>(DiagramContext);
const { setNextNodePosition } = useLayout();
const [dropMutation, { data: droponDiagramElementData, error: droponDiagramError }] = useMutation<
GQLDropOnDiagramData,
GQLDropOnDiagramVariables
Expand Down Expand Up @@ -87,6 +89,7 @@ export const useDrop = (): UseDropValue => {
};

dropMutation({ variables: { input } });
setNextNodePosition(event, diagramElementId ? diagramElementId : '');
};

const onDragOver = (event: React.DragEvent) => {
Expand Down
Expand Up @@ -18,6 +18,7 @@ import { Node, NodeDragHandler, useReactFlow } from 'reactflow';
import { DiagramContext } from '../../contexts/DiagramContext';
import { DiagramContextValue } from '../../contexts/DiagramContext.types';
import { EdgeData, NodeData } from '../DiagramRenderer.types';
import { useLayout } from '../layout/useLayout';
import { DropNodeContext } from './DropNodeContext';
import { DropNodeContextValue } from './DropNodeContext.types';
import {
Expand Down Expand Up @@ -144,6 +145,8 @@ export const useDropNode = (): UseDropNodeValue => {
}
}, [data, loading, error, setDropNodeCompatibilityData]);

const { setNextNodePosition } = useLayout();

const onDropNode = useDropNodeMutation();
const { getNodes, getIntersectingNodes } = useReactFlow<NodeData, EdgeData>();

Expand Down Expand Up @@ -193,7 +196,7 @@ export const useDropNode = (): UseDropNodeValue => {
};

const onNodeDragStop: (onDragCancelled: (node: Node) => void) => NodeDragHandler = (onDragCancelled) => {
return (_event, _node) => {
return (event, _node) => {
if (draggedNode && draggedNode.id === dropData.draggedNodeId) {
const oldParentId: string | null = draggedNode.parentNode || null;
const newParentId: string | null = dropData.targetNodeId;
Expand All @@ -207,6 +210,9 @@ export const useDropNode = (): UseDropNodeValue => {
onDragCancelled(draggedNode);
}
}
if (newParentId) {
setNextNodePosition(event, newParentId);
}
}
setDraggedNode(null);
setDropData({
Expand Down
Expand Up @@ -20,7 +20,6 @@ export interface UseDropNodeValue {
dropData: NodeDropData;
dropFeedbackStyleProvider: StyleProvider;
}

export interface StyleProvider {
getNodeStyle(nodeId: string): React.CSSProperties;

Expand Down
Expand Up @@ -28,6 +28,7 @@ export class IconLabelNodeLayoutHandler implements INodeLayoutHandler<IconLabelN
node: Node<IconLabelNodeData>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
_directChildren: Node<NodeData, DiagramNodeType>[],
_newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
const nodeIndex = this.findNodeIndex(visibleNodes, node.id);
Expand Down
Expand Up @@ -46,10 +46,11 @@ export class ImageNodeLayoutHandler implements INodeLayoutHandler<ImageNodeData>
node: Node<ImageNodeData, 'imageNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
if (directChildren.length > 0) {
this.handleParentNode(layoutEngine, previousDiagram, node, visibleNodes, directChildren);
this.handleParentNode(layoutEngine, previousDiagram, node, visibleNodes, directChildren, newlyAddedNode);
} else {
node.width = forceWidth ?? defaultWidth;
node.height = defaultHeight;
Expand All @@ -61,9 +62,10 @@ export class ImageNodeLayoutHandler implements INodeLayoutHandler<ImageNodeData>
previousDiagram: Diagram | null,
node: Node<ImageNodeData, 'imageNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[]
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[]
) {
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren);
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, newlyAddedNode);

const previousNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === node.id);

Expand Down
@@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

import React, { useCallback, useState } from 'react';
import {
LayoutContextContextProviderProps,
LayoutContextContextProviderState,
LayoutContextContextValue,
} from './LayoutContext.types';
import { FutureNodePosition } from './useLayout.types';

const defaultValue: LayoutContextContextValue = {
futureNodePosition: null,
setFuturNodePosition: () => {},
resetFuturNodePosition: () => {},
};

export const LayoutContext = React.createContext<LayoutContextContextValue>(defaultValue);

export const LayoutContextContextProvider = ({ children }: LayoutContextContextProviderProps) => {
const [state, setState] = useState<LayoutContextContextProviderState>({
futureNodePosition: null,
});

const setFuturNodePosition = useCallback((futureNodePosition: FutureNodePosition) => {
setState((prevState) => ({ ...prevState, futureNodePosition }));
}, []);

const resetFuturNodePosition = useCallback(() => {
setState((prevState) => ({ ...prevState, futureNodePosition: null }));
}, []);

return (
<LayoutContext.Provider
value={{
futureNodePosition: state.futureNodePosition,
setFuturNodePosition,
resetFuturNodePosition,
}}>
{children}
</LayoutContext.Provider>
);
};
@@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import { FutureNodePosition } from './useLayout.types';
export interface LayoutContextContextValue {
futureNodePosition: FutureNodePosition | null;
setFuturNodePosition: (futureNodePosition: FutureNodePosition) => void;
resetFuturNodePosition: () => void;
}

export interface LayoutContextContextProviderProps {
children: React.ReactNode;
}

export interface LayoutContextContextProviderState {
futureNodePosition: FutureNodePosition | null;
}
Expand Up @@ -32,6 +32,7 @@ export class LayoutEngine implements ILayoutEngine {
previousDiagram: Diagram | null,
visibleNodes: Node<NodeData, DiagramNodeType>[],
nodesToLayout: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
nodesToLayout.forEach((node) => {
Expand All @@ -40,7 +41,7 @@ export class LayoutEngine implements ILayoutEngine {
);
if (nodeLayoutHandler) {
const directChildren = visibleNodes.filter((visibleNode) => visibleNode.parentNode === node.id);
nodeLayoutHandler.handle(this, previousDiagram, node, visibleNodes, directChildren, forceWidth);
nodeLayoutHandler.handle(this, previousDiagram, node, visibleNodes, directChildren, newlyAddedNode, forceWidth);

node.style = {
...node.style,
Expand Down
Expand Up @@ -20,6 +20,7 @@ export interface ILayoutEngine {
previousDiagram: Diagram | null,
visibleNodes: Node<NodeData, DiagramNodeType>[],
nodesToLayout: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
);
}
Expand All @@ -32,6 +33,7 @@ export interface INodeLayoutHandler<T extends NodeData> {
node: Node<T>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
);
}
Expand Up @@ -40,14 +40,24 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler<ListNodeData> {
node: Node<ListNodeData, 'listNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
const nodeIndex = findNodeIndex(visibleNodes, node.id);
const nodeElement = document.getElementById(`${node.id}-listNode-${nodeIndex}`)?.children[0];
const borderWidth = nodeElement ? parseFloat(window.getComputedStyle(nodeElement).borderWidth) : 0;

if (directChildren.length > 0) {
this.handleParentNode(layoutEngine, previousDiagram, node, visibleNodes, directChildren, borderWidth, forceWidth);
this.handleParentNode(
layoutEngine,
previousDiagram,
node,
visibleNodes,
directChildren,
newlyAddedNode,
borderWidth,
forceWidth
);
} else {
this.handleLeafNode(previousDiagram, node, visibleNodes, borderWidth, forceWidth);
}
Expand All @@ -74,10 +84,11 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler<ListNodeData> {
node: Node<ListNodeData, 'listNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
borderWidth: number,
forceWidth?: number
) {
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, forceWidth);
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, newlyAddedNode, forceWidth);

const nodeIndex = findNodeIndex(visibleNodes, node.id);
const labelElement = document.getElementById(`${node.id}-label-${nodeIndex}`);
Expand All @@ -97,7 +108,7 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler<ListNodeData> {
southBorderNodeFootprintWidth
);

layoutEngine.layoutNodes(previousDiagram, visibleNodes, directNodesChildren, widerWidth);
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directNodesChildren, newlyAddedNode, widerWidth);
}

directNodesChildren.forEach((child, index) => {
Expand Down
Expand Up @@ -42,14 +42,24 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler<Rectangula
node: Node<RectangularNodeData, 'rectangularNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
const nodeIndex = findNodeIndex(visibleNodes, node.id);
const nodeElement = document.getElementById(`${node.id}-rectangularNode-${nodeIndex}`)?.children[0];
const borderWidth = nodeElement ? parseFloat(window.getComputedStyle(nodeElement).borderWidth) : 0;

if (directChildren.length > 0) {
this.handleParentNode(layoutEngine, previousDiagram, node, visibleNodes, directChildren, borderWidth, forceWidth);
this.handleParentNode(
layoutEngine,
previousDiagram,
node,
visibleNodes,
directChildren,
newlyAddedNode,
borderWidth,
forceWidth
);
} else {
this.handleLeafNode(previousDiagram, node, visibleNodes, borderWidth, forceWidth);
}
Expand All @@ -61,10 +71,11 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler<Rectangula
node: Node<RectangularNodeData, 'rectangularNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
newlyAddedNode: Node<NodeData, DiagramNodeType>[],
borderWidth: number,
forceWidth?: number
) {
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren);
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, newlyAddedNode);

const nodeIndex = findNodeIndex(visibleNodes, node.id);
const labelElement = document.getElementById(`${node.id}-label-${nodeIndex}`);
Expand All @@ -76,7 +87,11 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler<Rectangula
directNodesChildren.forEach((child, index) => {
const previousNode = (previousDiagram?.nodes ?? []).find((previouseNode) => previouseNode.id === child.id);

if (previousNode) {
const createdNode = newlyAddedNode.find((n) => n.id === child.id);

if (!!createdNode) {
child.position = createdNode.position;
} else if (previousNode) {
child.position = previousNode.position;
} else {
child.position = getChildNodePosition(visibleNodes, child, labelElement, borderWidth);
Expand All @@ -90,7 +105,6 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler<Rectangula
// Update node to layout size
// WARN: We suppose label are always on top of children (that wrong)
const childrenContentBox = computeNodesBox(visibleNodes, directNodesChildren); // WARN: The current content box algorithm does not take the margin of direct children (it should)

const directChildrenAwareNodeWidth = childrenContentBox.x + childrenContentBox.width + rectangularNodePadding;
const northBorderNodeFootprintWidth = getNorthBorderNodeFootprintWidth(visibleNodes, borderNodes, previousDiagram);
const southBorderNodeFootprintWidth = getSouthBorderNodeFootprintWidth(visibleNodes, borderNodes, previousDiagram);
Expand All @@ -115,7 +129,6 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler<Rectangula
const nodeHeight =
Math.max(directChildrenAwareNodeHeight, eastBorderNodeFootprintHeight, westBorderNodeFootprintHeight) +
borderWidth * 2;

node.width = forceWidth ?? getNodeOrMinWidth(nodeWidth);
node.height = getNodeOrMinHeight(nodeHeight);

Expand Down

0 comments on commit 67a7194

Please sign in to comment.