cloudmr-ux 4.4.3 → 4.4.4

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.
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import "./Toolbar.scss";
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
5
  export type CloudMrDrawToolkitProps = Omit<DrawToolkitProps, "brushSize" | "updateBrushSize">;
@@ -34,5 +34,9 @@ export interface CloudMrNiivuePanelProps {
34
34
  gamma: number;
35
35
  gammaKey: number;
36
36
  setGamma: (val: number) => void;
37
+ shapeDraft: import("./shapeDraftUtils").ShapeDraft | null;
38
+ onShapeDraftChange: (draft: import("./shapeDraftUtils").ShapeDraft) => void;
39
+ onApplyShapeDraft: () => void;
40
+ onCancelShapeDraft: () => void;
37
41
  }
38
42
  export declare function CloudMrNiivuePanel(props: CloudMrNiivuePanelProps): import("react/jsx-runtime").JSX.Element;
@@ -50,11 +50,12 @@ import React from "react";
50
50
  import { Box } from "@mui/material";
51
51
  import { NI_PEN_TYPE } from "./niivuePenType";
52
52
  import LocationTable from "../../core/common/components/NiivueTools/components/LocationTable";
53
- import "./Toolbar.scss";
53
+ import "./Toolbar.css";
54
54
  import { NiivueSlicePosition } from "../niivue-slice-position/NiivueSlicePosition";
55
55
  import { NiivueContrastAdjustments } from "../niivue-contrast-adjustments/NiivueContrastAdjustments";
56
56
  import { NiivueRoiTable } from "../niivue-roi-table/NiivueRoiTable";
57
57
  import { MroDrawToolkit } from "./mro-draw-toolkit/MroDrawToolkit";
58
+ import { ShapeDraftOverlay } from "./ShapeDraftOverlay";
58
59
  export function CloudMrNiivuePanel(props) {
59
60
  var _this = this;
60
61
  var canvas = React.useRef(null);
@@ -105,8 +106,12 @@ export function CloudMrNiivuePanel(props) {
105
106
  props.resampleImage();
106
107
  }, [histogram]);
