diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 1dab7ebe67..e1d5c5a143 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -104,6 +104,7 @@ In read-only mode, the palette can not be displayed, some of the panel action ar - https://github.com/eclipse-sirius/sirius-web/issues/3020[#3020] [sirius-web] Simplify the contribution of views in the workbench - https://github.com/eclipse-sirius/sirius-web/issues/3066[#3066] [core] The core object-related services now can also find/resolve representations. - [portal] Use MUI tooltips for Portal toolbar elements +- https://github.com/eclipse-sirius/sirius-web/issues/2480[#2480] [diagram] Merge the rectangle node and the image node into FreeFormNode. == v2024.1.0 @@ -258,7 +259,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/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts index 27c819c9a2..5403c18321 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts @@ -33,7 +33,7 @@ const toIconLabelNode = ( gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, - nodeDescription: GQLNodeDescription | undefined, + nodeDescription: GQLNodeDescription, isBorderNode: boolean ): Node => { const { @@ -144,6 +144,8 @@ export class IconLabelNodeConverter implements INodeConverter { nodeDescriptions: GQLNodeDescription[] ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); - nodes.push(toIconLabelNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode)); + if (nodeDescription) { + nodes.push(toIconLabelNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode)); + } } } 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..5a97e60869 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts @@ -17,10 +17,10 @@ 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 { convertLineStyle } from './convertDiagram'; import { convertHandles } from './convertHandles'; import { convertLabelStyle, convertOutsideLabels } from './convertLabel'; @@ -30,10 +30,10 @@ const toImageNode = ( gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, - nodeDescription: GQLNodeDescription | undefined, + nodeDescription: GQLNodeDescription, 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, @@ -157,7 +152,9 @@ export class ImageNodeConverter implements INodeConverter { nodeDescriptions: GQLNodeDescription[] ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); - nodes.push(toImageNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + if (nodeDescription) { + nodes.push(toImageNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + } const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( (nodeDescriptionId) => diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts index d1f79c3a92..8f7e1d8072 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts @@ -40,7 +40,7 @@ const toListNode = ( gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, - nodeDescription: GQLNodeDescription | undefined, + nodeDescription: GQLNodeDescription, isBorderNode: boolean, gqlEdges: GQLEdge[] ): Node => { @@ -208,7 +208,9 @@ export class ListNodeConverter implements INodeConverter { nodeDescriptions: GQLNodeDescription[] ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); - nodes.push(toListNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + if (nodeDescription) { + nodes.push(toListNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + } const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( (nodeDescriptionId) => 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..21a4ba0ec0 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'; @@ -36,10 +36,10 @@ const toRectangularNode = ( gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, - nodeDescription: GQLNodeDescription | undefined, + nodeDescription: GQLNodeDescription, 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, @@ -171,7 +173,9 @@ export class RectangleNodeConverter implements INodeConverter { nodeDescriptions: GQLNodeDescription[] ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); - nodes.push(toRectangularNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + if (nodeDescription) { + nodes.push(toRectangularNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + } const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( (nodeDescriptionId) => 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 5242566886..8c205e1e93 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 @@ -80,6 +80,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/DiagramRenderer.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.types.ts index 24144203d8..1bbf054032 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.types.ts @@ -54,7 +54,7 @@ export interface NodeData { outsideLabels: OutsideLabels; faded: boolean; pinned: boolean; - nodeDescription: GQLNodeDescription | undefined; + nodeDescription: GQLNodeDescription; defaultWidth: number | null; defaultHeight: number | null; isBorderNode: 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 1d965348d8..4d2adfc273 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/Label.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/Label.tsx @@ -34,7 +34,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 8e345f7838..0774207aab 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 3c392aa7a5..0000000000 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/ImageNodeLayoutHandler.ts +++ /dev/null @@ -1,140 +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, - getDefaultOrMinHeight, - getDefaultOrMinWidth, - getEastBorderNodeFootprintHeight, - 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 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 nodeMinComputeWidth = - Math.max(directChildrenAwareNodeWidth, northBorderNodeFootprintWidth, southBorderNodeFootprintWidth) + - borderLeftAndRight; - - // 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 nodeMinComputeHeight = - Math.max(directChildrenAwareNodeHeight, eastBorderNodeFootprintHeight, westBorderNodeFootprintHeight) + - borderTopAndBottom; - - const nodeWith = getDefaultOrMinWidth(nodeMinComputeWidth, node); - const nodeHeight = getDefaultOrMinHeight(nodeMinComputeHeight, node); - const previousNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === node.id); - const previousDimensions = computePreviousSize(previousNode, node); - if (node.data.nodeDescription?.userResizable) { - if (nodeMinComputeWidth > previousDimensions.width) { - node.width = nodeMinComputeWidth; - } else { - node.width = previousDimensions.width; - } - if (nodeMinComputeHeight > previousDimensions.height) { - node.height = nodeMinComputeHeight; - } else { - node.height = previousDimensions.height; - } - } else { - node.width = nodeWith; - node.height = nodeHeight; - } - - 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 ?? getDefaultOrMinWidth(undefined, node); - const minNodeHeight = getDefaultOrMinHeight(undefined, node); - - const previousNode = (previousDiagram?.nodes ?? []).find((previous) => previous.id === node.id); - const previousDimensions = computePreviousSize(previousNode, node); - - if (node.data.resizedByUser) { - node.width = previousDimensions.width; - 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 ba53840404..66144a23a6 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,12 +11,13 @@ * 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 { DiagramContext } from '../../contexts/DiagramContext'; import { DiagramContextValue } from '../../contexts/DiagramContext.types'; +import { BorderNodePosition } from '../DiagramRenderer.types'; import { Label } from '../Label'; import { useConnector } from '../connector/useConnector'; import { useDrop } from '../drop/useDrop'; @@ -26,28 +27,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 => { @@ -62,22 +87,30 @@ const resizeHandleStyle = (theme: Theme): React.CSSProperties => { }; }; -export const RectangularNode = memo(({ data, id, selected }: NodeProps) => { +export const FreeFormNode = memo(({ data, id, selected }: NodeProps) => { const { readOnly } = useContext(DiagramContext); + 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); }; useRefreshConnectionHandles(id, data.connectionHandles); + return ( <> - {data.nodeDescription?.userResizable && !readOnly ? ( + {data.nodeDescription.userResizable && !readOnly ? ( - {data.insideLabel ? ( -