Skip to content

Commit

Permalink
[1139] Implement TreePropertySection on the frontend
Browse files Browse the repository at this point in the history
Bug: #1139
Signed-off-by: Pierre-Charles David <[email protected]>
  • Loading branch information
pcdavid committed Apr 22, 2022
1 parent 033288c commit c7f4279
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.Optional;

import org.eclipse.sirius.components.forms.TreeNode;
import org.eclipse.sirius.components.forms.description.TreeDescription;
Expand Down Expand Up @@ -49,31 +49,27 @@ public Element render() {

String id = treeDescription.getIdProvider().apply(variableManager);
String label = treeDescription.getLabelProvider().apply(variableManager);
List<Element> children = List.of(new Element(DiagnosticComponent.class, new DiagnosticComponentProps(treeDescription, variableManager)));

// Compute the hierarchy and store the semantic elements by parent id
Map<String, List<?>> nodesHierarchy = new LinkedHashMap<>();
List<?> rootItems = treeDescription.getElementsProvider().apply(variableManager);
nodesHierarchy.put(null, rootItems);
for (Object item : rootItems) {
this.addChildren(item, variableManager, treeDescription, nodesHierarchy);
}
// Compute the recursive structure of semantic elements
Map<String, List<?>> childrenByParentId = new LinkedHashMap<>();
this.computeChildren(null, variableManager, treeDescription, childrenByParentId);

// Build the actual TreeNodes
// Build the actual flat TreeNode list to represent it
List<TreeNode> nodes = new ArrayList<>();
for (var entry : nodesHierarchy.entrySet()) {
for (var entry : childrenByParentId.entrySet()) {
String parentId = entry.getKey();
for (Object item : entry.getValue()) {
List<?> semanticChildren = entry.getValue();
for (Object semanticChild : semanticChildren) {
VariableManager itemVariableManager = variableManager.createChild();
itemVariableManager.put(CANDIDATE_VARIABLE, item);
TreeNode node = this.renderNode(itemVariableManager, treeDescription, parentId);
nodes.add(node);
itemVariableManager.put(CANDIDATE_VARIABLE, semanticChild);
nodes.add(this.renderNode(itemVariableManager, treeDescription, parentId));
}
}

// Expand all for now
List<String> expandedIds = nodes.stream().map(TreeNode::getId).collect(Collectors.toList());

List<Element> children = List.of(new Element(DiagnosticComponent.class, new DiagnosticComponentProps(treeDescription, variableManager)));
VariableManager expansionVariableManager = variableManager.createChild();
expansionVariableManager.put("nodes", nodes); //$NON-NLS-1$
List<String> expandedIds = treeDescription.getExpandedNodeIdsProvider().apply(expansionVariableManager);

// @formatter:off
TreeElementProps treeElementProps = TreeElementProps.newTreeElementProps(id)
Expand All @@ -87,17 +83,22 @@ public Element render() {
return new Element(TreeElementProps.TYPE, treeElementProps);
}

private void addChildren(Object item, VariableManager variableManager, TreeDescription treeDescription, Map<String, List<?>> nodesHierarchy) {
private void computeChildren(Object semanticElement, VariableManager variableManager, TreeDescription treeDescription, Map<String, List<?>> childrenByParentId) {
VariableManager itemVariableManager = variableManager.createChild();
itemVariableManager.put(CANDIDATE_VARIABLE, item);
String parentId = treeDescription.getNodeIdProvider().apply(itemVariableManager);
boolean hasChildren = treeDescription.getHasChildrenProvider().apply(itemVariableManager);
if (hasChildren) {
var children = treeDescription.getChildrenProvider().apply(itemVariableManager);
nodesHierarchy.put(parentId, children);
for (Object childItem : children) {
this.addChildren(childItem, variableManager, treeDescription, nodesHierarchy);
}
Object candidate = Optional.ofNullable(semanticElement).orElse(variableManager.get(VariableManager.SELF, Object.class).orElse(null));
itemVariableManager.put(CANDIDATE_VARIABLE, candidate);

String parentId;
if (semanticElement != null) {
parentId = treeDescription.getNodeIdProvider().apply(itemVariableManager);
} else {
parentId = null;
}

List<?> semanticChildren = treeDescription.getChildrenProvider().apply(itemVariableManager);
childrenByParentId.put(parentId, semanticChildren);
for (Object child : semanticChildren) {
this.computeChildren(child, variableManager, treeDescription, childrenByParentId);
}
}

Expand Down
53 changes: 47 additions & 6 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@apollo/client": "3.4.7",
"@material-ui/core": "4.12.3",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "^4.0.0-alpha.61",
"@rollup/plugin-commonjs": "21.0.1",
"@rollup/plugin-image": "2.1.1",
"@rollup/plugin-node-resolve": "13.1.1",
Expand Down
17 changes: 16 additions & 1 deletion frontend/src/form/Form.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Obeo.
* Copyright (c) 2021, 2022 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
Expand Down Expand Up @@ -112,3 +112,18 @@ export interface Link extends Widget {
label: string;
url: string;
}

export interface Tree extends Widget {
label: string;
nodes: TreeNode[];
expandedNodesIds: string[];
}

export interface TreeNode {
id: string;
parentId: string;
label: string;
kind: string;
imageURL: string;
selectable: Boolean;
}
14 changes: 13 additions & 1 deletion frontend/src/form/FormEventFragments.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Obeo.
* Copyright (c) 2021, 2022 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
Expand Down Expand Up @@ -105,6 +105,18 @@ export const formRefreshedEventPayloadFragment = gql`
label
url
}
... on TreeWidget {
label
expandedNodesIds
nodes {
id
parentId
label
kind
imageURL
selectable
}
}
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/form/FormEventFragments.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,18 @@ export interface GQLLink extends GQLWidget {
label: string;
url: string;
}

export interface GQLTree extends GQLWidget {
label: string;
nodes: GQLTreeNode[];
expandedNodesIds: string[];
}

export interface GQLTreeNode {
id: string;
parentId: string;
label: string;
kind: string;
imageURL: string;
selectable: Boolean;
}
15 changes: 14 additions & 1 deletion frontend/src/properties/Group.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2021 Obeo.
* Copyright (c) 2019, 2022 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
Expand All @@ -21,6 +21,7 @@ import {
Select,
Textarea,
Textfield,
Tree,
Widget,
WidgetSubscription,
} from 'form/Form.types';
Expand All @@ -34,6 +35,7 @@ import { SelectPropertySection } from 'properties/propertysections/SelectPropert
import { TextfieldPropertySection } from 'properties/propertysections/TextfieldPropertySection';
import React from 'react';
import { Selection } from 'workbench/Workbench.types';
import { TreePropertySection } from './propertysections/TreePropertySection';

const useGroupStyles = makeStyles((theme) => ({
group: {
Expand Down Expand Up @@ -79,6 +81,7 @@ const isMultiSelect = (widget: Widget): widget is MultiSelect => widget.__typena
const isRadio = (widget: Widget): widget is Radio => widget.__typename === 'Radio';
const isList = (widget: Widget): widget is List => widget.__typename === 'List';
const isLink = (widget: Widget): widget is Link => widget.__typename === 'Link';
const isTree = (widget: Widget): widget is Tree => widget.__typename === 'TreeWidget';

const widgetToPropertySection = (
editingContextId: string,
Expand Down Expand Up @@ -163,6 +166,16 @@ const widgetToPropertySection = (
);
} else if (isLink(widget)) {
propertySection = <LinkPropertySection widget={widget} key={widget.id} />;
} else if (isTree(widget)) {
propertySection = (
<TreePropertySection
editingContextId={editingContextId}
formId={formId}
widget={widget}
key={widget.id}
setSelection={setSelection}
/>
);
} else {
console.error(`Unsupported widget type ${widget.__typename}`);
}
Expand Down
81 changes: 81 additions & 0 deletions frontend/src/properties/propertysections/TreePropertySection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright (c) 2022 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 { makeStyles } from '@material-ui/core';
import { ClassNameMap } from '@material-ui/core/styles/withStyles';
import Typography from '@material-ui/core/Typography';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { TreeItem } from '@material-ui/lab';
import TreeView from '@material-ui/lab/TreeView';
import { httpOrigin } from 'common/URL';
import { TreeNode } from 'form/Form.types';
import React from 'react';
import { SelectionEntry } from 'workbench/Workbench.types';
import { TreePropertySectionProps } from './TreePropertySection.types';

const useTreeWidgetStyles = makeStyles((theme) => ({
nodeLabel: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
columnGap: theme.spacing(1),
},
}));

const renderTree = (styles: ClassNameMap, nodes: TreeNode[], onClick): JSX.Element => {
const renderTreeItem = (node: TreeNode): JSX.Element => {
const children = nodes.filter((g) => g.parentId === node.id);

const label = (
<div className={styles.nodeLabel} onClick={() => onClick(node)}>
<img height="16" width="16" title={node.label} alt={node.label} src={httpOrigin + node.imageURL}></img>
<Typography>{node.label}</Typography>
</div>
);

return (
<TreeItem key={node.id} nodeId={node.id} label={label}>
{children.map(renderTreeItem)}
</TreeItem>
);
};

return <>{...nodes.filter((f) => f.parentId == null).map(renderTreeItem)}</>;
};

export const TreePropertySection = ({ widget, setSelection }: TreePropertySectionProps) => {
const styles: ClassNameMap = useTreeWidgetStyles();
const updateSelection = (node: TreeNode) => {
if (node.selectable) {
const newSelection: SelectionEntry = {
id: node.id,
label: node.label,
kind: node.kind,
};
setSelection({ entries: [newSelection] });
}
};
return (
<div>
<Typography>{widget.label}</Typography>
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpanded={widget.expandedNodesIds}
defaultExpandIcon={<ChevronRightIcon />}
>
{renderTree(styles, widget.nodes, updateSelection)}
</TreeView>
</div>
);
};
Loading

0 comments on commit c7f4279

Please sign in to comment.