in packages/dmn-editor/src/boxedExpressions/BoxedExpressionScreen.tsx [97:551]
export function BoxedExpressionScreen({ container }: { container: React.RefObject<HTMLElement> }) {
const { externalModelsByNamespace } = useExternalModels();
const settings = useSettings();
const dmnEditorStoreApi = useDmnEditorStoreApi();
const thisDmn = useDmnEditorStore((s) => s.dmn);
const activeDrgElementId = useDmnEditorStore((s) => s.boxedExpressionEditor.activeDrgElementId);
const isPropertiesPanelOpen = useDmnEditorStore((s) => s.boxedExpressionEditor.propertiesPanel.isOpen);
const externalDmnsByNamespace = useDmnEditorStore(
(s) => s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).dmns
);
const dataTypesTree = useDmnEditorStore((s) => s.computed(s).getDataTypes(externalModelsByNamespace).dataTypesTree);
const importsByNamespace = useDmnEditorStore((s) => s.computed(s).importsByNamespace());
const externalPmmlsByNamespace = useDmnEditorStore(
(s) => s.computed(s).getDirectlyIncludedExternalModelsByNamespace(externalModelsByNamespace).pmmls
);
const isAlternativeInputDataShape = useDmnEditorStore((s) => s.computed(s).isAlternativeInputDataShape());
const drdIndex = useDmnEditorStore((s) => s.computed(s).getDrdIndex());
const externalDmnModelsByNamespaceMap = useDmnEditorStore((s) =>
s.computed(s).getExternalDmnModelsByNamespaceMap(externalModelsByNamespace)
);
const { evaluationResultsByNodeId } = useDmnEditor();
const isEvaluationHighlightsEnabled = useDmnEditorStore((s) => s.diagram.overlays.enableEvaluationHighlights);
const onRequestFeelIdentifiers = useCallback(() => {
return new FeelIdentifiers({
_readonly_dmnDefinitions: dmnEditorStoreApi.getState().dmn.model.definitions,
_readonly_externalDefinitions: externalDmnModelsByNamespaceMap,
});
}, [dmnEditorStoreApi, externalDmnModelsByNamespaceMap]);
const drgElementIndex = useMemo(() => {
if (!activeDrgElementId) {
return undefined;
}
return (thisDmn.model.definitions.drgElement ?? []).findIndex((e) => e["@_id"] === activeDrgElementId);
}, [activeDrgElementId, thisDmn.model.definitions.drgElement]);
const drgElement = useMemo(() => {
if (drgElementIndex === undefined) {
return undefined;
}
const drgElement = thisDmn.model.definitions.drgElement?.[drgElementIndex];
if (!(drgElement?.__$$element === "decision" || drgElement?.__$$element === "businessKnowledgeModel")) {
return undefined;
}
return drgElement;
}, [drgElementIndex, thisDmn.model.definitions.drgElement]);
// BEGIN (setState batching for `expression` and `widthsById`)
//
// These hooks are responsible for building the `expression` and `widthsById` values, passed
// to the BoxedExpressionEditor component.
// More than that, they are responsible for maintaining an up-to-date ref for each one of
// those values, so that batching works normally without having the `onChange` handlers be
// recalculated, breaking batching.
const widthsById = useMemo(() => {
return (
thisDmn.model.definitions["dmndi:DMNDI"]?.["dmndi:DMNDiagram"]?.[drdIndex]["di:extension"]?.[
"kie:ComponentsWidthsExtension"
]?.["kie:ComponentWidths"] ?? []
).reduce((acc, c) => {
if (c["@_dmnElementRef"] === undefined) {
return acc;
} else {
return acc.set(
c["@_dmnElementRef"],
(c["kie:width"] ?? []).map((vv) => vv.__$$text)
);
}
}, new Map<string, number[]>());
}, [drdIndex, thisDmn.model.definitions]);
const expression = useMemo(() => {
if (!drgElement) {
return undefined;
}
return {
boxedExpression: drgElementToBoxedExpression(drgElement),
drgElementIndex,
drgElement,
drgElementType: drgElement.__$$element,
};
}, [drgElement, drgElementIndex]);
const widthsByIdRef = useRef<Map<string, number[]>>(widthsById);
const boxedExpressionRef = useRef<Normalized<BoxedExpression> | undefined>(expression?.boxedExpression);
useEffect(() => {
widthsByIdRef.current = widthsById;
}, [widthsById]);
useEffect(() => {
boxedExpressionRef.current = expression?.boxedExpression;
}, [expression?.boxedExpression]);
const onWidthsChange: React.Dispatch<React.SetStateAction<Map<string, number[]>>> = useCallback(
(newWidthsByIdAction) => {
dmnEditorStoreApi.setState((state) => {
const newWidthsById =
typeof newWidthsByIdAction === "function"
? newWidthsByIdAction(widthsByIdRef.current ?? new Map())
: newWidthsByIdAction;
widthsByIdRef.current = newWidthsById;
updateExpressionWidths({
definitions: state.dmn.model.definitions,
drdIndex: state.computed(state).getDrdIndex(),
widthsById: newWidthsById,
});
});
},
[dmnEditorStoreApi]
);
const setExpression = useCallback(
(args: { definitions: Normalized<DMN15__tDefinitions>; expression: Normalized<BoxedExpression | undefined> }) => {
boxedExpressionRef.current = args.expression;
updateExpression({
definitions: args.definitions,
expression: args.expression!,
drgElementIndex: expression?.drgElementIndex ?? 0,
externalDmnModelsByNamespaceMap,
});
},
[expression?.drgElementIndex, externalDmnModelsByNamespaceMap]
);
const [isRefactorModalOpen, setIsRefactorModalOpen] = useState(false);
const [variableChangedArgs, setVariableChangedArgs] = useState<VariableChangedArgs>();
const [newExpression, setNewExpression] = useState<Normalized<BoxedExpression | undefined>>();
const refactor = useCallback(() => {
if (!variableChangedArgs) {
throw new Error("Can not refactor because `variableChangedArgs` are not set in BoxedExpressionScreen.");
}
dmnEditorStoreApi.setState((state) => {
const drgElement = state.dmn.model.definitions.drgElement?.[expression?.drgElementIndex ?? 0];
if (drgElement?.["@_id"] === variableChangedArgs.variableUuid) {
renameDrgElement({
definitions: state.dmn.model.definitions,
newName: newExpression?.["@_label"] ?? drgElement!["@_name"]!,
index: expression?.drgElementIndex ?? 0,
externalDmnModelsByNamespaceMap,
shouldRenameReferencedExpressions: true,
});
} else {
const identifiersRefactor = new IdentifiersRefactor({
writeableDmnDefinitions: state.dmn.model.definitions,
_readonly_externalDmnModelsByNamespaceMap: externalDmnModelsByNamespaceMap,
});
identifiersRefactor.rename({
identifierUuid: variableChangedArgs.variableUuid,
newName: variableChangedArgs.nameChange?.to ?? "",
});
}
});
}, [
dmnEditorStoreApi,
expression?.drgElementIndex,
externalDmnModelsByNamespaceMap,
newExpression,
variableChangedArgs,
]);
const onExpressionChange = useCallback<OnExpressionChange>(
(args) => {
dmnEditorStoreApi.setState((state) => {
const newExpression =
typeof args.setExpressionAction === "function"
? args.setExpressionAction(boxedExpressionRef.current)
: args.setExpressionAction;
if (!args.expressionChangedArgs) {
setExpression({ definitions: state.dmn.model.definitions, expression: newExpression });
} else {
if (args.expressionChangedArgs.action === Action.VariableChanged) {
if (args.expressionChangedArgs.typeChange && !args.expressionChangedArgs.nameChange) {
updateDrgElementType({
definitions: state.dmn.model.definitions,
expression: newExpression!,
drgElementIndex: expression?.drgElementIndex ?? 0,
});
setExpression({ definitions: state.dmn.model.definitions, expression: newExpression });
} else if (args.expressionChangedArgs.typeChange) {
const identifiersRefactor = new IdentifiersRefactor({
writeableDmnDefinitions: state.dmn.model.definitions,
_readonly_externalDmnModelsByNamespaceMap: externalDmnModelsByNamespaceMap,
});
identifiersRefactor.changeType({
identifierUuid: args.expressionChangedArgs.variableUuid,
newType: args.expressionChangedArgs.typeChange.to,
});
updateDrgElementType({
definitions: state.dmn.model.definitions,
expression: newExpression!,
drgElementIndex: expression?.drgElementIndex ?? 0,
});
}
if (args.expressionChangedArgs.nameChange) {
setVariableChangedArgs(args.expressionChangedArgs);
setNewExpression(newExpression);
if (
isIdentifierReferencedInSomeExpression({
identifierUuid: args.expressionChangedArgs.variableUuid,
dmnDefinitions: state.dmn.model.definitions,
externalDmnModelsByNamespaceMap,
})
) {
setIsRefactorModalOpen(true);
} else {
setExpression({ definitions: state.dmn.model.definitions, expression: newExpression });
}
}
} else {
setExpression({ definitions: state.dmn.model.definitions, expression: newExpression });
}
}
});
},
[dmnEditorStoreApi, expression?.drgElementIndex, externalDmnModelsByNamespaceMap, setExpression]
);
// END (setState batching for `expression` and `widthsById`)
const isResetSupportedOnRootExpression = useMemo(() => {
return expression?.drgElementType === "decision"; // BKMs are ALWAYS functions, and can't be reset.
}, [expression?.drgElementType]);
////
const dataTypes = useMemo<DmnDataType[]>(() => {
const customDataTypes = dataTypesTree.map((d) => ({
isCustom: true,
name: d.feelName,
}));
return [...builtInFeelTypes, ...customDataTypes];
}, [dataTypesTree]);
const pmmlDocuments = useMemo<PmmlDocument[]>(() => {
return [...externalPmmlsByNamespace.entries()].flatMap(([namespace, pmml]) => {
const documentData = getPmmlDocumentData(pmml.model);
const _import = importsByNamespace.get(namespace);
if (!_import) {
return [];
}
return {
document: _import["@_name"],
modelsFromDocument: documentData.models.map((m) => ({
model: m.modelName,
parametersFromModel: m.fields.map((f) => ({
"@_id": generateUuid(),
"@_name": f.fieldName,
description: { __$$text: f.fieldName },
})),
})),
};
});
}, [importsByNamespace, externalPmmlsByNamespace]);
const beeGwtService = useMemo<BeeGwtService>(() => {
return {
getDefaultExpressionDefinition(logicType, typeRef, isRoot) {
const s = dmnEditorStoreApi.getState();
const c = s.computed(s);
const allTopLevelDataTypesByFeelName = c.getDataTypes(externalModelsByNamespace).allTopLevelDataTypesByFeelName;
const nodesById = c.getDiagramData(externalModelsByNamespace).nodesById;
const defaultWidthsById = new Map<string, number[]>();
const defaultExpression = getDefaultBoxedExpression({
logicType,
typeRef,
allTopLevelDataTypesByFeelName,
widthsById: defaultWidthsById,
getDefaultColumnWidth,
getInputs: () => {
const drgElement = s.dmn.model.definitions.drgElement?.[drgElementIndex ?? 0];
if (!isRoot || drgElement?.__$$element !== "decision") {
return undefined;
} else {
return determineInputsForDecision(drgElement, allTopLevelDataTypesByFeelName, nodesById);
}
},
});
return {
expression: defaultExpression,
widthsById: defaultWidthsById,
};
},
selectObject(uuid) {
dmnEditorStoreApi.setState((state) => {
state.boxedExpressionEditor.selectedObjectId = uuid;
});
},
openDataTypePage() {
dmnEditorStoreApi.setState((state) => {
state.navigation.tab = DmnEditorTab.DATA_TYPES;
});
},
};
}, [dmnEditorStoreApi, drgElementIndex, externalModelsByNamespace]);
////
const Icon = useMemo(() => {
if (!drgElement) {
throw new Error("A node Icon must exist for all types of node");
}
const nodeType = getNodeTypeFromDmnObject(drgElement);
if (nodeType === undefined) {
throw new Error("Can't determine node icon with undefined node type");
}
return NodeIcon({ nodeType, isAlternativeInputDataShape });
}, [drgElement, isAlternativeInputDataShape]);
const onConfirmExpressionRefactor = useCallback(() => {
if (!variableChangedArgs) {
throw new Error(
"Can not update variable name because 'variableChangedArgs' is not set in the BoxedExpressionScreen."
);
}
refactor();
setVariableChangedArgs(undefined);
setNewExpression(undefined);
setIsRefactorModalOpen(false);
}, [refactor, variableChangedArgs]);
const onConfirmRenameOnly = useCallback(() => {
setVariableChangedArgs(undefined);
setNewExpression(undefined);
setIsRefactorModalOpen(false);
dmnEditorStoreApi.setState((state) => {
setExpression({ definitions: state.dmn.model.definitions, expression: newExpression });
});
}, [dmnEditorStoreApi, newExpression, setExpression]);
return (
<>
<>
<Flex
className={"kie-dmn-editor--sticky-top-glass-header kie-dmn-editor--boxed-expression-header"}
justifyContent={{ default: "justifyContentSpaceBetween" }}
alignItems={{ default: "alignItemsCenter" }}
direction={{ default: "row" }}
>
<FlexItem>
<Label
className={"kie-dmn-editor--boxed-expression-back"}
onClick={() => {
dmnEditorStoreApi.setState((state) => {
state.dispatch(state).boxedExpressionEditor.close();
});
}}
icon={<ArrowRightIcon style={{ transform: "scale(-1, -1)", marginRight: "8px", marginTop: "4px" }} />}
>
<p>Back to Diagram</p>
</Label>
</FlexItem>
<FlexItem>
<Flex
flexWrap={{ default: "nowrap" }}
justifyContent={{ default: "justifyContentSpaceBetween" }}
alignItems={{ default: "alignItemsCenter" }}
>
<FlexItem>
<div style={{ height: "40px", width: "40px" }}>
<Icon />
</div>
</FlexItem>
<FlexItem>
<TextContent>
<Text component={TextVariants.h2}>{expression?.drgElement["@_name"]}</Text>
</TextContent>
</FlexItem>
<FlexItem style={{ width: "105px" }} />
</Flex>
</FlexItem>
<Flex>
<EvaluationHighlightsBadge />
<aside
className={"kie-dmn-editor--properties-panel-toggle"}
style={{ visibility: isPropertiesPanelOpen ? "hidden" : undefined }}
>
<button
className={"kie-dmn-editor--properties-panel-toggle-button"}
title={"Properties panel"}
onClick={() => {
dmnEditorStoreApi.setState((state) => {
state.boxedExpressionEditor.propertiesPanel.isOpen =
!state.boxedExpressionEditor.propertiesPanel.isOpen;
});
}}
>
<InfoIcon />
</button>
</aside>
</Flex>
</Flex>
<RefactorConfirmationDialog
onConfirmExpressionRefactor={onConfirmExpressionRefactor}
onConfirmRenameOnly={onConfirmRenameOnly}
isRefactorModalOpen={isRefactorModalOpen}
fromName={variableChangedArgs?.nameChange?.from}
toName={variableChangedArgs?.nameChange?.to}
onCancel={() => {
setIsRefactorModalOpen(false);
setVariableChangedArgs(undefined);
setNewExpression(undefined);
}}
/>
<div style={{ flexGrow: 1 }}>
<BoxedExpressionEditor
beeGwtService={beeGwtService}
pmmlDocuments={pmmlDocuments}
isResetSupportedOnRootExpression={isResetSupportedOnRootExpression}
expressionHolderId={activeDrgElementId!}
expressionHolderName={drgElement?.variable?.["@_name"] ?? drgElement?.["@_name"] ?? ""}
expressionHolderTypeRef={drgElement?.variable?.["@_typeRef"] ?? expression?.boxedExpression?.["@_typeRef"]}
expression={expression?.boxedExpression}
onExpressionChange={onExpressionChange}
dataTypes={dataTypes}
scrollableParentRef={container}
onRequestFeelIdentifiers={onRequestFeelIdentifiers}
widthsById={widthsById}
onWidthsChange={onWidthsChange}
isReadOnly={settings.isReadOnly}
evaluationHitsCountById={
isEvaluationHighlightsEnabled
? evaluationResultsByNodeId?.get(activeDrgElementId ?? "")?.evaluationHitsCountByRuleOrRowId
: undefined
}
/>
</div>
</>
</>
);
}