This is an automated email from the ASF dual-hosted git repository.
tiagobento pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git
The following commit(s) were added to refs/heads/main by this push:
new d5eec252d1a kie-issues#669: The DMN Editor should allow using the new
alternative representation of InputData nodes (DMN 1.5) (#2146)
d5eec252d1a is described below
commit d5eec252d1a8eb2c8802f09eeaf55ffd6e4d7776
Author: Luiz João Motta <[email protected]>
AuthorDate: Mon Feb 12 11:57:37 2024 -0300
kie-issues#669: The DMN Editor should allow using the new alternative
representation of InputData nodes (DMN 1.5) (#2146)
---
packages/dmn-editor/src/DmnEditor.css | 23 ++
packages/dmn-editor/src/DmnEditor.tsx | 23 +-
.../dmn-editor/src/autolayout/AutolayoutButton.tsx | 12 +-
.../src/boxedExpressions/BoxedExpression.tsx | 54 ++--
packages/dmn-editor/src/diagram/Diagram.tsx | 74 +++--
.../dmn-editor/src/diagram/DrdSelectorPanel.tsx | 164 ++++++++---
packages/dmn-editor/src/diagram/Palette.tsx | 4 +-
.../src/diagram/connections/ConnectionLine.tsx | 4 +-
packages/dmn-editor/src/diagram/maths/DmnMaths.ts | 18 +-
.../dmn-editor/src/diagram/nodes/DefaultSizes.ts | 56 ++--
.../src/diagram/nodes/EditableNodeLabel.css | 4 +
.../src/diagram/nodes/EditableNodeLabel.tsx | 4 +
packages/dmn-editor/src/diagram/nodes/NodeStyle.ts | 12 +-
packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx | 58 +++-
packages/dmn-editor/src/diagram/nodes/Nodes.tsx | 310 +++++++++++++++------
.../src/externalNodes/DmnObjectListItem.tsx | 15 +-
packages/dmn-editor/src/icons/Icons.tsx | 56 +++-
.../src/mutations/addDecisionToDecisionService.ts | 1 +
packages/dmn-editor/src/mutations/addOrGetDrd.ts | 2 +
packages/dmn-editor/src/mutations/resizeNode.ts | 4 +-
.../mutations/updateDecisionServiceDividerLine.ts | 4 +-
.../src/propertiesPanel/SingleNodeProperties.tsx | 14 +-
packages/dmn-editor/src/store/Store.ts | 10 +
.../src/store/computed/computeDiagramData.ts | 11 +-
packages/dmn-editor/src/store/computed/initial.ts | 4 +
packages/dmn-editor/src/svg/DmnDiagramSvg.tsx | 79 +++++-
26 files changed, 781 insertions(+), 239 deletions(-)
diff --git a/packages/dmn-editor/src/DmnEditor.css
b/packages/dmn-editor/src/DmnEditor.css
index ee68a8b6e29..22e6d6103a3 100644
--- a/packages/dmn-editor/src/DmnEditor.css
+++ b/packages/dmn-editor/src/DmnEditor.css
@@ -152,6 +152,25 @@
}
/* (end) external node */
+/* (begin) alternative input node */
+.kie-dmn-editor--selected-alternative-input-data-node:before {
+ content: "";
+ display: block;
+ position: absolute;
+ pointer-events: none;
+ border-radius: 6px;
+ top: -10px;
+ left: -10px;
+ width: calc(100% + 20px);
+ height: var(--selected-alternative-input-data-node-shape--height);
+ backdrop-filter: blur(8px);
+}
+.kie-dmn-editor--selected-alternative-input-data-node:before {
+ border: 1px dashed #006ba4;
+ background-color: rgb(161 222 255 / 20%); /* Very slightly blue hue */
+}
+/* (end) alternative input node */
+
/* (begin) textAnnotation, and unknown nodes */
.react-flow__node-node_unknown.selected:before,
.react-flow__node-node_textAnnotation.selected:before {
@@ -236,6 +255,10 @@
.kie-dmn-editor--drd-list button:active {
filter: brightness(90%);
}
+.kie-dmn-editor--drd-properties--input-data-node-shape button {
+ height: 36px;
+}
+
.kie-dmn-editor--palette {
width: 42px;
background: #fff;
diff --git a/packages/dmn-editor/src/DmnEditor.tsx
b/packages/dmn-editor/src/DmnEditor.tsx
index d1eb175c043..fdd349ff53f 100644
--- a/packages/dmn-editor/src/DmnEditor.tsx
+++ b/packages/dmn-editor/src/DmnEditor.tsx
@@ -40,7 +40,10 @@ import { BoxedExpression } from
"./boxedExpressions/BoxedExpression";
import { DataTypes } from "./dataTypes/DataTypes";
import { Diagram, DiagramRef } from "./diagram/Diagram";
import { DmnVersionLabel } from "./diagram/DmnVersionLabel";
-import { DmnEditorExternalModelsContextProvider } from
"./includedModels/DmnEditorDependenciesContext";
+import {
+ DmnEditorExternalModelsContextProvider,
+ useExternalModels,
+} from "./includedModels/DmnEditorDependenciesContext";
import { IncludedModels } from "./includedModels/IncludedModels";
import { BeePropertiesPanel } from "./propertiesPanel/BeePropertiesPanel";
import { DiagramPropertiesPanel } from
"./propertiesPanel/DiagramPropertiesPanel";
@@ -166,6 +169,7 @@ export const DmnEditorInternal = ({
const dmnEditorStoreApi = useDmnEditorStoreApi();
const { dmnModelBeforeEditingRef, dmnEditorRootElementRef } = useDmnEditor();
+ const { externalModelsByNamespace } = useExternalModels();
// Refs
@@ -189,12 +193,16 @@ export const DmnEditorInternal = ({
}
const bounds = RF.getNodesBounds(nodes);
+ const state = dmnEditorStoreApi.getState();
const svg = document.createElementNS("http://www.w3.org/2000/svg",
"svg");
svg.setAttribute("width", bounds.width + SVG_PADDING * 2 + "");
- svg.setAttribute("height", bounds.height + SVG_PADDING * 2 + "");
-
- const state = dmnEditorStoreApi.getState();
+ svg.setAttribute(
+ "height",
+ // It's not possible to calculate the text height which is outside
of the node
+ // for the alternative input data shape
+ bounds.height + (state.computed(state).isAlternativeInputDataShape()
? SVG_PADDING * 5 : SVG_PADDING * 2) + ""
+ );
// We're still on React 17.
// eslint-disable-next-line react/no-deprecated
@@ -207,6 +215,11 @@ export const DmnEditorInternal = ({
snapGrid={state.diagram.snapGrid}
importsByNamespace={state.computed(state).importsByNamespace()}
thisDmn={state.dmn}
+
isAlternativeInputDataShape={state.computed(state).isAlternativeInputDataShape()}
+
allDataTypesById={state.computed(state).getDataTypes(externalModelsByNamespace).allDataTypesById}
+ allTopLevelItemDefinitionUniqueNames={
+
state.computed(state).getDataTypes(externalModelsByNamespace).allTopLevelItemDefinitionUniqueNames
+ }
/>
</g>,
svg
@@ -215,7 +228,7 @@ export const DmnEditorInternal = ({
return new XMLSerializer().serializeToString(svg);
},
}),
- [dmnEditorStoreApi]
+ [dmnEditorStoreApi, externalModelsByNamespace]
);
// Make sure the DMN Editor reacts to props changing.
diff --git a/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
b/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
index f207394ed89..4f706ff887d 100644
--- a/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
+++ b/packages/dmn-editor/src/autolayout/AutolayoutButton.tsx
@@ -33,7 +33,7 @@ import { addEdge } from "../mutations/addEdge";
import { repositionNode } from "../mutations/repositionNode";
import { resizeNode } from "../mutations/resizeNode";
import { updateDecisionServiceDividerLine } from
"../mutations/updateDecisionServiceDividerLine";
-import { useDmnEditorStoreApi } from "../store/StoreContext";
+import { useDmnEditorStore, useDmnEditorStoreApi } from
"../store/StoreContext";
const elk = new ELK();
@@ -85,6 +85,7 @@ const FAKE_MARKER = "__$FAKE$__";
export function AutolayoutButton() {
const dmnEditorStoreApi = useDmnEditorStoreApi();
const { externalModelsByNamespace } = useExternalModels();
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
const onApply = React.useCallback(async () => {
const parentNodesById = new Map<string, AutolayoutParentNode>();
@@ -119,7 +120,7 @@ export function AutolayoutButton() {
const idOfFakeNodeForOutputSection =
`${node.id}${FAKE_MARKER}dsOutput`;
const idOfFakeNodeForEncapsulatedSection =
`${node.id}${FAKE_MARKER}dsEncapsulated`;
- const dsSize = MIN_NODE_SIZES[NODE_TYPES.decisionService](snapGrid);
+ const dsSize = MIN_NODE_SIZES[NODE_TYPES.decisionService]({ snapGrid
});
parentNodesById.set(node.id, {
elkNode: {
id: node.id,
@@ -178,7 +179,7 @@ export function AutolayoutButton() {
targets: [idOfFakeNodeForOutputSection],
});
} else if (node.data?.dmnObject?.__$$element === "group") {
- const groupSize = DEFAULT_NODE_SIZES[NODE_TYPES.group](snapGrid);
+ const groupSize = DEFAULT_NODE_SIZES[NODE_TYPES.group]({ snapGrid });
const groupBounds = node.data.shape["dc:Bounds"];
parentNodesById.set(node.id, {
decisionServiceSection: "n/a",
@@ -200,6 +201,7 @@ export function AutolayoutButton() {
bounds: bounds!,
container: groupBounds!,
snapGrid,
+ isAlternativeInputDataShape,
containerMinSizes: MIN_NODE_SIZES[NODE_TYPES.group],
boundsMinSizes: MIN_NODE_SIZES[nodesById.get(id)?.type as
NodeType],
}).isInside,
@@ -218,7 +220,7 @@ export function AutolayoutButton() {
return [];
}
- const defaultSize = DEFAULT_NODE_SIZES[node.type as NodeType](snapGrid);
+ const defaultSize = DEFAULT_NODE_SIZES[node.type as NodeType]({
snapGrid, isAlternativeInputDataShape });
const elkNode: Elk.ElkNode = {
id: node.id,
width: node.data.shape["dc:Bounds"]?.["@_width"] ??
defaultSize["@_width"],
@@ -505,7 +507,7 @@ export function AutolayoutButton() {
});
}
});
- }, [dmnEditorStoreApi, externalModelsByNamespace]);
+ }, [dmnEditorStoreApi, externalModelsByNamespace,
isAlternativeInputDataShape]);
return (
<button className={"kie-dmn-editor--autolayout-panel-toggle-button"}
onClick={onApply}>
diff --git a/packages/dmn-editor/src/boxedExpressions/BoxedExpression.tsx
b/packages/dmn-editor/src/boxedExpressions/BoxedExpression.tsx
index b8972cf0ff1..56cda1420ea 100644
--- a/packages/dmn-editor/src/boxedExpressions/BoxedExpression.tsx
+++ b/packages/dmn-editor/src/boxedExpressions/BoxedExpression.tsx
@@ -96,33 +96,22 @@ export function BoxedExpression({ container }: { container:
React.RefObject<HTML
//
- const thisDmn = useDmnEditorStore((s) => {
- return s.dmn;
- });
- const diagram = useDmnEditorStore((s) => {
- return s.diagram;
- });
- const boxedExpressionEditor = useDmnEditorStore((s) => {
- return s.boxedExpressionEditor;
- });
- const externalDmnsByNamespace = useDmnEditorStore((s) => {
- return
s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns;
- });
- const dataTypesTree = useDmnEditorStore((s) => {
- return s.computed(s).getDataTypes(externalModelsByNamespace).dataTypesTree;
- });
- const allTopLevelDataTypesByFeelName = useDmnEditorStore((s) => {
- return
s.computed(s).getDataTypes(externalModelsByNamespace).allTopLevelDataTypesByFeelName;
- });
- const nodesById = useDmnEditorStore((s) => {
- return s.computed(s).getDiagramData(externalModelsByNamespace).nodesById;
- });
- const importsByNamespace = useDmnEditorStore((s) => {
- return s.computed(s).importsByNamespace();
- });
- const externalPmmlsByNamespace = useDmnEditorStore((s) => {
- return
s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).pmmls;
- });
+ const thisDmn = useDmnEditorStore((s) => s.dmn);
+ const diagram = useDmnEditorStore((s) => s.diagram);
+ const boxedExpressionEditor = useDmnEditorStore((s) =>
s.boxedExpressionEditor);
+ const externalDmnsByNamespace = useDmnEditorStore(
+ (s) =>
s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns
+ );
+ const dataTypesTree = useDmnEditorStore((s) =>
s.computed(s).getDataTypes(externalModelsByNamespace).dataTypesTree);
+ const allTopLevelDataTypesByFeelName = useDmnEditorStore(
+ (s) =>
s.computed(s).getDataTypes(externalModelsByNamespace).allTopLevelDataTypesByFeelName
+ );
+ const nodesById = useDmnEditorStore((s) =>
s.computed(s).getDiagramData(externalModelsByNamespace).nodesById);
+ const importsByNamespace = useDmnEditorStore((s) =>
s.computed(s).importsByNamespace());
+ const externalPmmlsByNamespace = useDmnEditorStore(
+ (s) =>
s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace).pmmls
+ );
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
//
const dmnEditorStoreApi = useDmnEditorStoreApi();
@@ -273,7 +262,16 @@ export function BoxedExpression({ container }: {
container: React.RefObject<HTML
////
- const Icon = expression ?
NodeIcon(getNodeTypeFromDmnObject(expression.drgElement)) : () => <></>;
+ const Icon = useMemo(() => {
+ if (expression?.drgElement === undefined) {
+ throw new Error("A node Icon must exist for all types of node");
+ }
+ const nodeType = getNodeTypeFromDmnObject(expression.drgElement);
+ if (nodeType === undefined) {
+ throw new Error("Can't determine node icon with undefined node type");
+ }
+ return NodeIcon({ nodeType, isAlternativeInputDataShape });
+ }, [expression?.drgElement, isAlternativeInputDataShape]);
return (
<>
diff --git a/packages/dmn-editor/src/diagram/Diagram.tsx
b/packages/dmn-editor/src/diagram/Diagram.tsx
index c12c9f56456..e6adf4d04e1 100644
--- a/packages/dmn-editor/src/diagram/Diagram.tsx
+++ b/packages/dmn-editor/src/diagram/Diagram.tsx
@@ -246,7 +246,7 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
(
nodeIdToIgnore: string,
bounds: DC__Bounds,
- minSizes: (snapGrid: SnapGrid) => DC__Dimension,
+ minSizes: (args: { snapGrid: SnapGrid; isAlternativeInputDataShape:
boolean }) => DC__Dimension,
snapGrid: SnapGrid
) =>
reactFlowInstance
@@ -259,11 +259,15 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
bounds: bounds!,
container: node.data.shape["dc:Bounds"]!,
snapGrid,
+ isAlternativeInputDataShape: dmnEditorStoreApi
+ .getState()
+ .computed(dmnEditorStoreApi.getState())
+ .isAlternativeInputDataShape(),
containerMinSizes: MIN_NODE_SIZES[node.type as NodeType],
boundsMinSizes: minSizes,
}).isInside
),
- [reactFlowInstance]
+ [reactFlowInstance, dmnEditorStoreApi]
);
const onDragOver = useCallback((e: React.DragEvent) => {
@@ -313,8 +317,14 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
bounds: {
"@_x": dropPoint.x,
"@_y": dropPoint.y,
- "@_width":
DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette](state.diagram.snapGrid)["@_width"],
- "@_height":
DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette](state.diagram.snapGrid)["@_height"],
+ "@_width": DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })["@_width"],
+ "@_height": DEFAULT_NODE_SIZES[typeOfNewNodeFromPalette]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })["@_height"],
},
},
});
@@ -341,7 +351,10 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
const externalNodeType =
getNodeTypeFromDmnObject(externalDrgElement)!;
- const defaultExternalNodeDimensions =
DEFAULT_NODE_SIZES[externalNodeType](state.diagram.snapGrid);
+ const defaultExternalNodeDimensions =
DEFAULT_NODE_SIZES[externalNodeType]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ });
const namespaceName = getXmlNamespaceDeclarationName({
model: state.dmn.model.definitions,
@@ -385,10 +398,16 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
DMN15__tDefinitions["drgElement"]
>;
- const nodeType = getNodeTypeFromDmnObject(drgElement)!;
+ const nodeType = getNodeTypeFromDmnObject(drgElement);
+ if (nodeType === undefined) {
+ throw new Error("DMN DIAGRAM: It wasn't possible to determine the
node type");
+ }
dmnEditorStoreApi.setState((state) => {
- const defaultNodeDimensions =
DEFAULT_NODE_SIZES[nodeType](state.diagram.snapGrid);
+ const defaultNodeDimensions = DEFAULT_NODE_SIZES[nodeType]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ });
addShape({
definitions: state.dmn.model.definitions,
drdIndex: state.diagram.drdIndex,
@@ -496,8 +515,14 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
bounds: {
"@_x": dropPoint.x,
"@_y": dropPoint.y,
- "@_width":
DEFAULT_NODE_SIZES[newNodeType](state.diagram.snapGrid)["@_width"],
- "@_height":
DEFAULT_NODE_SIZES[newNodeType](state.diagram.snapGrid)["@_height"],
+ "@_width": DEFAULT_NODE_SIZES[newNodeType]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })["@_width"],
+ "@_height": DEFAULT_NODE_SIZES[newNodeType]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })["@_height"],
},
},
});
@@ -571,7 +596,10 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
const snappedShape = snapShapeDimensions(
state.diagram.snapGrid,
node.data.shape,
- MIN_NODE_SIZES[node.type as
NodeType](state.diagram.snapGrid)
+ MIN_NODE_SIZES[node.type as NodeType]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })
);
if (
snappedShape.width !== change.dimensions.width ||
@@ -1194,8 +1222,12 @@ function DmnDiagramEmptyState({
bounds: {
"@_x": 100,
"@_y": 100,
- "@_width":
DEFAULT_NODE_SIZES[NODE_TYPES.decision](state.diagram.snapGrid)["@_width"],
- "@_height":
DEFAULT_NODE_SIZES[NODE_TYPES.decision](state.diagram.snapGrid)["@_height"],
+ "@_width": DEFAULT_NODE_SIZES[NODE_TYPES.decision]({
+ snapGrid: state.diagram.snapGrid,
+ })["@_width"],
+ "@_height": DEFAULT_NODE_SIZES[NODE_TYPES.decision]({
+ snapGrid: state.diagram.snapGrid,
+ })["@_height"],
},
},
});
@@ -1231,8 +1263,14 @@ function DmnDiagramEmptyState({
const inputDataNodeBounds: DC__Bounds = {
"@_x": 100,
"@_y": 300,
- "@_width":
DEFAULT_NODE_SIZES[NODE_TYPES.inputData](state.diagram.snapGrid)["@_width"],
- "@_height":
DEFAULT_NODE_SIZES[NODE_TYPES.inputData](state.diagram.snapGrid)["@_height"],
+ "@_width": DEFAULT_NODE_SIZES[NODE_TYPES.inputData]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })["@_width"],
+ "@_height": DEFAULT_NODE_SIZES[NODE_TYPES.inputData]({
+ snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
+ })["@_height"],
};
const { href: inputDataNodeHref, shapeId: inputDataShapeId }
= addStandaloneNode({
@@ -1259,8 +1297,12 @@ function DmnDiagramEmptyState({
bounds: {
"@_x": 100,
"@_y": 100,
- "@_width":
DEFAULT_NODE_SIZES[NODE_TYPES.decision](state.diagram.snapGrid)["@_width"],
- "@_height":
DEFAULT_NODE_SIZES[NODE_TYPES.decision](state.diagram.snapGrid)["@_height"],
+ "@_width": DEFAULT_NODE_SIZES[NODE_TYPES.decision]({
+ snapGrid: state.diagram.snapGrid,
+ })["@_width"],
+ "@_height": DEFAULT_NODE_SIZES[NODE_TYPES.decision]({
+ snapGrid: state.diagram.snapGrid,
+ })["@_height"],
},
},
});
diff --git a/packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx
b/packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx
index 1aab48d71dc..40a3b787f61 100644
--- a/packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx
+++ b/packages/dmn-editor/src/diagram/DrdSelectorPanel.tsx
@@ -21,66 +21,150 @@ import * as React from "react";
import { useDmnEditorStore, useDmnEditorStoreApi } from
"../store/StoreContext";
import { addOrGetDrd, getDefaultDrdName } from "../mutations/addOrGetDrd";
import { Text, TextContent } from
"@patternfly/react-core/dist/js/components/Text";
-import { Flex, FlexItem } from "@patternfly/react-core/dist/js/layouts/Flex";
import { Button, ButtonVariant } from
"@patternfly/react-core/dist/js/components/Button";
import { PlusCircleIcon } from
"@patternfly/react-icons/dist/js/icons/plus-circle-icon";
import { Divider } from "@patternfly/react-core/dist/js/components/Divider";
-import { ArrowRightIcon } from
"@patternfly/react-icons/dist/js/icons/arrow-right-icon";
import { DiagramNodesPanel } from "../store/Store";
import { getDrdId } from "./drd/drdId";
+import { Title } from "@patternfly/react-core/dist/js/components/Title";
+import { Form, FormGroup, FormSection } from
"@patternfly/react-core/dist/js/components/Form";
+import { ToggleGroup, ToggleGroupItem } from
"@patternfly/react-core/dist/js/components/ToggleGroup";
+import { AlternativeInputDataIcon, InputDataIcon } from "../icons/Icons";
export function DrdSelectorPanel() {
const thisDmn = useDmnEditorStore((s) => s.dmn);
const diagram = useDmnEditorStore((s) => s.diagram);
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
+ const drdName = useDmnEditorStore(
+ (s) =>
+
s.dmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.[s.diagram.drdIndex]["@_name"]
||
+ getDefaultDrdName({ drdIndex: s.diagram.drdIndex })
+ );
const dmnEditorStoreApi = useDmnEditorStoreApi();
return (
<>
- <Flex justifyContent={{ default: "justifyContentSpaceBetween" }}>
- <TextContent>
- <Text component="h3">DRDs</Text>
- </TextContent>
- <Button
- variant={ButtonVariant.link}
- onClick={() => {
- dmnEditorStoreApi.setState((state) => {
- const allDrds =
state.dmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"] ?? [];
- const newIndex = allDrds.length;
-
- addOrGetDrd({
- definitions: state.dmn.model.definitions,
- drdIndex: newIndex,
- });
-
- state.diagram.drdIndex = newIndex;
- state.diagram.drdSelector.isOpen = false;
- state.diagram.openNodesPanel = DiagramNodesPanel.DRG_NODES;
- state.focus.consumableId = getDrdId({ drdIndex: newIndex });
- });
- }}
- >
- <PlusCircleIcon />
- </Button>
- </Flex>
- <Divider style={{ marginBottom: "8px" }} />
- <div className={"kie-dmn-editor--drd-list"}>
-
{thisDmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.map((drd, i)
=> (
- <React.Fragment key={drd["@_id"]!}>
- <button
- className={i === diagram.drdIndex ? "active" : undefined}
+ <div
+ style={{
+ display: "grid",
+ gridTemplateColumns: "300px 300px",
+ gridTemplateRows: "auto auto auto",
+ gridTemplateAreas: `
+ 'header-list header-properties'
+ 'divider-list divider-properties'
+ 'content-list content-properties'
+ `,
+ columnGap: "40px",
+ }}
+ >
+ <div style={{ gridArea: "header-list" }}>
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
+ <TextContent>
+ <Text component="h3">DRDs</Text>
+ </TextContent>
+ <Button
+ variant={ButtonVariant.link}
onClick={() => {
dmnEditorStoreApi.setState((state) => {
- state.diagram.drdIndex = i;
+ const allDrds =
state.dmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"] ?? [];
+ const newIndex = allDrds.length;
+
+ addOrGetDrd({
+ definitions: state.dmn.model.definitions,
+ drdIndex: newIndex,
+ });
+
+ state.diagram.drdIndex = newIndex;
state.diagram.drdSelector.isOpen = false;
+ state.diagram.openNodesPanel = DiagramNodesPanel.DRG_NODES;
+ state.focus.consumableId = getDrdId({ drdIndex: newIndex });
});
}}
>
- {`${i + 1}. ${drd["@_name"] || getDefaultDrdName({ drdIndex: i
})}`}
- </button>
- <br />
- </React.Fragment>
- ))}
+ <PlusCircleIcon />
+ </Button>
+ </div>
+ </div>
+ <div style={{ gridArea: "divider-list" }}>
+ <Divider style={{ marginBottom: "8px" }} />
+ </div>
+ <div style={{ gridArea: "content-list" }}
className={"kie-dmn-editor--drd-list"}>
+
{thisDmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.map((drd, i)
=> (
+ <React.Fragment key={drd["@_id"]!}>
+ <button
+ className={i === diagram.drdIndex ? "active" : undefined}
+ onClick={() => {
+ dmnEditorStoreApi.setState((state) => {
+ state.diagram.drdIndex = i;
+ });
+ }}
+ >
+ {`${i + 1}. ${drd["@_name"] || getDefaultDrdName({ drdIndex: i
})}`}
+ </button>
+ <br />
+ </React.Fragment>
+ ))}
+ </div>
+
+ <div style={{ gridArea: "header-properties" }}>
+ <Title headingLevel="h3" style={{ height: "36px" }}>
+ {drdName}
+ </Title>
+ </div>
+ <div style={{ gridArea: "divider-properties" }}>
+ <Divider style={{ marginBottom: "8px" }} />
+ </div>
+ <div style={{ gridArea: "content-properties" }}>
+ <Form>
+ <FormSection>
+ <FormGroup label={"Input Data node shape"}>
+ <ToggleGroup
+ aria-label="Tweak the shape of the input data node"
+
className={"kie-dmn-editor--drd-properties--input-data-node-shape"}
+ >
+ <ToggleGroupItem
+ text="Classic"
+ icon={<InputDataIcon padding={"2px 0 0 0"} height={22} />}
+ buttonId="classic-input-node-shape"
+ isSelected={isAlternativeInputDataShape === false}
+ onChange={() =>
+ dmnEditorStoreApi.setState((state) => {
+ state.dmn.model.definitions["dmndi:DMNDI"] ??= {};
+
state.dmn.model.definitions["dmndi:DMNDI"]["dmndi:DMNDiagram"] ??= [];
+
state.dmn.model.definitions["dmndi:DMNDI"]["dmndi:DMNDiagram"][state.diagram.drdIndex][
+ "@_useAlternativeInputDataShape"
+ ] = false;
+ })
+ }
+ />
+ <ToggleGroupItem
+ text="Alternative"
+ icon={
+ <AlternativeInputDataIcon
+ padding={"1px 0 0 0"}
+ height={22}
+ viewBox={160}
+ transform={"translate(40, 30)"}
+ />
+ }
+ buttonId="alternative-input-node-shape"
+ isSelected={isAlternativeInputDataShape === true}
+ onChange={() =>
+ dmnEditorStoreApi.setState((state) => {
+ state.dmn.model.definitions["dmndi:DMNDI"] ??= {};
+
state.dmn.model.definitions["dmndi:DMNDI"]["dmndi:DMNDiagram"] ??= [];
+
state.dmn.model.definitions["dmndi:DMNDI"]["dmndi:DMNDiagram"][state.diagram.drdIndex][
+ "@_useAlternativeInputDataShape"
+ ] = true;
+ })
+ }
+ />
+ </ToggleGroup>
+ </FormGroup>
+ </FormSection>
+ </Form>
+ </div>
</div>
</>
);
diff --git a/packages/dmn-editor/src/diagram/Palette.tsx
b/packages/dmn-editor/src/diagram/Palette.tsx
index cb3640e19c5..9f1b6c0734f 100644
--- a/packages/dmn-editor/src/diagram/Palette.tsx
+++ b/packages/dmn-editor/src/diagram/Palette.tsx
@@ -30,6 +30,7 @@ import { Popover } from
"@patternfly/react-core/dist/js/components/Popover";
import { ExternalNodesPanel } from "../externalNodes/ExternalNodesPanel";
import { MigrationIcon } from
"@patternfly/react-icons/dist/js/icons/migration-icon";
import {
+ AlternativeInputDataIcon,
BkmIcon,
DecisionIcon,
DecisionServiceIcon,
@@ -61,6 +62,7 @@ export function Palette({ pulse }: { pulse: boolean }) {
const diagram = useDmnEditorStore((s) => s.diagram);
const thisDmn = useDmnEditorStore((s) => s.dmn.model);
const rfStoreApi = RF.useStoreApi();
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
const groupNodes = useCallback(() => {
dmnEditorStoreApi.setState((state) => {
@@ -154,7 +156,7 @@ export function Palette({ pulse }: { pulse: boolean }) {
onDragStart={(event) => onDragStart(event, NODE_TYPES.inputData)}
draggable={true}
>
- <InputDataIcon />
+ {isAlternativeInputDataShape ? <AlternativeInputDataIcon /> :
<InputDataIcon />}
</div>
<div
title="Decision"
diff --git a/packages/dmn-editor/src/diagram/connections/ConnectionLine.tsx
b/packages/dmn-editor/src/diagram/connections/ConnectionLine.tsx
index ea393275710..6dd7833a71c 100644
--- a/packages/dmn-editor/src/diagram/connections/ConnectionLine.tsx
+++ b/packages/dmn-editor/src/diagram/connections/ConnectionLine.tsx
@@ -49,7 +49,7 @@ export function ConnectionLine({ toX, toY, fromNode,
fromHandle }: RF.Connection
: undefined
);
const kieEdgePath = useKieEdgePath(edge?.source, edge?.target, edge?.data);
-
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
// This works because nodes are configured with:
// - Source handles with ids matching EDGE_TYPES or NODE_TYPES
// - Target handles with ids matching TargetHandleId
@@ -91,7 +91,7 @@ export function ConnectionLine({ toX, toY, fromNode,
fromHandle }: RF.Connection
const nodeType = handleId as NodeType;
const { "@_x": toXsnapped, "@_y": toYsnapped } = snapPoint(snapGrid, {
"@_x": toX, "@_y": toY });
- const defaultSize = DEFAULT_NODE_SIZES[nodeType](snapGrid);
+ const defaultSize = DEFAULT_NODE_SIZES[nodeType]({ snapGrid,
isAlternativeInputDataShape });
const [toXauto, toYauto] = getPositionalHandlePosition(
{ x: toXsnapped, y: toYsnapped, width: defaultSize["@_width"], height:
defaultSize["@_height"] },
{ x: fromX, y: fromY, width: 1, height: 1 }
diff --git a/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
b/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
index 39846e985a2..e663b97c0c2 100644
--- a/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
+++ b/packages/dmn-editor/src/diagram/maths/DmnMaths.ts
@@ -187,6 +187,7 @@ export function getContainmentRelationship({
container,
divingLineLocalY,
snapGrid,
+ isAlternativeInputDataShape,
containerMinSizes,
boundsMinSizes,
}: {
@@ -194,13 +195,22 @@ export function getContainmentRelationship({
container: DC__Bounds;
divingLineLocalY?: number;
snapGrid: SnapGrid;
- containerMinSizes: (snapGrid: SnapGrid) => DC__Dimension;
- boundsMinSizes: (snapGrid: SnapGrid) => DC__Dimension;
+ isAlternativeInputDataShape: boolean;
+ containerMinSizes: (args: { snapGrid: SnapGrid; isAlternativeInputDataShape:
boolean }) => DC__Dimension;
+ boundsMinSizes: (args: { snapGrid: SnapGrid; isAlternativeInputDataShape:
boolean }) => DC__Dimension;
}): { isInside: true; section: "upper" | "lower" } | { isInside: false } {
const { x: cx, y: cy } = snapBoundsPosition(snapGrid, container);
- const { width: cw, height: ch } = snapBoundsDimensions(snapGrid, container,
containerMinSizes(snapGrid));
+ const { width: cw, height: ch } = snapBoundsDimensions(
+ snapGrid,
+ container,
+ containerMinSizes({ snapGrid, isAlternativeInputDataShape })
+ );
const { x: bx, y: by } = snapBoundsPosition(snapGrid, bounds);
- const { width: bw, height: bh } = snapBoundsDimensions(snapGrid, bounds,
boundsMinSizes(snapGrid));
+ const { width: bw, height: bh } = snapBoundsDimensions(
+ snapGrid,
+ bounds,
+ boundsMinSizes({ snapGrid, isAlternativeInputDataShape })
+ );
const center = getDmnBoundsCenterPoint({
"@_height": bh,
diff --git a/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
b/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
index c4c3a1b45c2..3cca7f000a1 100644
--- a/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
+++ b/packages/dmn-editor/src/diagram/nodes/DefaultSizes.ts
@@ -24,36 +24,49 @@ import { NodeType } from "../connections/graphStructure";
import { NODE_TYPES } from "./NodeTypes";
import { CONTAINER_NODES_DESIRABLE_PADDING } from "../maths/DmnMaths";
-export const MIN_NODE_SIZES: Record<NodeType, (snapGrid: SnapGrid) =>
DC__Dimension> = {
- [NODE_TYPES.inputData]: (snapGrid) => {
+export type NodeSizes<T extends NodeType = NodeType> = {
+ [K in T]: K extends typeof NODE_TYPES.inputData
+ ? (args: { snapGrid: SnapGrid; isAlternativeInputDataShape: boolean }) =>
DC__Dimension
+ : (args: { snapGrid: SnapGrid }) => DC__Dimension;
+};
+
+export const MIN_NODE_SIZES: NodeSizes = {
+ [NODE_TYPES.inputData]: ({ snapGrid, isAlternativeInputDataShape }) => {
+ if (isAlternativeInputDataShape) {
+ const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, NODE_MIN_WIDTH / 2,
NODE_MIN_HEIGHT + 20);
+ return {
+ "@_width": snappedMinSize.width,
+ "@_height": snappedMinSize.height,
+ };
+ }
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.decision]: (snapGrid) => {
+ [NODE_TYPES.decision]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.bkm]: (snapGrid) => {
+ [NODE_TYPES.bkm]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.knowledgeSource]: (snapGrid) => {
+ [NODE_TYPES.knowledgeSource]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.decisionService]: (snapGrid) => {
+ [NODE_TYPES.decisionService]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(
snapGrid,
NODE_MIN_WIDTH + CONTAINER_NODES_DESIRABLE_PADDING * 2,
@@ -64,14 +77,14 @@ export const MIN_NODE_SIZES: Record<NodeType, (snapGrid:
SnapGrid) => DC__Dimens
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.textAnnotation]: (snapGrid) => {
+ [NODE_TYPES.textAnnotation]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, 200, 60);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.group]: (snapGrid) => {
+ [NODE_TYPES.group]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(
snapGrid,
NODE_MIN_WIDTH + CONTAINER_NODES_DESIRABLE_PADDING * 2,
@@ -82,7 +95,7 @@ export const MIN_NODE_SIZES: Record<NodeType, (snapGrid:
SnapGrid) => DC__Dimens
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.unknown]: (snapGrid) => {
+ [NODE_TYPES.unknown]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
@@ -91,57 +104,64 @@ export const MIN_NODE_SIZES: Record<NodeType, (snapGrid:
SnapGrid) => DC__Dimens
},
};
-export const DEFAULT_NODE_SIZES: Record<NodeType, (snapGrid: SnapGrid) =>
DC__Dimension> = {
- [NODE_TYPES.inputData]: (snapGrid) => {
+export const DEFAULT_NODE_SIZES: NodeSizes = {
+ [NODE_TYPES.inputData]: ({ snapGrid, isAlternativeInputDataShape }) => {
+ if (isAlternativeInputDataShape) {
+ const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, NODE_MIN_WIDTH / 2,
NODE_MIN_HEIGHT + 20);
+ return {
+ "@_width": snappedMinSize.width,
+ "@_height": snappedMinSize.height,
+ };
+ }
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.decision]: (snapGrid) => {
+ [NODE_TYPES.decision]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.bkm]: (snapGrid) => {
+ [NODE_TYPES.bkm]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.knowledgeSource]: (snapGrid) => {
+ [NODE_TYPES.knowledgeSource]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.decisionService]: (snapGrid) => {
+ [NODE_TYPES.decisionService]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, NODE_MIN_WIDTH * 2,
NODE_MIN_WIDTH * 2); // This is not a mistake, we want the DecisionService node
to be a bigger square.
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.textAnnotation]: (snapGrid) => {
+ [NODE_TYPES.textAnnotation]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, 200, 200);
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.group]: (snapGrid) => {
+ [NODE_TYPES.group]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid, NODE_MIN_WIDTH * 2,
NODE_MIN_WIDTH * 2); // This is not a mistake, we want the Group node to be a
bigger square.
return {
"@_width": snappedMinSize.width,
"@_height": snappedMinSize.height,
};
},
- [NODE_TYPES.unknown]: (snapGrid) => {
+ [NODE_TYPES.unknown]: ({ snapGrid }) => {
const snappedMinSize = MIN_SIZE_FOR_NODES(snapGrid);
return {
"@_width": snappedMinSize.width,
diff --git a/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.css
b/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.css
index eea0ff18d9d..cca8e9e70c9 100644
--- a/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.css
+++ b/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.css
@@ -14,6 +14,10 @@
text-align: left;
padding: 8px;
}
+.kie-dmn-editor--editable-node-name-input.center-bottom {
+ text-align: center;
+ justify-content: center;
+}
.kie-dmn-editor--editable-node-name-input.center-center {
align-items: center;
text-align: center;
diff --git a/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
b/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
index 3c43bc3a3f9..82abcac5c85 100644
--- a/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/EditableNodeLabel.tsx
@@ -52,6 +52,7 @@ export function EditableNodeLabel({
skipValidation,
onGetAllUniqueNames,
fontCssProperties,
+ setLabelHeight,
enableAutoFocusing,
}: {
id?: string;
@@ -68,6 +69,7 @@ export function EditableNodeLabel({
skipValidation?: boolean;
onGetAllUniqueNames: (s: State) => UniqueNameIndex;
fontCssProperties?: React.CSSProperties;
+ setLabelHeight?: React.Dispatch<React.SetStateAction<number>>;
enableAutoFocusing?: boolean;
}) {
const displayValue = useDmnEditorStore((s) => {
@@ -234,6 +236,8 @@ export function EditableNodeLabel({
/>
)) || (
<span
+ // clientHeight isn't affected by the zoom in/out
+ ref={(ref) => setLabelHeight?.(ref?.clientHeight ?? 0)}
style={{
whiteSpace: "pre-wrap",
...fontCssProperties,
diff --git a/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
b/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
index 9374cc576e8..eefd9a7a5a8 100644
--- a/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
+++ b/packages/dmn-editor/src/diagram/nodes/NodeStyle.ts
@@ -192,9 +192,19 @@ export function getFontCssProperties(dmnFontStyle?:
DmnFontStyle): React.CSSProp
};
}
-export function getNodeLabelPosition(nodeType: NodeType): NodeLabelPosition {
+type NodeLabelPositionProps =
+ | { nodeType: Extract<NodeType, typeof NODE_TYPES.inputData>;
isAlternativeInputDataShape: boolean }
+ | { nodeType: Exclude<NodeType, typeof NODE_TYPES.inputData>;
isAlternativeInputDataShape?: boolean };
+
+export function getNodeLabelPosition({
+ nodeType,
+ isAlternativeInputDataShape,
+}: NodeLabelPositionProps): NodeLabelPosition {
switch (nodeType) {
case NODE_TYPES.inputData:
+ if (isAlternativeInputDataShape) {
+ return "center-bottom";
+ }
return "center-center";
case NODE_TYPES.decision:
return "center-center";
diff --git a/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
b/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
index ec66ae7148b..dac36faa500 100644
--- a/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/NodeSvgs.tsx
@@ -22,7 +22,7 @@ import * as RF from "reactflow";
import { DEFAULT_INTRACTION_WIDTH } from "../maths/DmnMaths";
import { DEFAULT_NODE_FILL, DEFAULT_NODE_STROKE_COLOR,
DEFAULT_NODE_STROKE_WIDTH } from "./NodeStyle";
-export type NodeLabelPosition = "center-center" | "top-center" | "center-left"
| "top-left";
+export type NodeLabelPosition = "center-bottom" | "center-center" |
"top-center" | "center-left" | "top-left";
export type NodeSvgProps = RF.Dimensions &
RF.XYPosition & {
@@ -112,6 +112,62 @@ export function InputDataNodeSvg(__props: NodeSvgProps & {
isCollection?: boolea
);
}
+export function AlternativeInputDataNodeSvg(
+ __props: NodeSvgProps & { isCollection?: boolean; isIcon: boolean;
transform?: string }
+) {
+ const {
+ strokeWidth,
+ x,
+ y,
+ width,
+ height,
+ fillColor,
+ strokeColor,
+ props: { isCollection, isIcon, ...props },
+ } = normalize(__props);
+
+ const bevel = 25;
+ const arrowStartingX = 6;
+ const arrowStartingY = 10;
+
+ return (
+ <>
+ <polygon
+ {...props}
+ points={`0,0 0,${height} ${width},${height} ${width},${bevel} ${width
- bevel},0 ${width - bevel},0`}
+ fill={fillColor ?? DEFAULT_NODE_FILL}
+ stroke={strokeColor ?? DEFAULT_NODE_STROKE_COLOR}
+ strokeLinejoin={"round"}
+ strokeWidth={strokeWidth}
+ transform={isIcon ? __props.transform : `translate(${x},${y})`}
+ />
+ {isIcon === false && (
+ <>
+ <polygon
+ {...props}
+ points={`${width - bevel},0 ${width - bevel},${bevel}
${width},${bevel}`}
+ fill={fillColor ?? DEFAULT_NODE_FILL}
+ stroke={strokeColor ?? DEFAULT_NODE_STROKE_COLOR}
+ strokeLinejoin={"round"}
+ strokeWidth={strokeWidth}
+ transform={`translate(${x},${y})`}
+ />
+ <polygon
+ {...props}
+ points={`${arrowStartingX},${arrowStartingY} ${arrowStartingX},20
20,20 20,26 30,15 20,4 20,${arrowStartingY} `}
+ fill={fillColor ?? DEFAULT_NODE_FILL}
+ stroke={strokeColor ?? DEFAULT_NODE_STROKE_COLOR}
+ strokeLinejoin={"round"}
+ strokeWidth={strokeWidth}
+ transform={`translate(${x},${y})`}
+ />
+ </>
+ )}
+ {isCollection && <NodeCollectionMarker {...__props} anchor={"bottom"} />}
+ </>
+ );
+}
+
export function DecisionNodeSvg(__props: NodeSvgProps & { isCollection?:
boolean }) {
const {
strokeWidth,
diff --git a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
index 102898856e7..d12b8989674 100644
--- a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
+++ b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx
@@ -56,6 +56,7 @@ import { EditableNodeLabel, OnEditableNodeLabelChange,
useEditableNodeLabel } fr
import { InfoNodePanel } from "./InfoNodePanel";
import { getNodeLabelPosition, useNodeStyle } from "./NodeStyle";
import {
+ AlternativeInputDataNodeSvg,
BkmNodeSvg,
DecisionNodeSvg,
DecisionServiceNodeSvg,
@@ -118,6 +119,7 @@ export const InputDataNode = React.memo(
const shouldActLikeHovered = useDmnEditorStore(
(s) => (isHovered || isResizing) && s.diagram.draggingNodes.length === 0
);
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
const { isEditingLabel, setEditingLabel, triggerEditing,
triggerEditingIfEnter } = useEditableNodeLabel(id);
useHoveredNodeAlwaysOnTop(ref, zIndex, shouldActLikeHovered, dragging,
selected, isEditingLabel);
@@ -127,7 +129,13 @@ export const InputDataNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.inputData,
+ snapGrid,
+ shape,
+ isExternal,
+ isAlternativeInputDataShape,
+ });
const setName = useCallback<OnEditableNodeLabelChange>(
(newName: string) => {
@@ -174,66 +182,133 @@ export const InputDataNode = React.memo(
);
});
+ const [alternativeEditableNodeHeight, setAlternativeEditableNodeHeight] =
React.useState<number>(0);
+ const alternativeSvgStyle = useMemo(() => {
+ // This is used to modify a css from a :before element.
+ // The --height is a css var which is used by the
kie-dmn-editor--selected-alternative-input-data-node class.
+ return isAlternativeInputDataShape
+ ? ({
+ display: "flex",
+ flexDirection: "column",
+ outline: "none",
+ "--selected-alternative-input-data-node-shape--height": `${
+ nodeDimensions.height + 20 + (isEditingLabel ? 20 :
alternativeEditableNodeHeight ?? 0)
+ }px`,
+ } as any)
+ : undefined;
+ // The dependecy should be "nodeDimension" to trigger an adjustment on
width changes as well.
+ }, [isAlternativeInputDataShape, nodeDimensions, isEditingLabel,
alternativeEditableNodeHeight]);
+
+ const selectedAlternativeClass = useMemo(
+ () => (isAlternativeInputDataShape && selected ?
"kie-dmn-editor--selected-alternative-input-data-node" : ""),
+ [isAlternativeInputDataShape, selected]
+ );
+
return (
<>
- <svg className={`kie-dmn-editor--node-shape ${additionalClasses}`}>
- <InputDataNodeSvg
- isCollection={isCollection}
- {...nodeDimensions}
- x={0}
- y={0}
- strokeWidth={shapeStyle.strokeWidth}
- fillColor={shapeStyle.fillColor}
- strokeColor={shapeStyle.strokeColor}
- />
+ <svg
+ className={`kie-dmn-editor--node-shape ${additionalClasses} ${
+ isAlternativeInputDataShape ? "alternative" : ""
+ } ${selected ? "selected" : ""}`}
+ >
+ {isAlternativeInputDataShape ? (
+ <AlternativeInputDataNodeSvg
+ isCollection={isCollection}
+ {...nodeDimensions}
+ x={0}
+ y={0}
+ strokeWidth={shapeStyle.strokeWidth}
+ fillColor={shapeStyle.fillColor}
+ strokeColor={shapeStyle.strokeColor}
+ isIcon={false}
+ />
+ ) : (
+ <InputDataNodeSvg
+ isCollection={isCollection}
+ {...nodeDimensions}
+ x={0}
+ y={0}
+ strokeWidth={shapeStyle.strokeWidth}
+ fillColor={shapeStyle.fillColor}
+ strokeColor={shapeStyle.strokeColor}
+ />
+ )}
</svg>
<PositionalNodeHandles isTargeted={isTargeted &&
isValidConnectionTarget} nodeId={id} />
<div
- ref={ref}
- className={`kie-dmn-editor--node kie-dmn-editor--input-data-node
${additionalClasses}`}
- tabIndex={-1}
onDoubleClick={triggerEditing}
onKeyDown={triggerEditingIfEnter}
+ style={alternativeSvgStyle}
+ className={selectedAlternativeClass}
+ ref={ref}
+ tabIndex={-1}
>
{/* {`render count: ${renderCount.current}`}
<br /> */}
- <InfoNodePanel isVisible={!isTargeted && shouldActLikeHovered} />
+ <div className={`kie-dmn-editor--node ${additionalClasses}`}>
+ <InfoNodePanel isVisible={!isTargeted && shouldActLikeHovered} />
- <OutgoingStuffNodePanel
- isVisible={!isTargeted && shouldActLikeHovered}
- nodeTypes={outgoingStructure[NODE_TYPES.inputData].nodes}
- edgeTypes={outgoingStructure[NODE_TYPES.inputData].edges}
- />
- <EditableNodeLabel
- id={id}
- namedElement={inputData}
- namedElementQName={dmnObjectQName}
- isEditing={isEditingLabel}
- setEditing={setEditingLabel}
- position={getNodeLabelPosition(type as NodeType)}
- value={inputData["@_label"] ?? inputData["@_name"]}
- onChange={setName}
- onGetAllUniqueNames={getAllFeelVariableUniqueNames}
- shouldCommitOnBlur={true}
- fontCssProperties={fontCssProperties}
- />
- {shouldActLikeHovered && (
- <NodeResizerHandle
- nodeType={type as NodeType}
- snapGrid={snapGrid}
- nodeId={id}
- nodeShapeIndex={shape.index}
+ <OutgoingStuffNodePanel
+ isVisible={!isTargeted && shouldActLikeHovered}
+ nodeTypes={outgoingStructure[NODE_TYPES.inputData].nodes}
+ edgeTypes={outgoingStructure[NODE_TYPES.inputData].edges}
+ />
+ {!isAlternativeInputDataShape && (
+ <EditableNodeLabel
+ id={id}
+ namedElement={inputData}
+ namedElementQName={dmnObjectQName}
+ isEditing={isEditingLabel}
+ setEditing={setEditingLabel}
+ position={getNodeLabelPosition({
+ nodeType: type as typeof NODE_TYPES.inputData,
+ isAlternativeInputDataShape,
+ })}
+ value={inputData["@_label"] ?? inputData["@_name"]}
+ onChange={setName}
+ onGetAllUniqueNames={getAllFeelVariableUniqueNames}
+ shouldCommitOnBlur={true}
+ fontCssProperties={fontCssProperties}
+ />
+ )}
+ {shouldActLikeHovered && (
+ <NodeResizerHandle
+ nodeType={type as typeof NODE_TYPES.inputData}
+ snapGrid={snapGrid}
+ nodeId={id}
+ nodeShapeIndex={shape.index}
+ isAlternativeInputDataShape={isAlternativeInputDataShape}
+ />
+ )}
+ <DataTypeNodePanel
+ isVisible={!isTargeted && shouldActLikeHovered}
+ variable={inputData.variable}
+ dmnObjectNamespace={dmnObjectNamespace}
+ shape={shape}
+ onCreate={onCreateDataType}
+ onChange={onTypeRefChange}
+ onToggle={setDataTypePanelExpanded}
+ />
+ </div>
+ {/* Creates a div element with the node size to push down the
<EditableNodeLabel /> */}
+ {isAlternativeInputDataShape && <div style={{ height:
nodeDimensions.height, flexShrink: 0 }} />}
+ {isAlternativeInputDataShape && (
+ <EditableNodeLabel
+ id={id}
+ namedElement={inputData}
+ namedElementQName={dmnObjectQName}
+ isEditing={isEditingLabel}
+ setEditing={setEditingLabel}
+ position={getNodeLabelPosition({ nodeType: type as NodeType,
isAlternativeInputDataShape })}
+ value={inputData["@_label"] ?? inputData["@_name"]}
+ onChange={setName}
+ onGetAllUniqueNames={getAllFeelVariableUniqueNames}
+ shouldCommitOnBlur={true}
+ // Keeps the text on top of the selected layer
+ fontCssProperties={{ ...fontCssProperties, zIndex: 2000 }}
+ setLabelHeight={setAlternativeEditableNodeHeight}
/>
)}
- <DataTypeNodePanel
- isVisible={!isTargeted && shouldActLikeHovered}
- variable={inputData.variable}
- dmnObjectNamespace={dmnObjectNamespace}
- shape={shape}
- onCreate={onCreateDataType}
- onChange={onTypeRefChange}
- onToggle={setDataTypePanelExpanded}
- />
</div>
</>
);
@@ -273,7 +348,12 @@ export const DecisionNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.decision,
+ snapGrid,
+ shape,
+ isExternal,
+ });
const setName = useCallback<OnEditableNodeLabelChange>(
(newName: string) => {
dmnEditorStoreApi.setState((state) => {
@@ -359,7 +439,7 @@ export const DecisionNode = React.memo(
namedElementQName={dmnObjectQName}
isEditing={isEditingLabel}
setEditing={setEditingLabel}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.decision })}
value={decision["@_label"] ?? decision["@_name"]}
onChange={setName}
onGetAllUniqueNames={getAllFeelVariableUniqueNames}
@@ -368,7 +448,7 @@ export const DecisionNode = React.memo(
/>
{shouldActLikeHovered && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.decision}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -422,7 +502,7 @@ export const BkmNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({ nodeType: type as typeof
NODE_TYPES.bkm, snapGrid, shape, isExternal });
const setName = useCallback<OnEditableNodeLabelChange>(
(newName: string) => {
dmnEditorStoreApi.setState((state) => {
@@ -492,7 +572,7 @@ export const BkmNode = React.memo(
namedElementQName={dmnObjectQName}
isEditing={isEditingLabel}
setEditing={setEditingLabel}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.bkm })}
value={bkm["@_label"] ?? bkm["@_name"]}
onChange={setName}
onGetAllUniqueNames={getAllFeelVariableUniqueNames}
@@ -501,7 +581,7 @@ export const BkmNode = React.memo(
/>
{shouldActLikeHovered && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.bkm}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -555,7 +635,12 @@ export const KnowledgeSourceNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.knowledgeSource,
+ snapGrid,
+ shape,
+ isExternal,
+ });
const setName = useCallback<OnEditableNodeLabelChange>(
(newName: string) => {
dmnEditorStoreApi.setState((state) => {
@@ -609,7 +694,7 @@ export const KnowledgeSourceNode = React.memo(
id={id}
namedElement={knowledgeSource}
namedElementQName={dmnObjectQName}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.knowledgeSource })}
isEditing={isEditingLabel}
setEditing={setEditingLabel}
value={knowledgeSource["@_label"] ?? knowledgeSource["@_name"]}
@@ -621,7 +706,7 @@ export const KnowledgeSourceNode = React.memo(
/>
{shouldActLikeHovered && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.knowledgeSource}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -666,7 +751,12 @@ export const TextAnnotationNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.textAnnotation,
+ snapGrid,
+ shape,
+ isExternal,
+ });
const setText = useCallback(
(newText: string) => {
dmnEditorStoreApi.setState((state) => {
@@ -720,7 +810,7 @@ export const TextAnnotationNode = React.memo(
id={id}
namedElement={undefined}
namedElementQName={undefined}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.textAnnotation })}
isEditing={isEditingLabel}
setEditing={setEditingLabel}
value={textAnnotation["@_label"] ?? textAnnotation.text?.__$$text}
@@ -732,7 +822,7 @@ export const TextAnnotationNode = React.memo(
/>
{shouldActLikeHovered && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.textAnnotation}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -780,7 +870,12 @@ export const DecisionServiceNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.decisionService,
+ snapGrid,
+ shape,
+ isExternal,
+ });
const setName = useCallback<OnEditableNodeLabelChange>(
(newName: string) => {
dmnEditorStoreApi.setState((state) => {
@@ -913,7 +1008,7 @@ export const DecisionServiceNode = React.memo(
id={id}
namedElement={decisionService}
namedElementQName={dmnObjectQName}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.decisionService })}
isEditing={isEditingLabel}
setEditing={setEditingLabel}
value={decisionService["@_label"] ?? decisionService["@_name"]}
@@ -924,7 +1019,7 @@ export const DecisionServiceNode = React.memo(
/>
{selected && !dragging && !isCollapsed && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.decisionService}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -977,7 +1072,12 @@ export const GroupNode = React.memo(
);
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.group,
+ snapGrid,
+ shape,
+ isExternal,
+ });
const setName = useCallback<OnEditableNodeLabelChange>(
(newName: string) => {
dmnEditorStoreApi.setState((state) => {
@@ -996,6 +1096,7 @@ export const GroupNode = React.memo(
bounds: n.data.shape["dc:Bounds"]!,
container: shape["dc:Bounds"]!,
snapGrid: state.diagram.snapGrid,
+ isAlternativeInputDataShape:
state.computed(state).isAlternativeInputDataShape(),
containerMinSizes: MIN_NODE_SIZES[NODE_TYPES.group],
boundsMinSizes: MIN_NODE_SIZES[n.type as NodeType],
}).isInside
@@ -1051,7 +1152,7 @@ export const GroupNode = React.memo(
id={id}
namedElement={undefined}
namedElementQName={undefined}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.group })}
isEditing={isEditingLabel}
setEditing={setEditingLabel}
value={group["@_label"] ?? group["@_name"]}
@@ -1063,7 +1164,7 @@ export const GroupNode = React.memo(
/>
{selected && !dragging && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.group}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -1092,7 +1193,6 @@ export const UnknownNode = React.memo(
const isExternal = !!dmnObjectQName.prefix;
const snapGrid = useDmnEditorStore((s) => s.diagram.snapGrid);
- const enableCustomNodeStyles = useDmnEditorStore((s) =>
s.diagram.overlays.enableCustomNodeStyles);
const isHovered = useIsHovered(ref);
const isResizing = useNodeResizing(id);
const shouldActLikeHovered = useDmnEditorStore(
@@ -1101,7 +1201,12 @@ export const UnknownNode = React.memo(
const { isTargeted, isValidConnectionTarget } =
useConnectionTargetStatus(id, shouldActLikeHovered);
const className = useNodeClassName(isValidConnectionTarget, id);
- const nodeDimensions = useNodeDimensions(type as NodeType, snapGrid,
shape, isExternal);
+ const nodeDimensions = useNodeDimensions({
+ nodeType: type as typeof NODE_TYPES.unknown,
+ snapGrid,
+ shape,
+ isExternal,
+ });
return (
<>
@@ -1120,7 +1225,7 @@ export const UnknownNode = React.memo(
id={id}
namedElement={undefined}
namedElementQName={undefined}
- position={getNodeLabelPosition(type as NodeType)}
+ position={getNodeLabelPosition({ nodeType: type as typeof
NODE_TYPES.unknown })}
isEditing={false}
setEditing={() => {}}
value={`? `}
@@ -1131,7 +1236,7 @@ export const UnknownNode = React.memo(
/>
{selected && !dragging && (
<NodeResizerHandle
- nodeType={type as NodeType}
+ nodeType={type as typeof NODE_TYPES.unknown}
snapGrid={snapGrid}
nodeId={id}
nodeShapeIndex={shape.index}
@@ -1161,13 +1266,25 @@ const resizerControlStyle = {
border: "none",
};
-export function NodeResizerHandle(props: {
+type NodeResizeHandleProps = {
snapGrid: SnapGrid;
nodeId: string;
- nodeType: NodeType;
nodeShapeIndex: number;
-}) {
- const minSize = MIN_NODE_SIZES[props.nodeType](props.snapGrid);
+} & (
+ | { nodeType: Extract<NodeType, typeof NODE_TYPES.inputData>;
isAlternativeInputDataShape: boolean }
+ | { nodeType: Exclude<NodeType, typeof NODE_TYPES.inputData> }
+);
+
+export function NodeResizerHandle(props: NodeResizeHandleProps) {
+ const minSize =
+ props.nodeType === NODE_TYPES.inputData
+ ? MIN_NODE_SIZES[props.nodeType]({
+ snapGrid: props.snapGrid,
+ isAlternativeInputDataShape: props.isAlternativeInputDataShape,
+ })
+ : MIN_NODE_SIZES[props.nodeType]({
+ snapGrid: props.snapGrid,
+ });
return (
<RF.NodeResizeControl style={resizerControlStyle}
minWidth={minSize["@_width"]} minHeight={minSize["@_height"]}>
<div
@@ -1189,22 +1306,39 @@ function useNodeResizing(id: string): boolean {
return RF.useStore((s) => s.nodeInternals.get(id)?.resizing ?? false);
}
-function useNodeDimensions(
- type: NodeType,
- snapGrid: SnapGrid,
- shape: DMNDI15__DMNShape,
- isExternal: boolean
-): RF.Dimensions {
- if (type === NODE_TYPES.decisionService && (isExternal ||
shape["@_isCollapsed"])) {
- return DECISION_SERVICE_COLLAPSED_DIMENSIONS;
- }
+type NodeDimensionsProps = {
+ snapGrid: SnapGrid;
+ shape: DMNDI15__DMNShape;
+ isExternal: boolean;
+} & (
+ | { nodeType: Extract<NodeType, typeof NODE_TYPES.inputData>;
isAlternativeInputDataShape: boolean }
+ | { nodeType: Exclude<NodeType, typeof NODE_TYPES.inputData> }
+);
- const minSizes = MIN_NODE_SIZES[type](snapGrid);
+function useNodeDimensions(props: NodeDimensionsProps): RF.Dimensions {
+ const { nodeType, snapGrid, shape, isExternal } = props;
+ const isAlternativeInputDataShape = nodeType === NODE_TYPES.inputData ?
props.isAlternativeInputDataShape : false;
+
+ return useMemo(() => {
+ if (nodeType === NODE_TYPES.decisionService && (isExternal ||
shape["@_isCollapsed"])) {
+ return DECISION_SERVICE_COLLAPSED_DIMENSIONS;
+ }
+
+ const minSizes =
+ nodeType === NODE_TYPES.inputData
+ ? MIN_NODE_SIZES[nodeType]({
+ snapGrid,
+ isAlternativeInputDataShape,
+ })
+ : MIN_NODE_SIZES[nodeType]({
+ snapGrid,
+ });
- return {
- width: snapShapeDimensions(snapGrid, shape, minSizes).width,
- height: snapShapeDimensions(snapGrid, shape, minSizes).height,
- };
+ return {
+ width: snapShapeDimensions(snapGrid, shape, minSizes).width,
+ height: snapShapeDimensions(snapGrid, shape, minSizes).height,
+ };
+ }, [isAlternativeInputDataShape, isExternal, shape, snapGrid, nodeType]);
}
function useHoveredNodeAlwaysOnTop(
diff --git a/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
b/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
index eb5ab1965db..67f341e1e3d 100644
--- a/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
+++ b/packages/dmn-editor/src/externalNodes/DmnObjectListItem.tsx
@@ -18,6 +18,7 @@
*/
import * as React from "react";
+import { useMemo } from "react";
import { DMN15__tDefinitions } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
import { Unpacked } from "../tsExt/tsExt";
import { TypeRefLabel } from "../dataTypes/TypeRefLabel";
@@ -29,8 +30,6 @@ import { DmnBuiltInDataType, generateUuid } from
"@kie-tools/boxed-expression-co
import { useDmnEditorStore } from "../store/StoreContext";
import { useExternalModels } from
"../includedModels/DmnEditorDependenciesContext";
import { DMN15_SPEC } from "../Dmn15Spec";
-import { useCallback } from "react";
-import { State } from "../store/Store";
export function DmnObjectListItem({
dmnObject,
@@ -48,6 +47,7 @@ export function DmnObjectListItem({
const allTopLevelDataTypesByFeelName = useDmnEditorStore(
(s) =>
s.computed(s).getDataTypes(externalModelsByNamespace).allTopLevelDataTypesByFeelName
);
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
const displayName = dmnObject
? buildFeelQNameFromNamespace({
@@ -66,7 +66,16 @@ export function DmnObjectListItem({
)
);
- const Icon = dmnObject ? NodeIcon(getNodeTypeFromDmnObject(dmnObject)) : ()
=> <></>;
+ const Icon = useMemo(() => {
+ if (dmnObject === undefined) {
+ throw new Error("Icon can't be defined without a DMN object");
+ }
+ const nodeType = getNodeTypeFromDmnObject(dmnObject);
+ if (nodeType === undefined) {
+ throw new Error("Can't determine node icon with undefined node type");
+ }
+ return NodeIcon({ nodeType, isAlternativeInputDataShape });
+ }, [dmnObject, isAlternativeInputDataShape]);
return !dmnObject ? (
<>{dmnObjectHref}</>
diff --git a/packages/dmn-editor/src/icons/Icons.tsx
b/packages/dmn-editor/src/icons/Icons.tsx
index 39f03b4d38a..e58f7558b33 100644
--- a/packages/dmn-editor/src/icons/Icons.tsx
+++ b/packages/dmn-editor/src/icons/Icons.tsx
@@ -18,7 +18,9 @@
*/
import * as React from "react";
+import { useMemo } from "react";
import {
+ AlternativeInputDataNodeSvg,
BkmNodeSvg,
DecisionNodeSvg,
DecisionServiceNodeSvg,
@@ -36,23 +38,43 @@ const radius = 34;
const svgViewboxPadding = Math.sqrt(Math.pow(radius, 2) / 2) - radius / 2; //
This lets us create a square that will perfectly fit inside the button circle.
const nodeSvgProps = { width: 200, height: 120, x: 16, y: 48, strokeWidth: 16
};
-const nodeSvgViewboxSize = nodeSvgProps.width + 2 * nodeSvgProps.strokeWidth;
-export function RoundSvg({ children }: React.PropsWithChildren<{}>) {
+export type NodeIcons =
+ | { isAlternativeInputDataShape: boolean; nodeType: typeof
NODE_TYPES.inputData }
+ | {
+ isAlternativeInputDataShape?: boolean;
+ nodeType: Exclude<NodeType, typeof NODE_TYPES.inputData>;
+ };
+
+export function RoundSvg({
+ children,
+ padding,
+ height,
+ viewBox,
+}: React.PropsWithChildren<{ padding?: string; height?: number; viewBox?:
number }>) {
+ const style = useMemo(
+ () => (padding !== undefined ? { padding, height } : { padding:
`${svgViewboxPadding}px`, height }),
+ [padding, height]
+ );
+
+ const nodeSvgViewboxSize = useMemo(() => {
+ return viewBox ?? nodeSvgProps.width + 2 * nodeSvgProps.strokeWidth;
+ }, [viewBox]);
+
return (
<svg
className={"kie-dmn-editor--round-svg-container"}
viewBox={`0 0 ${nodeSvgViewboxSize} ${nodeSvgViewboxSize}`}
- style={{ padding: `${svgViewboxPadding}px` }}
+ style={style}
>
{children}
</svg>
);
}
-export function NodeIcon(nodeType?: NodeType) {
+export function NodeIcon({ isAlternativeInputDataShape, nodeType }: NodeIcons)
{
return switchExpression(nodeType, {
- [NODE_TYPES.inputData]: InputDataIcon,
+ [NODE_TYPES.inputData]: isAlternativeInputDataShape ?
AlternativeInputDataIcon : InputDataIcon,
[NODE_TYPES.decision]: DecisionIcon,
[NODE_TYPES.bkm]: BkmIcon,
[NODE_TYPES.knowledgeSource]: KnowledgeSourceIcon,
@@ -64,14 +86,34 @@ export function NodeIcon(nodeType?: NodeType) {
});
}
-export function InputDataIcon() {
+export function InputDataIcon(props: { padding?: string; height?: number }) {
return (
- <RoundSvg>
+ <RoundSvg padding={props.padding} height={props.height}>
<InputDataNodeSvg {...nodeSvgProps} />
</RoundSvg>
);
}
+export function AlternativeInputDataIcon(props: {
+ padding?: string;
+ height?: number;
+ viewBox?: number;
+ transform?: string;
+}) {
+ return (
+ <RoundSvg padding={props.padding ?? "0px"} height={props.height}
viewBox={props.viewBox}>
+ <AlternativeInputDataNodeSvg
+ {...nodeSvgProps}
+ isIcon={true}
+ width={80}
+ height={100}
+ strokeWidth={8}
+ transform={props.transform ?? "translate(80, 60)"}
+ />
+ </RoundSvg>
+ );
+}
+
export function DecisionIcon() {
return (
<RoundSvg>
diff --git a/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
b/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
index f2d511238c8..aa33665f767 100644
--- a/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
+++ b/packages/dmn-editor/src/mutations/addDecisionToDecisionService.ts
@@ -95,6 +95,7 @@ export function getSectionForDecisionInsideDecisionService({
container: decisionServiceShape["dc:Bounds"],
divingLineLocalY:
getDecisionServiceDividerLineLocalY(decisionServiceShape),
snapGrid,
+ isAlternativeInputDataShape: false,
containerMinSizes: MIN_NODE_SIZES[NODE_TYPES.decisionService],
boundsMinSizes: MIN_NODE_SIZES[NODE_TYPES.decision],
});
diff --git a/packages/dmn-editor/src/mutations/addOrGetDrd.ts
b/packages/dmn-editor/src/mutations/addOrGetDrd.ts
index 3165f05833b..a1ff36b1681 100644
--- a/packages/dmn-editor/src/mutations/addOrGetDrd.ts
+++ b/packages/dmn-editor/src/mutations/addOrGetDrd.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import { generateUuid } from "@kie-tools/boxed-expression-component/dist/api";
import { DMN15__tDefinitions } from
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
export function getDefaultDrdName({ drdIndex }: { drdIndex: number }) {
@@ -32,6 +33,7 @@ export function addOrGetDrd({ definitions, drdIndex }: {
definitions: DMN15__tDe
definitions["dmndi:DMNDI"]["dmndi:DMNDiagram"][drdIndex] ??= {};
const defaultDiagram =
definitions["dmndi:DMNDI"]["dmndi:DMNDiagram"][drdIndex];
+ defaultDiagram["@_id"] ??= generateUuid();
defaultDiagram["@_name"] ??= defaultName;
defaultDiagram["@_useAlternativeInputDataShape"] ??= false;
defaultDiagram["dmndi:DMNDiagramElement"] ??= [];
diff --git a/packages/dmn-editor/src/mutations/resizeNode.ts
b/packages/dmn-editor/src/mutations/resizeNode.ts
index 132dc7c6019..bd6e3cafe7d 100644
--- a/packages/dmn-editor/src/mutations/resizeNode.ts
+++ b/packages/dmn-editor/src/mutations/resizeNode.ts
@@ -77,7 +77,7 @@ export function resizeNode({
if (!change.isExternal) {
ds.encapsulatedDecision?.forEach((ed) => {
const edShape = dmnShapesByHref.get(ed["@_href"])!;
- const dim = snapShapeDimensions(snapGrid, edShape,
MIN_NODE_SIZES[NODE_TYPES.decision](snapGrid));
+ const dim = snapShapeDimensions(snapGrid, edShape,
MIN_NODE_SIZES[NODE_TYPES.decision]({ snapGrid }));
const pos = snapShapePosition(snapGrid, edShape);
if (pos.x + dim.width > limit.x) {
limit.x = pos.x + dim.width;
@@ -91,7 +91,7 @@ export function resizeNode({
// Output Decisions don't limit the resizing vertically, only
horizontally.
ds.outputDecision?.forEach((ed) => {
const edShape = dmnShapesByHref.get(ed["@_href"])!;
- const dim = snapShapeDimensions(snapGrid, edShape,
MIN_NODE_SIZES[NODE_TYPES.decision](snapGrid));
+ const dim = snapShapeDimensions(snapGrid, edShape,
MIN_NODE_SIZES[NODE_TYPES.decision]({ snapGrid }));
const pos = snapShapePosition(snapGrid, edShape);
if (pos.x + dim.width > limit.x) {
limit.x = pos.x + dim.width;
diff --git
a/packages/dmn-editor/src/mutations/updateDecisionServiceDividerLine.ts
b/packages/dmn-editor/src/mutations/updateDecisionServiceDividerLine.ts
index 88fb40d7bde..f3b7f56dd5c 100644
--- a/packages/dmn-editor/src/mutations/updateDecisionServiceDividerLine.ts
+++ b/packages/dmn-editor/src/mutations/updateDecisionServiceDividerLine.ts
@@ -62,8 +62,8 @@ export function updateDecisionServiceDividerLine({
throw new Error("DMN MUTATION: Cannot reposition divider line of
non-existent Decision Service");
}
- const decisionMinSizes = MIN_NODE_SIZES[NODE_TYPES.decision](snapGrid);
- const decisionServiceMinSizes =
MIN_NODE_SIZES[NODE_TYPES.decisionService](snapGrid);
+ const decisionMinSizes = MIN_NODE_SIZES[NODE_TYPES.decision]({ snapGrid });
+ const decisionServiceMinSizes = MIN_NODE_SIZES[NODE_TYPES.decisionService]({
snapGrid });
const snappedPosition = snapShapePosition(snapGrid, shape);
const snappedDimensions = snapShapeDimensions(snapGrid, shape,
decisionServiceMinSizes);
diff --git a/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
b/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
index 12a5ccff7f6..695efbea223 100644
--- a/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
@@ -56,14 +56,24 @@ export function SingleNodeProperties({ nodeId }: { nodeId:
string }) {
const { externalModelsByNamespace } = useExternalModels();
const node = useDmnEditorStore((s) =>
s.computed(s).getDiagramData(externalModelsByNamespace).nodesById.get(nodeId));
const [isSectionExpanded, setSectionExpanded] = useState<boolean>(true);
+ const isAlternativeInputDataShape = useDmnEditorStore((s) =>
s.computed(s).isAlternativeInputDataShape());
const nodeIds = useMemo(() => (node?.id ? [node.id] : []), [node?.id]);
+ const Icon = useMemo(() => {
+ if (node?.data?.dmnObject === undefined) {
+ throw new Error("Icon can't be defined without a DMN object");
+ }
+ const nodeType = getNodeTypeFromDmnObject(node.data.dmnObject);
+ if (nodeType === undefined) {
+ throw new Error("Can't determine node icon with undefined node type");
+ }
+ return NodeIcon({ nodeType, isAlternativeInputDataShape });
+ }, [isAlternativeInputDataShape, node?.data.dmnObject]);
+
if (!node) {
return <>Node not found: {nodeId}</>;
}
- const Icon = NodeIcon(getNodeTypeFromDmnObject(node!.data!.dmnObject!));
-
return (
<Form>
<FormSection
diff --git a/packages/dmn-editor/src/store/Store.ts
b/packages/dmn-editor/src/store/Store.ts
index a02b2de50e2..62b5a9289b2 100644
--- a/packages/dmn-editor/src/store/Store.ts
+++ b/packages/dmn-editor/src/store/Store.ts
@@ -129,6 +129,8 @@ export type Computed = {
getDiagramData(e: ExternalModelsIndex | undefined): ReturnType<typeof
computeDiagramData>;
+ isAlternativeInputDataShape(): boolean;
+
isDropTargetNodeValidForSelection(e: ExternalModelsIndex | undefined):
boolean;
getExternalModelTypesByNamespace: (
@@ -342,6 +344,13 @@ export function createDmnEditorStore(model:
State["dmn"]["model"], computedCache
]);
},
+ isAlternativeInputDataShape: () =>
+ computedCache.cached(
+ "isAlternativeInputDataShape",
+ (drdIndex, dmnDiagram) =>
dmnDiagram?.[drdIndex]["@_useAlternativeInputDataShape"] ?? false,
+ [s.diagram.drdIndex,
s.dmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]] as const
+ ),
+
isDropTargetNodeValidForSelection: (externalModelsByNamespace:
ExternalModelsIndex | undefined) =>
computedCache.cached("isDropTargetNodeValidForSelection",
computeIsDropTargetNodeValidForSelection, [
s.diagram.dropTargetNode,
@@ -374,6 +383,7 @@ export function createDmnEditorStore(model:
State["dmn"]["model"], computedCache
s.dmn.model.definitions,
s.computed(s).getExternalModelTypesByNamespace(externalModelsByNamespace),
s.computed(s).indexes(),
+ s.computed(s).isAlternativeInputDataShape(),
]),
};
},
diff --git a/packages/dmn-editor/src/store/computed/computeDiagramData.ts
b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
index bc71ae64dd0..21484fc6632 100644
--- a/packages/dmn-editor/src/store/computed/computeDiagramData.ts
+++ b/packages/dmn-editor/src/store/computed/computeDiagramData.ts
@@ -61,7 +61,8 @@ export function computeDiagramData(
diagram: State["diagram"],
definitions: State["dmn"]["model"]["definitions"],
externalModelTypesByNamespace:
TypeOrReturnType<Computed["getExternalModelTypesByNamespace"]>,
- indexes: TypeOrReturnType<Computed["indexes"]>
+ indexes: TypeOrReturnType<Computed["indexes"]>,
+ isAlternativeInputDataShape: boolean
) {
// console.time("nodes");
___NASTY_HACK_FOR_SAFARI_to_force_redrawing_svgs_and_avoid_repaint_glitches.flag
=
@@ -182,7 +183,13 @@ export function computeDiagramData(
position: snapShapePosition(diagram.snapGrid, shape),
data,
zIndex: NODE_LAYERS.NODES,
- style: { ...snapShapeDimensions(diagram.snapGrid, shape,
MIN_NODE_SIZES[type](diagram.snapGrid)) },
+ style: {
+ ...snapShapeDimensions(
+ diagram.snapGrid,
+ shape,
+ MIN_NODE_SIZES[type]({ snapGrid: diagram.snapGrid,
isAlternativeInputDataShape })
+ ),
+ },
};
if (dmnObject?.__$$element === "decisionService") {
diff --git a/packages/dmn-editor/src/store/computed/initial.ts
b/packages/dmn-editor/src/store/computed/initial.ts
index 6b497a254a9..12d509df42b 100644
--- a/packages/dmn-editor/src/store/computed/initial.ts
+++ b/packages/dmn-editor/src/store/computed/initial.ts
@@ -37,6 +37,10 @@ export const INITIAL_COMPUTED_CACHE: Cache<Computed> = {
value: undefined,
dependencies: [],
},
+ isAlternativeInputDataShape: {
+ value: false,
+ dependencies: [],
+ },
isDropTargetNodeValidForSelection: {
value: undefined,
dependencies: [],
diff --git a/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx
b/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx
index 14ed32fd83b..f232d5dab75 100644
--- a/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx
+++ b/packages/dmn-editor/src/svg/DmnDiagramSvg.tsx
@@ -41,6 +41,7 @@ import {
TextAnnotationNodeSvg,
UnknownNodeSvg,
NodeLabelPosition,
+ AlternativeInputDataNodeSvg,
} from "../diagram/nodes/NodeSvgs";
import { NODE_TYPES } from "../diagram/nodes/NodeTypes";
import { useMemo } from "react";
@@ -56,6 +57,8 @@ import { NodeType } from
"../diagram/connections/graphStructure";
import { buildFeelQNameFromXmlQName } from "../feel/buildFeelQName";
import { Text } from "@visx/text";
import { TypeOrReturnType } from "../store/ComputedStateCache";
+import { UniqueNameIndex } from "../Dmn15Spec";
+import { DataTypeIndex } from "../dataTypes/DataTypes";
export function DmnDiagramSvg({
nodes,
@@ -63,12 +66,18 @@ export function DmnDiagramSvg({
snapGrid,
thisDmn,
importsByNamespace,
+ isAlternativeInputDataShape,
+ allDataTypesById,
+ allTopLevelItemDefinitionUniqueNames,
}: {
nodes: RF.Node<DmnDiagramNodeData>[];
edges: RF.Edge<DmnDiagramEdgeData>[];
snapGrid: SnapGrid;
thisDmn: State["dmn"];
importsByNamespace: TypeOrReturnType<Computed["importsByNamespace"]>;
+ isAlternativeInputDataShape: boolean;
+ allDataTypesById: DataTypeIndex;
+ allTopLevelItemDefinitionUniqueNames: UniqueNameIndex;
}) {
const { nodesSvg, nodesById } = useMemo(() => {
const nodesById = new Map<string, RF.Node<DmnDiagramNodeData>>();
@@ -88,6 +97,17 @@ export function DmnDiagramSvg({
const { height, width, ...style } = node.style!;
+ const isCollection =
+ node.data?.dmnObject?.__$$element === "inputData"
+ ? allDataTypesById.get(
+
allTopLevelItemDefinitionUniqueNames.get(node.data.dmnObject.variable?.["@_typeRef"]
?? "") ?? ""
+ )?.itemDefinition?.["@_isCollection"] ?? false
+ : node.data?.dmnObject?.__$$element === "decision"
+ ? allDataTypesById.get(
+
allTopLevelItemDefinitionUniqueNames.get(node.data.dmnObject.variable?.["@_typeRef"]
?? "") ?? ""
+ )?.itemDefinition?.["@_isCollection"] ?? false
+ : false;
+
const label =
node.data?.dmnObject?.__$$element === "group"
? node.data.dmnObject?.["@_label"] ??
node.data?.dmnObject?.["@_name"] ?? "<Empty>"
@@ -103,16 +123,29 @@ export function DmnDiagramSvg({
return (
<g data-kie-dmn-node-id={node.id} key={node.id}>
- {node.type === NODE_TYPES.inputData && (
- <InputDataNodeSvg
- width={node.width!}
- height={node.height!}
- x={node.positionAbsolute!.x}
- y={node.positionAbsolute!.y}
- {...style}
- {...shapeStyle}
- />
- )}
+ {node.type === NODE_TYPES.inputData &&
+ (isAlternativeInputDataShape ? (
+ <AlternativeInputDataNodeSvg
+ width={node.width!}
+ height={node.height!}
+ x={node.positionAbsolute!.x}
+ y={node.positionAbsolute!.y}
+ {...style}
+ {...shapeStyle}
+ isIcon={false}
+ isCollection={isCollection}
+ />
+ ) : (
+ <InputDataNodeSvg
+ width={node.width!}
+ height={node.height!}
+ x={node.positionAbsolute!.x}
+ y={node.positionAbsolute!.y}
+ {...style}
+ {...shapeStyle}
+ isCollection={isCollection}
+ />
+ ))}
{node.type === NODE_TYPES.decision && (
<DecisionNodeSvg
width={node.width!}
@@ -121,6 +154,7 @@ export function DmnDiagramSvg({
y={node.positionAbsolute!.y}
{...style}
{...shapeStyle}
+ isCollection={isCollection}
/>
)}
{node.type === NODE_TYPES.bkm && (
@@ -192,7 +226,10 @@ export function DmnDiagramSvg({
lineHeight={fontStyle.lineHeight}
style={{ ...fontStyle }}
dy={`calc(1.5em * ${i})`}
- {...getNodeLabelSvgTextAlignmentProps(node,
getNodeLabelPosition(node.type as NodeType))}
+ {...getNodeLabelSvgTextAlignmentProps(
+ node,
+ getNodeLabelPosition({ nodeType: node.type as NodeType,
isAlternativeInputDataShape })
+ )}
>
{labelLine}
</Text>
@@ -203,7 +240,14 @@ export function DmnDiagramSvg({
});
return { nodesSvg, nodesById };
- }, [importsByNamespace, nodes, thisDmn.model.definitions]);
+ }, [
+ allDataTypesById,
+ allTopLevelItemDefinitionUniqueNames,
+ importsByNamespace,
+ isAlternativeInputDataShape,
+ nodes,
+ thisDmn.model.definitions,
+ ]);
return (
<>
@@ -250,6 +294,17 @@ const SVG_NODE_LABEL_TEXT_ADDITIONAL_PADDING_TOP_LEFT = 8;
export function getNodeLabelSvgTextAlignmentProps(n:
RF.Node<DmnDiagramNodeData>, labelPosition: NodeLabelPosition) {
switch (labelPosition) {
+ case "center-bottom":
+ const cbTx = n.position.x! + n.width! / 2;
+ const cbTy = n.position.y! + n.height! + 4;
+ const cbWidth = n.width!;
+ return {
+ verticalAnchor: "start",
+ textAnchor: "middle",
+ transform: `translate(${cbTx},${cbTy})`,
+ width: cbWidth,
+ } as const;
+
case "center-center":
const ccTx = n.position.x! + n.width! / 2;
const ccTy = n.position.y! + n.height! / 2;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]