diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 49e1f75892..8abec433bf 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -52,6 +52,7 @@ Its body operations are now bind on set/add instead of click. - https://github.com/eclipse-sirius/sirius-web/issues/2988[#2988] [diagram] Fix an issue with ReactFlow OnNodesChange event that were slowing down the application. - https://github.com/eclipse-sirius/sirius-web/issues/2978[#2978] [diagram] Fix issues with ReactFlow edge and drag events that were slowing down the application. - https://github.com/eclipse-sirius/sirius-web/issues/3000[#3000] [portal] Portals which already contain representation now open in "direct" mode. +- https://github.com/eclipse-sirius/sirius-web/issues/2480[#2480] [diagram] Merge the rectangle node and the image node into FreeFormNode. == v2024.1.0 @@ -205,7 +206,7 @@ The size of nodes that do not have been resized manually tries to fit the node d - https://github.com/eclipse-sirius/sirius-web/issues/2644[#2644] [diagram] Highlight nodes on hover in React Flow diagrams - https://github.com/eclipse-sirius/sirius-web/issues/2766[#2766] [tree] Tree representations (including the explorer) now support dragging any kind of element (not just semantic elements). It is the responsibility of the drop targets (e.g. a diagram) to validate the dropped element(s) type and ignore the one it does not support. -- https://github.com/eclipse-sirius/sirius-web/issues/2772[#2772] Improve performance of the Palette +- https://github.com/eclipse-sirius/sirius-web/issues/2772[#2772] [diagram] Improve the Palette performance. - https://github.com/eclipse-sirius/sirius-web/issues/2679[#2679] [diagram] When editing a label, place input element closer to the label's location (centered). - https://github.com/eclipse-sirius/sirius-web/issues/2804[#2804] [sirius-web] Add the ability to declare custom nodes while using the `sirius-web-application` frontend package. - https://github.com/eclipse-sirius/sirius-web/issues/2718[#2718] Replacing useNodes and useEdges by useReactFlow to improve performances. diff --git a/integration-tests/cypress/e2e/project/diagrams/diagram.reactflow.cy.js b/integration-tests/cypress/e2e/project/diagrams/diagram.reactflow.cy.js index 0364586039..67e8c093bb 100644 --- a/integration-tests/cypress/e2e/project/diagrams/diagram.reactflow.cy.js +++ b/integration-tests/cypress/e2e/project/diagrams/diagram.reactflow.cy.js @@ -38,8 +38,7 @@ describe('/projects/:projectId/edit - Diagram', () => { cy.getByTestId('rf__wrapper').should('exist'); cy.get('.react-flow__edgelabel-renderer').children().should('have.length', 7); cy.get('.react-flow__nodes').children().should('have.length', 18); - cy.get('.react-flow__node-rectangularNode').should('have.length', 2); - cy.get('.react-flow__node-imageNode').should('have.length', 10); + cy.get('.react-flow__node-freeFormNode').should('have.length', 12); cy.get('.react-flow__node-listNode').should('have.length', 2); cy.get('.react-flow__node-iconLabelNode').should('have.length', 4); }); diff --git a/integration-tests/cypress/e2e/project/diagrams/node-aspect-ratio.cy.js b/integration-tests/cypress/e2e/project/diagrams/node-aspect-ratio.cy.js index 63bb4f124e..df29618ba3 100644 --- a/integration-tests/cypress/e2e/project/diagrams/node-aspect-ratio.cy.js +++ b/integration-tests/cypress/e2e/project/diagrams/node-aspect-ratio.cy.js @@ -72,12 +72,12 @@ describe('/projects/:projectId/edit - Node aspect ratio', () => { if (match) { scale = match[1]; } - cy.getByTestId('Rectangle - ') + cy.getByTestId('FreeForm - ') .invoke('css', 'width') .then((nodeWidth) => { expect(parseInt(nodeWidth) / scale).to.approximately(200, 2); }); - cy.getByTestId('Rectangle - ') + cy.getByTestId('FreeForm - ') .invoke('css', 'height') .then((nodeHeight) => expect(parseInt(nodeHeight) / scale).to.approximately(200, 2)); }); @@ -85,7 +85,7 @@ describe('/projects/:projectId/edit - Node aspect ratio', () => { cy.getByTestId('Entity1').click(); cy.getByTestId('Name').type('Larger label but should keep aspect ratio{enter}'); cy.getByTestId('Larger label but should keep aspect ratio').click(); - cy.getByTestId('Rectangle - Larger label but should keep aspect ratio').then(($node) => { + cy.getByTestId('FreeForm - Larger label but should keep aspect ratio').then(($node) => { const nodeWidth = $node.width(); const nodeHeight = $node.height(); expect(Math.trunc(nodeWidth)).to.eq(Math.trunc(nodeHeight)); @@ -194,7 +194,7 @@ describe('/projects/:projectId/edit - Node aspect ratio', () => { cy.getByTestId('childCreationDescription').get('[data-value="Relation Sub Node"]').should('exist').click(); cy.getByTestId('create-object').click(); - cy.getByTestId('Rectangle - ').then(($node) => { + cy.getByTestId('FreeForm - ').then(($node) => { const nodeWidth = $node.width(); const nodeHeight = $node.height(); expect(Math.trunc(nodeWidth)).to.eq(Math.trunc(nodeHeight)); @@ -301,7 +301,7 @@ describe('/projects/:projectId/edit - Node aspect ratio', () => { cy.getByTestId('childCreationDescription').get('[data-value="Relation Sub Node"]').should('exist').click(); cy.getByTestId('create-object').click(); - cy.getByTestId('Rectangle - ').then(($node) => { + cy.getByTestId('FreeForm - ').then(($node) => { const nodeWidth = $node.width(); const nodeHeight = $node.height(); expect(Math.trunc(nodeWidth)).to.eq(Math.trunc(nodeHeight)); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts index 0924c10e0d..7e41bc1741 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts @@ -17,12 +17,12 @@ import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLImageNodeStyle, GQLNode, GQLNodeStyle, GQLViewModifier } from '../graphql/subscription/nodeFragment.types'; import { BorderNodePosition } from '../renderer/DiagramRenderer.types'; import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; -import { ImageNodeData } from '../renderer/node/ImageNode.types'; +import { FreeFormNodeData } from '../renderer/node/FreeFormNode.types'; import { GQLDiagramDescription } from '../representation/DiagramRepresentation.types'; import { IConvertEngine, INodeConverter } from './ConvertEngine.types'; -import { AlignmentMap } from './convertDiagram.types'; import { convertHandles } from './convertHandles'; import { convertLabelStyle, convertOutsideLabels } from './convertLabel'; +import { convertLineStyle } from './convertDiagram'; const defaultPosition: XYPosition = { x: 0, y: 0 }; @@ -33,7 +33,7 @@ const toImageNode = ( nodeDescription: GQLNodeDescription | undefined, isBorderNode: boolean, gqlEdges: GQLEdge[] -): Node => { +): Node => { const { targetObjectId, targetObjectLabel, @@ -55,7 +55,7 @@ const toImageNode = ( const isNew = gqlNodeLayoutData === undefined; const resizedByUser = gqlNodeLayoutData?.resizedByUser ?? false; - const data: ImageNodeData = { + const data: FreeFormNodeData = { targetObjectId, targetObjectLabel, targetObjectKind, @@ -63,7 +63,12 @@ const toImageNode = ( insideLabel: null, outsideLabels: convertOutsideLabels(outsideLabels), imageURL: style.imageURL, - style: {}, + style: { + borderColor: style.borderColor, + borderRadius: style.borderRadius, + borderWidth: style.borderSize, + borderStyle: convertLineStyle(style.borderStyle), + }, faded: state === GQLViewModifier.Faded, pinned, nodeDescription, @@ -79,39 +84,29 @@ const toImageNode = ( }; if (insideLabel) { - const labelStyle = insideLabel.style; - data.insideLabel = { - id: insideLabel.id, - text: insideLabel.text, - iconURL: labelStyle.iconURL, - style: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - padding: '8px 16px', - textAlign: 'center', - ...convertLabelStyle(labelStyle), + const { + id, + text, + style: labelStyle, + style: { iconURL }, + } = insideLabel; + data.outsideLabels = { + BOTTOM_MIDDLE: { + id, + text, + iconURL, + style: { + ...convertLabelStyle(labelStyle), + justifyContent: 'center', + padding: '8px 16px', + }, }, - isHeader: insideLabel.isHeader, - displayHeaderSeparator: insideLabel.displayHeaderSeparator, }; - - const alignement = AlignmentMap[insideLabel.insideLabelLocation]; - if (alignement.isPrimaryVerticalAlignment) { - if (alignement.primaryAlignment === 'TOP') { - data.style = { ...data.style, display: 'flex', flexDirection: 'column', justifyContent: 'flex-start' }; - } - if (alignement.secondaryAlignment === 'CENTER') { - data.style = { ...data.style, alignItems: 'stretch' }; - data.insideLabel.style = { ...data.insideLabel.style, justifyContent: 'center' }; - } - } } - const node: Node = { + const node: Node = { id, - type: 'imageNode', + type: 'freeFormNode', data, position: defaultPosition, hidden: state === GQLViewModifier.Hidden, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts index 474b210d56..99e5555e1d 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts @@ -22,7 +22,7 @@ import { } from '../graphql/subscription/nodeFragment.types'; import { BorderNodePosition } from '../renderer/DiagramRenderer.types'; import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; -import { RectangularNodeData } from '../renderer/node/RectangularNode.types'; +import { FreeFormNodeData } from '../renderer/node/FreeFormNode.types'; import { GQLDiagramDescription } from '../representation/DiagramRepresentation.types'; import { IConvertEngine, INodeConverter } from './ConvertEngine.types'; import { convertLineStyle } from './convertDiagram'; @@ -39,7 +39,7 @@ const toRectangularNode = ( nodeDescription: GQLNodeDescription | undefined, isBorderNode: boolean, gqlEdges: GQLEdge[] -): Node => { +): Node => { const { targetObjectId, targetObjectLabel, @@ -61,7 +61,7 @@ const toRectangularNode = ( const isNew = gqlNodeLayoutData === undefined; const resizedByUser = gqlNodeLayoutData?.resizedByUser ?? false; - const data: RectangularNodeData = { + const data: FreeFormNodeData = { targetObjectId, targetObjectLabel, targetObjectKind, @@ -76,6 +76,7 @@ const toRectangularNode = ( }, insideLabel: null, outsideLabels: convertOutsideLabels(outsideLabels), + imageURL: null, faded: state === GQLViewModifier.Faded, pinned, nodeDescription, @@ -84,6 +85,7 @@ const toRectangularNode = ( isBorderNode: isBorderNode, borderNodePosition: isBorderNode ? BorderNodePosition.EAST : null, labelEditable, + positionDependentRotation: false, connectionHandles, isNew, resizedByUser, @@ -123,9 +125,9 @@ const toRectangularNode = ( } } - const node: Node = { + const node: Node = { id, - type: 'rectangularNode', + type: 'freeFormNode', data, position: defaultPosition, hidden: state === GQLViewModifier.Hidden, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/graphql/subscription/nodeFragment.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/graphql/subscription/nodeFragment.types.ts index 16d55523ed..3b4f981a92 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/graphql/subscription/nodeFragment.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/graphql/subscription/nodeFragment.types.ts @@ -78,6 +78,10 @@ export interface GQLRectangularNodeStyle extends GQLNodeStyle { export interface GQLImageNodeStyle extends GQLNodeStyle { imageURL: string; + borderColor: string; + borderStyle: string; + borderSize: string; + borderRadius: number; positionDependentRotation: boolean; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/Label.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/Label.tsx index f6c89b9a87..2364bc0b5d 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/Label.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/Label.tsx @@ -32,7 +32,7 @@ const labelStyle = ( flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', - whiteSpace: 'pre-line', + whiteSpace: 'nowrap', ...style, color: style.color ? getCSSColor(String(style.color), theme) : undefined, }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/RectangleNodeLayoutHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/FreeFormNodeLayoutHandler.ts similarity index 95% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/RectangleNodeLayoutHandler.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/FreeFormNodeLayoutHandler.ts index 36605e0353..7a8f3d78a3 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/RectangleNodeLayoutHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/FreeFormNodeLayoutHandler.ts @@ -13,8 +13,8 @@ import { Node } from 'reactflow'; import { NodeData } from '../DiagramRenderer.types'; +import { FreeFormNodeData } from '../node/FreeFormNode.types'; import { DiagramNodeType } from '../node/NodeTypes.types'; -import { RectangularNodeData } from '../node/RectangularNode.types'; import { ILayoutEngine, INodeLayoutHandler } from './LayoutEngine.types'; import { computePreviousPosition, computePreviousSize } from './bounds'; import { RawDiagram } from './layout.types'; @@ -35,22 +35,22 @@ import { } from './layoutNode'; import { rectangularNodePadding } from './layoutParams'; -export class RectangleNodeLayoutHandler implements INodeLayoutHandler { +export class FreeFormNodeLayoutHandler implements INodeLayoutHandler { public canHandle(node: Node) { - return node.type === 'rectangularNode'; + return node.type === 'freeFormNode'; } public handle( layoutEngine: ILayoutEngine, previousDiagram: RawDiagram | null, - node: Node, + node: Node, visibleNodes: Node[], directChildren: Node[], newlyAddedNode: Node | undefined, forceWidth?: number ) { const nodeIndex = findNodeIndex(visibleNodes, node.id); - const nodeElement = document.getElementById(`${node.id}-rectangularNode-${nodeIndex}`)?.children[0]; + const nodeElement = document.getElementById(`${node.id}-freeFormNode-${nodeIndex}`)?.children[0]; const borderWidth = nodeElement ? parseFloat(window.getComputedStyle(nodeElement).borderWidth) : 0; if (directChildren.length > 0) { @@ -72,7 +72,7 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler, + node: Node, visibleNodes: Node[], directChildren: Node[], newlyAddedNode: Node | undefined, @@ -201,7 +201,7 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler, + node: Node, visibleNodes: Node[], borderWidth: number, forceWidth?: number diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/ImageNodeLayoutHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/ImageNodeLayoutHandler.ts deleted file mode 100644 index e4571942aa..0000000000 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/ImageNodeLayoutHandler.ts +++ /dev/null @@ -1,147 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023, 2024 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 { Node } from 'reactflow'; -import { NodeData } from '../DiagramRenderer.types'; -import { ImageNodeData } from '../node/ImageNode.types'; -import { DiagramNodeType } from '../node/NodeTypes.types'; -import { ILayoutEngine, INodeLayoutHandler } from './LayoutEngine.types'; -import { computePreviousSize } from './bounds'; -import { RawDiagram } from './layout.types'; -import { getBorderNodeExtent } from './layoutBorderNodes'; -import { - applyRatioOnNewNodeSizeValue, - computeNodesBox, - getEastBorderNodeFootprintHeight, - getNodeOrMinHeight, - getNodeOrMinWidth, - getNorthBorderNodeFootprintWidth, - getSouthBorderNodeFootprintWidth, - getWestBorderNodeFootprintHeight, - setBorderNodesPosition, -} from './layoutNode'; -import { borderLeftAndRight, borderTopAndBottom, rectangularNodePadding } from './layoutParams'; - -export class ImageNodeLayoutHandler implements INodeLayoutHandler { - public canHandle(node: Node) { - return node.type === 'imageNode'; - } - - public handle( - layoutEngine: ILayoutEngine, - previousDiagram: RawDiagram | null, - node: Node, - visibleNodes: Node[], - directChildren: Node[], - newlyAddedNode: Node | undefined, - forceWidth?: number - ) { - if (directChildren.length > 0) { - this.handleParentNode(layoutEngine, previousDiagram, node, visibleNodes, directChildren, newlyAddedNode); - } else { - this.handleLeafNode(previousDiagram, node, forceWidth); - } - } - - private handleParentNode( - layoutEngine: ILayoutEngine, - previousDiagram: RawDiagram | null, - node: Node, - visibleNodes: Node[], - directChildren: Node[], - newlyAddedNode: Node | undefined - ) { - layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, newlyAddedNode); - - const previousNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === node.id); - const previousDimensions = computePreviousSize(previousNode, node); - if (previousDimensions) { - node.width = getNodeOrMinWidth(previousDimensions.width, node); - node.height = getNodeOrMinHeight(previousDimensions.height, node); - } else { - node.width = getNodeOrMinWidth(undefined, node); - node.height = getNodeOrMinHeight(undefined, node); - } - - const borderNodes = directChildren.filter((node) => node.data.isBorderNode); - const directNodesChildren = directChildren.filter((child) => !child.data.isBorderNode); - - 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); - const nodeWidth = Math.max( - directChildrenAwareNodeWidth, - node.width, - northBorderNodeFootprintWidth, - southBorderNodeFootprintWidth - ); - - // WARN: the label is not used for the height because children are already position under the label - const directChildrenAwareNodeHeight = childrenContentBox.y + childrenContentBox.height + rectangularNodePadding; - const eastBorderNodeFootprintHeight = getEastBorderNodeFootprintHeight(visibleNodes, borderNodes, previousDiagram); - const westBorderNodeFootprintHeight = getWestBorderNodeFootprintHeight(visibleNodes, borderNodes, previousDiagram); - const nodeHeight = Math.max( - directChildrenAwareNodeHeight, - node.height, - eastBorderNodeFootprintHeight, - westBorderNodeFootprintHeight - ); - - node.width = getNodeOrMinWidth(nodeWidth + borderLeftAndRight, node); - node.height = getNodeOrMinHeight(nodeHeight + borderTopAndBottom, node); - - if (node.data.nodeDescription?.keepAspectRatio) { - applyRatioOnNewNodeSizeValue(node); - } - - // Update border nodes positions - borderNodes.forEach((borderNode) => { - borderNode.extent = getBorderNodeExtent(node, borderNode); - }); - setBorderNodesPosition(borderNodes, node, previousDiagram); - } - - private handleLeafNode( - previousDiagram: RawDiagram | null, - node: Node, - forceWidth?: number - ) { - const minNodeWith = forceWidth ?? getNodeOrMinWidth(undefined, node); - const minNodeHeight = getNodeOrMinHeight(undefined, node); - - const previousNode = (previousDiagram?.nodes ?? []).find((previous) => previous.id === node.id); - const previousDimensions = computePreviousSize(previousNode, node); - - if (node.data.resizedByUser) { - if (minNodeWith > previousDimensions.width) { - node.width = minNodeWith; - } else { - node.width = previousDimensions.width; - } - if (minNodeHeight > previousDimensions.height) { - node.height = minNodeHeight; - } else { - node.height = previousDimensions.height; - } - } else { - node.width = minNodeWith; - node.height = minNodeHeight; - } - - if (node.data.nodeDescription?.keepAspectRatio) { - applyRatioOnNewNodeSizeValue(node); - } - } -} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/LayoutEngine.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/LayoutEngine.ts index d5c1b07950..4bf5f3e136 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/LayoutEngine.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/LayoutEngine.ts @@ -14,18 +14,16 @@ import { Node } from 'reactflow'; import { NodeData } from '../DiagramRenderer.types'; import { DiagramNodeType } from '../node/NodeTypes.types'; +import { FreeFormNodeLayoutHandler } from './FreeFormNodeLayoutHandler'; import { IconLabelNodeLayoutHandler } from './IconLabelNodeLayoutHandler'; -import { ImageNodeLayoutHandler } from './ImageNodeLayoutHandler'; import { ILayoutEngine, INodeLayoutHandler } from './LayoutEngine.types'; import { ListNodeLayoutHandler } from './ListNodeLayoutHandler'; -import { RectangleNodeLayoutHandler } from './RectangleNodeLayoutHandler'; import { RawDiagram } from './layout.types'; export class LayoutEngine implements ILayoutEngine { nodeLayoutHandlers: INodeLayoutHandler[] = [ - new RectangleNodeLayoutHandler(), + new FreeFormNodeLayoutHandler(), new ListNodeLayoutHandler(), - new ImageNodeLayoutHandler(), new IconLabelNodeLayoutHandler(), ]; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layout.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layout.tsx index c565f5a3cd..616595f56f 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layout.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layout.tsx @@ -22,11 +22,11 @@ import { GQLReferencePosition } from '../../graphql/subscription/diagramEventSub import { NodeData } from '../DiagramRenderer.types'; import { Label } from '../Label'; import { DiagramDirectEditContextProvider } from '../direct-edit/DiagramDirectEditContext'; +import { FreeFormNode } from '../node/FreeFormNode'; +import { FreeFormNodeData } from '../node/FreeFormNode.types'; import { ListNode } from '../node/ListNode'; import { ListNodeData } from '../node/ListNode.types'; import { DiagramNodeType } from '../node/NodeTypes.types'; -import { RectangularNode } from '../node/RectangularNode'; -import { RectangularNodeData } from '../node/RectangularNode.types'; import { LayoutEngine } from './LayoutEngine'; import { ILayoutEngine, INodeLayoutHandler } from './LayoutEngine.types'; import { computePreviousPosition } from './bounds'; @@ -54,7 +54,7 @@ const emptyRectangularNodeProps = { }; const isListNode = (node: Node): node is Node => node.type === 'listNode'; -const isRectangularNode = (node: Node): node is Node => node.type === 'rectangularNode'; +const isRectangularNode = (node: Node): node is Node => node.type === 'rectangularNode'; export const prepareLayoutArea = ( diagram: RawDiagram, @@ -119,7 +119,7 @@ export const prepareLayoutArea = ( if (hiddenContainer && node) { const children: JSX.Element[] = []; if (isRectangularNode(node)) { - const element = createElement(RectangularNode, { + const element = createElement(FreeFormNode, { ...emptyRectangularNodeProps, id: node.id, data: node.data, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/node/RectangularNode.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/node/FreeFormNode.tsx similarity index 63% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/node/RectangularNode.tsx rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/node/FreeFormNode.tsx index 4fe87b33c4..400db72777 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/node/RectangularNode.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/node/FreeFormNode.tsx @@ -11,10 +11,11 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { getCSSColor } from '@eclipse-sirius/sirius-components-core'; +import { ServerContext, ServerContextValue, getCSSColor } from '@eclipse-sirius/sirius-components-core'; import { Theme, useTheme } from '@material-ui/core/styles'; import React, { memo, useContext } from 'react'; import { NodeProps, NodeResizer } from 'reactflow'; +import { BorderNodePosition } from '../DiagramRenderer.types'; import { Label } from '../Label'; import { useConnector } from '../connector/useConnector'; import { useDrop } from '../drop/useDrop'; @@ -24,28 +25,52 @@ import { ConnectionHandles } from '../handles/ConnectionHandles'; import { ConnectionTargetHandle } from '../handles/ConnectionTargetHandle'; import { useRefreshConnectionHandles } from '../handles/useRefreshConnectionHandles'; import { DiagramElementPalette } from '../palette/DiagramElementPalette'; +import { FreeFormNodeData } from './FreeFormNode.types'; import { NodeContext } from './NodeContext'; import { NodeContextValue } from './NodeContext.types'; -import { RectangularNodeData } from './RectangularNode.types'; -const rectangularNodeStyle = ( +const freeFormNodeStyle = ( theme: Theme, style: React.CSSProperties, selected: boolean, hovered: boolean, - faded: boolean + faded: boolean, + rotation: string | undefined, + imageURL: string | undefined ): React.CSSProperties => { - const rectangularNodeStyle: React.CSSProperties = { + const freeFormNodeStyle: React.CSSProperties = { width: '100%', height: '100%', opacity: faded ? '0.4' : '', + transform: rotation, ...style, backgroundColor: getCSSColor(String(style.backgroundColor), theme), }; if (selected || hovered) { - rectangularNodeStyle.outline = `${theme.palette.selected} solid 1px`; + freeFormNodeStyle.outline = `${theme.palette.selected} solid 1px`; } - return rectangularNodeStyle; + if (imageURL) { + freeFormNodeStyle.backgroundImage = `url(${imageURL})`; + freeFormNodeStyle.backgroundRepeat = 'no-repeat'; + freeFormNodeStyle.backgroundSize = '100% 100%'; + } + return freeFormNodeStyle; +}; + +const computeBorderRotation = (data: FreeFormNodeData): string | undefined => { + if (data?.isBorderNode && data.positionDependentRotation) { + switch (data.borderNodePosition) { + case BorderNodePosition.NORTH: + return 'rotate(90deg)'; + case BorderNodePosition.EAST: + return 'rotate(180deg)'; + case BorderNodePosition.SOUTH: + return 'rotate(270deg)'; + default: + return undefined; + } + } + return undefined; }; const resizeLineStyle = (theme: Theme): React.CSSProperties => { @@ -60,12 +85,18 @@ const resizeHandleStyle = (theme: Theme): React.CSSProperties => { }; }; -export const RectangularNode = memo(({ data, id, selected }: NodeProps) => { +export const FreeFormNode = memo(({ data, id, selected }: NodeProps) => { + const { httpOrigin } = useContext(ServerContext); const theme = useTheme(); const { onDrop, onDragOver } = useDrop(); const { newConnectionStyleProvider } = useConnector(); const { style: dropFeedbackStyle } = useDropNodeStyle(id); const { hoveredNode } = useContext(NodeContext); + const rotation = computeBorderRotation(data); + let imageURL: string | undefined = undefined; + if (data.imageURL) { + imageURL = httpOrigin + data.imageURL; + } const handleOnDrop = (event: React.DragEvent) => { onDrop(event, id); @@ -86,16 +117,14 @@ export const RectangularNode = memo(({ data, id, selected }: NodeProps - {data.insideLabel ? ( -