cloudmr-ux 4.4.7 → 4.4.9

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.
@@ -2,7 +2,17 @@ import React from "react";
2
2
  import "./Toolbar.css";
3
3
  import type { DrawToolkitProps } from "../draw-toolkit/DrawToolkit";
4
4
  /** Props for {@link DrawToolkit} minus brush controls when using {@link MroDrawToolkit}. */
5
- export type CloudMrDrawToolkitProps = Omit<DrawToolkitProps, "brushSize" | "updateBrushSize">;
5
+ export type CloudMrDrawToolkitProps = Omit<DrawToolkitProps, "brushSize" | "updateBrushSize"> & {
6
+ penDrawMode?: "freehand" | "polyline";
7
+ onPenDrawModeChange?: (mode: "freehand" | "polyline") => void;
8
+ polylineVertexCount?: number;
9
+ onCancelPolyline?: () => void;
10
+ onFinishPolyline?: () => void;
11
+ onCloseFillPolyline?: () => void;
12
+ shapeDraftActive?: boolean;
13
+ onApplyShapeDraft?: () => void;
14
+ onCancelShapeDraft?: () => void;
15
+ };
6
16
  export interface CloudMrNiivuePanelProps {
7
17
  nv: any;
8
18
  pipelineID: string;
@@ -106,9 +106,13 @@ export function CloudMrNiivuePanel(props) {
106
106
  props.resampleImage();
107
107
  }, [histogram]);