107
108
  function applyDrawShapeTool(tool) {
109
+ if (props.shapeDraft) {
110
+ props.onCancelShapeDraft();
111
+ }
108
112
  props.setDrawShapeTool(tool);
109
113
  var nv = props.nv;
114
+ nv.opts.deferShapeCommit = tool === "rectangle" || tool === "ellipse";
110
115
  nv.opts.penType =
111
116
  tool === "rectangle"
112
117
  ? NI_PEN_TYPE.RECTANGLE
@@ -137,7 +142,12 @@ export function CloudMrNiivuePanel(props) {
137
142
  alignItems: "stretch",
138
143
  justifyContent: "flex-start",
139
144
  mb: { xs: 2, md: 0 }
140
- } }, { children: [_jsx(MroDrawToolkit, __assign({}, props.drawToolkitProps, { drawShapeTool: props.drawShapeTool, onDrawShapeToolChange: applyDrawShapeTool, onExitDrawMode: function () { return props.setDrawShapeTool(null); }, style: {
145
+ } }, { children: [_jsx(MroDrawToolkit, __assign({}, props.drawToolkitProps, { drawShapeTool: props.drawShapeTool, onDrawShapeToolChange: applyDrawShapeTool, onExitDrawMode: function () {
146
+ if (props.shapeDraft) {
147
+ props.onCancelShapeDraft();
148
+ }
149
+ props.setDrawShapeTool(null);
150
+ }, shapeDraftActive: props.shapeDraft != null, onApplyShapeDraft: props.onApplyShapeDraft, onCancelShapeDraft: props.onCancelShapeDraft, style: {
141
151
  marginBottom: 0,
142
152
  width: "100%",
143
153
  flexShrink: 0
@@ -148,7 +158,7 @@ export function CloudMrNiivuePanel(props) {
148
158
  color: "white",
149
159
  background: "black",
150
160
  flexShrink: 0
151
- } }), _jsx(Box, __assign({ sx: {
161
+ } }), _jsxs(Box, __assign({ sx: {
152
162
  position: "relative",
153
163
  width: "100%",
154
164
  flexShrink: 0,
@@ -162,13 +172,13 @@ export function CloudMrNiivuePanel(props) {
162
172
  sm: 400,
163
173
  md: 1035
164
174
  }
165
- } }, { children: _jsx("canvas", { id: "niiCanvas", ref: canvas, style: {
166
- position: "absolute",
167
- top: 0,
168
- left: 0,
169
- width: "100%",
170
- height: "100%"
171
- } }) }))] })), _jsxs(Box, __assign({ sx: {
175
+ } }, { children: [_jsx("canvas", { id: "niiCanvas", ref: canvas, style: {
176
+ position: "absolute",
177
+ top: 0,
178
+ left: 0,
179
+ width: "100%",
180
+ height: "100%"
181
+ } }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
172
182
  width: {
173
183
  xs: "100%",
174
184
  md: "35%"
@@ -168,11 +168,16 @@ export default function CloudMrNiivueViewer(props) {
168
168
  var _20 = useState({ a: 1, b: 0 }), transformFactors = _20[0], setTransformFactors = _20[1];
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
+ var _23 = useState(null), shapeDraft = _23[0], setShapeDraft = _23[1];
172
+ var shapeDraftRef = React.useRef(null);
173
+ var drawShapeToolRef = React.useRef(null);
174
+ drawShapeToolRef.current = drawShapeTool;
175
+ shapeDraftRef.current = shapeDraft;
171
176
  React.useEffect(function () {
172
177
  nv.opts.penBounds = 0;
173
178
  }, []);
174
- var _23 = React.useState(1.0), gamma = _23[0], setGamma = _23[1];
175
- var _24 = React.useState(0), gammaKey = _24[0], setGammaKey = _24[1];
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];
176
181
  // Niivue → React bridge so other places (Toolbar) can force the UI to reset
177
182
  nv.onResetGamma = function () {
178
183
  setGamma(1.0);
@@ -217,13 +222,13 @@ export default function CloudMrNiivueViewer(props) {
217
222
  // setLayers([...nv.volumes])
218
223
  // }, [])
219
224
  // values dualslider
220
- var _25 = useState(0), rangeKey = _25[0], setRangeKey = _25[1];
225
+ var _26 = useState(0), rangeKey = _26[0], setRangeKey = _26[1];
221
226
  nv.onResetContrast = function () {
222
227
  setRangeKey(rangeKey + 1);
223
228
  };
224
- var _26 = useState([0, 0, 0]), boundMins = _26[0], setBoundMins = _26[1];
225
- var _27 = useState([1, 1, 1]), boundMaxs = _27[0], setBoundMaxs = _27[1];
226
- var _28 = useState([0.5, 0.5, 0.5]), mms = _28[0], setMMs = _28[1];
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];
227
232
  nv.onImageLoaded = function () {
228
233
  var _a, _b;
229
234
  var oldCrosshairPos = __spreadArray([], nv.scene.crosshairPos, true);
@@ -414,8 +419,12 @@ export default function CloudMrNiivueViewer(props) {
414
419
  // }
415
420
  // console.log(nv.scene.pan2Dxyzmm);
416
421
  };
417
- nv.onMouseUp = function (data) {
418
- if (drawingEnabled) {
422
+ nv.onMouseUp = function () {
423
+ if (nv._cloudMrSuppressDrawingChangedMouseUp) {
424
+ nv._cloudMrSuppressDrawingChangedMouseUp = false;
425
+ return;
426
+ }
427
+ if (nv.opts.drawingEnabled) {
419
428
  setDrawingChanged(true);
420
429
  resampleImage();
421
430
  }
@@ -502,7 +511,7 @@ export default function CloudMrNiivueViewer(props) {
502
511
  setTextsVisible(true);
503
512
  }
504
513
  }
505
- var _29 = useState("pan"), dragMode = _29[0], setDragMode = _29[1];
514
+ var _30 = useState("pan"), dragMode = _30[0], setDragMode = _30[1];
506
515
  function nvSetDragMode(dragMode) {
507
516
  switch (dragMode) {
508
517
  case "none":
@@ -711,7 +720,7 @@ export default function CloudMrNiivueViewer(props) {
711
720
  nv.opts.crosshairWidth = w;
712
721
  nv.drawScene();
713
722
  }
714
- var _30 = useState({}), labelMapping = _30[0], setLabelMapping = _30[1];
723
+ var _31 = useState({}), labelMapping = _31[0], setLabelMapping = _31[1];
715
724
  function resampleImage(mapping) {
716
725
  if (mapping === void 0) { mapping = labelMapping; }
717
726
  var el = typeof document !== "undefined" ? document.getElementById("histoplot") : null;
@@ -724,11 +733,64 @@ export default function CloudMrNiivueViewer(props) {
724
733
  setROIs(next);
725
734
  }
726
735
  }
736
+ function cancelShapeDraft() {
737
+ var draft = shapeDraftRef.current;
738
+ if (!draft)
739
+ return;
740
+ nv.drawBitmap.set(draft.baseBitmap);
741
+ nv.refreshDrawing(true, false);
742
+ nv.drawScene();
743
+ setShapeDraft(null);
744
+ shapeDraftRef.current = null;
745
+ nv._cloudMrShapeDraftActive = false;
746
+ if (drawShapeToolRef.current) {
747
+ nvSetDrawingEnabled(true);
748
+ }
749
+ }
750
+ function applyShapeDraft() {
751
+ if (!shapeDraftRef.current)
752
+ return;
753
+ nv.drawAddUndoBitmap(nv.drawFillOverwrites);
754
+ setDrawingChanged(true);
755
+ setShapeDraft(null);
756
+ shapeDraftRef.current = null;
757
+ nv._cloudMrShapeDraftActive = false;
758
+ if (drawShapeToolRef.current) {
759
+ nvSetDrawingEnabled(true);
760
+ }
761
+ resampleImage();
762
+ }
763
+ function onShapeDraftChange(draft) {
764
+ setShapeDraft(draft);
765
+ shapeDraftRef.current = draft;
766
+ }
767
+ nv.onShapeDraftReady = function (draft) {
768
+ setShapeDraft(draft);
769
+ shapeDraftRef.current = draft;
770
+ nv._cloudMrShapeDraftActive = true;
771
+ nvSetDrawingEnabled(false);
772
+ };
773
+ React.useEffect(function () {
774
+ if (!shapeDraft)
775
+ return undefined;
776
+ var onKeyDown = function (event) {
777
+ if (event.key === "Enter") {
778
+ event.preventDefault();
779
+ applyShapeDraft();
780
+ }
781
+ else if (event.key === "Escape") {
782
+ event.preventDefault();
783
+ cancelShapeDraft();
784
+ }
785
+ };
786
+ window.addEventListener("keydown", onKeyDown);
787
+ return function () { return window.removeEventListener("keydown", onKeyDown); };
788
+ }, [shapeDraft]);
727
789
  function nvUpdateSelectionBoxColor(rgb01) {
728
790
  setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
729
791
  nv.setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
730
792
  }
731
- var _31 = React.useState('axial'), sliceType = _31[0], setSliceType = _31[1];
793
+ var _32 = React.useState('axial'), sliceType = _32[0], setSliceType = _32[1];
732
794
  function nvUpdateSliceType(newSliceType) {
733
795
  setSliceType(newSliceType);
734
796
  if (newSliceType === 'axial') {
@@ -868,14 +930,14 @@ export default function CloudMrNiivueViewer(props) {
868
930
  return [2 /*return*/];
869
931
  });
870
932
  }); };
871
- var _32 = useState(''), selectedROI = _32[0], setSelectedDrawingLayer = _32[1];
872
- var _33 = useState(false), saveDialogOpen = _33[0], setSaveDialogOpen = _33[1];
873
- var _34 = useState(function () {
874
- }), saveConfirmCallback = _34[0], setSaveConfirmCallback = _34[1];
875
- var _35 = useState(false), confirmationOpen = _35[0], setConfirmationOpen = _35[1];
876
- var _36 = useState(function () { }), warningConfirmationCallback = _36[0], setWarningConfirmationCallback = _36[1];
877
- var _37 = useState(function () { }), warningCancelCallback = _37[0], setWarningCancelCallback = _37[1];
878
- var _38 = useState(false), drawingChanged = _38[0], setDrawingChanged = _38[1];
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];
879
941
  // When new drawing strokes are made on top of a saved ROI, reset the dropdown
880
942
  // so it shows the "ROI Layer" placeholder instead of the stale saved name.
881
943
  React.useEffect(function () {
@@ -1198,6 +1260,10 @@ export default function CloudMrNiivueViewer(props) {
1198
1260
  changesMade: drawingChanged,
1199
1261
  // toggleSampleDistribution,
1200
1262
  drawUndo: function () {
1263
+ if (shapeDraftRef.current) {
1264
+ cancelShapeDraft();
1265
+ return;
1266
+ }
1201
1267
  nv.drawUndo();
1202
1268
  resampleImage();
1203
1269
  if (nv.drawBitmap && nv.drawBitmap.every(function (v) { return v === 0; })) {
@@ -1225,7 +1291,7 @@ export default function CloudMrNiivueViewer(props) {
1225
1291
  props.rois[selectedROI].filename : undefined) }), props.niis[selectedVolume] != undefined && _jsx(CloudMrNiivuePanel, { nv: nv, transformFactors: transformFactors, decimalPrecision: decimalPrecision, locationData: locationData, locationTableVisible: locationTableVisible, pipelineID: props.pipelineID, resampleImage: resampleImage, rois: rois, drawToolkitProps: drawToolkitProps, drawShapeTool: drawShapeTool, setDrawShapeTool: setDrawShapeTool, mins: boundMins, maxs: boundMaxs, mms: mms, min: min, max: max, setMin: setMin, setMax: setMax, unzipAndRenderROI: unpackROI, zipAndSendROI: zipAndSendDrawingLayer, setLabelAlias: setLabelAlias, onAfterRoiUpload: function () {
1226
1292
  var _a;
1227
1293
  void ((_a = props.refreshPipelineRois) === null || _a === void 0 ? void 0 : _a.call(props));
1228
- }, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma }, "".concat(selectedVolume))] })));
1294
+ }, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma, shapeDraft: shapeDraft, onShapeDraftChange: onShapeDraftChange, onApplyShapeDraft: applyShapeDraft, onCancelShapeDraft: cancelShapeDraft }, "".concat(selectedVolume))] })));
1229
1295
  }
1230
1296
  function niiToVolume(nii) {
1231
1297
  return {
@@ -2,6 +2,11 @@
2
2
  * This file patches the original NiiVue library to produce customized behaviors and effects.
3
3
  */
4
4
  import { Niivue, NVImage, NVImageFromUrlOptions } from "@niivue/niivue";
5
+ import {
6
+ captureDeferredShapeDraft,
7
+ isDraftTooSmall,
8
+ shouldDeferShapeCommit,
9
+ } from "./shapeDraftUtils.js";
5
10
 
6
11
  /*
7
12
  * bitmapOverlay — in-browser state only: remembers each voxel's label *before* Group
@@ -1399,4 +1404,37 @@ Niivue.prototype.drawCrossLinesMM = function (sliceIndex, axCorSag, axiMM, corMM
1399
1404
  // );
1400
1405
  // gl.bindVertexArray(this.unusedVAO);
1401
1406
  // }
1407
+
1408
+ const _drawAddUndoBitmap = Niivue.prototype.drawAddUndoBitmap;
1409
+ Niivue.prototype.drawAddUndoBitmap = function cloudMrDrawAddUndoBitmap(...args) {
1410
+ if (this._cloudMrSkipNextUndoBitmap) {
1411
+ this._cloudMrSkipNextUndoBitmap = false;
1412
+ return;
1413
+ }
1414
+ return _drawAddUndoBitmap.apply(this, args);
1415
+ };
1416
+
1417
+ const _mouseUpListener = Niivue.prototype.mouseUpListener;
1418
+ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
1419
+ let pendingDraft = null;
1420
+ if (shouldDeferShapeCommit(this)) {
1421
+ this._cloudMrSkipNextUndoBitmap = true;
1422
+ pendingDraft = captureDeferredShapeDraft(this);
1423
+ }
1424
+ _mouseUpListener.call(this);
1425
+ if (!pendingDraft?.baseBitmap) {
1426
+ return;
1427
+ }
1428
+ if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
1429
+ this.drawBitmap.set(pendingDraft.baseBitmap);
1430
+ this.refreshDrawing(true, false);
1431
+ this.drawScene();
1432
+ return;
1433
+ }
1434
+ this._cloudMrSuppressDrawingChangedMouseUp = true;
1435
+ if (typeof this.onShapeDraftReady === "function") {
1436
+ this.onShapeDraftReady(pendingDraft);
1437
+ }
1438
+ };
1439
+
1402
1440
  export { Niivue };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Overlay handles for adjusting a rectangle/ellipse draft before commit.
3
+ * @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
4
+ */
5
+ export function ShapeDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
6
+ nv: any;
7
+ draft: import('./shapeDraftUtils').ShapeDraft;
8
+ onDraftChange: (d: any) => void;
9
+ overlayKey?: unknown;
10
+ }): import("react/jsx-runtime").JSX.Element | null;
11
+ export default ShapeDraftOverlay;
@@ -0,0 +1,175 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
13
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
14
+ if (ar || !(i in from)) {
15
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
16
+ ar[i] = from[i];
17
+ }
18
+ }
19
+ return to.concat(ar || Array.prototype.slice.call(from));
20
+ };
21
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
23
+ import { boundsToCorners, canvasDeltaToVoxDelta, clientToCanvasPos, normalizeBounds, redrawDraftShape, resizeDraftCorner, translatePt, voxToOverlayPos, voxUnderClient, } from "./shapeDraftUtils";
24
+ var HANDLE_SIZE = 10;
25
+ var ACCENT = "#580f8b";
26
+ /**
27
+ * Overlay handles for adjusting a rectangle/ellipse draft before commit.
28
+ * @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
29
+ */
30
+ export function ShapeDraftOverlay(_a) {
31
+ var _b;
32
+ var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
33
+ var dragRef = useRef(null);
34
+ var draftRef = useRef(draft);
35
+ draftRef.current = draft;
36
+ var onPointerMoveRef = useRef(null);
37
+ var finishDragRef = useRef(null);
38
+ var _c = useState(0), setTick = _c[1];
39
+ var bump = useCallback(function () { return setTick(function (t) { return t + 1; }); }, []);
40
+ useEffect(function () {
41
+ var onMove = function () { return bump(); };
42
+ window.addEventListener("resize", onMove);
43
+ return function () { return window.removeEventListener("resize", onMove); };
44
+ }, [bump]);
45
+ var dims = (_b = nv === null || nv === void 0 ? void 0 : nv.back) === null || _b === void 0 ? void 0 : _b.dims;
46
+ var bounds = useMemo(function () {
47
+ if (!dims || !draft)
48
+ return null;
49
+ return normalizeBounds(draft.ptA, draft.ptB, dims);
50
+ }, [dims, draft, overlayKey]);
51
+ var corners = useMemo(function () {
52
+ if (!bounds || draft == null)
53
+ return [];
54
+ return boundsToCorners(bounds, draft.axCorSag);
55
+ }, [bounds, draft === null || draft === void 0 ? void 0 : draft.axCorSag, overlayKey]);
56
+ var cornerCss = useMemo(function () {
57
+ return corners
58
+ .map(function (vox) { return voxToOverlayPos(nv, vox, draft.axCorSag); })
59
+ .filter(Boolean);
60
+ }, [corners, draft === null || draft === void 0 ? void 0 : draft.axCorSag, nv, overlayKey]);
61
+ var centerCss = useMemo(function () {
62
+ if (!bounds || !draft)
63
+ return null;
64
+ var cx = (bounds.x1 + bounds.x2) / 2;
65
+ var cy = (bounds.y1 + bounds.y2) / 2;
66
+ var cz = (bounds.z1 + bounds.z2) / 2;
67
+ return voxToOverlayPos(nv, [cx, cy, cz], draft.axCorSag);
68
+ }, [bounds, draft === null || draft === void 0 ? void 0 : draft.axCorSag, nv, overlayKey]);
69
+ var boxStyle = useMemo(function () {
70
+ if (cornerCss.length < 4)
71
+ return null;
72
+ var xs = cornerCss.map(function (p) { return p.x; });
73
+ var ys = cornerCss.map(function (p) { return p.y; });
74
+ var left = Math.min.apply(Math, xs);
75
+ var top = Math.min.apply(Math, ys);
76
+ var width = Math.max.apply(Math, xs) - left;
77
+ var height = Math.max.apply(Math, ys) - top;
78
+ if (width < 2 || height < 2)
79
+ return null;
80
+ return { left: left, top: top, width: width, height: height };
81
+ }, [cornerCss]);
82
+ var applyDraft = useCallback(function (nextDraft) {
83
+ redrawDraftShape(nv, nextDraft);
84
+ onDraftChange(nextDraft);
85
+ }, [nv, onDraftChange]);
86
+ finishDragRef.current = function () {
87
+ dragRef.current = null;
88
+ if (onPointerMoveRef.current) {
89
+ window.removeEventListener("pointermove", onPointerMoveRef.current);
90
+ }
91
+ if (finishDragRef.current) {
92
+ window.removeEventListener("pointerup", finishDragRef.current);
93
+ }
94
+ };
95
+ onPointerMoveRef.current = function (event) {
96
+ var drag = dragRef.current;
97
+ var currentDraft = draftRef.current;
98
+ if (!drag || !currentDraft)
99
+ return;
100
+ event.preventDefault();
101
+ if (drag.mode === "move") {
102
+ var canvas = nv.canvas || document.getElementById("niiCanvas");
103
+ if (!canvas)
104
+ return;
105
+ var startCanvas = clientToCanvasPos(canvas, drag.startClientX, drag.startClientY);
106
+ var endCanvas = clientToCanvasPos(canvas, event.clientX, event.clientY);
107
+ var delta = canvasDeltaToVoxDelta(nv, startCanvas, endCanvas);
108
+ var next = __assign(__assign({}, currentDraft), { ptA: translatePt(drag.startPtA, delta), ptB: translatePt(drag.startPtB, delta) });
109
+ applyDraft(next);
110
+ return;
111
+ }
112
+ if (drag.mode === "corner") {
113
+ var vox = voxUnderClient(nv, event.clientX, event.clientY);
114
+ if (!vox)
115
+ return;
116
+ var next = resizeDraftCorner(nv, currentDraft, drag.cornerIndex, vox);
117
+ applyDraft(next);
118
+ }
119
+ };
120
+ var startDrag = useCallback(function (event, mode, cornerIndex) {
121
+ if (cornerIndex === void 0) { cornerIndex = -1; }
122
+ event.preventDefault();
123
+ event.stopPropagation();
124
+ dragRef.current = {
125
+ mode: mode,
126
+ cornerIndex: cornerIndex,
127
+ startClientX: event.clientX,
128
+ startClientY: event.clientY,
129
+ startPtA: __spreadArray([], draft.ptA, true),
130
+ startPtB: __spreadArray([], draft.ptB, true)
131
+ };
132
+ window.addEventListener("pointermove", onPointerMoveRef.current);
133
+ window.addEventListener("pointerup", finishDragRef.current);
134
+ }, [applyDraft, draft]);
135
+ useEffect(function () { return function () {
136
+ if (finishDragRef.current) {
137
+ finishDragRef.current();
138
+ }
139
+ }; }, []);
140
+ if (!draft || !boxStyle) {
141
+ return null;
142
+ }
143
+ var handleStyle = {
144
+ position: "absolute",
145
+ width: HANDLE_SIZE,
146
+ height: HANDLE_SIZE,
147
+ marginLeft: -HANDLE_SIZE / 2,
148
+ marginTop: -HANDLE_SIZE / 2,
149
+ borderRadius: "50%",
150
+ background: "#fff",
151
+ border: "2px solid ".concat(ACCENT),
152
+ boxSizing: "border-box",
153
+ cursor: "pointer",
154
+ zIndex: 3,
155
+ touchAction: "none"
156
+ };
157
+ return (_jsxs("div", __assign({ style: {
158
+ position: "absolute",
159
+ inset: 0,
160
+ zIndex: 2,
161
+ pointerEvents: "auto",
162
+ overflow: "hidden"
163
+ }, "aria-label": "Adjust ROI shape" }, { children: [_jsx("div", { style: {
164
+ position: "absolute",
165
+ left: boxStyle.left,
166
+ top: boxStyle.top,
167
+ width: boxStyle.width,
168
+ height: boxStyle.height,
169
+ border: "2px dashed ".concat(ACCENT),
170
+ background: "rgba(88, 15, 139, 0.08)",
171
+ boxSizing: "border-box",
172
+ pointerEvents: "none"
173
+ } }), centerCss && (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "move"); }, style: __assign(__assign({}, handleStyle), { left: centerCss.x, top: centerCss.y, cursor: "move", borderRadius: 2, width: 12, height: 12, marginLeft: -6, marginTop: -6 }), title: "Move shape" })), cornerCss.map(function (pos, i) { return (_jsx("div", { role: "presentation", onPointerDown: function (e) { return startDrag(e, "corner", i); }, style: __assign(__assign({}, handleStyle), { left: pos.x, top: pos.y, cursor: "nwse-resize" }), title: "Resize shape" }, "corner-".concat(i))); })] })));
174
+ }
175
+ export default ShapeDraftOverlay;
@@ -9,7 +9,7 @@ var __assign = (this && this.__assign) || function () {
9
9
  };
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  /**
14
14
  * MROptimum fork of cloudmr-ux DrawToolkit.
15
15
  * Click-away exits drawing mode when you focus elsewhere (slice controls, histogram, etc.),
@@ -31,7 +31,8 @@ import DeleteIcon from "@mui/icons-material/Delete";
31
31
  import VisibilityIcon from "@mui/icons-material/Visibility";
32
32
  import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
33
33
  import OpacityIcon from "@mui/icons-material/Opacity";
34
- import ClickAwayListener from "@mui/material/ClickAwayListener";
34
+ import CheckIcon from "@mui/icons-material/Check";
35
+ import CloseIcon from "@mui/icons-material/Close";
35
36
  import DrawColorPlatte from "./DrawColorPlatte";
36
37
  import EraserPlatte from "./EraserPlatte";
37
38
  import MaskPlatte from "./MaskPlatte";
@@ -141,6 +142,8 @@ export function MroDrawToolkit(props) {
141
142
  var _a;
142
143
  if (clickTargetIsNiivueCanvas(event.target))
143
144
  return;
145
+ if (props.shapeDraftActive)
146
+ return;
144
147
  setExpandedOption("n");
145
148
  setExpandOpacityOptions(false);
146
149
  props.setDrawingEnabled(false);
@@ -160,7 +163,7 @@ export function MroDrawToolkit(props) {
160
163
  alignItems: "center",
161
164
  gap: 4,
162
165
  overflow: "visible"
163
- } }, { 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 })] })), _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 })] })), _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 () {
166
+ } }, { 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 })] })), _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 })] })), _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 } }) })) })), props.shapeDraftActive && (_jsxs(_Fragment, { children: [_jsx(Tooltip, __assign({ title: "Apply shape (Enter)" }, { children: _jsx(IconButton, __assign({ "aria-label": "apply shape", size: "small", onClick: function () { var _a; return (_a = props.onApplyShapeDraft) === null || _a === void 0 ? void 0 : _a.call(props); }, sx: __assign(__assign({}, toolBtnSx), { backgroundColor: "rgba(88, 15, 139, 0.12)", color: "#580f8b" }) }, { children: _jsx(CheckIcon, { sx: { color: "inherit" } }) })) })), _jsx(Tooltip, __assign({ title: "Cancel shape (Esc)" }, { children: _jsx(IconButton, __assign({ "aria-label": "cancel shape", size: "small", onClick: function () { var _a; return (_a = props.onCancelShapeDraft) === null || _a === void 0 ? void 0 : _a.call(props); }, sx: toolBtnSx }, { children: _jsx(CloseIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Typography, __assign({ component: "span", sx: { fontSize: "0.72rem", color: "#580f8b", userSelect: "none", ml: 0.5 } }, { children: "Adjust shape, then Apply" }))] })), _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 () {
164
167
  props.nv.clearDrawing();
165
168
  props.resampleImage();
166
169
  props.setDrawingChanged(false);
@@ -0,0 +1,60 @@
1
+ /** @typedef {'rectangle' | 'ellipse'} ShapeDraftKind */
2
+ /**
3
+ * @typedef {Object} ShapeDraft
4
+ * @property {[number, number, number]} ptA
5
+ * @property {[number, number, number]} ptB
6
+ * @property {number} penValue
7
+ * @property {number} axCorSag
8
+ * @property {number} penType
9
+ * @property {Uint8Array} baseBitmap
10
+ */
11
+ export function penTypeToKind(penType: any): "rectangle" | "ellipse";
12
+ export function normalizeBounds(ptA: any, ptB: any, dims: any): {
13
+ x1: number;
14
+ y1: number;
15
+ z1: number;
16
+ x2: number;
17
+ y2: number;
18
+ z2: number;
19
+ };
20
+ /** @returns {[number, number, number][]} four corners on the draft slice plane */
21
+ export function boundsToCorners(bounds: any, axCorSag: any): [number, number, number][];
22
+ export function cornersToPtAB(corners: any): {
23
+ ptA: any[];
24
+ ptB: any[];
25
+ };
26
+ export function isDraftTooSmall(ptA: any, ptB: any): boolean;
27
+ /** @param {ShapeDraft} draft */
28
+ export function redrawDraftShape(nv: any, draft: ShapeDraft): void;
29
+ export function clientToCanvasPos(canvas: any, clientX: any, clientY: any): number[];
30
+ export function canvasDeltaToVoxDelta(nv: any, startCanvas: any, endCanvas: any): number[];
31
+ export function translatePt(pt: any, delta: any): any[];
32
+ export function voxUnderClient(nv: any, clientX: any, clientY: any): any[] | null;
33
+ /** @returns {{ x: number, y: number } | null} CSS pixels relative to canvas element */
34
+ export function voxToOverlayPos(nv: any, vox: any, axCorSag: any): {
35
+ x: number;
36
+ y: number;
37
+ } | null;
38
+ /**
39
+ * Move one corner while keeping the opposite corner fixed.
40
+ * @param {number} cornerIndex 0..3 matching boundsToCorners order
41
+ */
42
+ export function resizeDraftCorner(nv: any, draft: any, cornerIndex: number, newVox: any): any;
43
+ export function captureDeferredShapeDraft(nv: any): {
44
+ ptA: any[];
45
+ ptB: any[];
46
+ penValue: any;
47
+ axCorSag: any;
48
+ penType: any;
49
+ baseBitmap: Uint8Array | null;
50
+ };
51
+ export function shouldDeferShapeCommit(nv: any): any;
52
+ export type ShapeDraftKind = 'rectangle' | 'ellipse';
53
+ export type ShapeDraft = {
54
+ ptA: [number, number, number];
55
+ ptB: [number, number, number];
56
+ penValue: number;
57
+ axCorSag: number;
58
+ penType: number;
59
+ baseBitmap: Uint8Array;
60
+ };
@@ -0,0 +1,188 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
13
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
14
+ if (ar || !(i in from)) {
15
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
16
+ ar[i] = from[i];
17
+ }
18
+ }
19
+ return to.concat(ar || Array.prototype.slice.call(from));
20
+ };
21
+ import { NI_PEN_TYPE } from "./niivuePenType";
22
+ /** @typedef {'rectangle' | 'ellipse'} ShapeDraftKind */
23
+ /**
24
+ * @typedef {Object} ShapeDraft
25
+ * @property {[number, number, number]} ptA
26
+ * @property {[number, number, number]} ptB
27
+ * @property {number} penValue
28
+ * @property {number} axCorSag
29
+ * @property {number} penType
30
+ * @property {Uint8Array} baseBitmap
31
+ */
32
+ export function penTypeToKind(penType) {
33
+ return penType === NI_PEN_TYPE.ELLIPSE ? "ellipse" : "rectangle";
34
+ }
35
+ export function normalizeBounds(ptA, ptB, dims) {
36
+ var dx = dims[1];
37
+ var dy = dims[2];
38
+ var dz = dims[3];
39
+ return {
40
+ x1: Math.min(Math.max(Math.min(ptA[0], ptB[0]), 0), dx - 1),
41
+ y1: Math.min(Math.max(Math.min(ptA[1], ptB[1]), 0), dy - 1),
42
+ z1: Math.min(Math.max(Math.min(ptA[2], ptB[2]), 0), dz - 1),
43
+ x2: Math.min(Math.max(Math.max(ptA[0], ptB[0]), 0), dx - 1),
44
+ y2: Math.min(Math.max(Math.max(ptA[1], ptB[1]), 0), dy - 1),
45
+ z2: Math.min(Math.max(Math.max(ptA[2], ptB[2]), 0), dz - 1)
46
+ };
47
+ }
48
+ /** @returns {[number, number, number][]} four corners on the draft slice plane */
49
+ export function boundsToCorners(bounds, axCorSag) {
50
+ var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
51
+ if (axCorSag === 0) {
52
+ var z = Math.round((z1 + z2) / 2);
53
+ return [
54
+ [x1, y1, z],
55
+ [x2, y1, z],
56
+ [x1, y2, z],
57
+ [x2, y2, z],
58
+ ];
59
+ }
60
+ if (axCorSag === 1) {
61
+ var y = Math.round((y1 + y2) / 2);
62
+ return [
63
+ [x1, y, z1],
64
+ [x2, y, z1],
65
+ [x1, y, z2],
66
+ [x2, y, z2],
67
+ ];
68
+ }
69
+ var x = Math.round((x1 + x2) / 2);
70
+ return [
71
+ [x, y1, z1],
72
+ [x, y2, z1],
73
+ [x, y1, z2],
74
+ [x, y2, z2],
75
+ ];
76
+ }
77
+ export function cornersToPtAB(corners) {
78
+ return {
79
+ ptA: __spreadArray([], corners[0], true),
80
+ ptB: __spreadArray([], corners[3], true)
81
+ };
82
+ }
83
+ export function isDraftTooSmall(ptA, ptB) {
84
+ return (Math.abs(ptA[0] - ptB[0]) < 1 &&
85
+ Math.abs(ptA[1] - ptB[1]) < 1 &&
86
+ Math.abs(ptA[2] - ptB[2]) < 1);
87
+ }
88
+ /** @param {ShapeDraft} draft */
89
+ export function redrawDraftShape(nv, draft) {
90
+ if (!(nv === null || nv === void 0 ? void 0 : nv.drawBitmap) || !(draft === null || draft === void 0 ? void 0 : draft.baseBitmap))
91
+ return;
92
+ nv.drawBitmap.set(draft.baseBitmap);
93
+ nv.drawPenAxCorSag = draft.axCorSag;
94
+ if (draft.penType === NI_PEN_TYPE.RECTANGLE) {
95
+ nv.drawRectangleMask(draft.ptA, draft.ptB, draft.penValue);
96
+ }
97
+ else {
98
+ nv.drawEllipseMask(draft.ptA, draft.ptB, draft.penValue);
99
+ }
100
+ nv.refreshDrawing(false, false);
101
+ nv.drawScene();
102
+ }
103
+ export function clientToCanvasPos(canvas, clientX, clientY) {
104
+ var rect = canvas.getBoundingClientRect();
105
+ var scaleX = canvas.width / rect.width;
106
+ var scaleY = canvas.height / rect.height;
107
+ return [(clientX - rect.left) * scaleX, (clientY - rect.top) * scaleY];
108
+ }
109
+ export function canvasDeltaToVoxDelta(nv, startCanvas, endCanvas) {
110
+ var f0 = nv.canvasPos2frac(startCanvas);
111
+ var f1 = nv.canvasPos2frac(endCanvas);
112
+ if (!f0 || !f1 || f0[0] < 0 || f1[0] < 0) {
113
+ return [0, 0, 0];
114
+ }
115
+ var v0 = nv.frac2vox(f0);
116
+ var v1 = nv.frac2vox(f1);
117
+ return [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
118
+ }
119
+ export function translatePt(pt, delta) {
120
+ return [pt[0] + delta[0], pt[1] + delta[1], pt[2] + delta[2]];
121
+ }
122
+ export function voxUnderClient(nv, clientX, clientY) {
123
+ var canvas = nv.canvas || document.getElementById("niiCanvas");
124
+ if (!canvas)
125
+ return null;
126
+ var canvasPos = clientToCanvasPos(canvas, clientX, clientY);
127
+ var frac = nv.canvasPos2frac(canvasPos);
128
+ if (!frac || frac[0] < 0)
129
+ return null;
130
+ var vox = nv.frac2vox(frac);
131
+ return [vox[0], vox[1], vox[2]];
132
+ }
133
+ /** @returns {{ x: number, y: number } | null} CSS pixels relative to canvas element */
134
+ export function voxToOverlayPos(nv, vox, axCorSag) {
135
+ var canvas = nv.canvas || document.getElementById("niiCanvas");
136
+ if (!canvas)
137
+ return null;
138
+ var frac = nv.vox2frac(vox);
139
+ var hit = nv.frac2canvasPosWithTile(frac, axCorSag);
140
+ if (!hit)
141
+ return null;
142
+ var rect = canvas.getBoundingClientRect();
143
+ return {
144
+ x: (hit.pos[0] / canvas.width) * rect.width,
145
+ y: (hit.pos[1] / canvas.height) * rect.height
146
+ };
147
+ }
148
+ /**
149
+ * Move one corner while keeping the opposite corner fixed.
150
+ * @param {number} cornerIndex 0..3 matching boundsToCorners order
151
+ */
152
+ export function resizeDraftCorner(nv, draft, cornerIndex, newVox) {
153
+ var _a;
154
+ var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
155
+ if (!dims)
156
+ return draft;
157
+ var bounds = normalizeBounds(draft.ptA, draft.ptB, dims);
158
+ var corners = boundsToCorners(bounds, draft.axCorSag);
159
+ corners[cornerIndex] = __spreadArray([], newVox, true);
160
+ var next = cornersToPtAB(corners);
161
+ return __assign(__assign({}, draft), { ptA: next.ptA, ptB: next.ptB });
162
+ }
163
+ export function captureDeferredShapeDraft(nv) {
164
+ var frac = nv.canvasPos2frac(nv.mousePos);
165
+ var ptB = __spreadArray([], nv.drawShapeStartLocation, true);
166
+ if (frac && frac[0] >= 0) {
167
+ var vox = nv.frac2vox(frac);
168
+ ptB = [vox[0], vox[1], vox[2]];
169
+ }
170
+ return {
171
+ ptA: __spreadArray([], nv.drawShapeStartLocation, true),
172
+ ptB: ptB,
173
+ penValue: nv.opts.penValue,
174
+ axCorSag: nv.drawPenAxCorSag,
175
+ penType: nv.opts.penType,
176
+ baseBitmap: nv.drawShapePreviewBitmap
177
+ ? new Uint8Array(nv.drawShapePreviewBitmap)
178
+ : null
179
+ };
180
+ }
181
+ export function shouldDeferShapeCommit(nv) {
182
+ var penType = nv.opts.penType;
183
+ return (nv.opts.deferShapeCommit &&
184
+ nv.opts.drawingEnabled &&
185
+ !Number.isNaN(nv.drawShapeStartLocation[0]) &&
186
+ (penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE) &&
187
+ nv.drawShapePreviewBitmap);
188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.4.3",
3
+ "version": "4.4.4",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",