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 c91692e0439 kie-issues#842: The new DMN editor resets the node color 
styles to its initial value after an edit (#2143)
c91692e0439 is described below

commit c91692e043901780779b340042900b20272d77e5
Author: Tiago Bento <[email protected]>
AuthorDate: Mon Feb 5 22:51:37 2024 -0500

    kie-issues#842: The new DMN editor resets the node color styles to its 
initial value after an edit (#2143)
---
 .../dmn-editor/src/propertiesPanel/FontOptions.tsx | 178 ++++++++--------
 .../src/propertiesPanel/ShapeOptions.tsx           | 225 ++++++++++++---------
 .../src/propertiesPanel/SingleNodeProperties.tsx   |   5 +-
 3 files changed, 226 insertions(+), 182 deletions(-)

diff --git a/packages/dmn-editor/src/propertiesPanel/FontOptions.tsx 
b/packages/dmn-editor/src/propertiesPanel/FontOptions.tsx
index d8ba5278f6c..0e9ba4418ee 100644
--- a/packages/dmn-editor/src/propertiesPanel/FontOptions.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/FontOptions.tsx
@@ -17,24 +17,24 @@
  * under the License.
  */
 
-import * as React from "react";
-import { useState, useMemo, useCallback, useEffect } from "react";
+import { DMNDI15__DMNShape } from 
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
+import { Button, ButtonVariant } from 
"@patternfly/react-core/dist/js/components/Button";
 import { FormSection } from "@patternfly/react-core/dist/js/components/Form";
-import { PencilAltIcon } from 
"@patternfly/react-icons/dist/js/icons/pencil-alt-icon";
-import { PropertiesPanelHeader } from "./PropertiesPanelHeader";
-import { State } from "../store/Store";
-import { useDmnEditorStore, useDmnEditorStoreApi } from 
"../store/StoreContext";
 import { NumberInput } from 
"@patternfly/react-core/dist/js/components/NumberInput";
-import { DMNDI15__DMNShape } from 
"@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/types";
-import { addOrGetDrd } from "../mutations/addOrGetDrd";
+import { Select, SelectOption, SelectVariant } from 
"@patternfly/react-core/dist/js/components/Select";
 import { ToggleGroup, ToggleGroupItem } from 
"@patternfly/react-core/dist/js/components/ToggleGroup";
-import { Select, SelectVariant, SelectOption } from 
"@patternfly/react-core/dist/js/components/Select";
-import { useInViewSelect } from "../responsiveness/useInViewSelect";
-import { useDmnEditor } from "../DmnEditorContext";
-import { Button, ButtonVariant } from 
"@patternfly/react-core/dist/js/components/Button";
-import { UndoAltIcon } from 
"@patternfly/react-icons/dist/js/icons/undo-alt-icon";
 import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip";
+import { PencilAltIcon } from 
"@patternfly/react-icons/dist/js/icons/pencil-alt-icon";
+import { UndoAltIcon } from 
"@patternfly/react-icons/dist/js/icons/undo-alt-icon";
+import * as React from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { useDmnEditor } from "../DmnEditorContext";
+import { addOrGetDrd } from "../mutations/addOrGetDrd";
+import { useInViewSelect } from "../responsiveness/useInViewSelect";
+import { State } from "../store/Store";
+import { useDmnEditorStore, useDmnEditorStoreApi } from 
"../store/StoreContext";
 import { ColorPicker } from "./ColorPicker";
+import { PropertiesPanelHeader } from "./PropertiesPanelHeader";
 import "./FontOptions.css";
 
 // https://www.w3schools.com/cssref/css_websafe_fonts.php
@@ -51,6 +51,7 @@ const WEBSAFE_FONTS_LIST = [
   "Brush Script MT",
 ];
 
+const DEFAULT_FONT_COLOR = { "@_blue": 0, "@_green": 0, "@_red": 0 };
 const DEFAULT_FONT_SIZE = 16;
 const MAX_FONT_SIZE = 72;
 const MIN_FONT_SIZE = 0;
@@ -65,40 +66,48 @@ enum FontStyleToggleOptions {
 
 export function FontOptions({ startExpanded, nodeIds }: { startExpanded: 
boolean; nodeIds: string[] }) {
   const dmnEditorStoreApi = useDmnEditorStoreApi();
-  const [isStyleSectionExpanded, setStyleSectionExpanded] = 
useState<boolean>(startExpanded);
 
-  const dmnShapesByHref = useDmnEditorStore((s) => 
s.computed(s).indexes().dmnShapesByHref);
-  const shapes = useMemo(() => nodeIds.map((nodeId) => 
dmnShapesByHref.get(nodeId)), [dmnShapesByHref, nodeIds]);
-  const shapesStyle = useMemo(() => shapes.map((shape) => 
shape?.["di:Style"]), [shapes]);
+  const shapeStyles = useDmnEditorStore((s) =>
+    nodeIds.map((nodeId) => 
s.computed(s).indexes().dmnShapesByHref.get(nodeId)?.["di:Style"])
+  );
 
-  const fontFamily = useMemo(() => shapesStyle[0]?.["@_fontFamily"], 
[shapesStyle]);
-  const isFontBold = useMemo(() => shapesStyle[0]?.["@_fontBold"] ?? false, 
[shapesStyle]);
-  const isFontItalic = useMemo(() => shapesStyle[0]?.["@_fontItalic"] ?? 
false, [shapesStyle]);
-  const isFontUnderline = useMemo(() => shapesStyle[0]?.["@_fontUnderline"], 
[shapesStyle]);
-  const isFontStrikeThrough = useMemo(() => 
shapesStyle[0]?.["@_fontStrikeThrough"] ?? false, [shapesStyle]);
-  const fontSize = useMemo(() => shapesStyle[0]?.["@_fontSize"] ?? 
DEFAULT_FONT_SIZE, [shapesStyle]);
+  const fontFamily = useMemo(() => shapeStyles[0]?.["@_fontFamily"], 
[shapeStyles]);
+  const isFontBold = useMemo(() => shapeStyles[0]?.["@_fontBold"] ?? false, 
[shapeStyles]);
+  const isFontItalic = useMemo(() => shapeStyles[0]?.["@_fontItalic"] ?? 
false, [shapeStyles]);
+  const isFontUnderline = useMemo(() => shapeStyles[0]?.["@_fontUnderline"], 
[shapeStyles]);
+  const isFontStrikeThrough = useMemo(() => 
shapeStyles[0]?.["@_fontStrikeThrough"] ?? false, [shapeStyles]);
+  const fontSize = useMemo(() => shapeStyles[0]?.["@_fontSize"] ?? 
DEFAULT_FONT_SIZE, [shapeStyles]);
   const fontColor = useMemo(() => {
-    const b = (shapesStyle[0]?.["dmndi:FontColor"]?.["@_blue"] ?? 
0).toString(16);
-    const g = (shapesStyle[0]?.["dmndi:FontColor"]?.["@_green"] ?? 
0).toString(16);
-    const r = (shapesStyle[0]?.["dmndi:FontColor"]?.["@_red"] ?? 
0).toString(16);
+    const b = (shapeStyles[0]?.["dmndi:FontColor"]?.["@_blue"] ?? 
DEFAULT_FONT_COLOR["@_red"]).toString(16);
+    const g = (shapeStyles[0]?.["dmndi:FontColor"]?.["@_green"] ?? 
DEFAULT_FONT_COLOR["@_green"]).toString(16);
+    const r = (shapeStyles[0]?.["dmndi:FontColor"]?.["@_red"] ?? 
DEFAULT_FONT_COLOR["@_blue"]).toString(16);
     return `#${r.length === 1 ? "0" + r : r}${g.length === 1 ? "0" + g : 
g}${b.length === 1 ? "0" + b : b}`;
-  }, [shapesStyle]);
+  }, [shapeStyles]);
+
+  const [isStyleSectionExpanded, setStyleSectionExpanded] = 
useState<boolean>(startExpanded);
 
-  const editShapeStyle = useCallback(
+  const setShapeStyles = useCallback(
     (callback: (shape: DMNDI15__DMNShape[], state: State) => void) => {
-      dmnEditorStoreApi.setState((state) => {
-        const { diagramElements } = addOrGetDrd({
-          definitions: state.dmn.model.definitions,
-          drdIndex: state.diagram.drdIndex,
-        });
-        const _shapes = shapes.map((shape) => diagramElements[shape?.index ?? 
0]);
-        _shapes.forEach((_shape, i, _shapes) => {
-          _shapes[i]["di:Style"] ??= { __$$element: "dmndi:DMNStyle" };
+      dmnEditorStoreApi.setState((s) => {
+        const { diagramElements } = addOrGetDrd({ definitions: 
s.dmn.model.definitions, drdIndex: s.diagram.drdIndex });
+
+        const shapes = nodeIds.map((nodeId) => {
+          const shape = s.computed(s).indexes().dmnShapesByHref.get(nodeId);
+          if (!shape) {
+            throw new Error(`DMN Shape for '${nodeId}' does not exist.`);
+          }
+
+          return diagramElements[shape.index];
         });
-        callback(_shapes, state);
+
+        for (const shape of shapes) {
+          shape["di:Style"] ??= { __$$element: "dmndi:DMNStyle" };
+        }
+
+        callback(shapes, s);
       });
     },
-    [dmnEditorStoreApi, shapes]
+    [dmnEditorStoreApi, nodeIds]
   );
 
   const { dmnEditorRootElementRef } = useDmnEditor();
@@ -109,20 +118,20 @@ export function FontOptions({ startExpanded, nodeIds }: { 
startExpanded: boolean
   const onSelectFont = useCallback(
     (e, value, isPlaceholder) => {
       if (isPlaceholder) {
-        editShapeStyle((shapes) => {
+        setShapeStyles((shapes) => {
           shapes.forEach((shape, i, shapes) => {
             shape["di:Style"]!["@_fontFamily"] ??= undefined;
           });
         });
-        return;
-      }
-      editShapeStyle((shapes) => {
-        shapes.forEach((shape, i, shapes) => {
-          shape["di:Style"]!["@_fontFamily"] = value;
+      } else {
+        setShapeStyles((shapes) => {
+          shapes.forEach((shape, i, shapes) => {
+            shape["di:Style"]!["@_fontFamily"] = value;
+          });
         });
-      });
+      }
     },
-    [editShapeStyle]
+    [setShapeStyles]
   );
 
   const validateFontSize = useCallback((value?: number): number => {
@@ -139,111 +148,108 @@ export function FontOptions({ startExpanded, nodeIds }: 
{ startExpanded: boolean
   }, []);
 
   const onMinus = useCallback(() => {
-    editShapeStyle((shapes) => {
+    setShapeStyles((shapes) => {
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontSize"] = validateFontSize(
           (shape!["di:Style"]?.["@_fontSize"] ?? DEFAULT_FONT_SIZE) - 1
         );
       });
     });
-  }, [editShapeStyle, validateFontSize]);
+  }, [setShapeStyles, validateFontSize]);
 
   const onChange = useCallback(
     (event: React.FormEvent<HTMLInputElement>) => {
-      editShapeStyle((shapes) => {
+      setShapeStyles((shapes) => {
         shapes.forEach((shape) => {
           shape["di:Style"]!["@_fontSize"] = +(event.target as 
HTMLInputElement).value;
         });
       });
     },
-    [editShapeStyle]
+    [setShapeStyles]
   );
 
   const onPlus = useCallback(() => {
-    editShapeStyle((shapes) => {
+    setShapeStyles((shapes) => {
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontSize"] = validateFontSize(
           (shape!["di:Style"]?.["@_fontSize"] ?? DEFAULT_FONT_SIZE) + 1
         );
       });
     });
-  }, [editShapeStyle, validateFontSize]);
+  }, [setShapeStyles, validateFontSize]);
 
   const onChangeBold = useCallback(() => {
-    editShapeStyle((shapes) => {
+    setShapeStyles((shapes) => {
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontBold"] = 
!shape?.["di:Style"]?.["@_fontBold"] ?? true;
       });
     });
-  }, [editShapeStyle]);
+  }, [setShapeStyles]);
 
   const onChangeItalic = useCallback(() => {
-    editShapeStyle((shapes) => {
+    setShapeStyles((shapes) => {
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontItalic"] = 
!shape?.["di:Style"]?.["@_fontItalic"] ?? true;
       });
     });
-  }, [editShapeStyle]);
+  }, [setShapeStyles]);
 
   const onChangeUnderline = useCallback(() => {
-    editShapeStyle((shapes) => {
+    setShapeStyles((shapes) => {
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontUnderline"] = 
!shape?.["di:Style"]?.["@_fontUnderline"] ?? true;
       });
     });
-  }, [editShapeStyle]);
+  }, [setShapeStyles]);
 
   const onChangeStrikeThrough = useCallback(() => {
-    editShapeStyle((shapes) => {
+    setShapeStyles((shapes) => {
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontStrikeThrough"] = 
!shape?.["di:Style"]?.["@_fontStrikeThrough"] ?? true;
       });
     });
-  }, [editShapeStyle]);
+  }, [setShapeStyles]);
 
   const colorPickerRef = React.useRef<HTMLInputElement>(null) as 
React.MutableRefObject<HTMLInputElement>;
 
-  const [temporaryFontColor, setTemporaryFontColor] = 
useState<string>("000000");
+  const [temporaryFontColor, setTemporaryFontColor] = useState<string | 
undefined>();
   const onChangeColor = useCallback(
     (newColor: string) => {
       setTemporaryFontColor(newColor.replace("#", ""));
-      editShapeStyle((shapes, state) => {
-        state!.diagram.isEditingStyle = true;
+      setShapeStyles((shapes, state) => {
+        state.diagram.isEditingStyle = true;
       });
     },
-    [editShapeStyle]
+    [setShapeStyles]
   );
 
   useEffect(() => {
     const timeout = setTimeout(() => {
-      const red = parseInt(temporaryFontColor.slice(0, 2), 16);
-      const green = parseInt(temporaryFontColor.slice(2, 4), 16);
-      const blue = parseInt(temporaryFontColor.slice(4, 6), 16);
-      editShapeStyle((shapes, state) => {
+      if (!temporaryFontColor) {
+        return;
+      }
+
+      setTemporaryFontColor(undefined);
+
+      setShapeStyles((shapes, state) => {
         shapes.forEach((shape) => {
-          if (
-            red !== shape?.["di:Style"]?.["dmndi:FontColor"]?.["@_red"] &&
-            green !== shape?.["di:Style"]?.["dmndi:FontColor"]?.["@_green"] &&
-            blue !== shape?.["di:Style"]?.["dmndi:FontColor"]?.["@_blue"]
-          ) {
-            state!.diagram.isEditingStyle = false;
-            shape!["di:Style"]!["dmndi:FontColor"] ??= { "@_blue": 0, 
"@_green": 0, "@_red": 0 };
-            shape!["di:Style"]!["dmndi:FontColor"]["@_red"] = red;
-            shape!["di:Style"]!["dmndi:FontColor"]["@_green"] = green;
-            shape!["di:Style"]!["dmndi:FontColor"]["@_blue"] = blue;
-          }
+          state.diagram.isEditingStyle = false;
+          shape["di:Style"]!["dmndi:FontColor"] ??= DEFAULT_FONT_COLOR;
+          shape["di:Style"]!["dmndi:FontColor"]["@_red"] = 
parseInt(temporaryFontColor.slice(0, 2), 16);
+          shape["di:Style"]!["dmndi:FontColor"]["@_green"] = 
parseInt(temporaryFontColor.slice(2, 4), 16);
+          shape["di:Style"]!["dmndi:FontColor"]["@_blue"] = 
parseInt(temporaryFontColor.slice(4, 6), 16);
         });
       });
     }, 0);
+
     return () => {
       clearTimeout(timeout);
     };
-  }, [editShapeStyle, temporaryFontColor]);
+  }, [setShapeStyles, temporaryFontColor]);
 
   const onReset = useCallback(() => {
-    setTemporaryFontColor("000000");
-    editShapeStyle((shapes, state) => {
-      state!.diagram.isEditingStyle = false;
+    setShapeStyles((shapes, state) => {
+      state.diagram.isEditingStyle = false;
       shapes.forEach((shape) => {
         shape["di:Style"]!["@_fontBold"] = undefined;
         shape["di:Style"]!["@_fontItalic"] = undefined;
@@ -251,9 +257,13 @@ export function FontOptions({ startExpanded, nodeIds }: { 
startExpanded: boolean
         shape["di:Style"]!["@_fontStrikeThrough"] = undefined;
         shape["di:Style"]!["@_fontSize"] = undefined;
         shape["di:Style"]!["@_fontFamily"] = undefined;
+        shape["di:Style"]!["dmndi:FontColor"] ??= DEFAULT_FONT_COLOR;
+        shape["di:Style"]!["dmndi:FontColor"]["@_red"] = 
DEFAULT_FONT_COLOR["@_red"];
+        shape["di:Style"]!["dmndi:FontColor"]["@_green"] = 
DEFAULT_FONT_COLOR["@_green"];
+        shape["di:Style"]!["dmndi:FontColor"]["@_blue"] = 
DEFAULT_FONT_COLOR["@_blue"];
       });
     });
-  }, [editShapeStyle]);
+  }, [setShapeStyles]);
 
   return (
     <>
diff --git a/packages/dmn-editor/src/propertiesPanel/ShapeOptions.tsx 
b/packages/dmn-editor/src/propertiesPanel/ShapeOptions.tsx
index 0b0bbdcb786..472efc48fe0 100644
--- a/packages/dmn-editor/src/propertiesPanel/ShapeOptions.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/ShapeOptions.tsx
@@ -35,6 +35,9 @@ import { ColorPicker } from "./ColorPicker";
 import { ToggleGroup, ToggleGroupItem } from 
"@patternfly/react-core/dist/js/components/ToggleGroup";
 import "./ShapeOptions.css";
 
+const DEFAULT_FILL_COLOR = { "@_blue": 255, "@_green": 255, "@_red": 255 };
+const DEFAULT_STROKE_COLOR = { "@_blue": 0, "@_green": 0, "@_red": 0 };
+
 export function ShapeOptions({
   startExpanded,
   nodeIds,
@@ -46,178 +49,208 @@ export function ShapeOptions({
   isDimensioningEnabled: boolean;
   isPositioningEnabled: boolean;
 }) {
-  const [isShapeSectionExpanded, setShapeSectionExpanded] = 
useState<boolean>(startExpanded);
   const dmnEditorStoreApi = useDmnEditorStoreApi();
-  const dmnShapesByHref = useDmnEditorStore((s) => 
s.computed(s).indexes().dmnShapesByHref);
 
-  const shapes = useMemo(() => nodeIds.map((nodeId) => 
dmnShapesByHref.get(nodeId)), [dmnShapesByHref, nodeIds]);
-  // it only edits the first selected node
-  const shapesBound = useMemo(() => shapes[0]?.["dc:Bounds"], [shapes]);
-  const shapesStyle = useMemo(() => shapes.map((shape) => 
shape?.["di:Style"]), [shapes]);
+  const shapes = useDmnEditorStore((s) => nodeIds.map((nodeId) => 
s.computed(s).indexes().dmnShapesByHref.get(nodeId)));
+  const shapeStyles = useMemo(() => shapes.map((shape) => 
shape?.["di:Style"]), [shapes]);
 
-  const boundWidth = useMemo(() => +(shapesBound?.["@_width"]?.toFixed(2) ?? 
""), [shapesBound]);
-  const boundHeight = useMemo(() => +(shapesBound?.["@_height"]?.toFixed(2) ?? 
""), [shapesBound]);
-  const boundPositionX = useMemo(() => +(shapesBound?.["@_x"]?.toFixed(2) ?? 
""), [shapesBound]);
-  const boundPositionY = useMemo(() => +(shapesBound?.["@_y"]?.toFixed(2) ?? 
""), [shapesBound]);
+  // For when a single node is selected.
+  const shapeBound = useMemo(() => shapes[0]?.["dc:Bounds"], [shapes]);
+  const boundWidth = useMemo(() => +(shapeBound?.["@_width"]?.toFixed(2) ?? 
""), [shapeBound]);
+  const boundHeight = useMemo(() => +(shapeBound?.["@_height"]?.toFixed(2) ?? 
""), [shapeBound]);
+  const boundPositionX = useMemo(() => +(shapeBound?.["@_x"]?.toFixed(2) ?? 
""), [shapeBound]);
+  const boundPositionY = useMemo(() => +(shapeBound?.["@_y"]?.toFixed(2) ?? 
""), [shapeBound]);
 
   const fillColor = useMemo(() => {
-    const b = (shapesStyle[0]?.["dmndi:FillColor"]?.["@_blue"] ?? 
255).toString(16);
-    const g = (shapesStyle[0]?.["dmndi:FillColor"]?.["@_green"] ?? 
255).toString(16);
-    const r = (shapesStyle[0]?.["dmndi:FillColor"]?.["@_red"] ?? 
255).toString(16);
+    const b = (shapeStyles[0]?.["dmndi:FillColor"]?.["@_blue"] ?? 
DEFAULT_FILL_COLOR["@_red"]).toString(16);
+    const g = (shapeStyles[0]?.["dmndi:FillColor"]?.["@_green"] ?? 
DEFAULT_FILL_COLOR["@_green"]).toString(16);
+    const r = (shapeStyles[0]?.["dmndi:FillColor"]?.["@_red"] ?? 
DEFAULT_FILL_COLOR["@_blue"]).toString(16);
     return `#${r.length === 1 ? "0" + r : r}${g.length === 1 ? "0" + g : 
g}${b.length === 1 ? "0" + b : b}`;
-  }, [shapesStyle]);
+  }, [shapeStyles]);
+
   const strokeColor = useMemo(() => {
-    const b = (shapesStyle[0]?.["dmndi:StrokeColor"]?.["@_blue"] ?? 
0).toString(16);
-    const g = (shapesStyle[0]?.["dmndi:StrokeColor"]?.["@_green"] ?? 
0).toString(16);
-    const r = (shapesStyle[0]?.["dmndi:StrokeColor"]?.["@_red"] ?? 
0).toString(16);
+    const b = (shapeStyles[0]?.["dmndi:StrokeColor"]?.["@_blue"] ?? 
DEFAULT_STROKE_COLOR["@_red"]).toString(16);
+    const g = (shapeStyles[0]?.["dmndi:StrokeColor"]?.["@_green"] ?? 
DEFAULT_STROKE_COLOR["@_green"]).toString(16);
+    const r = (shapeStyles[0]?.["dmndi:StrokeColor"]?.["@_red"] ?? 
DEFAULT_STROKE_COLOR["@_blue"]).toString(16);
     return `#${r.length === 1 ? "0" + r : r}${g.length === 1 ? "0" + g : 
g}${b.length === 1 ? "0" + b : b}`;
-  }, [shapesStyle]);
-
-  const editNodeBound = useCallback(
-    (callback: (bound?: DC__Bounds, state?: State) => void) => {
-      dmnEditorStoreApi.setState((state) => {
-        const { diagramElements } = addOrGetDrd({
-          definitions: state.dmn.model.definitions,
-          drdIndex: state.diagram.drdIndex,
-        });
-        const shape = diagramElements?.[shapes[0]?.index ?? 0] as 
DMNDI15__DMNShape | undefined;
-        callback(shape?.["dc:Bounds"], state);
+  }, [shapeStyles]);
+
+  const [isShapeSectionExpanded, setShapeSectionExpanded] = 
useState<boolean>(startExpanded);
+
+  const setBounds = useCallback(
+    (callback: (bounds: DC__Bounds, state: State) => void) => {
+      dmnEditorStoreApi.setState((s) => {
+        const { diagramElements } = addOrGetDrd({ definitions: 
s.dmn.model.definitions, drdIndex: s.diagram.drdIndex });
+
+        const index = nodeIds.map((nodeId) => 
s.computed(s).indexes().dmnShapesByHref.get(nodeId))[0]?.index ?? -1;
+        if (index < 0) {
+          throw new Error(`DMN Shape for '${nodeIds[0]}' does not exist.`);
+        }
+
+        const shape = diagramElements?.[index];
+
+        if (shape.__$$element !== "dmndi:DMNShape") {
+          throw new Error(`DMN Element with index ${index} is not a 
DMNShape.`);
+        }
+
+        shape["dc:Bounds"] ??= { "@_height": 0, "@_width": 0, "@_x": 0, "@_y": 
0 };
+
+        callback(shape["dc:Bounds"], s);
       });
     },
-    [dmnEditorStoreApi, shapes]
+    [dmnEditorStoreApi, nodeIds]
   );
 
   const onChangeWidth = useCallback(
     (newWidth: string) => {
-      editNodeBound((bound) => {
-        bound!["@_width"] = +parseFloat(newWidth).toFixed(2);
+      setBounds((bounds) => {
+        bounds["@_width"] = +parseFloat(newWidth).toFixed(2);
       });
     },
-    [editNodeBound]
+    [setBounds]
   );
 
   const onChangeHeight = useCallback(
     (newHeight: string) => {
-      editNodeBound((bound) => {
-        bound!["@_height"] = +parseFloat(newHeight).toFixed(2);
+      setBounds((bounds) => {
+        bounds["@_height"] = +parseFloat(newHeight).toFixed(2);
       });
     },
-    [editNodeBound]
+    [setBounds]
   );
 
   const onChangePositionX = useCallback(
     (newX: string) => {
-      editNodeBound((bound) => {
-        bound!["@_x"] = +parseFloat(newX).toFixed(2);
+      setBounds((bounds) => {
+        bounds["@_x"] = +parseFloat(newX).toFixed(2);
       });
     },
-    [editNodeBound]
+    [setBounds]
   );
 
   const onChangePositionY = useCallback(
     (newY: string) => {
-      editNodeBound((bound) => {
-        bound!["@_y"] = +parseFloat(newY).toFixed(2);
+      setBounds((bounds) => {
+        bounds["@_y"] = +parseFloat(newY).toFixed(2);
       });
     },
-    [editNodeBound]
+    [setBounds]
   );
 
-  const editShapeStyle = useCallback(
-    (callback: (shape: DMNDI15__DMNShape[], state?: State) => void) => {
-      dmnEditorStoreApi.setState((state) => {
-        const { diagramElements } = addOrGetDrd({
-          definitions: state.dmn.model.definitions,
-          drdIndex: state.diagram.drdIndex,
-        });
-        const _shapes = shapes.map((shape) => diagramElements[shape?.index ?? 
0]);
-        _shapes.forEach((_shape, i, _shapes) => {
-          _shapes[i]["di:Style"] ??= { __$$element: "dmndi:DMNStyle" };
+  const setShapeStyles = useCallback(
+    (callback: (shape: DMNDI15__DMNShape[], state: State) => void) => {
+      dmnEditorStoreApi.setState((s) => {
+        const { diagramElements } = addOrGetDrd({ definitions: 
s.dmn.model.definitions, drdIndex: s.diagram.drdIndex });
+
+        const shapes = nodeIds.map((nodeId) => {
+          const shape = s.computed(s).indexes().dmnShapesByHref.get(nodeId);
+          if (!shape) {
+            throw new Error(`DMN Shape for '${nodeId}' does not exist.`);
+          }
+
+          return diagramElements[shape.index];
         });
-        callback(_shapes, state);
+
+        let i = 0;
+        for (const shape of shapes) {
+          if (shape.__$$element !== "dmndi:DMNShape") {
+            throw new Error(`DMN Element with index ${i++} is not a 
DMNShape.`);
+          }
+
+          shape["di:Style"] ??= { __$$element: "dmndi:DMNStyle" };
+        }
+
+        callback(shapes, s);
       });
     },
-    [dmnEditorStoreApi, shapes]
+    [dmnEditorStoreApi, nodeIds]
   );
 
-  const [temporaryStrokeColor, setTemporaryStrokeColor] = 
useState<string>("000000");
+  const [temporaryStrokeColor, setTemporaryStrokeColor] = useState<string | 
undefined>();
   const onChangeStrokeColor = useCallback(
     (newColor: string) => {
       setTemporaryStrokeColor(newColor.replace("#", ""));
-      editShapeStyle((shapes, state) => {
-        state!.diagram.isEditingStyle = true;
+      setShapeStyles((shapes, state) => {
+        state.diagram.isEditingStyle = true;
       });
     },
-    [editShapeStyle]
+    [setShapeStyles]
   );
 
   useEffect(() => {
     const timeout = setTimeout(() => {
-      const red = parseInt(temporaryStrokeColor.slice(0, 2), 16);
-      const green = parseInt(temporaryStrokeColor.slice(2, 4), 16);
-      const blue = parseInt(temporaryStrokeColor.slice(4, 6), 16);
-      editShapeStyle((shapes, state) => {
+      if (!temporaryStrokeColor) {
+        return;
+      }
+
+      setTemporaryStrokeColor(undefined);
+
+      setShapeStyles((shapes, state) => {
         shapes.forEach((shape) => {
-          if (
-            red !== shape?.["di:Style"]?.["dmndi:StrokeColor"]?.["@_red"] &&
-            green !== shape?.["di:Style"]?.["dmndi:StrokeColor"]?.["@_green"] 
&&
-            blue !== shape?.["di:Style"]?.["dmndi:StrokeColor"]?.["@_blue"]
-          ) {
-            state!.diagram.isEditingStyle = false;
-            shape!["di:Style"]!["dmndi:StrokeColor"] ??= { "@_blue": 0, 
"@_green": 0, "@_red": 0 };
-            shape!["di:Style"]!["dmndi:StrokeColor"]["@_red"] = red;
-            shape!["di:Style"]!["dmndi:StrokeColor"]["@_green"] = green;
-            shape!["di:Style"]!["dmndi:StrokeColor"]["@_blue"] = blue;
-          }
+          state.diagram.isEditingStyle = false;
+          shape!["di:Style"]!["dmndi:StrokeColor"] ??= DEFAULT_STROKE_COLOR;
+          shape!["di:Style"]!["dmndi:StrokeColor"]["@_red"] = 
parseInt(temporaryStrokeColor.slice(0, 2), 16);
+          shape!["di:Style"]!["dmndi:StrokeColor"]["@_green"] = 
parseInt(temporaryStrokeColor.slice(2, 4), 16);
+          shape!["di:Style"]!["dmndi:StrokeColor"]["@_blue"] = 
parseInt(temporaryStrokeColor.slice(4, 6), 16);
         });
       });
     }, 0);
+
     return () => {
       clearTimeout(timeout);
     };
-  }, [editShapeStyle, temporaryStrokeColor]);
+  }, [setShapeStyles, temporaryStrokeColor]);
 
-  const [temporaryFillColor, setTemporaryFillColor] = 
useState<string>("ffffff");
+  const [temporaryFillColor, setTemporaryFillColor] = useState<string | 
undefined>();
   const onChangeFillColor = useCallback(
     (newColor: string) => {
       setTemporaryFillColor(newColor.replace("#", ""));
-      editShapeStyle((shapes, state) => {
-        state!.diagram.isEditingStyle = true;
+      setShapeStyles((shapes, state) => {
+        state.diagram.isEditingStyle = true;
       });
     },
-    [editShapeStyle]
+    [setShapeStyles]
   );
 
   useEffect(() => {
     const timeout = setTimeout(() => {
-      const red = parseInt(temporaryFillColor.slice(0, 2), 16);
-      const green = parseInt(temporaryFillColor.slice(2, 4), 16);
-      const blue = parseInt(temporaryFillColor.slice(4, 6), 16);
-      editShapeStyle((shapes, state) => {
+      if (!temporaryFillColor) {
+        return;
+      }
+
+      setTemporaryFillColor(undefined);
+
+      setShapeStyles((shapes, state) => {
         shapes.forEach((shape) => {
-          if (
-            red !== shape?.["di:Style"]?.["dmndi:FillColor"]?.["@_red"] &&
-            green !== shape?.["di:Style"]?.["dmndi:FillColor"]?.["@_green"] &&
-            blue !== shape?.["di:Style"]?.["dmndi:FillColor"]?.["@_blue"]
-          ) {
-            state!.diagram.isEditingStyle = false;
-            shape!["di:Style"]!["dmndi:FillColor"] ??= { "@_blue": 255, 
"@_green": 255, "@_red": 255 };
-            shape!["di:Style"]!["dmndi:FillColor"]["@_red"] = red;
-            shape!["di:Style"]!["dmndi:FillColor"]["@_green"] = green;
-            shape!["di:Style"]!["dmndi:FillColor"]["@_blue"] = blue;
-          }
+          state.diagram.isEditingStyle = false;
+          shape!["di:Style"]!["dmndi:FillColor"] ??= DEFAULT_FILL_COLOR;
+          shape!["di:Style"]!["dmndi:FillColor"]["@_red"] = 
parseInt(temporaryFillColor.slice(0, 2), 16);
+          shape!["di:Style"]!["dmndi:FillColor"]["@_green"] = 
parseInt(temporaryFillColor.slice(2, 4), 16);
+          shape!["di:Style"]!["dmndi:FillColor"]["@_blue"] = 
parseInt(temporaryFillColor.slice(4, 6), 16);
         });
       });
     }, 0);
+
     return () => {
       clearTimeout(timeout);
     };
-  }, [editShapeStyle, temporaryFillColor]);
+  }, [setShapeStyles, temporaryFillColor]);
 
   const onReset = useCallback(() => {
-    setTemporaryStrokeColor("000000");
-    setTemporaryFillColor("ffffff");
-  }, []);
+    setShapeStyles((shapes) => {
+      shapes.forEach((shape) => {
+        shape!["di:Style"]!["dmndi:FillColor"] ??= DEFAULT_FILL_COLOR;
+        shape!["di:Style"]!["dmndi:FillColor"]["@_red"] = 
DEFAULT_FILL_COLOR["@_red"];
+        shape!["di:Style"]!["dmndi:FillColor"]["@_green"] = 
DEFAULT_FILL_COLOR["@_green"];
+        shape!["di:Style"]!["dmndi:FillColor"]["@_blue"] = 
DEFAULT_FILL_COLOR["@_blue"];
+
+        shape!["di:Style"]!["dmndi:StrokeColor"] ??= DEFAULT_STROKE_COLOR;
+        shape!["di:Style"]!["dmndi:StrokeColor"]["@_red"] = 
DEFAULT_STROKE_COLOR["@_red"];
+        shape!["di:Style"]!["dmndi:StrokeColor"]["@_green"] = 
DEFAULT_STROKE_COLOR["@_green"];
+        shape!["di:Style"]!["dmndi:StrokeColor"]["@_blue"] = 
DEFAULT_STROKE_COLOR["@_blue"];
+      });
+    });
+  }, [setShapeStyles]);
 
   const strokeColorPickerRef = React.useRef<HTMLInputElement>(null) as 
React.MutableRefObject<HTMLInputElement>;
   const fillColorPickerRef = React.useRef<HTMLInputElement>(null) as 
React.MutableRefObject<HTMLInputElement>;
diff --git a/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx 
b/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
index cd258c487c5..12a5ccff7f6 100644
--- a/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
+++ b/packages/dmn-editor/src/propertiesPanel/SingleNodeProperties.tsx
@@ -56,6 +56,7 @@ 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 nodeIds = useMemo(() => (node?.id ? [node.id] : []), [node?.id]);
 
   if (!node) {
     return <>Node not found: {nodeId}</>;
@@ -177,10 +178,10 @@ export function SingleNodeProperties({ nodeId }: { 
nodeId: string }) {
           </>
         )}
 
-        <FontOptions startExpanded={false} nodeIds={[node.id]} />
+        <FontOptions startExpanded={false} nodeIds={nodeIds} />
         <ShapeOptions
           startExpanded={false}
-          nodeIds={[node.id]}
+          nodeIds={nodeIds}
           isDimensioningEnabled={true}
           isPositioningEnabled={true}
         />


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to