108
108
  function applyDrawShapeTool(tool) {
109
+ var _a, _b;
109
110
  if (props.shapeDraft) {
110
111
  props.onCancelShapeDraft();
111
112
  }
113
+ if (tool !== "pen") {
114
+ (_b = (_a = props.drawToolkitProps).onPenDrawModeChange) === null || _b === void 0 ? void 0 : _b.call(_a, "freehand");
115
+ }
112
116
  props.setDrawShapeTool(tool);
113
117
  var nv = props.nv;
114
118
  nv.opts.deferShapeCommit = tool === "rectangle" || tool === "ellipse";
@@ -169,6 +169,8 @@ export default function CloudMrNiivueViewer(props) {
169
169
  var _21 = useState(false), saving = _21[0], setSaving = _21[1];
170
170
  var _22 = useState(null), drawShapeTool = _22[0], setDrawShapeTool = _22[1];
171
171
  var _23 = useState(null), shapeDraft = _23[0], setShapeDraft = _23[1];
172
+ var _24 = useState("freehand"), penDrawMode = _24[0], setPenDrawMode = _24[1];
173
+ var _25 = useState(0), polylineVertexCount = _25[0], setPolylineVertexCount = _25[1];
172
174
  var shapeDraftRef = React.useRef(null);
173
175
  var drawShapeToolRef = React.useRef(null);
174
176
  drawShapeToolRef.current = drawShapeTool;
@@ -176,8 +178,8 @@ export default function CloudMrNiivueViewer(props) {
176
178
  React.useEffect(function () {
177
179
  nv.opts.penBounds = 0;
178
180
  }, []);
179
- var _24 = React.useState(1.0), gamma = _24[0], setGamma = _24[1];
180
- var _25 = React.useState(0), gammaKey = _25[0], setGammaKey = _25[1];
181
+ var _26 = React.useState(1.0), gamma = _26[0], setGamma = _26[1];
182
+ var _27 = React.useState(0), gammaKey = _27[0], setGammaKey = _27[1];
181
183
  // Niivue → React bridge so other places (Toolbar) can force the UI to reset
182
184
  nv.onResetGamma = function () {
183
185
  setGamma(1.0);
@@ -222,13 +224,13 @@ export default function CloudMrNiivueViewer(props) {
222
224
  // setLayers([...nv.volumes])
223
225
  // }, [])
224
226
  // values dualslider
225
- var _26 = useState(0), rangeKey = _26[0], setRangeKey = _26[1];
227
+ var _28 = useState(0), rangeKey = _28[0], setRangeKey = _28[1];
226
228
  nv.onResetContrast = function () {
227
229
  setRangeKey(rangeKey + 1);
228
230
  };
229
- var _27 = useState([0, 0, 0]), boundMins = _27[0], setBoundMins = _27[1];
230
- var _28 = useState([1, 1, 1]), boundMaxs = _28[0], setBoundMaxs = _28[1];
231
- var _29 = useState([0.5, 0.5, 0.5]), mms = _29[0], setMMs = _29[1];
231
+ var _29 = useState([0, 0, 0]), boundMins = _29[0], setBoundMins = _29[1];
232
+ var _30 = useState([1, 1, 1]), boundMaxs = _30[0], setBoundMaxs = _30[1];
233
+ var _31 = useState([0.5, 0.5, 0.5]), mms = _31[0], setMMs = _31[1];
232
234
  nv.onImageLoaded = function () {
233
235
  var _a, _b;
234
236
  var oldCrosshairPos = __spreadArray([], nv.scene.crosshairPos, true);
@@ -511,7 +513,7 @@ export default function CloudMrNiivueViewer(props) {
511
513
  setTextsVisible(true);
512
514
  }
513
515
  }
514
- var _30 = useState("pan"), dragMode = _30[0], setDragMode = _30[1];
516
+ var _32 = useState("pan"), dragMode = _32[0], setDragMode = _32[1];
515
517
  function nvSetDragMode(dragMode) {
516
518
  switch (dragMode) {
517
519
  case "none":
@@ -720,7 +722,7 @@ export default function CloudMrNiivueViewer(props) {
720
722
  nv.opts.crosshairWidth = w;
721
723
  nv.drawScene();
722
724
  }
723
- var _31 = useState({}), labelMapping = _31[0], setLabelMapping = _31[1];
725
+ var _33 = useState({}), labelMapping = _33[0], setLabelMapping = _33[1];
724
726
  function resampleImage(mapping) {
725
727
  if (mapping === void 0) { mapping = labelMapping; }
726
728
  var el = typeof document !== "undefined" ? document.getElementById("histoplot") : null;
@@ -733,6 +735,34 @@ export default function CloudMrNiivueViewer(props) {
733
735
  setROIs(next);
734
736
  }
735
737
  }
738
+ function syncPenDrawMode(mode) {
739
+ var _a;
740
+ setPenDrawMode(mode);
741
+ nv.opts.polylinePenMode = mode === "polyline";
742
+ nv.opts.isFilledPen = mode === "freehand";
743
+ if (mode === "freehand") {
744
+ (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
745
+ }
746
+ }
747
+ function cancelPolylineDraft() {
748
+ var _a;
749
+ (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
750
+ }
751
+ function finishPolylineOpen() {
752
+ if (nv.cloudMrFinishPolyline(false)) {
753
+ setDrawingChanged(true);
754
+ resampleImage();
755
+ }
756
+ }
757
+ function finishPolylineClosed() {
758
+ if (nv.cloudMrFinishPolyline(true)) {
759
+ setDrawingChanged(true);
760
+ resampleImage();
761
+ }
762
+ }
763
+ nv.onPolylineChange = function (count) {
764
+ setPolylineVertexCount(count);
765
+ };
736
766
  function cancelShapeDraft() {
737
767
  var draft = shapeDraftRef.current;
738
768
  if (!draft)
@@ -771,26 +801,38 @@ export default function CloudMrNiivueViewer(props) {
771
801
  nvSetDrawingEnabled(false);
772
802
  };
773
803
  React.useEffect(function () {
774
- if (!shapeDraft)
804
+ if (!shapeDraft && !(penDrawMode === "polyline" && polylineVertexCount >= 2)) {
775
805
  return undefined;
806
+ }
776
807
  var onKeyDown = function (event) {
777
- if (event.key === "Enter") {
808
+ if (event.key === "Escape") {
778
809
  event.preventDefault();
779
- applyShapeDraft();
810
+ if (shapeDraft) {
811
+ cancelShapeDraft();
812
+ }
813
+ else if (polylineVertexCount >= 2) {
814
+ cancelPolylineDraft();
815
+ }
816
+ return;
780
817
  }
781
- else if (event.key === "Escape") {
818
+ if (event.key === "Enter") {
782
819
  event.preventDefault();
783
- cancelShapeDraft();
820
+ if (shapeDraft) {
821
+ applyShapeDraft();
822
+ }
823
+ else if (penDrawMode === "polyline" && polylineVertexCount >= 2) {
824
+ finishPolylineOpen();
825
+ }
784
826
  }
785
827
  };
786
828
  window.addEventListener("keydown", onKeyDown);
787
829
  return function () { return window.removeEventListener("keydown", onKeyDown); };
788
- }, [shapeDraft]);
830
+ }, [shapeDraft, penDrawMode, polylineVertexCount]);
789
831
  function nvUpdateSelectionBoxColor(rgb01) {
790
832
  setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
791
833
  nv.setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
792
834
  }
793
- var _32 = React.useState('axial'), sliceType = _32[0], setSliceType = _32[1];
835
+ var _34 = React.useState('axial'), sliceType = _34[0], setSliceType = _34[1];
794
836
  function nvUpdateSliceType(newSliceType) {
795
837
  setSliceType(newSliceType);
796
838
  if (newSliceType === 'axial') {
@@ -930,14 +972,14 @@ export default function CloudMrNiivueViewer(props) {
930
972
  return [2 /*return*/];
931
973
  });
932
974
  }); };
933
- var _33 = useState(''), selectedROI = _33[0], setSelectedDrawingLayer = _33[1];
934
- var _34 = useState(false), saveDialogOpen = _34[0], setSaveDialogOpen = _34[1];
935
- var _35 = useState(function () {
936
- }), saveConfirmCallback = _35[0], setSaveConfirmCallback = _35[1];
937
- var _36 = useState(false), confirmationOpen = _36[0], setConfirmationOpen = _36[1];
938
- var _37 = useState(function () { }), warningConfirmationCallback = _37[0], setWarningConfirmationCallback = _37[1];
939
- var _38 = useState(function () { }), warningCancelCallback = _38[0], setWarningCancelCallback = _38[1];
940
- var _39 = useState(false), drawingChanged = _39[0], setDrawingChanged = _39[1];
975
+ var _35 = useState(''), selectedROI = _35[0], setSelectedDrawingLayer = _35[1];
976
+ var _36 = useState(false), saveDialogOpen = _36[0], setSaveDialogOpen = _36[1];
977
+ var _37 = useState(function () {
978
+ }), saveConfirmCallback = _37[0], setSaveConfirmCallback = _37[1];
979
+ var _38 = useState(false), confirmationOpen = _38[0], setConfirmationOpen = _38[1];
980
+ var _39 = useState(function () { }), warningConfirmationCallback = _39[0], setWarningConfirmationCallback = _39[1];
981
+ var _40 = useState(function () { }), warningCancelCallback = _40[0], setWarningCancelCallback = _40[1];
982
+ var _41 = useState(false), drawingChanged = _41[0], setDrawingChanged = _41[1];
941
983
  // When new drawing strokes are made on top of a saved ROI, reset the dropdown
942
984
  // so it shows the "ROI Layer" placeholder instead of the stale saved name.
943
985
  React.useEffect(function () {
@@ -1264,6 +1306,10 @@ export default function CloudMrNiivueViewer(props) {
1264
1306
  cancelShapeDraft();
1265
1307
  return;
1266
1308
  }
1309
+ if (polylineVertexCount > 0) {
1310
+ cancelPolylineDraft();
1311
+ return;
1312
+ }
1267
1313
  nv.drawUndo();
1268
1314
  resampleImage();
1269
1315
  if (nv.drawBitmap && nv.drawBitmap.every(function (v) { return v === 0; })) {
@@ -1275,7 +1321,13 @@ export default function CloudMrNiivueViewer(props) {
1275
1321
  toggleROIVisible: toggleROIVisible,
1276
1322
  drawingOpacity: drawingOpacity,
1277
1323
  setDrawingOpacity: nvUpdateDrawingOpacity,
1278
- setDrawingChanged: setDrawingChanged
1324
+ setDrawingChanged: setDrawingChanged,
1325
+ penDrawMode: penDrawMode,
1326
+ onPenDrawModeChange: syncPenDrawMode,
1327
+ polylineVertexCount: polylineVertexCount,
1328
+ onCancelPolyline: cancelPolylineDraft,
1329
+ onFinishPolyline: finishPolylineOpen,
1330
+ onCloseFillPolyline: finishPolylineClosed
1279
1331
  };
1280
1332
  return (_jsxs(Box, __assign({ sx: {
1281
1333
  display: 'flex',
@@ -7,6 +7,15 @@ import {
7
7
  isDraftTooSmall,
8
8
  shouldDeferShapeCommit,
9
9
  } from "./shapeDraftUtils.js";
10
+ import {
11
+ addPolylineVertex,
12
+ cancelPolyline,
13
+ finishPolyline,
14
+ isClickWithoutDrag,
15
+ isPolylinePenActive,
16
+ previewPolylineSegment,
17
+ resetPolylineState,
18
+ } from "./polylinePenUtils.js";
10
19
 
11
20
  /*
12
21
  * bitmapOverlay — in-browser state only: remembers each voxel's label *before* Group
@@ -1414,14 +1423,63 @@ Niivue.prototype.drawAddUndoBitmap = function cloudMrDrawAddUndoBitmap(...args)
1414
1423
  return _drawAddUndoBitmap.apply(this, args);
1415
1424
  };
1416
1425
 
1426
+ Niivue.prototype.cloudMrCancelPolyline = function cloudMrCancelPolyline() {
1427
+ cancelPolyline(this);
1428
+ };
1429
+
1430
+ Niivue.prototype.cloudMrFinishPolyline = function cloudMrFinishPolyline(fillClosed = false) {
1431
+ return finishPolyline(this, { fillClosed });
1432
+ };
1433
+
1434
+ Niivue.prototype.cloudMrResetPolyline = function cloudMrResetPolyline() {
1435
+ resetPolylineState(this);
1436
+ };
1437
+
1438
+ const _mouseClick = Niivue.prototype.mouseClick;
1439
+ Niivue.prototype.mouseClick = function cloudMrMouseClick(...args) {
1440
+ if (isPolylinePenActive(this)) {
1441
+ if (this._cloudMrPolylineVertices?.length > 0) {
1442
+ previewPolylineSegment(this);
1443
+ }
1444
+ return false;
1445
+ }
1446
+ return _mouseClick.apply(this, args);
1447
+ };
1448
+
1449
+ const _mouseMoveListener = Niivue.prototype.mouseMoveListener;
1450
+ Niivue.prototype.mouseMoveListener = function cloudMrMouseMoveListener(event) {
1451
+ const result = _mouseMoveListener.call(this, event);
1452
+ if (isPolylinePenActive(this) && this._cloudMrPolylineVertices?.length > 0) {
1453
+ previewPolylineSegment(this);
1454
+ }
1455
+ return result;
1456
+ };
1457
+
1417
1458
  const _mouseUpListener = Niivue.prototype.mouseUpListener;
1418
1459
  Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
1419
1460
  let pendingDraft = null;
1461
+ let polylineClick = false;
1462
+
1420
1463
  if (shouldDeferShapeCommit(this)) {
1421
1464
  this._cloudMrSkipNextUndoBitmap = true;
1422
1465
  pendingDraft = captureDeferredShapeDraft(this);
1423
1466
  }
1467
+
1468
+ if (isPolylinePenActive(this) && isClickWithoutDrag(this.uiData)) {
1469
+ this.drawPenLocation = [NaN, NaN, NaN];
1470
+ this.drawPenFillPts = [];
1471
+ this._cloudMrSkipNextUndoBitmap = true;
1472
+ this._cloudMrSuppressDrawingChangedMouseUp = true;
1473
+ polylineClick = true;
1474
+ }
1475
+
1424
1476
  _mouseUpListener.call(this);
1477
+
1478
+ if (polylineClick) {
1479
+ addPolylineVertex(this);
1480
+ return;
1481
+ }
1482
+
1425
1483
  if (!pendingDraft?.baseBitmap) {
1426
1484
  return;
1427
1485
  }
@@ -1,7 +1,14 @@
1
- export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, shapeDraftActive, onApplyShapeDraft, onCancelShapeDraft, }: {
1
+ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, onCancelPolyline, onFinishPolyline, onCloseFillPolyline, shapeDraftActive, onApplyShapeDraft, onCancelShapeDraft, }: {
2
2
  expanded: any;
3
3
  updateDrawPen: any;
4
4
  setDrawingEnabled: any;
5
+ showPenModes?: boolean | undefined;
6
+ penDrawMode?: string | undefined;
7
+ onPenDrawModeChange: any;
8
+ polylineVertexCount?: number | undefined;
9
+ onCancelPolyline: any;
10
+ onFinishPolyline: any;
11
+ onCloseFillPolyline: any;
5
12
  shapeDraftActive?: boolean | undefined;
6
13
  onApplyShapeDraft: any;
7
14
  onCancelShapeDraft: any;
@@ -12,9 +12,9 @@ var __assign = (this && this.__assign) || function () {
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  /**
14
14
  * Shared ROI label color row (NiiVue pen indices 1–6). Used by pen, rectangle, and ellipse tools.
15
- * Rectangle/ellipse palettes may also show Apply/Cancel while a shape draft is being adjusted.
15
+ * Pen palette adds freehand vs straight-line mode; rectangle/ellipse show Apply/Cancel while adjusting.
16
16
  */
17
- import { Stack, IconButton, Button, Tooltip } from "@mui/material";
17
+ import { Stack, IconButton, Button, Tooltip, Typography } from "@mui/material";
18
18
  import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
19
19
  import CheckIcon from "@mui/icons-material/Check";
20
20
  import CloseIcon from "@mui/icons-material/Close";
@@ -28,24 +28,56 @@ var FILLED_COLORS = [
28
28
  ];
29
29
  var ACTION_FONT_SIZE = "0.75rem";
30
30
  var ACTION_ICON_SIZE = "0.875rem";
31
+ var modeBtnSx = function (active) { return ({
32
+ color: active ? "#c9a0e8" : "#bbb",
33
+ fontSize: ACTION_FONT_SIZE,
34
+ textTransform: "none",
35
+ minWidth: 0,
36
+ py: 0.25,
37
+ px: 0.75
38
+ }); };
31
39
  export default function DrawColorPlatte(_a) {
32
- var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.shapeDraftActive, shapeDraftActive = _b === void 0 ? false : _b, onApplyShapeDraft = _a.onApplyShapeDraft, 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, onCancelPolyline = _a.onCancelPolyline, onFinishPolyline = _a.onFinishPolyline, onCloseFillPolyline = _a.onCloseFillPolyline, _e = _a.shapeDraftActive, shapeDraftActive = _e === void 0 ? false : _e, onApplyShapeDraft = _a.onApplyShapeDraft, onCancelShapeDraft = _a.onCancelShapeDraft;
33
41
  return (_jsxs(Stack, __assign({ style: {
34
42
  position: "absolute",
35
43
  top: "100%",
36
44
  left: 0,
37
45
  zIndex: 1500,
38
46
  border: "".concat(expanded ? "1px" : 0, " solid #bbb"),
39
- maxWidth: expanded ? 300 : 0,
47
+ maxWidth: expanded ? 320 : 0,
48
+ minWidth: expanded ? 240 : 0,
40
49
  overflow: expanded ? "visible" : "hidden",
41
50
  borderRadius: "16px",
42
51
  borderTopLeftRadius: "6pt",
43
52
  borderTopRightRadius: "6pt",
44
53
  background: "#333"
45
- }, direction: "column", spacing: 0.5, sx: { py: shapeDraftActive && expanded ? 0.5 : 0 } }, { children: [_jsx(Stack, __assign({ direction: "row" }, { children: FILLED_COLORS.map(function (color, index) { return (_jsx(IconButton, __assign({ onClick: function () {
54
+ }, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [showPenModes && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.75, pt: 0.25 } }, { children: [_jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("freehand"); }, sx: modeBtnSx(penDrawMode === "freehand") }, { children: "Freehand" })), _jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("polyline"); }, sx: modeBtnSx(penDrawMode === "polyline") }, { children: "Straight lines" }))] }))), _jsx(Stack, __assign({ direction: "row" }, { children: FILLED_COLORS.map(function (color, index) { return (_jsx(IconButton, __assign({ onClick: function () {
46
55
  updateDrawPen({ target: { value: index + 1 } });
47
56
  setDrawingEnabled(true);
48
- } }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), shapeDraftActive && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "flex-end", spacing: 1.5, sx: { px: 1, py: 0.5, borderTop: "1px solid #555" } }, { 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: {
57
+ } }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each corner to draw connected straight lines" }))), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount >= 2 && (_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: "Discard current outline" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "cancel polyline", onClick: function () { return onCancelPolyline === null || onCancelPolyline === void 0 ? void 0 : onCancelPolyline(); }, startIcon: _jsx(CloseIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
58
+ color: "#ccc",
59
+ fontSize: ACTION_FONT_SIZE,
60
+ textTransform: "none",
61
+ minWidth: 0,
62
+ py: 0.25,
63
+ px: 0.75,
64
+ "& .MuiButton-startIcon": { mr: 0.5, ml: 0 }
65
+ } }, { children: "Cancel" })) })), _jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 1 }, { children: [polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: "Connect last point to first and fill" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "close and fill polyline", onClick: function () { return onCloseFillPolyline === null || onCloseFillPolyline === void 0 ? void 0 : onCloseFillPolyline(); }, 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: "Close & fill" })) }))), _jsx(Tooltip, __assign({ title: "Keep outline only (Enter)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "finish polyline", onClick: function () { return onFinishPolyline === null || onFinishPolyline === void 0 ? void 0 : onFinishPolyline(); }, 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: "Done" })) }))] }))] }))), 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: {
49
81
  color: "#ccc",
50
82
  fontSize: ACTION_FONT_SIZE,
51
83
  textTransform: "none",
@@ -170,7 +170,7 @@ export function MroDrawToolkit(props) {
170
170
  alignItems: "center",
171
171
  gap: 4,
172
172
  overflow: "visible"
173
- } }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: toolBtnSx }, { children: filled || expandedOption !== "e" ? (_jsx(EraserIcon, {})) : (_jsx(AutoFixNormalOutlinedIcon, { sx: { color: ICON_COLOR } })) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled })] })), _jsx(Tooltip, __assign({ title: "Undo" }, { children: _jsx(IconButton, __assign({ "aria-label": "revert", size: "small", onClick: function () { return props.drawUndo(); }, sx: toolBtnSx }, { children: _jsx(ReplyIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "Save screenshot" }, { children: _jsx("span", { children: _jsx(IconButton, __assign({ "aria-label": "capture", size: "small", disabled: !vol, onClick: function () { return vol && props.nv.saveScene("".concat(vol.name, "_drawing.png")); }, sx: toolBtnSx }, { children: _jsx(CameraAltIcon, { sx: { color: ICON_COLOR } }) })) }) })), _jsx(Tooltip, __assign({ title: "Clear drawing" }, { children: _jsx(IconButton, __assign({ "aria-label": "delete", size: "small", onClick: function () {
173
+ } }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, onFinishPolyline: props.onFinishPolyline, onCloseFillPolyline: props.onCloseFillPolyline })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: toolBtnSx }, { children: filled || expandedOption !== "e" ? (_jsx(EraserIcon, {})) : (_jsx(AutoFixNormalOutlinedIcon, { sx: { color: ICON_COLOR } })) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled })] })), _jsx(Tooltip, __assign({ title: "Undo" }, { children: _jsx(IconButton, __assign({ "aria-label": "revert", size: "small", onClick: function () { return props.drawUndo(); }, sx: toolBtnSx }, { children: _jsx(ReplyIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "Save screenshot" }, { children: _jsx("span", { children: _jsx(IconButton, __assign({ "aria-label": "capture", size: "small", disabled: !vol, onClick: function () { return vol && props.nv.saveScene("".concat(vol.name, "_drawing.png")); }, sx: toolBtnSx }, { children: _jsx(CameraAltIcon, { sx: { color: ICON_COLOR } }) })) }) })), _jsx(Tooltip, __assign({ title: "Clear drawing" }, { children: _jsx(IconButton, __assign({ "aria-label": "delete", size: "small", onClick: function () {
174
174
  props.nv.clearDrawing();
175
175
  props.resampleImage();
176
176
  props.setDrawingChanged(false);
@@ -0,0 +1,12 @@
1
+ export function isPolylinePenActive(nv: any): any;
2
+ export function isClickWithoutDrag(uiData: any, thresholdPx?: number): boolean;
3
+ export function axCorSagFromMouse(nv: any): any;
4
+ export function voxFromMouse(nv: any): any[] | null;
5
+ export function notifyPolylineChange(nv: any, count: any): void;
6
+ export function resetPolylineState(nv: any): void;
7
+ export function cancelPolyline(nv: any): void;
8
+ export function previewPolylineSegment(nv: any): void;
9
+ export function addPolylineVertex(nv: any): boolean;
10
+ export function finishPolyline(nv: any, { fillClosed }?: {
11
+ fillClosed?: boolean | undefined;
12
+ }): boolean;
@@ -0,0 +1,123 @@
1
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
2
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
3
+ if (ar || !(i in from)) {
4
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
5
+ ar[i] = from[i];
6
+ }
7
+ }
8
+ return to.concat(ar || Array.prototype.slice.call(from));
9
+ };
10
+ import { NI_PEN_TYPE } from "./niivuePenType";
11
+ var CLICK_DRAG_PX = 5;
12
+ export function isPolylinePenActive(nv) {
13
+ return (nv.opts.polylinePenMode &&
14
+ nv.opts.drawingEnabled &&
15
+ nv.opts.penType === NI_PEN_TYPE.PEN);
16
+ }
17
+ export function isClickWithoutDrag(uiData, thresholdPx) {
18
+ if (thresholdPx === void 0) { thresholdPx = CLICK_DRAG_PX; }
19
+ var dx = uiData.dragStart[0] - uiData.dragEnd[0];
20
+ var dy = uiData.dragStart[1] - uiData.dragEnd[1];
21
+ return dx * dx + dy * dy <= thresholdPx * thresholdPx;
22
+ }
23
+ export function axCorSagFromMouse(nv) {
24
+ var _a;
25
+ var tile = nv.tileIndex(nv.mousePos[0], nv.mousePos[1]);
26
+ if (tile >= 0 && ((_a = nv.screenSlices) === null || _a === void 0 ? void 0 : _a[tile])) {
27
+ return nv.screenSlices[tile].axCorSag;
28
+ }
29
+ return nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0;
30
+ }
31
+ export function voxFromMouse(nv) {
32
+ var frac = nv.canvasPos2frac(nv.mousePos);
33
+ if (!frac || frac[0] < 0)
34
+ return null;
35
+ var vox = nv.frac2vox(frac);
36
+ return [vox[0], vox[1], vox[2]];
37
+ }
38
+ export function notifyPolylineChange(nv, count) {
39
+ if (typeof nv.onPolylineChange === "function") {
40
+ nv.onPolylineChange(count);
41
+ }
42
+ }
43
+ export function resetPolylineState(nv) {
44
+ nv._cloudMrPolylineVertices = [];
45
+ nv._cloudMrPolylineBaseBitmap = null;
46
+ nv._cloudMrPolylineSessionStartBitmap = null;
47
+ nv._cloudMrPolylineAxCorSag = -1;
48
+ notifyPolylineChange(nv, 0);
49
+ }
50
+ export function cancelPolyline(nv) {
51
+ if (nv._cloudMrPolylineSessionStartBitmap) {
52
+ nv.drawBitmap.set(nv._cloudMrPolylineSessionStartBitmap);
53
+ nv.refreshDrawing(true, false);
54
+ nv.drawScene();
55
+ }
56
+ resetPolylineState(nv);
57
+ }
58
+ export function previewPolylineSegment(nv) {
59
+ var verts = nv._cloudMrPolylineVertices;
60
+ if (!(verts === null || verts === void 0 ? void 0 : verts.length) || !nv._cloudMrPolylineBaseBitmap)
61
+ return;
62
+ var cursor = voxFromMouse(nv);
63
+ if (!cursor)
64
+ return;
65
+ nv.drawBitmap.set(nv._cloudMrPolylineBaseBitmap);
66
+ nv.drawPenAxCorSag = nv._cloudMrPolylineAxCorSag;
67
+ nv.drawPenLine(cursor, verts[verts.length - 1], nv.opts.penValue);
68
+ nv.refreshDrawing(false, false);
69
+ nv.drawScene();
70
+ }
71
+ export function addPolylineVertex(nv) {
72
+ var pt = voxFromMouse(nv);
73
+ if (!pt)
74
+ return false;
75
+ var penValue = nv.opts.penValue;
76
+ var verts = nv._cloudMrPolylineVertices || (nv._cloudMrPolylineVertices = []);
77
+ if (verts.length === 0) {
78
+ nv._cloudMrPolylineSessionStartBitmap = nv.drawBitmap.slice();
79
+ nv._cloudMrPolylineBaseBitmap = nv._cloudMrPolylineSessionStartBitmap.slice();
80
+ nv._cloudMrPolylineAxCorSag = axCorSagFromMouse(nv);
81
+ nv.drawPenAxCorSag = nv._cloudMrPolylineAxCorSag;
82
+ verts.push(pt);
83
+ nv.drawPt(pt[0], pt[1], pt[2], penValue);
84
+ nv.refreshDrawing(false, false);
85
+ }
86
+ else {
87
+ var prev = verts[verts.length - 1];
88
+ if (prev[0] === pt[0] && prev[1] === pt[1] && prev[2] === pt[2]) {
89
+ return false;
90
+ }
91
+ nv.drawPenAxCorSag = nv._cloudMrPolylineAxCorSag;
92
+ nv.drawPenLine(pt, prev, penValue);
93
+ verts.push(pt);
94
+ nv._cloudMrPolylineBaseBitmap = nv.drawBitmap.slice();
95
+ nv.refreshDrawing(false, false);
96
+ }
97
+ nv.drawScene();
98
+ notifyPolylineChange(nv, verts.length);
99
+ return true;
100
+ }
101
+ export function finishPolyline(nv, _a) {
102
+ var _b = _a === void 0 ? {} : _a, _c = _b.fillClosed, fillClosed = _c === void 0 ? false : _c;
103
+ var verts = nv._cloudMrPolylineVertices;
104
+ if (!verts || verts.length < 2) {
105
+ resetPolylineState(nv);
106
+ return false;
107
+ }
108
+ if (fillClosed && verts.length >= 3) {
109
+ var first = verts[0];
110
+ var last = verts[verts.length - 1];
111
+ nv.drawPenAxCorSag = nv._cloudMrPolylineAxCorSag;
112
+ nv.drawPenLine(first, last, nv.opts.penValue);
113
+ nv.drawPenFillPts = verts.map(function (v) { return __spreadArray([], v, true); });
114
+ nv.drawPenFilled();
115
+ }
116
+ nv.drawAddUndoBitmap(nv.drawFillOverwrites);
117
+ if (typeof nv.onDrawingChanged === "function") {
118
+ nv.onDrawingChanged("draw");
119
+ }
120
+ resetPolylineState(nv);
121
+ nv.drawScene();
122
+ return true;
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.4.7",
3
+ "version": "4.4.9",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",