Skip to content

Commit

Permalink
[2289] Use layout strategy to layout icon label nodes
Browse files Browse the repository at this point in the history
Bug: #2289
Signed-off-by: Guillaume Coutable <guillaume.coutable@obeo.fr>
  • Loading branch information
gcoutable committed Sep 20, 2023
1 parent 062c9fe commit 79cd540
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 86 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -126,6 +126,7 @@ To illustrate this new feature, we contribute a new tool on the _Papaya Diagram_
- https://github.com/eclipse-sirius/sirius-web/issues/2358[#2358] [form] Adds diagnostic messages to the reference widget.
- https://github.com/eclipse-sirius/sirius-web/issues/2260[#2260] [diagram] Keep the layout stable over time
- https://github.com/eclipse-sirius/sirius-web/issues/2288[#2288] [diagram] Make IconLabel a react flow node
- https://github.com/eclipse-sirius/sirius-web/issues/2289[#2289] [diagram] Use the layout strategy to layout icon label nodes

== v2023.8.0

Expand Down
Expand Up @@ -11,6 +11,29 @@
* Obeo - initial API and implementation
*******************************************************************************/
describe('/projects/:projectId/edit - Robot Diagram', () => {
const fadeByElementTestId = (elementTestId) => {
cy.getByTestId(elementTestId).should('have.css', 'opacity', '1');
cy.getByTestId(elementTestId).first().click({ force: true });
cy.wait(200); // wait for the palette to be loaded completely
cy.getByTestId('Fade-elements').should('exist').click({ force: true });
cy.getByTestId(elementTestId).should('have.css', 'opacity', '0.4');
};

const hideByElementTestId = (elementTestId) => {
cy.getByTestId(elementTestId).then((elementBefore) => {
console.log(elementTestId);
const countBefore = elementBefore.length ?? 0;
console.log(countBefore);
cy.getByTestId(elementTestId).first().click({ force: true });
cy.wait(200); // wait for the palette to be loaded completely
cy.getByTestId('Hide-elements').should('exist').click({ force: true });
cy.getByTestId(elementTestId).then((elementAfter) => {
console.log(elementAfter.length ?? 0);
cy.expect((elementAfter.length ?? 0) + 1).to.equal(countBefore);
});
});
};

beforeEach(() => {
cy.deleteAllProjects();
cy.createProject('Cypress Project').then((res) => {
Expand Down Expand Up @@ -42,4 +65,18 @@ describe('/projects/:projectId/edit - Robot Diagram', () => {
cy.getByTestId('Hide-elements').should('not.exist');
cy.getByTestId('Fade-elements').should('not.exist');
});

it('can fade any type of nodes', () => {
fadeByElementTestId('IconLabel - Temperature: 25');
fadeByElementTestId('Image - Motion_Engine');
fadeByElementTestId('Rectangle - Central_Unit');
fadeByElementTestId('List - Description');
});

it.only('can hide any type of nodes', () => {
hideByElementTestId('IconLabel - Temperature: 25');
hideByElementTestId('Image - Motion_Engine');
hideByElementTestId('List - Description');
hideByElementTestId('Rectangle - Central_Unit');
});
});
Expand Up @@ -44,10 +44,21 @@ type Node {
position: Position!
state: ViewModifier!
style: INodeStyle!
childrenLayoutStrategy: ILayoutStrategy
borderNodes: [Node!]!
childNodes: [Node!]!
}

type FreeFormLayoutStrategy {
kind: String!
}

type ListLayoutStrategy {
kind: String!
}

union ILayoutStrategy = FreeFormLayoutStrategy | ListLayoutStrategy

type Label {
id: ID!
text: String!
Expand Down
Expand Up @@ -279,9 +279,7 @@ const toImageNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Node<Imag

const convertNode = (gqlNode: GQLNode, parentNode: GQLNode | null, nodes: Node[]): void => {
if (gqlNode.style.__typename === 'RectangularNodeStyle') {
const isList =
(gqlNode.childNodes ?? []).filter((gqlChildNode) => gqlChildNode.style.__typename === 'IconLabelNodeStyle')
.length > 0;
const isList = gqlNode.childrenLayoutStrategy?.kind === 'List';
if (!isList) {
nodes.push(toRectangularNode(gqlNode, parentNode));

Expand Down
Expand Up @@ -44,6 +44,15 @@ fragment nodeFragment on Node {
backgroundColor
}
}
childrenLayoutStrategy {
__typename
... on ListLayoutStrategy {
kind
}
... on FreeFormLayoutStrategy {
kind
}
}
userResizable
}
`;
Expand Up @@ -22,12 +22,26 @@ export interface GQLNode {
state: GQLViewModifier;
label: GQLLabel;
style: GQLNodeStyle;
childrenLayoutStrategy?: ILayoutStrategy;
borderNodes: GQLNode[] | undefined;
childNodes: GQLNode[] | undefined;
position: GQLPosition;
size: GQLSize;
}

export interface ILayoutStrategy {
__typename: string;
kind: string;
}

export interface ListLayoutStrategy extends ILayoutStrategy {
kind: 'List';
}

export interface FreeFormLayoutStrategy extends ILayoutStrategy {
kind: 'FreeForm';
}

export enum GQLViewModifier {
Normal = 'Normal',
Faded = 'Faded',
Expand Down
Expand Up @@ -16,6 +16,8 @@ import { IconLabelNodeData } from '../node/IconsLabelNode.types';
import { DiagramNodeType } from '../node/NodeTypes.types';
import { ILayoutEngine, INodeLayoutHandler } from './LayoutEngine.types';

const rectangularNodePadding = 8;

export class IconLabelNodeLayoutHandler implements INodeLayoutHandler<IconLabelNodeData> {
canHandle(node: Node<NodeData, DiagramNodeType>) {
return node.type === 'iconLabelNode';
Expand All @@ -25,12 +27,15 @@ export class IconLabelNodeLayoutHandler implements INodeLayoutHandler<IconLabelN
_previousDiagram: Diagram | null,
node: Node<IconLabelNodeData>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
_directChildren: Node<NodeData, DiagramNodeType>[]
_directChildren: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
const nodeIndex = this.findNodeIndex(visibleNodes, node.id);
const labelElement = document.getElementById(`${node.id}-label-${nodeIndex}`);

node.width = labelElement?.getBoundingClientRect().width;
node.width =
forceWidth ??
rectangularNodePadding + (labelElement?.getBoundingClientRect().width ?? 0) + rectangularNodePadding;
node.height = labelElement?.getBoundingClientRect().height;
}

Expand Down
Expand Up @@ -30,9 +30,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>[],
forceWidth?: number
) {
node.width = defaultWidth;
node.width = forceWidth ?? defaultWidth;
node.height = defaultHeight;

const previousNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === node.id);
Expand Down
Expand Up @@ -31,15 +31,16 @@ export class LayoutEngine implements ILayoutEngine {
public layoutNodes(
previousDiagram: Diagram | null,
visibleNodes: Node<NodeData, DiagramNodeType>[],
nodesToLayout: Node<NodeData, DiagramNodeType>[]
nodesToLayout: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
nodesToLayout.forEach((node) => {
const nodeLayoutHandler: INodeLayoutHandler<NodeData> | undefined = this.nodeLayoutHandlers.find((handler) =>
handler.canHandle(node)
);
if (nodeLayoutHandler) {
const directChildren = visibleNodes.filter((visibleNode) => visibleNode.parentNode === node.id);
nodeLayoutHandler.handle(this, previousDiagram, node, visibleNodes, directChildren);
nodeLayoutHandler.handle(this, previousDiagram, node, visibleNodes, directChildren, forceWidth);

node.style = {
...node.style,
Expand Down
Expand Up @@ -19,7 +19,8 @@ export interface ILayoutEngine {
layoutNodes(
previousDiagram: Diagram | null,
visibleNodes: Node<NodeData, DiagramNodeType>[],
nodesToLayout: Node<NodeData, DiagramNodeType>[]
nodesToLayout: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
);
}

Expand All @@ -30,6 +31,7 @@ export interface INodeLayoutHandler<T extends NodeData> {
previousDiagram: Diagram | null,
node: Node<T>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[]
directChildren: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
);
}
Expand Up @@ -17,7 +17,6 @@ import { ListNodeData } from '../node/ListNode.types';
import { DiagramNodeType } from '../node/NodeTypes.types';
import { ILayoutEngine, INodeLayoutHandler } from './LayoutEngine.types';

const rectangularNodePadding = 8;
const defaultWidth = 150;
const defaultHeight = 70;

Expand All @@ -31,75 +30,73 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler<ListNodeData> {
previousDiagram: Diagram | null,
node: Node<ListNodeData, 'listNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[]
directChildren: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
const nodeIndex = this.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);
this.handleParentNode(layoutEngine, previousDiagram, node, visibleNodes, directChildren, borderWidth, forceWidth);
} else {
this.handleLeafNode(previousDiagram, node, visibleNodes, borderWidth);
this.handleLeafNode(previousDiagram, node, visibleNodes, borderWidth, forceWidth);
}
}
handleLeafNode(
_previousDiagram: Diagram | null,
node: Node<ListNodeData, 'listNode'>,
_visibleNodes: Node<NodeData, DiagramNodeType>[],
_borderWidth: number
visibleNodes: Node<NodeData, DiagramNodeType>[],
borderWidth: number,
forceWidth?: number
) {
node.width = this.getNodeOrMinWidth(undefined);
node.height = this.getNodeOrMinHeight(undefined);
const labelElement = document.getElementById(`${node.id}-label-${this.findNodeIndex(visibleNodes, node.id)}`);

const nodeWidth = (labelElement?.getBoundingClientRect().width ?? 0) + borderWidth * 2;
const nodeHeight = (labelElement?.getBoundingClientRect().height ?? 0) + borderWidth * 2;
node.width = forceWidth ?? this.getNodeOrMinWidth(nodeWidth);
node.height = this.getNodeOrMinHeight(nodeHeight);
}
private handleParentNode(
layoutEngine: ILayoutEngine,
previousDiagram: Diagram | null,
node: Node<ListNodeData, 'listNode'>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
borderWidth: number
borderWidth: number,
forceWidth?: number
) {
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren);
layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, forceWidth);

const nodeIndex = this.findNodeIndex(visibleNodes, node.id);
const labelElement = document.getElementById(`${node.id}-label-${nodeIndex}`);

const iconLabelNodes = directChildren.filter((child) => child.type === 'iconLabelNode');
iconLabelNodes.forEach((child, index) => {
if (!forceWidth) {
const widerWidth = directChildren.reduce<number>(
(widerWidth, child) => Math.max(child.width ?? 0, widerWidth),
labelElement?.getBoundingClientRect().width ?? 0
);

layoutEngine.layoutNodes(previousDiagram, visibleNodes, directChildren, widerWidth);
}

directChildren.forEach((child, index) => {
child.position = {
x: 0,
y: (labelElement?.getBoundingClientRect().height ?? 0) + rectangularNodePadding,
x: borderWidth,
y: borderWidth + (labelElement?.getBoundingClientRect().height ?? 0),
};
const previousSibling = iconLabelNodes[index - 1];
const previousSibling = directChildren[index - 1];
if (previousSibling) {
child.position = { ...child.position, y: previousSibling.position.y + (previousSibling.height ?? 0) };
}
});

const childrenFootprint = this.getChildrenFootprint(iconLabelNodes);
const childrenAwareNodeWidth = childrenFootprint.x + childrenFootprint.width + rectangularNodePadding;
const labelOnlyWidth =
rectangularNodePadding + (labelElement?.getBoundingClientRect().width ?? 0) + rectangularNodePadding;
const nodeWidth = Math.max(childrenAwareNodeWidth, labelOnlyWidth) + borderWidth * 2;
node.width = this.getNodeOrMinWidth(nodeWidth);
node.height = this.getNodeOrMinHeight(
(labelElement?.getBoundingClientRect().height ?? 0) +
rectangularNodePadding +
childrenFootprint.height +
borderWidth * 2
);

if (nodeWidth > childrenAwareNodeWidth) {
// we need to adjust the width of children
iconLabelNodes.forEach((child) => {
child.width = nodeWidth;
child.style = {
...child.style,
width: `${nodeWidth}px`,
};
});
}
const childrenFootprint = this.getChildrenFootprint(directChildren);
const labelOnlyWidth = labelElement?.getBoundingClientRect().width ?? 0;
const nodeWidth = Math.max(childrenFootprint.width, labelOnlyWidth) + borderWidth * 2;
const nodeHeight = (labelElement?.getBoundingClientRect().height ?? 0) + childrenFootprint.height + borderWidth * 2;
node.width = forceWidth ?? this.getNodeOrMinWidth(nodeWidth);
node.height = this.getNodeOrMinHeight(nodeHeight);
}

private findNodeIndex(nodes: Node<NodeData>[], nodeId: string): number {
Expand Down

0 comments on commit 79cd540

Please sign in to comment.