cloudmr-ux 4.6.5 → 4.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -586,7 +586,6 @@ export default function CloudMrNiivueViewer(props) {
586
586
  setPenDraft(null);
587
587
  penDraftRef.current = null;
588
588
  nv._cloudMrPenDraftActive = false;
589
- clearActivePenDraftRef();
590
589
  setPolylineVertexCount(0);
591
590
  }
592
591
  if (shapeDraftRef.current) {
@@ -611,7 +610,6 @@ export default function CloudMrNiivueViewer(props) {
611
610
  setPenDraft(null);
612
611
  penDraftRef.current = null;
613
612
  nv._cloudMrPenDraftActive = false;
614
- clearActivePenDraftRef();
615
613
  }
616
614
  setDrawShapeTool(null);
617
615
  nv.opts.penType = NI_PEN_TYPE.PEN;
@@ -643,7 +641,6 @@ export default function CloudMrNiivueViewer(props) {
643
641
  setPenDraft(null);
644
642
  penDraftRef.current = null;
645
643
  nv._cloudMrPenDraftActive = false;
646
- clearActivePenDraftRef();
647
644
  }
648
645
  nvUpdateDrawPen({ target: { value: 8 } });
649
646
  nvSetDrawingEnabled(true);
@@ -835,18 +832,6 @@ export default function CloudMrNiivueViewer(props) {
835
832
  (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
836
833
  }
837
834
  }
838
- function syncActiveShapeDraftRef() {
839
- nv._cloudMrActiveShapeDraft = shapeDraftRef.current;
840
- }
841
- function clearActiveShapeDraftRef() {
842
- nv._cloudMrActiveShapeDraft = null;
843
- }
844
- function syncActivePenDraftRef() {
845
- nv._cloudMrActivePenDraft = penDraftRef.current;
846
- }
847
- function clearActivePenDraftRef() {
848
- nv._cloudMrActivePenDraft = null;
849
- }
850
835
  function cancelPenDraftHandler() {
851
836
  var _a;
852
837
  var draft = penDraftRef.current;
@@ -860,7 +845,6 @@ export default function CloudMrNiivueViewer(props) {
860
845
  setPenDraft(null);
861
846
  penDraftRef.current = null;
862
847
  nv._cloudMrPenDraftActive = false;
863
- clearActivePenDraftRef();
864
848
  if (drawShapeToolRef.current === "pen") {
865
849
  nvSetDrawingEnabled(true);
866
850
  }
@@ -878,7 +862,6 @@ export default function CloudMrNiivueViewer(props) {
878
862
  setPenDraft(null);
879
863
  penDraftRef.current = null;
880
864
  nv._cloudMrPenDraftActive = false;
881
- clearActivePenDraftRef();
882
865
  setDrawingChanged(true);
883
866
  if (drawShapeToolRef.current === "pen") {
884
867
  nvSetDrawingEnabled(true);
@@ -895,7 +878,6 @@ export default function CloudMrNiivueViewer(props) {
895
878
  function onPenDraftChange(draft) {
896
879
  setPenDraft(draft);
897
880
  penDraftRef.current = draft;
898
- syncActivePenDraftRef();
899
881
  if (draft.kind === "polyline") {
900
882
  syncPolylineDraftToNv(nv, draft);
901
883
  }
@@ -907,7 +889,6 @@ export default function CloudMrNiivueViewer(props) {
907
889
  setPenDraft(draft);
908
890
  penDraftRef.current = draft;
909
891
  nv._cloudMrPenDraftActive = true;
910
- syncActivePenDraftRef();
911
892
  nvSetDrawingEnabled(false);
912
893
  };
913
894
  nv.onPolylineChange = function (count) {
@@ -922,13 +903,11 @@ export default function CloudMrNiivueViewer(props) {
922
903
  if (draft) {
923
904
  setPenDraft(draft);
924
905
  penDraftRef.current = draft;
925
- syncActivePenDraftRef();
926
906
  }
927
907
  }
928
908
  else if (((_b = penDraftRef.current) === null || _b === void 0 ? void 0 : _b.kind) === "polyline") {
929
909
  setPenDraft(null);
930
910
  penDraftRef.current = null;
931
- clearActivePenDraftRef();
932
911
  }
933
912
  };
934
913
  function cancelShapeDraft() {
@@ -941,7 +920,6 @@ export default function CloudMrNiivueViewer(props) {
941
920
  setShapeDraft(null);
942
921
  shapeDraftRef.current = null;
943
922
  nv._cloudMrShapeDraftActive = false;
944
- clearActiveShapeDraftRef();
945
923
  if (drawShapeToolRef.current) {
946
924
  nvSetDrawingEnabled(true);
947
925
  }
@@ -954,22 +932,26 @@ export default function CloudMrNiivueViewer(props) {
954
932
  setShapeDraft(null);
955
933
  shapeDraftRef.current = null;
956
934
  nv._cloudMrShapeDraftActive = false;
957
- clearActiveShapeDraftRef();
958
- if (drawShapeToolRef.current) {
959
- nvSetDrawingEnabled(true);
960
- }
935
+ // Deactivate tool entirely — palette closes, user re-enters edit by clicking the ROI
936
+ setDrawShapeTool(null);
937
+ nv.opts.deferShapeCommit = false;
938
+ nv.opts.penType = NI_PEN_TYPE.PEN;
939
+ nvSetDrawingEnabled(false);
961
940
  resampleImage();
962
941
  }
963
942
  function onShapeDraftChange(draft) {
964
943
  setShapeDraft(draft);
965
944
  shapeDraftRef.current = draft;
966
- syncActiveShapeDraftRef();
967
945
  }
968
946
  nv.onShapeDraftReady = function (draft) {
969
947
  setShapeDraft(draft);
970
948
  shapeDraftRef.current = draft;
971
949
  nv._cloudMrShapeDraftActive = true;
972
- syncActiveShapeDraftRef();
950
+ // Auto-select the correct tool so the palette opens on re-entry via ROI click
951
+ var tool = draft.penType === NI_PEN_TYPE.ELLIPSE ? "ellipse" : "rectangle";
952
+ setDrawShapeTool(tool);
953
+ nv.opts.penType = draft.penType;
954
+ nv.opts.deferShapeCommit = true;
973
955
  nvSetDrawingEnabled(false);
974
956
  };
975
957
  nv.onApplyActiveDraft = function () {
@@ -986,6 +968,32 @@ export default function CloudMrNiivueViewer(props) {
986
968
  React.useEffect(function () {
987
969
  nv._cloudMrShapeDraftActive = shapeDraft != null;
988
970
  }, [shapeDraft]);
971
+ React.useEffect(function () {
972
+ if (!shapeDraft && !penDraft) {
973
+ return undefined;
974
+ }
975
+ var canvas = document.getElementById("niiCanvas");
976
+ if (!canvas) {
977
+ return undefined;
978
+ }
979
+ var applyOnRightClick = function (event) {
980
+ var _a;
981
+ event.preventDefault();
982
+ event.stopPropagation();
983
+ (_a = nv.onApplyActiveDraft) === null || _a === void 0 ? void 0 : _a.call(nv);
984
+ };
985
+ var onMouseDown = function (event) {
986
+ if (event.button === 2) {
987
+ applyOnRightClick(event);
988
+ }
989
+ };
990
+ canvas.addEventListener("mousedown", onMouseDown, true);
991
+ canvas.addEventListener("contextmenu", applyOnRightClick, true);
992
+ return function () {
993
+ canvas.removeEventListener("mousedown", onMouseDown, true);
994
+ canvas.removeEventListener("contextmenu", applyOnRightClick, true);
995
+ };
996
+ }, [shapeDraft, penDraft]);
989
997
  React.useEffect(function () {
990
998
  if (!shapeDraft && !penDraft) {
991
999
  return undefined;
@@ -999,6 +1007,16 @@ export default function CloudMrNiivueViewer(props) {
999
1007
  else if (penDraft) {
1000
1008
  cancelPenDraftHandler();
1001
1009
  }
1010
+ return;
1011
+ }
1012
+ if (event.key === "Enter") {
1013
+ event.preventDefault();
1014
+ if (shapeDraft) {
1015
+ applyShapeDraft();
1016
+ }
1017
+ else if (penDraft) {
1018
+ applyPenDraftHandler();
1019
+ }
1002
1020
  }
1003
1021
  };
1004
1022
  window.addEventListener("keydown", onKeyDown);
@@ -6,7 +6,6 @@ import {
6
6
  captureDeferredShapeDraft,
7
7
  captureShapeDraftFromClick,
8
8
  isDraftTooSmall,
9
- isVoxelPartOfDraft,
10
9
  redrawDraftShape,
11
10
  shouldDeferShapeCommit,
12
11
  } from "./shapeDraftUtils.js";
@@ -1469,136 +1468,52 @@ Niivue.prototype.cloudMrResetPolyline = function cloudMrResetPolyline() {
1469
1468
  resetPolylineState(this);
1470
1469
  };
1471
1470
 
1471
+ const RIGHT_MOUSE_BUTTON = 2;
1472
+
1472
1473
  function cloudMrHasApplyableDraft(nv) {
1473
1474
  return !!(nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive);
1474
1475
  }
1475
1476
 
1476
- function isClickOnActiveShapeDraft(nv) {
1477
- const draft = nv._cloudMrActiveShapeDraft;
1478
- if (!draft || !nv._cloudMrShapeDraftActive) return false;
1479
- const vox = voxFromMouse(nv);
1480
- if (!vox) return false;
1481
- return isVoxelPartOfDraft(nv, draft, vox);
1482
- }
1483
-
1484
- function isClickOnActivePenDraft(nv) {
1485
- const draft = nv._cloudMrActivePenDraft;
1486
- if (!draft || !nv._cloudMrPenDraftActive) return false;
1487
- const vox = voxFromMouse(nv);
1488
- if (!vox) return false;
1489
- return isVoxelPartOfDraft(nv, draft, vox);
1490
- }
1491
-
1492
- function voxelLabelAt(nv, vox) {
1493
- const dims = nv.back?.dims;
1494
- if (!dims || !nv.drawBitmap || !vox) return 0;
1495
- const dx = dims[1];
1496
- const dy = dims[2];
1497
- const idx = vox[0] + vox[1] * dx + vox[2] * dx * dy;
1498
- return nv.drawBitmap[idx] || 0;
1499
- }
1500
-
1501
- function canReopenShapeDraftOnClick(nv) {
1502
- const penType = nv.opts.penType;
1503
- return (
1504
- nv.opts.deferShapeCommit &&
1505
- nv.opts.drawingEnabled &&
1506
- !nv._cloudMrShapeDraftActive &&
1507
- !nv._cloudMrPenDraftActive &&
1508
- (penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE)
1509
- );
1510
- }
1511
-
1512
- function cloudMrOpenShapeDraftFromClick(nv) {
1513
- const reopenDraft = captureShapeDraftFromClick(nv);
1514
- if (!reopenDraft) return false;
1515
- redrawDraftShape(nv, reopenDraft);
1516
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1517
- if (typeof nv.onShapeDraftReady === "function") {
1518
- nv.onShapeDraftReady(reopenDraft);
1519
- }
1520
- return true;
1521
- }
1522
-
1523
- /**
1524
- * Re-enter rectangle/ellipse edit mode when clicking an existing shape ROI.
1525
- */
1526
- function cloudMrTryOpenShapeDraftOnMouseDown(nv) {
1527
- if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) {
1528
- return false;
1529
- }
1530
- if (!canReopenShapeDraftOnClick(nv)) {
1477
+ function cloudMrTryApplyDraftOnRightClick(nv, event) {
1478
+ if (!cloudMrHasApplyableDraft(nv)) {
1531
1479
  return false;
1532
1480
  }
1533
- const vox = voxFromMouse(nv);
1534
- if (!vox || !voxelLabelAt(nv, vox)) {
1535
- return false;
1536
- }
1537
- return cloudMrOpenShapeDraftFromClick(nv);
1538
- }
1539
-
1540
- /**
1541
- * Click-away exits shape edit mode; clicking another shape selects it for editing.
1542
- * Disconnected same-color shapes are distinguished by flood-fill from the click point.
1543
- */
1544
- function cloudMrHandleShapeDraftClickOnMouseDown(nv) {
1545
- if (!nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) {
1546
- return false;
1547
- }
1548
- if (!nv.opts.deferShapeCommit || !nv.opts.drawingEnabled) {
1549
- return false;
1550
- }
1551
-
1552
- const vox = voxFromMouse(nv);
1553
- if (!vox) {
1554
- return false;
1555
- }
1556
- if (isClickOnActiveShapeDraft(nv)) {
1557
- return false;
1558
- }
1559
-
1481
+ event.preventDefault();
1482
+ event.stopPropagation();
1560
1483
  if (typeof nv.onApplyActiveDraft === "function") {
1561
1484
  nv.onApplyActiveDraft();
1562
1485
  }
1563
-
1564
- if (voxelLabelAt(nv, vox) > 0) {
1565
- cloudMrOpenShapeDraftFromClick(nv);
1566
- }
1567
1486
  return true;
1568
1487
  }
1569
1488
 
1570
- /** Click-away applies a pen draft without starting a new stroke. */
1571
- function cloudMrHandlePenDraftClickOnMouseDown(nv) {
1572
- if (!nv._cloudMrPenDraftActive) {
1573
- return false;
1574
- }
1575
-
1576
- const vox = voxFromMouse(nv);
1577
- if (!vox) {
1489
+ /**
1490
+ * Re-enter rectangle/ellipse edit mode when clicking an existing applied ROI.
1491
+ * Works regardless of which tool is currently active (or whether any tool is active),
1492
+ * so clicking an ROI after apply always re-enters edit mode.
1493
+ */
1494
+ function cloudMrTryReopenShapeDraftOnClick(nv) {
1495
+ if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) {
1578
1496
  return false;
1579
1497
  }
1580
- if (isClickOnActivePenDraft(nv)) {
1498
+ if (!isClickWithoutDrag(nv.uiData)) {
1581
1499
  return false;
1582
1500
  }
1583
1501
 
1584
- if (typeof nv.onApplyActiveDraft === "function") {
1585
- nv.onApplyActiveDraft();
1502
+ const reopenDraft = captureShapeDraftFromClick(nv);
1503
+ if (!reopenDraft) return false;
1504
+
1505
+ redrawDraftShape(nv, reopenDraft);
1506
+ nv._cloudMrSuppressDrawingChangedMouseUp = true;
1507
+ if (typeof nv.onShapeDraftReady === "function") {
1508
+ nv.onShapeDraftReady(reopenDraft);
1586
1509
  }
1587
1510
  return true;
1588
1511
  }
1589
1512
 
1590
1513
  const _mouseDownListener = Niivue.prototype.mouseDownListener;
1591
1514
  Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
1592
- if (e.button === 0) {
1593
- if (cloudMrHandleShapeDraftClickOnMouseDown(this)) {
1594
- return;
1595
- }
1596
- if (cloudMrHandlePenDraftClickOnMouseDown(this)) {
1597
- return;
1598
- }
1599
- if (cloudMrTryOpenShapeDraftOnMouseDown(this)) {
1600
- return;
1601
- }
1515
+ if (e.button === RIGHT_MOUSE_BUTTON && cloudMrTryApplyDraftOnRightClick(this, e)) {
1516
+ return;
1602
1517
  }
1603
1518
  if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
1604
1519
  this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
@@ -1668,12 +1583,14 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
1668
1583
  }
1669
1584
 
1670
1585
  if (!pendingDraft?.baseBitmap) {
1586
+ cloudMrTryReopenShapeDraftOnClick(this);
1671
1587
  return;
1672
1588
  }
1673
1589
  if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
1674
1590
  this.drawBitmap.set(pendingDraft.baseBitmap);
1675
1591
  this.refreshDrawing(true, false);
1676
1592
  this.drawScene();
1593
+ cloudMrTryReopenShapeDraftOnClick(this);
1677
1594
  return;
1678
1595
  }
1679
1596
  this._cloudMrSuppressDrawingChangedMouseUp = true;
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Adjust handles for polyline (vertex drag) or freehand (move only) drafts.
3
- * @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
4
3
  */
5
4
  export function PenDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
6
5
  nv: any;
7
6
  draft: any;
8
- onDraftChange: (d: any) => void;
9
- overlayKey?: unknown;
7
+ onDraftChange: any;
8
+ overlayKey: any;
10
9
  }): import("react/jsx-runtime").JSX.Element | null;
11
10
  export default PenDraftOverlay;
@@ -29,7 +29,6 @@ function cloneFreehandDraft(draft) {
29
29
  }
30
30
  /**
31
31
  * Adjust handles for polyline (vertex drag) or freehand (move only) drafts.
32
- * @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
33
32
  */
34
33
  export function PenDraftOverlay(_a) {
35
34
  var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
@@ -1,4 +1,4 @@
1
- export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onCancelPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onCancelShapeDraft, }: {
1
+ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onApplyPenDraft, onCancelPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, onCancelShapeDraft, }: {
2
2
  expanded: any;
3
3
  updateDrawPen: any;
4
4
  setDrawingEnabled: any;
@@ -7,10 +7,12 @@ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEna
7
7
  onPenDrawModeChange: any;
8
8
  polylineVertexCount?: number | undefined;
9
9
  penDraftActive?: boolean | undefined;
10
+ onApplyPenDraft: any;
10
11
  onCancelPenDraft: any;
11
12
  onFillPenDraft: any;
12
13
  brushSize?: number | undefined;
13
14
  updateBrushSize: any;
14
15
  shapeDraftActive?: boolean | undefined;
16
+ onApplyShapeDraft: any;
15
17
  onCancelShapeDraft: any;
16
18
  }): import("react/jsx-runtime").JSX.Element;
@@ -11,13 +11,12 @@ var __assign = (this && this.__assign) || function () {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  /**
14
- * Pen palette adds freehand vs polyline mode; pen/shape drafts show Cancel while adjusting.
15
- * Shapes are applied automatically on mouse release.
14
+ * Pen palette adds freehand vs polyline mode; pen/shape drafts show Apply/Cancel while adjusting.
16
15
  */
17
16
  import { Stack, IconButton, Button, Tooltip, Typography } from "@mui/material";
18
17
  import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
18
+ import CheckIcon from "@mui/icons-material/Check";
19
19
  import CloseIcon from "@mui/icons-material/Close";
20
- import FormatColorFillIcon from "@mui/icons-material/FormatColorFill";
21
20
  import { BrushSizeSlider } from "./BrushSizeSlider";
22
21
  var FILLED_COLORS = [
23
22
  { sx: { color: "red" } },
@@ -38,7 +37,7 @@ var modeBtnSx = function (active) { return ({
38
37
  px: 0.75
39
38
  }); };
40
39
  export default function DrawColorPlatte(_a) {
41
- var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, onCancelPenDraft = _a.onCancelPenDraft, onFillPenDraft = _a.onFillPenDraft, _f = _a.brushSize, brushSize = _f === void 0 ? 1 : _f, updateBrushSize = _a.updateBrushSize, _g = _a.shapeDraftActive, shapeDraftActive = _g === void 0 ? false : _g, onCancelShapeDraft = _a.onCancelShapeDraft;
40
+ var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, onApplyPenDraft = _a.onApplyPenDraft, onCancelPenDraft = _a.onCancelPenDraft, onFillPenDraft = _a.onFillPenDraft, _f = _a.brushSize, brushSize = _f === void 0 ? 1 : _f, updateBrushSize = _a.updateBrushSize, _g = _a.shapeDraftActive, shapeDraftActive = _g === void 0 ? false : _g, onApplyShapeDraft = _a.onApplyShapeDraft, onCancelShapeDraft = _a.onCancelShapeDraft;
42
41
  return (_jsxs(Stack, __assign({ style: {
43
42
  position: "absolute",
44
43
  top: "100%",
@@ -63,22 +62,36 @@ export default function DrawColorPlatte(_a) {
63
62
  py: 0.25,
64
63
  px: 0.75,
65
64
  "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
66
- } }, { children: "Cancel" })) })), penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Fill interior (keeps outline editable until release)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "fill polyline", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, startIcon: _jsx(FormatColorFillIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
65
+ } }, { children: "Cancel" })) })), _jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 1 }, { children: [penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Fill interior (keeps outline editable until Apply)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "fill polyline", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, sx: {
66
+ color: "#c9a0e8",
67
+ fontSize: ACTION_FONT_SIZE,
68
+ textTransform: "none",
69
+ minWidth: 0,
70
+ py: 0.25,
71
+ px: 0.75
72
+ } }, { children: "Fill" })) }))), _jsx(Tooltip, __assign({ title: "Apply shape (enter or right-click)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "apply pen draft", onClick: function () { return onApplyPenDraft === null || onApplyPenDraft === void 0 ? void 0 : onApplyPenDraft(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
73
+ color: "#c9a0e8",
74
+ fontSize: ACTION_FONT_SIZE,
75
+ textTransform: "none",
76
+ minWidth: 0,
77
+ py: 0.25,
78
+ px: 0.75,
79
+ "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
80
+ } }, { children: "Apply" })) }))] }))] }))), shapeDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "space-between", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [_jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel shape", onClick: function () { return onCancelShapeDraft === null || onCancelShapeDraft === void 0 ? void 0 : onCancelShapeDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
81
+ color: "#ccc",
82
+ fontSize: ACTION_FONT_SIZE,
83
+ textTransform: "none",
84
+ minWidth: 0,
85
+ py: 0.25,
86
+ px: 0.75,
87
+ "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
88
+ } }, { children: "Cancel" })) })), _jsx(Tooltip, __assign({ title: "Apply shape (enter or right-click)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "apply shape", onClick: function () { return onApplyShapeDraft === null || onApplyShapeDraft === void 0 ? void 0 : onApplyShapeDraft(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
67
89
  color: "#c9a0e8",
68
90
  fontSize: ACTION_FONT_SIZE,
69
91
  textTransform: "none",
70
92
  minWidth: 0,
71
93
  py: 0.25,
72
94
  px: 0.75,
73
- ml: "auto",
74
95
  "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
75
- } }, { children: "Fill" })) })))] }))), shapeDraftActive && expanded && (_jsx(Stack, __assign({ direction: "row", alignItems: "center", sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: _jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel shape", onClick: function () { return onCancelShapeDraft === null || onCancelShapeDraft === void 0 ? void 0 : onCancelShapeDraft(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
76
- color: "#ccc",
77
- fontSize: ACTION_FONT_SIZE,
78
- textTransform: "none",
79
- minWidth: 0,
80
- py: 0.25,
81
- px: 0.75,
82
- "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
83
- } }, { children: "Cancel" })) })) })))] })));
96
+ } }, { children: "Apply" })) }))] })))] })));
84
97
  }
