From ebbda9fd2513d38823da92445a2b8f6454ad163a Mon Sep 17 00:00:00 2001 From: Guillaume Coutable Date: Wed, 13 Dec 2023 14:27:41 +0100 Subject: [PATCH] [2480] Merge Rectangle and Image nodes together Bug: https://github.com/eclipse-sirius/sirius-web/issues/2480 --- CHANGELOG.adoc | 1 + .../converter/ImageNodeConverterHandler.ts | 10 +- .../RectangleNodeConverterHandler.ts | 12 +- .../layout/FreeFormNodeLayoutHandler.ts | 15 +- .../src/renderer/layout/layout.tsx | 8 +- .../{RectangularNode.tsx => FreeFormNode.tsx} | 51 +++++-- ...ageNode.types.ts => FreeFormNode.types.ts} | 4 +- .../src/renderer/node/ImageNode.tsx | 135 ------------------ .../src/renderer/node/NodeTypes.ts | 6 +- .../renderer/node/RectangularNode.types.ts | 16 --- 10 files changed, 69 insertions(+), 189 deletions(-) rename packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/{RectangularNode.tsx => FreeFormNode.tsx} (72%) rename packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/{ImageNode.types.ts => FreeFormNode.types.ts} (88%) delete mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ImageNode.tsx delete mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.types.ts diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index cb5db1eede..3e109ee506 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -80,6 +80,7 @@ info: { - https://github.com/eclipse-sirius/sirius-web/issues/2720[#2720] [releng] Improve our build process to be faster in development mode - https://github.com/eclipse-sirius/sirius-web/issues/2780[#2780] [releng] Upgrade github/aws actions versions in workflows - 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/2480[#2480] [diagram] Merge the rectangle node and the image node into FreeFormNode. == v2023.12.0 diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts index 85a1d44255..7cce185923 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts @@ -17,7 +17,7 @@ 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 { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; import { convertHandles } from './convertHandles'; import { convertLabelStyle, convertOutsideLabels } from './convertLabel'; @@ -31,7 +31,7 @@ const toImageNode = ( nodeDescription: GQLNodeDescription | undefined, isBorderNode: boolean, gqlEdges: GQLEdge[] -): Node => { +): Node => { const { targetObjectId, targetObjectLabel, @@ -48,7 +48,7 @@ const toImageNode = ( const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges); const isNew = gqlDiagram.layoutData.nodeLayoutData.find((nodeLayoutData) => nodeLayoutData.id === id) === undefined; - const data: ImageNodeData = { + const data: FreeFormNodeData = { targetObjectId, targetObjectLabel, targetObjectKind, @@ -88,9 +88,9 @@ const toImageNode = ( }; } - 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-reactflow/src/converter/RectangleNodeConverterHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.ts index d3416bfeb7..db40d46529 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.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 { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; import { convertLineStyle } from './convertDiagram'; import { AlignmentMap } from './convertDiagram.types'; @@ -38,7 +38,7 @@ const toRectangularNode = ( nodeDescription: GQLNodeDescription | undefined, isBorderNode: boolean, gqlEdges: GQLEdge[] -): Node => { +): Node => { const { targetObjectId, targetObjectLabel, @@ -55,7 +55,7 @@ const toRectangularNode = ( const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges); const isNew = gqlDiagram.layoutData.nodeLayoutData.find((nodeLayoutData) => nodeLayoutData.id === id) === undefined; - const data: RectangularNodeData = { + const data: FreeFormNodeData = { targetObjectId, targetObjectLabel, targetObjectKind, @@ -70,6 +70,7 @@ const toRectangularNode = ( }, insideLabel: null, outsideLabels: convertOutsideLabels(outsideLabels), + imageURL: null, faded: state === GQLViewModifier.Faded, nodeDescription, defaultWidth: gqlNode.defaultWidth, @@ -77,6 +78,7 @@ const toRectangularNode = ( isBorderNode: isBorderNode, borderNodePosition: isBorderNode ? BorderNodePosition.EAST : null, labelEditable, + positionDependentRotation: false, connectionHandles, isNew, }; @@ -160,9 +162,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-reactflow/src/renderer/layout/FreeFormNodeLayoutHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/FreeFormNodeLayoutHandler.ts index cf44c026ae..1f7cbe5b8f 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/FreeFormNodeLayoutHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/FreeFormNodeLayoutHandler.ts @@ -13,9 +13,8 @@ import { Node } from 'reactflow'; import { NodeData } from '../DiagramRenderer.types'; -import { ImageNodeData } from '../node/ImageNode.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'; @@ -36,22 +35,22 @@ import { } from './layoutNode'; import { rectangularNodePadding } from './layoutParams'; -export class FreeFormNodeLayoutHandler implements INodeLayoutHandler { +export class FreeFormNodeLayoutHandler implements INodeLayoutHandler { public canHandle(node: Node) { - return node.type === 'rectangularNode' || node.type === 'imageNode'; + 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) { @@ -73,7 +72,7 @@ export class FreeFormNodeLayoutHandler implements INodeLayoutHandler, + node: Node, visibleNodes: Node[], directChildren: Node[], newlyAddedNode: Node | undefined, @@ -202,7 +201,7 @@ export class FreeFormNodeLayoutHandler implements INodeLayoutHandler, + node: Node, visibleNodes: Node[], borderWidth: number, forceWidth?: number diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layout.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layout.tsx index f806f14e20..ed0c848f4e 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layout.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/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'; @@ -55,7 +55,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, @@ -177,7 +177,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-reactflow/src/renderer/node/RectangularNode.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/FreeFormNode.tsx similarity index 72% rename from packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.tsx rename to packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/FreeFormNode.tsx index a0e27f403f..56611c501e 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/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 outsideBottomLabelAreaStyle = (): React.CSSProperties => { @@ -59,12 +84,18 @@ const outsideBottomLabelAreaStyle = (): 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); @@ -125,13 +156,13 @@ export const RectangularNode = memo(({ data, id, selected }: NodeProps + data-testid={`FreeForm - ${data?.targetObjectLabel}`}> {data.insideLabel ? (