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 c31e2744ceb kie-issues#793: The keyboard shortcuts panel doesn't show
the new DMN Editor keyboard shortcuts (#2279)
c31e2744ceb is described below
commit c31e2744ceb1d622d495987f9bb3c80bbb73beeb
Author: Luiz João Motta <[email protected]>
AuthorDate: Mon May 6 14:01:51 2024 -0300
kie-issues#793: The keyboard shortcuts panel doesn't show the new DMN
Editor keyboard shortcuts (#2279)
---
packages/dmn-editor-envelope/package.json | 1 +
.../dmn-editor-envelope/src/DmnEditorFactory.tsx | 1 +
packages/dmn-editor-envelope/src/DmnEditorRoot.tsx | 177 ++++++++
packages/dmn-editor/src/DmnEditor.css | 1 +
packages/dmn-editor/src/DmnEditor.tsx | 12 +-
.../src/commands/CommandsContextProvider.tsx | 91 +++++
packages/dmn-editor/src/diagram/Diagram.tsx | 411 +------------------
.../dmn-editor/src/diagram/DiagramCommands.tsx | 449 +++++++++++++++++++++
.../envelope/DefaultKeyboardShortcutsService.ts | 1 +
pnpm-lock.yaml | 3 +
10 files changed, 737 insertions(+), 410 deletions(-)
diff --git a/packages/dmn-editor-envelope/package.json
b/packages/dmn-editor-envelope/package.json
index 026f7e09739..44963a5078a 100644
--- a/packages/dmn-editor-envelope/package.json
+++ b/packages/dmn-editor-envelope/package.json
@@ -27,6 +27,7 @@
"@kie-tools-core/editor": "workspace:*",
"@kie-tools-core/envelope": "workspace:*",
"@kie-tools-core/envelope-bus": "workspace:*",
+ "@kie-tools-core/keyboard-shortcuts": "workspace:*",
"@kie-tools-core/notifications": "workspace:*",
"@kie-tools-core/react-hooks": "workspace:*",
"@kie-tools-core/workspace": "workspace:*",
diff --git a/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
b/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
index b5012e4cb2f..411287583be 100644
--- a/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
+++ b/packages/dmn-editor-envelope/src/DmnEditorFactory.tsx
@@ -150,6 +150,7 @@ function DmnEditorRootWrapper({
onOpenFileFromNormalizedPosixPathRelativeToTheWorkspaceRoot
}
workspaceRootAbsolutePosixPath={workspaceRootAbsolutePosixPath}
+ keyboardShortcutsService={envelopeContext?.services.keyboardShortcuts}
/>
);
}
diff --git a/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
b/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
index 50907d890a0..3b06bd42875 100644
--- a/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
+++ b/packages/dmn-editor-envelope/src/DmnEditorRoot.tsx
@@ -41,6 +41,7 @@ import {
imperativePromiseHandle,
PromiseImperativeHandle,
} from "@kie-tools-core/react-hooks/dist/useImperativePromiseHandler";
+import { KeyboardShortcutsService } from
"@kie-tools-core/keyboard-shortcuts/dist/envelope/KeyboardShortcutsService";
export const EXTERNAL_MODELS_SEARCH_GLOB_PATTERN = "**/*.{dmn,pmml}";
@@ -60,6 +61,7 @@ export type DmnEditorRootProps = {
onRequestWorkspaceFileContent:
WorkspaceChannelApi["kogitoWorkspace_resourceContentRequest"];
onOpenFileFromNormalizedPosixPathRelativeToTheWorkspaceRoot:
WorkspaceChannelApi["kogitoWorkspace_openFile"];
workspaceRootAbsolutePosixPath: string;
+ keyboardShortcutsService: KeyboardShortcutsService | undefined;
};
export type DmnEditorRootState = {
@@ -70,6 +72,8 @@ export type DmnEditorRootState = {
externalModelsByNamespace: DmnEditor.ExternalModelsIndex;
readonly: boolean;
externalModelsManagerDoneBootstraping: boolean;
+ keyboardShortcutsRegisterIds: number[];
+ keyboardShortcutsRegistred: boolean;
};
export class DmnEditorRoot extends React.Component<DmnEditorRootProps,
DmnEditorRootState> {
@@ -89,6 +93,8 @@ export class DmnEditorRoot extends
React.Component<DmnEditorRootProps, DmnEditor
openFilenormalizedPosixPathRelativeToTheWorkspaceRoot: undefined,
readonly: true,
externalModelsManagerDoneBootstraping: false,
+ keyboardShortcutsRegisterIds: [],
+ keyboardShortcutsRegistred: false,
};
}
@@ -271,6 +277,177 @@ export class DmnEditorRoot extends
React.Component<DmnEditorRootProps, DmnEditor
);
};
+ public componentDidUpdate(
+ prevProps: Readonly<DmnEditorRootProps>,
+ prevState: Readonly<DmnEditorRootState>,
+ snapshot?: any
+ ): void {
+ if (this.props.keyboardShortcutsService === undefined ||
this.state.keyboardShortcutsRegistred === true) {
+ return;
+ }
+
+ const commands = this.dmnEditorRef.current?.getCommands();
+ if (commands === undefined) {
+ return;
+ }
+ const cancelAction =
this.props.keyboardShortcutsService.registerKeyPress("Escape", "Edit |
Unselect", async () =>
+ commands.cancelAction()
+ );
+ const deleteSelectionBackspace =
this.props.keyboardShortcutsService.registerKeyPress(
+ "Backspace",
+ "Edit | Delete selection",
+ async () => {}
+ );
+ const deleteSelectionDelete =
this.props.keyboardShortcutsService.registerKeyPress(
+ "Delete",
+ "Edit | Delete selection",
+ async () => {}
+ );
+ const selectAll = this.props.keyboardShortcutsService?.registerKeyPress(
+ "A",
+ "Edit | Select/Deselect all",
+ async () => commands.selectAll()
+ );
+ const createGroup = this.props.keyboardShortcutsService?.registerKeyPress(
+ "G",
+ "Edit | Create group wrapping selection",
+ async () => {
+ console.log(" KEY GROUP PRESSED, ", commands);
+ return commands.createGroup();
+ }
+ );
+ const hideFromDrd =
this.props.keyboardShortcutsService?.registerKeyPress("X", "Edit | Hide from
DRD", async () =>
+ commands.hideFromDrd()
+ );
+ const copy =
this.props.keyboardShortcutsService?.registerKeyPress("Ctrl+C", "Edit | Copy
nodes", async () =>
+ commands.copy()
+ );
+ const cut =
this.props.keyboardShortcutsService?.registerKeyPress("Ctrl+X", "Edit | Cut
nodes", async () =>
+ commands.cut()
+ );
+ const paste =
this.props.keyboardShortcutsService?.registerKeyPress("Ctrl+V", "Edit | Paste
nodes", async () =>
+ commands.paste()
+ );
+ const togglePropertiesPanel =
this.props.keyboardShortcutsService?.registerKeyPress(
+ "I",
+ "Misc | Open/Close properties panel",
+ async () => commands.togglePropertiesPanel()
+ );
+ const toggleHierarchyHighlight =
this.props.keyboardShortcutsService?.registerKeyPress(
+ "H",
+ "Misc | Toggle hierarchy highlights",
+ async () => commands.toggleHierarchyHighlight()
+ );
+ const moveUp = this.props.keyboardShortcutsService.registerKeyPress(
+ "Up",
+ "Move | Move selection up",
+ async () => {}
+ );
+ const moveDown = this.props.keyboardShortcutsService.registerKeyPress(
+ "Down",
+ "Move | Move selection down",
+ async () => {}
+ );
+ const moveLeft = this.props.keyboardShortcutsService.registerKeyPress(
+ "Left",
+ "Move | Move selection left",
+ async () => {}
+ );
+ const moveRight = this.props.keyboardShortcutsService.registerKeyPress(
+ "Right",
+ "Move | Move selection right",
+ async () => {}
+ );
+ const bigMoveUp = this.props.keyboardShortcutsService.registerKeyPress(
+ "Shift + Up",
+ "Move | Move selection up a big distance",
+ async () => {}
+ );
+ const bigMoveDown = this.props.keyboardShortcutsService.registerKeyPress(
+ "Shift + Down",
+ "Move | Move selection down a big distance",
+ async () => {}
+ );
+ const bigMoveLeft = this.props.keyboardShortcutsService.registerKeyPress(
+ "Shift + Left",
+ "Move | Move selection left a big distance",
+ async () => {}
+ );
+ const bigMoveRight = this.props.keyboardShortcutsService.registerKeyPress(
+ "Shift + Right",
+ "Move | Move selection right a big distance",
+ async () => {}
+ );
+ const focusOnBounds =
this.props.keyboardShortcutsService?.registerKeyPress(
+ "B",
+ "Navigate | Focus on selection",
+ async () => commands.focusOnSelection()
+ );
+ const resetPosition =
this.props.keyboardShortcutsService?.registerKeyPress(
+ "Space",
+ "Navigate | Reset position to origin",
+ async () => commands.resetPosition()
+ );
+ const pan = this.props.keyboardShortcutsService?.registerKeyDownThenUp(
+ "Alt",
+ "Navigate | Hold and drag to Pan",
+ async () => commands.panDown(),
+ async () => commands.panUp()
+ );
+ const zoom = this.props.keyboardShortcutsService?.registerKeyPress(
+ "Ctrl",
+ "Navigate | Hold and scroll to zoom in/out",
+ async () => {}
+ );
+ const navigateHorizontally =
this.props.keyboardShortcutsService?.registerKeyPress(
+ "Shift",
+ "Navigate | Hold and scroll to navigate horizontally",
+ async () => {}
+ );
+
+ this.setState((prev) => ({
+ ...prev,
+ keyboardShortcutsRegistred: true,
+ keyboardShortcutsRegisterIds: [
+ bigMoveDown,
+ bigMoveLeft,
+ bigMoveRight,
+ bigMoveUp,
+ cancelAction,
+ copy,
+ createGroup,
+ cut,
+ deleteSelectionBackspace,
+ deleteSelectionDelete,
+ focusOnBounds,
+ hideFromDrd,
+ moveDown,
+ moveLeft,
+ moveRight,
+ moveUp,
+ navigateHorizontally,
+ pan,
+ paste,
+ resetPosition,
+ selectAll,
+ toggleHierarchyHighlight,
+ togglePropertiesPanel,
+ zoom,
+ ],
+ }));
+ }
+
+ public componentWillUnmount() {
+ const keyboardShortcuts = this.dmnEditorRef.current?.getCommands();
+ if (keyboardShortcuts === undefined) {
+ return;
+ }
+
+ this.state.keyboardShortcutsRegisterIds.forEach((id) => {
+ this.props.keyboardShortcutsService?.deregister(id);
+ });
+ }
+
public render() {
return (
<>
diff --git a/packages/dmn-editor/src/DmnEditor.css
b/packages/dmn-editor/src/DmnEditor.css
index 1011c465840..7187edd7719 100644
--- a/packages/dmn-editor/src/DmnEditor.css
+++ b/packages/dmn-editor/src/DmnEditor.css
@@ -71,6 +71,7 @@
z-index: 1;
}
.kie-dmn-editor--input-data-node {
+ outline: none;
width: 100%;
height: 100%;
}
diff --git a/packages/dmn-editor/src/DmnEditor.tsx
b/packages/dmn-editor/src/DmnEditor.tsx
index 58b6f73ce4d..571d18ec019 100644
--- a/packages/dmn-editor/src/DmnEditor.tsx
+++ b/packages/dmn-editor/src/DmnEditor.tsx
@@ -57,6 +57,7 @@ import { INITIAL_COMPUTED_CACHE } from
"./store/computed/initial";
import "@kie-tools/dmn-marshaller/dist/kie-extensions"; // This is here
because of the KIE Extension for DMN.
import "./DmnEditor.css"; // Leave it for last, as this overrides some of the
PF and RF styles.
+import { Commands, CommandsContextProvider, useCommands } from
"./commands/CommandsContextProvider";
const ON_MODEL_CHANGE_DEBOUNCE_TIME_IN_MS = 500;
@@ -65,6 +66,7 @@ const SVG_PADDING = 20;
export type DmnEditorRef = {
reset: (mode: DmnLatestModel) => void;
getDiagramSvg: () => Promise<string | undefined>;
+ getCommands: () => Commands;
};
export type EvaluationResults = Record<string, any>;
@@ -171,14 +173,13 @@ export const DmnEditorInternal = ({
const navigationTab = useDmnEditorStore((s) => s.navigation.tab);
const dmn = useDmnEditorStore((s) => s.dmn);
const isDiagramEditingInProgress = useDmnEditorStore((s) =>
s.computed(s).isDiagramEditingInProgress());
-
const dmnEditorStoreApi = useDmnEditorStoreApi();
+ const { commandsRef } = useCommands();
const { dmnModelBeforeEditingRef, dmnEditorRootElementRef } = useDmnEditor();
const { externalModelsByNamespace } = useExternalModels();
// Refs
-
const diagramRef = useRef<DiagramRef>(null);
const diagramContainerRef = useRef<HTMLDivElement>(null);
const beeContainerRef = useRef<HTMLDivElement>(null);
@@ -233,8 +234,9 @@ export const DmnEditorInternal = ({
return new XMLSerializer().serializeToString(svg);
},
+ getCommands: () => commandsRef.current,
}),
- [dmnEditorStoreApi, externalModelsByNamespace]
+ [dmnEditorStoreApi, externalModelsByNamespace, commandsRef]
);
// Make sure the DMN Editor reacts to props changing.
@@ -407,7 +409,9 @@ export const DmnEditor = React.forwardRef((props:
DmnEditorProps, ref: React.Ref
<ErrorBoundary FallbackComponent={DmnEditorErrorFallback}
onReset={resetState}>
<DmnEditorExternalModelsContextProvider {...props}>
<DmnEditorStoreApiContext.Provider value={storeRef.current}>
- <DmnEditorInternal forwardRef={ref} {...props} />
+ <CommandsContextProvider>
+ <DmnEditorInternal forwardRef={ref} {...props} />
+ </CommandsContextProvider>
</DmnEditorStoreApiContext.Provider>
</DmnEditorExternalModelsContextProvider>
</ErrorBoundary>
diff --git a/packages/dmn-editor/src/commands/CommandsContextProvider.tsx
b/packages/dmn-editor/src/commands/CommandsContextProvider.tsx
new file mode 100644
index 00000000000..ab664a26388
--- /dev/null
+++ b/packages/dmn-editor/src/commands/CommandsContextProvider.tsx
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as React from "react";
+import { useContext, useRef } from "react";
+
+export interface Commands {
+ hideFromDrd: () => void;
+ toggleHierarchyHighlight: () => void;
+ togglePropertiesPanel: () => void;
+ createGroup: () => void;
+ selectAll: () => void;
+ panDown: () => void;
+ panUp: () => void;
+ paste: () => void;
+ copy: () => void;
+ cut: () => void;
+ cancelAction: () => void;
+ focusOnSelection: () => void;
+ resetPosition: () => void;
+}
+
+const CommandsContext = React.createContext<{
+ commandsRef: React.MutableRefObject<Commands>;
+}>({} as any);
+
+export function useCommands() {
+ return useContext(CommandsContext);
+}
+
+export function CommandsContextProvider(props: React.PropsWithChildren<{}>) {
+ const commandsRef = useRef<Commands>({
+ hideFromDrd: () => {
+ throw new Error("DMN EDITOR: hideFromDrd command not implemented.");
+ },
+ toggleHierarchyHighlight: () => {
+ throw new Error("DMN EDITOR: toggleHierarchyHighlight command not
implemented.");
+ },
+ togglePropertiesPanel: () => {
+ throw new Error("DMN EDITOR: togglePropertiesPanel command not
implemented.");
+ },
+ createGroup: () => {
+ throw new Error("DMN EDITOR: createGroup command not implemented.");
+ },
+ selectAll: () => {
+ throw new Error("DMN EDITOR: selectAll command not implemented.");
+ },
+ panDown: () => {
+ throw new Error("DMN EDITOR: panDown command not implemented.");
+ },
+ panUp: () => {
+ throw new Error("DMN EDITOR: panUp command not implemented.");
+ },
+ paste: () => {
+ throw new Error("DMN EDITOR: paste command not implemented.");
+ },
+ copy: () => {
+ throw new Error("DMN EDITOR: copy command not implemented.");
+ },
+ cut: () => {
+ throw new Error("DMN EDITOR: cut command not implemented.");
+ },
+ cancelAction: () => {
+ throw new Error("DMN EDITOR: cancelAction command not implemented.");
+ },
+ focusOnSelection: () => {
+ throw new Error("DMN EDITOR: focusOnSelection command not implemented.");
+ },
+ resetPosition: () => {
+ throw new Error("DMN EDITOR: resetPosition command not implemented.");
+ },
+ });
+
+ return <CommandsContext.Provider value={{ commandsRef
}}>{props.children}</CommandsContext.Provider>;
+}
diff --git a/packages/dmn-editor/src/diagram/Diagram.tsx
b/packages/dmn-editor/src/diagram/Diagram.tsx
index bc3393e9ba7..57f591c6f9e 100644
--- a/packages/dmn-editor/src/diagram/Diagram.tsx
+++ b/packages/dmn-editor/src/diagram/Diagram.tsx
@@ -49,28 +49,19 @@ import { useDmnEditor } from "../DmnEditorContext";
import { AutolayoutButton } from "../autolayout/AutolayoutButton";
import { getDefaultColumnWidth } from
"../boxedExpressions/getDefaultColumnWidth";
import { getDefaultBoxedExpression } from
"../boxedExpressions/getDefaultBoxedExpression";
-import {
- DMN_EDITOR_DIAGRAM_CLIPBOARD_MIME_TYPE,
- DmnEditorDiagramClipboard,
- buildClipboardFromDiagram,
- getClipboard,
-} from "../clipboard/Clipboard";
import {
ExternalNode,
MIME_TYPE_FOR_DMN_EDITOR_EXTERNAL_NODES_FROM_INCLUDED_MODELS,
} from "../externalNodes/ExternalNodesPanel";
-import { getNewDmnIdRandomizer } from "../idRandomizer/dmnIdRandomizer";
import { NodeNature, nodeNatures } from "../mutations/NodeNature";
import { addConnectedNode } from "../mutations/addConnectedNode";
import { addDecisionToDecisionService } from
"../mutations/addDecisionToDecisionService";
import { addEdge } from "../mutations/addEdge";
-import { addOrGetDrd } from "../mutations/addOrGetDrd";
import { addShape } from "../mutations/addShape";
import { addStandaloneNode } from "../mutations/addStandaloneNode";
import { deleteDecisionFromDecisionService } from
"../mutations/deleteDecisionFromDecisionService";
import { EdgeDeletionMode, deleteEdge } from "../mutations/deleteEdge";
import { NodeDeletionMode, canRemoveNodeFromDrdOnly, deleteNode } from
"../mutations/deleteNode";
-import { repopulateInputDataAndDecisionsOnAllDecisionServices } from
"../mutations/repopulateInputDataAndDecisionsOnDecisionService";
import { repositionNode } from "../mutations/repositionNode";
import { resizeNode } from "../mutations/resizeNode";
import { updateExpression } from "../mutations/updateExpression";
@@ -99,12 +90,12 @@ import {
} from "./edges/Edges";
import { buildHierarchy } from "./graph/graph";
import {
- CONTAINER_NODES_DESIRABLE_PADDING,
- getBounds,
getDmnBoundsCenterPoint,
getContainmentRelationship,
getHandlePosition,
getNodeTypeFromDmnObject,
+ getBounds,
+ CONTAINER_NODES_DESIRABLE_PADDING,
} from "./maths/DmnMaths";
import { DEFAULT_NODE_SIZES, MIN_NODE_SIZES } from "./nodes/DefaultSizes";
import { NODE_TYPES } from "./nodes/NodeTypes";
@@ -126,6 +117,7 @@ import {
getDecisionServicePropertiesRelativeToThisDmn,
} from "../mutations/addExistingDecisionServiceToDrd";
import { updateExpressionWidths } from "../mutations/updateExpressionWidths";
+import { DiagramCommands } from "./DiagramCommands";
const isFirefox = typeof (window as any).InstallTrigger !== "undefined"; //
See
https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browsers
@@ -133,7 +125,7 @@ const PAN_ON_DRAG = [1, 2];
const FIT_VIEW_OPTIONS: RF.FitViewOptions = { maxZoom: 1, minZoom: 0.1,
duration: 400 };
-const DEFAULT_VIEWPORT = { x: 100, y: 100, zoom: 1 };
+export const DEFAULT_VIEWPORT = { x: 100, y: 100, zoom: 1 };
const DELETE_NODE_KEY_CODES = ["Backspace", "Delete"];
@@ -167,7 +159,6 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
const { externalModelsByNamespace } = useExternalModels();
const snapGrid = useDmnEditorStore((s) => s.diagram.snapGrid);
const thisDmn = useDmnEditorStore((s) => s.dmn);
-
const { dmnModelBeforeEditingRef } = useDmnEditor();
// State
@@ -177,7 +168,6 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
>(undefined);
// Refs
-
React.useImperativeHandle(
ref,
() => ({
@@ -1181,8 +1171,7 @@ export const Diagram = React.forwardRef<DiagramRef, {
container: React.RefObject
<SelectionStatus />
<Palette pulse={isEmptyStateShowing} />
<TopRightCornerPanels />
- <PanWhenAltPressed />
- <KeyboardShortcuts />
+ <DiagramCommands />
{!isFirefox && <RF.Background />}
<RF.Controls fitViewOptions={FIT_VIEW_OPTIONS}
position={"bottom-right"} />
<SetConnectionToReactFlowStore />
@@ -1487,393 +1476,3 @@ export function SelectionStatus() {
</>
);
}
-
-export function KeyboardShortcuts(props: {}) {
- const rfStoreApi = RF.useStoreApi();
- const dmnEditorStoreApi = useDmnEditorStoreApi();
- const { externalModelsByNamespace } = useExternalModels();
-
- const rf = RF.useReactFlow<DmnDiagramNodeData, DmnDiagramEdgeData>();
-
- // Reset position to origin
- const space = RF.useKeyPress(["Space"]);
- useEffect(() => {
- if (!space) {
- return;
- }
-
- rf.setViewport(DEFAULT_VIEWPORT, { duration: 200 });
- }, [rf, space]);
-
- // Focus on node bounds
- const b = RF.useKeyPress(["b"]);
- useEffect(() => {
- if (!b) {
- return;
- }
-
- const selectedNodes = rf.getNodes().filter((s) => s.selected);
- if (selectedNodes.length <= 0) {
- return;
- }
-
- const bounds = getBounds({
- nodes: selectedNodes,
- padding: 100,
- });
-
- rf.fitBounds(
- {
- x: bounds["@_x"],
- y: bounds["@_y"],
- width: bounds["@_width"],
- height: bounds["@_height"],
- },
- { duration: 200 }
- );
- }, [b, rf]);
-
- // Cancel action
- const esc = RF.useKeyPress(["Escape"]);
- useEffect(() => {
- if (!esc) {
- return;
- }
-
- rfStoreApi.setState((rfState) => {
- if (rfState.connectionNodeId) {
- console.debug("DMN DIAGRAM: Esc pressed. Cancelling connection.");
- rfState.cancelConnection();
- dmnEditorStoreApi.setState((state) => {
- state.diagram.ongoingConnection = undefined;
- });
- } else {
- (document.activeElement as any)?.blur?.();
- }
-
- return rfState;
- });
- }, [esc, dmnEditorStoreApi, rfStoreApi]);
-
- // Cut
- const cut = RF.useKeyPress(["Meta+x"]);
- useEffect(() => {
- if (!cut) {
- return;
- }
- console.debug("DMN DIAGRAM: Cutting selected nodes...");
-
- const { clipboard, copiedEdgesById, danglingEdgesById, copiedNodesById } =
buildClipboardFromDiagram(
- rfStoreApi.getState(),
- dmnEditorStoreApi.getState()
- );
-
- navigator.clipboard.writeText(JSON.stringify(clipboard)).then(() => {
- dmnEditorStoreApi.setState((state) => {
- // Delete edges
- [...copiedEdgesById.values(),
...danglingEdgesById.values()].forEach((edge) => {
- deleteEdge({
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
- mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
- });
- state.dispatch(state).diagram.setEdgeStatus(edge.id, {
- selected: false,
- draggingWaypoint: false,
- });
- });
-
- // Delete nodes
- rfStoreApi
- .getState()
- .getNodes()
- .forEach((node: RF.Node<DmnDiagramNodeData>) => {
- if (copiedNodesById.has(node.id)) {
- deleteNode({
- drgEdges:
state.computed(state).getDiagramData(externalModelsByNamespace).drgEdges,
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- dmnObjectNamespace: node.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
- dmnObjectQName: node.data.dmnObjectQName,
- dmnObjectId: node.data.dmnObject?.["@_id"],
- nodeNature: nodeNatures[node.type as NodeType],
- mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
- externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace)
- .dmns,
- });
- state.dispatch(state).diagram.setNodeStatus(node.id, {
- selected: false,
- dragging: false,
- resizing: false,
- });
- }
- });
- });
- });
- }, [cut, dmnEditorStoreApi, rfStoreApi, externalModelsByNamespace]);
-
- // Copy
- const copy = RF.useKeyPress(["Meta+c"]);
- useEffect(() => {
- if (!copy) {
- return;
- }
-
- console.debug("DMN DIAGRAM: Copying selected nodes...");
-
- const { clipboard } = buildClipboardFromDiagram(rfStoreApi.getState(),
dmnEditorStoreApi.getState());
- navigator.clipboard.writeText(JSON.stringify(clipboard));
- }, [copy, dmnEditorStoreApi, rfStoreApi]);
-
- // Paste
- const paste = RF.useKeyPress(["Meta+v"]);
- useEffect(() => {
- if (!paste) {
- return;
- }
-
- console.debug("DMN DIAGRAM: Pasting nodes...");
-
- navigator.clipboard.readText().then((text) => {
- const clipboard = getClipboard<DmnEditorDiagramClipboard>(text,
DMN_EDITOR_DIAGRAM_CLIPBOARD_MIME_TYPE);
- if (!clipboard) {
- return;
- }
-
- getNewDmnIdRandomizer()
- .ack({
- json: clipboard.drgElements,
- type: "DMN15__tDefinitions",
- attr: "drgElement",
- })
- .ack({
- json: clipboard.artifacts,
- type: "DMN15__tDefinitions",
- attr: "artifact",
- })
- .ack({
- json: clipboard.shapes,
- type: "DMNDI15__DMNDiagram",
- attr: "dmndi:DMNDiagramElement",
- __$$element: "dmndi:DMNShape",
- })
- .ack({
- json: clipboard.edges,
- type: "DMNDI15__DMNDiagram",
- attr: "dmndi:DMNDiagramElement",
- __$$element: "dmndi:DMNEdge",
- })
- .ack<any>({
- // This `any` argument ideally wouldn't be here, but the type of
DMN's `meta` is not composed with KIE's `meta` in compile-time
- json: clipboard.widths,
- type: "KIE__tComponentsWidthsExtension",
- attr: "kie:ComponentWidths",
- })
- .randomize();
-
- dmnEditorStoreApi.setState((state) => {
- state.dmn.model.definitions.drgElement ??= [];
- state.dmn.model.definitions.drgElement.push(...clipboard.drgElements);
- state.dmn.model.definitions.artifact ??= [];
- state.dmn.model.definitions.artifact.push(...clipboard.artifacts);
-
- const { diagramElements, widths } = addOrGetDrd({
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- });
- diagramElements.push(...clipboard.shapes.map((s) => ({ ...s,
__$$element: "dmndi:DMNShape" as const })));
- diagramElements.push(...clipboard.edges.map((s) => ({ ...s,
__$$element: "dmndi:DMNEdge" as const })));
-
- widths.push(...clipboard.widths);
-
- repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions:
state.dmn.model.definitions });
-
- state.diagram._selectedNodes = [...clipboard.drgElements,
...clipboard.artifacts].map((s) =>
- buildXmlHref({ id: s["@_id"]! })
- );
-
- if (state.diagram._selectedNodes.length === 1) {
- state.focus.consumableId =
parseXmlHref(state.diagram._selectedNodes[0]).id;
- }
- });
- });
- }, [paste, dmnEditorStoreApi]);
-
- // Select/deselect all
- const selectAll = RF.useKeyPress(["a", "Meta+a"]);
- useEffect(() => {
- if (!selectAll) {
- return;
- }
-
- const allNodeIds = rfStoreApi
- .getState()
- .getNodes()
- .map((s) => s.id);
-
- const allEdgeIds = rfStoreApi.getState().edges.map((s) => s.id);
-
- dmnEditorStoreApi.setState((state) => {
- const allSelectedNodesSet = new Set(state.diagram._selectedNodes);
- const allSelectedEdgesSet = new Set(state.diagram._selectedEdges);
-
- // If everything is selected, deselect everything.
- if (
- allNodeIds.every((id) => allSelectedNodesSet.has(id) &&
allEdgeIds.every((id) => allSelectedEdgesSet.has(id)))
- ) {
- state.diagram._selectedNodes = [];
- state.diagram._selectedEdges = [];
- } else {
- state.diagram._selectedNodes = allNodeIds;
- state.diagram._selectedEdges = allEdgeIds;
- }
- });
- }, [selectAll, dmnEditorStoreApi, rfStoreApi]);
-
- // Create group wrapping selection
- const g = RF.useKeyPress(["g"]);
- useEffect(() => {
- if (!g) {
- return;
- }
-
- const selectedNodes = rf.getNodes().filter((s) => s.selected);
- if (selectedNodes.length <= 0) {
- return;
- }
-
- dmnEditorStoreApi.setState((state) => {
- if (state.diagram._selectedNodes.length <= 0) {
- return;
- }
-
- const { href: newNodeId } = addStandaloneNode({
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- newNode: {
- type: NODE_TYPES.group,
- bounds: getBounds({
- nodes: selectedNodes,
- padding: CONTAINER_NODES_DESIRABLE_PADDING,
- }),
- },
- });
-
- state.dispatch(state).diagram.setNodeStatus(newNodeId, { selected: true
});
- });
- }, [g, dmnEditorStoreApi, rf]);
-
- // Toggle hierarchy highlights
- const h = RF.useKeyPress(["h"]);
- useEffect(() => {
- if (!h) {
- return;
- }
-
- dmnEditorStoreApi.setState((state) => {
- state.diagram.overlays.enableNodeHierarchyHighlight =
!state.diagram.overlays.enableNodeHierarchyHighlight;
- });
- }, [h, dmnEditorStoreApi]);
-
- // Show Properties panel
- const i = RF.useKeyPress(["i"]);
- useEffect(() => {
- if (!i) {
- return;
- }
-
- dmnEditorStoreApi.setState((state) => {
- state.diagram.propertiesPanel.isOpen =
!state.diagram.propertiesPanel.isOpen;
- });
- }, [i, dmnEditorStoreApi]);
-
- // Hide from DRD
- const x = RF.useKeyPress(["x"]);
- useEffect(() => {
- if (!x) {
- return;
- }
-
- const nodesById = rf
- .getNodes()
- .reduce((acc, s) => acc.set(s.id, s), new Map<string,
RF.Node<DmnDiagramNodeData>>());
-
- dmnEditorStoreApi.setState((state) => {
- const selectedNodeIds = new Set(state.diagram._selectedNodes);
- for (const edge of rf.getEdges()) {
- if (
- (selectedNodeIds.has(edge.source) &&
- canRemoveNodeFromDrdOnly({
- externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- dmnObjectNamespace:
- nodesById.get(edge.source)!.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
- dmnObjectId:
nodesById.get(edge.source)!.data.dmnObject?.["@_id"],
- })) ||
- (selectedNodeIds.has(edge.target) &&
- canRemoveNodeFromDrdOnly({
- externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- dmnObjectNamespace:
- nodesById.get(edge.target)!.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
- dmnObjectId:
nodesById.get(edge.target)!.data.dmnObject?.["@_id"],
- }))
- ) {
- deleteEdge({
- definitions: state.dmn.model.definitions,
- drdIndex: state.diagram.drdIndex,
- edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
- mode: EdgeDeletionMode.FROM_CURRENT_DRD_ONLY,
- });
- state.dispatch(state).diagram.setEdgeStatus(edge.id, { selected:
false, draggingWaypoint: false });
- }
- }
-
- for (const node of rf.getNodes().filter((s) => s.selected)) {
- // Prevent hiding artifact nodes from DRD;
- if (nodeNatures[node.type as NodeType] === NodeNature.ARTIFACT) {
- continue;
- }
- const { deletedDmnShapeOnCurrentDrd: deletedShape } = deleteNode({
- drgEdges: [], // Deleting from DRD only.
- definitions: state.dmn.model.definitions,
- externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
- drdIndex: state.diagram.drdIndex,
- dmnObjectNamespace: node.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
- dmnObjectQName: node.data.dmnObjectQName,
- dmnObjectId: node.data.dmnObject?.["@_id"],
- nodeNature: nodeNatures[node.type as NodeType],
- mode: NodeDeletionMode.FROM_CURRENT_DRD_ONLY,
- });
-
- if (deletedShape) {
- state.dispatch(state).diagram.setNodeStatus(node.id, {
- selected: false,
- dragging: false,
- resizing: false,
- });
- }
- }
- });
- }, [x, dmnEditorStoreApi, rf, externalModelsByNamespace]);
-
- return <></>;
-}
-
-export function PanWhenAltPressed() {
- const altPressed = RF.useKeyPress("Alt");
- const rfStoreApi = RF.useStoreApi();
-
- useEffect(() => {
- rfStoreApi.setState({
- nodesDraggable: !altPressed,
- nodesConnectable: !altPressed,
- elementsSelectable: !altPressed,
- });
- }, [altPressed, rfStoreApi]);
-
- return <></>;
-}
diff --git a/packages/dmn-editor/src/diagram/DiagramCommands.tsx
b/packages/dmn-editor/src/diagram/DiagramCommands.tsx
new file mode 100644
index 00000000000..9d073b1c98c
--- /dev/null
+++ b/packages/dmn-editor/src/diagram/DiagramCommands.tsx
@@ -0,0 +1,449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import * as RF from "reactflow";
+import * as React from "react";
+import { useEffect } from "react";
+import {
+ DMN_EDITOR_DIAGRAM_CLIPBOARD_MIME_TYPE,
+ DmnEditorDiagramClipboard,
+ buildClipboardFromDiagram,
+ getClipboard,
+} from "../clipboard/Clipboard";
+import { getNewDmnIdRandomizer } from "../idRandomizer/dmnIdRandomizer";
+import { NodeNature, nodeNatures } from "../mutations/NodeNature";
+import { addOrGetDrd } from "../mutations/addOrGetDrd";
+import { addStandaloneNode } from "../mutations/addStandaloneNode";
+import { EdgeDeletionMode, deleteEdge } from "../mutations/deleteEdge";
+import { NodeDeletionMode, canRemoveNodeFromDrdOnly, deleteNode } from
"../mutations/deleteNode";
+import { repopulateInputDataAndDecisionsOnAllDecisionServices } from
"../mutations/repopulateInputDataAndDecisionsOnDecisionService";
+import { useDmnEditorStoreApi } from "../store/StoreContext";
+import { DmnDiagramEdgeData } from "./edges/Edges";
+import { CONTAINER_NODES_DESIRABLE_PADDING, getBounds } from
"./maths/DmnMaths";
+import { NODE_TYPES } from "./nodes/NodeTypes";
+import { DmnDiagramNodeData } from "./nodes/Nodes";
+import { useExternalModels } from
"../includedModels/DmnEditorDependenciesContext";
+import { NodeType } from "./connections/graphStructure";
+import { buildXmlHref, parseXmlHref } from "../xml/xmlHrefs";
+import { DEFAULT_VIEWPORT } from "./Diagram";
+import { useCommands } from "../commands/CommandsContextProvider";
+
+export function DiagramCommands(props: {}) {
+ const rfStoreApi = RF.useStoreApi();
+ const dmnEditorStoreApi = useDmnEditorStoreApi();
+ const { commandsRef } = useCommands();
+ const { externalModelsByNamespace } = useExternalModels();
+ const rf = RF.useReactFlow<DmnDiagramNodeData, DmnDiagramEdgeData>();
+
+ // Cancel action
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.cancelAction = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Canceling action...");
+ rfStoreApi.setState((rfState) => {
+ if (rfState.connectionNodeId) {
+ rfState.cancelConnection();
+ dmnEditorStoreApi.setState((state) => {
+ state.diagram.ongoingConnection = undefined;
+ });
+ } else {
+ (document.activeElement as any)?.blur?.();
+ }
+
+ return rfState;
+ });
+ };
+ }, [dmnEditorStoreApi, commandsRef, rfStoreApi]);
+
+ // Reset position to origin
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.resetPosition = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Reseting position...");
+ rf.setViewport(DEFAULT_VIEWPORT, { duration: 200 });
+ };
+ }, [commandsRef, rf]);
+
+ // Focus on selection
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.focusOnSelection = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Focusing on selected bounds...");
+ const selectedNodes = rf.getNodes().filter((s) => s.selected);
+ if (selectedNodes.length <= 0) {
+ return;
+ }
+
+ const bounds = getBounds({
+ nodes: selectedNodes,
+ padding: 100,
+ });
+
+ rf.fitBounds(
+ {
+ x: bounds["@_x"],
+ y: bounds["@_y"],
+ width: bounds["@_width"],
+ height: bounds["@_height"],
+ },
+ { duration: 200 }
+ );
+ };
+ }, [commandsRef, rf]);
+
+ // Cut nodes
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.cut = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Cutting selected nodes...");
+ const { clipboard, copiedEdgesById, danglingEdgesById, copiedNodesById }
= buildClipboardFromDiagram(
+ rfStoreApi.getState(),
+ dmnEditorStoreApi.getState()
+ );
+
+ navigator.clipboard.writeText(JSON.stringify(clipboard)).then(() => {
+ dmnEditorStoreApi.setState((state) => {
+ // Delete edges
+ [...copiedEdgesById.values(),
...danglingEdgesById.values()].forEach((edge) => {
+ deleteEdge({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
+ mode: EdgeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ });
+ state.dispatch(state).diagram.setEdgeStatus(edge.id, {
+ selected: false,
+ draggingWaypoint: false,
+ });
+ });
+
+ // Delete nodes
+ rfStoreApi
+ .getState()
+ .getNodes()
+ .forEach((node: RF.Node<DmnDiagramNodeData>) => {
+ if (copiedNodesById.has(node.id)) {
+ deleteNode({
+ drgEdges:
state.computed(state).getDiagramData(externalModelsByNamespace).drgEdges,
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace: node.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
+ dmnObjectQName: node.data.dmnObjectQName,
+ dmnObjectId: node.data.dmnObject?.["@_id"],
+ nodeNature: nodeNatures[node.type as NodeType],
+ mode: NodeDeletionMode.FROM_DRG_AND_ALL_DRDS,
+ externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace)
+ .dmns,
+ });
+ state.dispatch(state).diagram.setNodeStatus(node.id, {
+ selected: false,
+ dragging: false,
+ resizing: false,
+ });
+ }
+ });
+ });
+ });
+ };
+ }, [dmnEditorStoreApi, externalModelsByNamespace, commandsRef, rfStoreApi]);
+
+ // Copy nodes
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.copy = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Copying selected nodes...");
+ const { clipboard } = buildClipboardFromDiagram(rfStoreApi.getState(),
dmnEditorStoreApi.getState());
+ navigator.clipboard.writeText(JSON.stringify(clipboard));
+ };
+ }, [dmnEditorStoreApi, commandsRef, rfStoreApi]);
+
+ // Paste nodes
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.paste = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Pasting nodes...");
+ navigator.clipboard.readText().then((text) => {
+ const clipboard = getClipboard<DmnEditorDiagramClipboard>(text,
DMN_EDITOR_DIAGRAM_CLIPBOARD_MIME_TYPE);
+ if (!clipboard) {
+ return;
+ }
+
+ getNewDmnIdRandomizer()
+ .ack({
+ json: clipboard.drgElements,
+ type: "DMN15__tDefinitions",
+ attr: "drgElement",
+ })
+ .ack({
+ json: clipboard.artifacts,
+ type: "DMN15__tDefinitions",
+ attr: "artifact",
+ })
+ .ack({
+ json: clipboard.shapes,
+ type: "DMNDI15__DMNDiagram",
+ attr: "dmndi:DMNDiagramElement",
+ __$$element: "dmndi:DMNShape",
+ })
+ .ack({
+ json: clipboard.edges,
+ type: "DMNDI15__DMNDiagram",
+ attr: "dmndi:DMNDiagramElement",
+ __$$element: "dmndi:DMNEdge",
+ })
+ .ack<any>({
+ // This `any` argument ideally wouldn't be here, but the type of
DMN's `meta` is not composed with KIE's `meta` in compile-time
+ json: clipboard.widths,
+ type: "KIE__tComponentsWidthsExtension",
+ attr: "kie:ComponentWidths",
+ })
+ .randomize();
+
+ dmnEditorStoreApi.setState((state) => {
+ state.dmn.model.definitions.drgElement ??= [];
+
state.dmn.model.definitions.drgElement.push(...clipboard.drgElements);
+ state.dmn.model.definitions.artifact ??= [];
+ state.dmn.model.definitions.artifact.push(...clipboard.artifacts);
+
+ const { diagramElements, widths } = addOrGetDrd({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ });
+ diagramElements.push(...clipboard.shapes.map((s) => ({ ...s,
__$$element: "dmndi:DMNShape" as const })));
+ diagramElements.push(...clipboard.edges.map((s) => ({ ...s,
__$$element: "dmndi:DMNEdge" as const })));
+
+ widths.push(...clipboard.widths);
+
+ repopulateInputDataAndDecisionsOnAllDecisionServices({ definitions:
state.dmn.model.definitions });
+
+ state.diagram._selectedNodes = [...clipboard.drgElements,
...clipboard.artifacts].map((s) =>
+ buildXmlHref({ id: s["@_id"]! })
+ );
+
+ if (state.diagram._selectedNodes.length === 1) {
+ state.focus.consumableId =
parseXmlHref(state.diagram._selectedNodes[0]).id;
+ }
+ });
+ });
+ };
+ }, [dmnEditorStoreApi, commandsRef]);
+
+ // Select/deselect all nodes
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.selectAll = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Selecting/Deselecting nodes...");
+ const allNodeIds = rfStoreApi
+ .getState()
+ .getNodes()
+ .map((s) => s.id);
+
+ const allEdgeIds = rfStoreApi.getState().edges.map((s) => s.id);
+
+ dmnEditorStoreApi.setState((state) => {
+ const allSelectedNodesSet = new Set(state.diagram._selectedNodes);
+ const allSelectedEdgesSet = new Set(state.diagram._selectedEdges);
+
+ // If everything is selected, deselect everything.
+ if (
+ allNodeIds.every((id) => allSelectedNodesSet.has(id) &&
allEdgeIds.every((id) => allSelectedEdgesSet.has(id)))
+ ) {
+ state.diagram._selectedNodes = [];
+ state.diagram._selectedEdges = [];
+ } else {
+ state.diagram._selectedNodes = allNodeIds;
+ state.diagram._selectedEdges = allEdgeIds;
+ }
+ });
+ };
+ }, [dmnEditorStoreApi, commandsRef, rfStoreApi]);
+
+ // Create group wrapping selection
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.createGroup = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Grouping nodes...");
+ const selectedNodes = rf.getNodes().filter((s) => s.selected);
+ if (selectedNodes.length <= 0) {
+ return;
+ }
+
+ dmnEditorStoreApi.setState((state) => {
+ if (state.diagram._selectedNodes.length <= 0) {
+ return;
+ }
+
+ const { href: newNodeId } = addStandaloneNode({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ newNode: {
+ type: NODE_TYPES.group,
+ bounds: getBounds({
+ nodes: selectedNodes,
+ padding: CONTAINER_NODES_DESIRABLE_PADDING,
+ }),
+ },
+ });
+
+ state.dispatch(state).diagram.setNodeStatus(newNodeId, { selected:
true });
+ });
+ };
+ }, [dmnEditorStoreApi, commandsRef, rf]);
+
+ // Toggle hierarchy highlights
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.toggleHierarchyHighlight = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Toggle hierarchy highlights...");
+ dmnEditorStoreApi.setState((state) => {
+ state.diagram.overlays.enableNodeHierarchyHighlight =
!state.diagram.overlays.enableNodeHierarchyHighlight;
+ });
+ };
+ }, [dmnEditorStoreApi, commandsRef]);
+
+ // Show Properties panel
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.togglePropertiesPanel = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Toggle properties panel...");
+ dmnEditorStoreApi.setState((state) => {
+ state.diagram.propertiesPanel.isOpen =
!state.diagram.propertiesPanel.isOpen;
+ });
+ };
+ }, [dmnEditorStoreApi, commandsRef]);
+
+ // Hide from DRD
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.hideFromDrd = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Hide node from DRD...");
+ const nodesById = rf
+ .getNodes()
+ .reduce((acc, s) => acc.set(s.id, s), new Map<string,
RF.Node<DmnDiagramNodeData>>());
+
+ dmnEditorStoreApi.setState((state) => {
+ const selectedNodeIds = new Set(state.diagram._selectedNodes);
+ for (const edge of rf.getEdges()) {
+ if (
+ (selectedNodeIds.has(edge.source) &&
+ canRemoveNodeFromDrdOnly({
+ externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace)
+ .dmns,
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace:
+ nodesById.get(edge.source)!.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
+ dmnObjectId:
nodesById.get(edge.source)!.data.dmnObject?.["@_id"],
+ })) ||
+ (selectedNodeIds.has(edge.target) &&
+ canRemoveNodeFromDrdOnly({
+ externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace)
+ .dmns,
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace:
+ nodesById.get(edge.target)!.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
+ dmnObjectId:
nodesById.get(edge.target)!.data.dmnObject?.["@_id"],
+ }))
+ ) {
+ deleteEdge({
+ definitions: state.dmn.model.definitions,
+ drdIndex: state.diagram.drdIndex,
+ edge: { id: edge.id, dmnObject: edge.data!.dmnObject },
+ mode: EdgeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ });
+ state.dispatch(state).diagram.setEdgeStatus(edge.id, { selected:
false, draggingWaypoint: false });
+ }
+ }
+
+ for (const node of rf.getNodes().filter((s) => s.selected)) {
+ // Prevent hiding artifact nodes from DRD;
+ if (nodeNatures[node.type as NodeType] === NodeNature.ARTIFACT) {
+ continue;
+ }
+ const { deletedDmnShapeOnCurrentDrd: deletedShape } = deleteNode({
+ drgEdges: [], // Deleting from DRD only.
+ definitions: state.dmn.model.definitions,
+ externalDmnsIndex:
state.computed(state).getExternalModelTypesByNamespace(externalModelsByNamespace).dmns,
+ drdIndex: state.diagram.drdIndex,
+ dmnObjectNamespace: node.data.dmnObjectNamespace ??
state.dmn.model.definitions["@_namespace"],
+ dmnObjectQName: node.data.dmnObjectQName,
+ dmnObjectId: node.data.dmnObject?.["@_id"],
+ nodeNature: nodeNatures[node.type as NodeType],
+ mode: NodeDeletionMode.FROM_CURRENT_DRD_ONLY,
+ });
+
+ if (deletedShape) {
+ state.dispatch(state).diagram.setNodeStatus(node.id, {
+ selected: false,
+ dragging: false,
+ resizing: false,
+ });
+ }
+ }
+ });
+ };
+ }, [dmnEditorStoreApi, externalModelsByNamespace, commandsRef, rf]);
+
+ useEffect(() => {
+ if (!commandsRef.current) {
+ return;
+ }
+ commandsRef.current.panDown = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Panning down");
+ rfStoreApi.setState({
+ nodesDraggable: false,
+ nodesConnectable: false,
+ elementsSelectable: false,
+ });
+ };
+ commandsRef.current.panUp = async () => {
+ console.debug("DMN DIAGRAM: COMMANDS: Panning up");
+ rfStoreApi.setState({
+ nodesDraggable: true,
+ nodesConnectable: true,
+ elementsSelectable: true,
+ });
+ };
+ }, [commandsRef, rfStoreApi]);
+
+ return <></>;
+}
diff --git
a/packages/keyboard-shortcuts/src/envelope/DefaultKeyboardShortcutsService.ts
b/packages/keyboard-shortcuts/src/envelope/DefaultKeyboardShortcutsService.ts
index 164621f12d9..1a68e012b5d 100644
---
a/packages/keyboard-shortcuts/src/envelope/DefaultKeyboardShortcutsService.ts
+++
b/packages/keyboard-shortcuts/src/envelope/DefaultKeyboardShortcutsService.ts
@@ -52,6 +52,7 @@ const KEY_CODES = new Map<string, string>([
["esc", "Escape"],
["delete", "Delete"],
["backspace", "Backspace"],
+ ["space", "Space"],
["right", "ArrowRight"],
["left", "ArrowLeft"],
["up", "ArrowUp"],
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a16f54497f3..d2526990c6a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3630,6 +3630,9 @@ importers:
"@kie-tools-core/envelope-bus":
specifier: workspace:*
version: link:../envelope-bus
+ "@kie-tools-core/keyboard-shortcuts":
+ specifier: workspace:*
+ version: link:../keyboard-shortcuts
"@kie-tools-core/notifications":
specifier: workspace:*
version: link:../notifications
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]