@@ -71,6 +71,11 @@ export function MroDrawToolkit(props) {
71
71
  var _d = useState(undefined), setMaskColor = _d[1];
72
72
  var filled = props.drawPen > 7;
73
73
  useEffect(function () {
74
+ // Close palette when tool is deactivated programmatically (e.g. after Apply)
75
+ if (drawShapeTool === null && !props.shapeDraftActive && !props.penDraftActive) {
76
+ setExpandedOption("n");
77
+ return;
78
+ }
74
79
  if (!props.shapeDraftActive && !props.penDraftActive)
75
80
  return;
76
81
  if (drawShapeTool === "rectangle")
@@ -49,27 +49,10 @@ export function captureDeferredShapeDraft(nv: any): {
49
49
  baseBitmap: Uint8Array | null;
50
50
  };
51
51
  export function shouldDeferShapeCommit(nv: any): any;
52
- /**
53
- * Flood-fill a connected voxel cluster from a seed.
54
- * @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
55
- */
56
- export function floodFillClusterFromVox(nv: any, seedVox: any): {
57
- label: number;
58
- visited: Set<number>;
59
- voxels: [number, number, number][];
60
- bounds: object;
61
- } | null;
62
- /** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
63
- export function isVoxelPartOfDraft(nv: any, draft: any, seedVox: any): boolean;
64
- export function eraseClusterFromBitmap(bitmap: any, visited: any): Uint8Array;
65
- /** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
66
- export function inferShapePenTypeFromCluster(cluster: any, axCorSag: any): 1 | 2;
67
52
  /**
68
53
  * When the user clicks on an existing filled ROI while the rectangle/ellipse tool
69
54
  * is active (but no draft is currently open), flood-fill the clicked cluster to
70
55
  * reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
71
- *
72
- * Returns null if the click didn't land on a labeled voxel.
73
56
  */
74
57
  export function captureShapeDraftFromClick(nv: any): {
75
58
  ptA: any[];
@@ -210,11 +210,18 @@ function inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, fallback) {
210
210
  return 2;
211
211
  return fallback;
212
212
  }
213
+ function sliceKey(axCorSag, x, y, z) {
214
+ if (axCorSag === 0)
215
+ return "".concat(x, ",").concat(y);
216
+ if (axCorSag === 1)
217
+ return "".concat(x, ",").concat(z);
218
+ return "".concat(y, ",").concat(z);
219
+ }
213
220
  /**
214
221
  * Flood-fill a connected voxel cluster from a seed.
215
222
  * @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
216
223
  */
217
- export function floodFillClusterFromVox(nv, seedVox) {
224
+ function floodFillClusterFromVox(nv, seedVox) {
218
225
  var _a;
219
226
  var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
220
227
  if (!dims || !nv.drawBitmap || !seedVox)
@@ -274,36 +281,15 @@ export function floodFillClusterFromVox(nv, seedVox) {
274
281
  bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
275
282
  };
276
283
  }
277
- /** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
278
- export function isVoxelPartOfDraft(nv, draft, seedVox) {
279
- var _a;
280
- if (!(draft === null || draft === void 0 ? void 0 : draft.baseBitmap) || !(nv === null || nv === void 0 ? void 0 : nv.drawBitmap) || !seedVox)
281
- return false;
282
- var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
283
- if (!dims)
284
- return false;
285
- var dx = dims[1];
286
- var dy = dims[2];
287
- var idx = voxelIndex(seedVox[0], seedVox[1], seedVox[2], dx, dy);
288
- return (nv.drawBitmap[idx] === draft.penValue &&
289
- draft.baseBitmap[idx] !== draft.penValue);
290
- }
291
- export function eraseClusterFromBitmap(bitmap, visited) {
284
+ function eraseClusterFromBitmap(bitmap, visited) {
292
285
  var next = new Uint8Array(bitmap);
293
286
  visited.forEach(function (idx) {
294
287
  next[idx] = 0;
295
288
  });
296
289
  return next;
297
290
  }
298
- function sliceKey(axCorSag, x, y, z) {
299
- if (axCorSag === 0)
300
- return "".concat(x, ",").concat(y);
301
- if (axCorSag === 1)
302
- return "".concat(x, ",").concat(z);
303
- return "".concat(y, ",").concat(z);
304
- }
305
291
  /** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
306
- export function inferShapePenTypeFromCluster(cluster, axCorSag) {
292
+ function inferShapePenTypeFromCluster(cluster, axCorSag) {
307
293
  var bounds = cluster.bounds, voxels = cluster.voxels;
308
294
  var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
309
295
  var filled = new Set();
@@ -358,8 +344,6 @@ export function inferShapePenTypeFromCluster(cluster, axCorSag) {
358
344
  * When the user clicks on an existing filled ROI while the rectangle/ellipse tool
359
345
  * is active (but no draft is currently open), flood-fill the clicked cluster to
360
346
  * reconstruct a ShapeDraft so the bounding-box overlay reappears for re-editing.
361
- *
362
- * Returns null if the click didn't land on a labeled voxel.
363
347
  */
364
348
  export function captureShapeDraftFromClick(nv) {
365
349
  var seedVox = voxFromMouse(nv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.6.5",
3
+ "version": "4.6.7",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",