cloudmr-ux 4.8.6 → 4.8.8
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.
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +24 -12
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.js +11 -2
- package/dist/CmrComponents/niivue-viewer/polylinePenUtils.js +12 -1
- package/dist/CmrComponents/tk-dualrange/TKDualRange.js +65 -30
- package/dist/CmrComponents/tk-dualrange/tk-dual-range.css +22 -0
- package/dist/core/common/components/NiivueTools/NiivuePatcher.js +13 -5
- package/package.json +1 -1
|
@@ -849,7 +849,15 @@ Niivue.prototype.groupLabelsFromSelection = function (sourceLabels = [], targetL
|
|
|
849
849
|
};
|
|
850
850
|
|
|
851
851
|
Niivue.prototype.resetScene = function () {
|
|
852
|
-
|
|
852
|
+
// Reset 2D pan + zoom
|
|
853
|
+
this.scene.pan2Dxyzmm = [0, 0, 0, 1];
|
|
854
|
+
// Reset 3D camera to Niivue defaults (azimuth=110, elevation=10, scale=1)
|
|
855
|
+
if (typeof this.setRenderAzimuthElevation === "function") {
|
|
856
|
+
this.setRenderAzimuthElevation(110, 10);
|
|
857
|
+
}
|
|
858
|
+
if (typeof this.setScale === "function") {
|
|
859
|
+
this.setScale(1.0);
|
|
860
|
+
}
|
|
853
861
|
this.drawScene();
|
|
854
862
|
}
|
|
855
863
|
|
|
@@ -871,19 +879,19 @@ Niivue.prototype.recenter = function () {
|
|
|
871
879
|
|
|
872
880
|
|
|
873
881
|
Niivue.prototype.resetZoom = function () {
|
|
874
|
-
//
|
|
875
|
-
// this.scene.pan2Dxyzmm[1] = 0;
|
|
876
|
-
// this.scene.pan2Dxyzmm[2] = 0;
|
|
877
|
-
|
|
882
|
+
// Reset 2D zoom (pan stays, only zoom component adjusted)
|
|
878
883
|
const zoom = 1;
|
|
879
|
-
|
|
880
|
-
const zoomChange = this.scene.pan2Dxyzmm[3] - zoom
|
|
884
|
+
const zoomChange = this.scene.pan2Dxyzmm[3] - zoom;
|
|
881
885
|
this.scene.pan2Dxyzmm[3] = zoom;
|
|
882
|
-
const mm = this.frac2mm(this.scene.crosshairPos)
|
|
883
|
-
this.scene.pan2Dxyzmm[0] += zoomChange * mm[0]
|
|
884
|
-
this.scene.pan2Dxyzmm[1] += zoomChange * mm[1]
|
|
885
|
-
this.scene.pan2Dxyzmm[2] += zoomChange * mm[2]
|
|
886
|
-
|
|
886
|
+
const mm = this.frac2mm(this.scene.crosshairPos);
|
|
887
|
+
this.scene.pan2Dxyzmm[0] += zoomChange * mm[0];
|
|
888
|
+
this.scene.pan2Dxyzmm[1] += zoomChange * mm[1];
|
|
889
|
+
this.scene.pan2Dxyzmm[2] += zoomChange * mm[2];
|
|
890
|
+
// Reset 3D zoom to default scale
|
|
891
|
+
if (typeof this.setScale === "function") {
|
|
892
|
+
this.setScale(1.0);
|
|
893
|
+
}
|
|
894
|
+
this.drawScene();
|
|
887
895
|
}
|
|
888
896
|
|
|
889
897
|
Niivue.prototype.setCenteredZoom = function (zoom) {
|
|
@@ -1528,6 +1536,10 @@ function clickedVoxelToolKind(nv) {
|
|
|
1528
1536
|
function cloudMrTryReopenDraftOnClick(nv) {
|
|
1529
1537
|
if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) return;
|
|
1530
1538
|
if (!isClickWithoutDrag(nv.uiData)) return;
|
|
1539
|
+
// Never reopen a draft when the eraser is the active tool — the user is
|
|
1540
|
+
// trying to erase, not re-select. The eraser sets penValue to 0 with
|
|
1541
|
+
// drawing enabled; that combination uniquely identifies the eraser here.
|
|
1542
|
+
if (nv.opts.drawingEnabled && nv.opts.penValue === 0) return;
|
|
1531
1543
|
|
|
1532
1544
|
const kind = clickedVoxelToolKind(nv);
|
|
1533
1545
|
if (kind === 1) {
|
|
@@ -440,11 +440,18 @@ export function capturePolylineDraftFromClick(nv) {
|
|
|
440
440
|
return null;
|
|
441
441
|
var dx = nv.back.dims[1];
|
|
442
442
|
var dy = nv.back.dims[2];
|
|
443
|
-
// Decode stored linear indices back to [x, y, z] triples
|
|
443
|
+
// Decode stored linear indices back to [x, y, z] triples, but only include
|
|
444
|
+
// voxels that are still present in the current bitmap. Registered indices
|
|
445
|
+
// that were cleared by the eraser must not be restored — otherwise re-opening
|
|
446
|
+
// the draft would silently undo the user's erasure.
|
|
444
447
|
var strokeVoxels = [];
|
|
448
|
+
var presentIndices = new Set();
|
|
445
449
|
var x1 = Infinity, y1 = Infinity, z1 = Infinity;
|
|
446
450
|
var x2 = -Infinity, y2 = -Infinity, z2 = -Infinity;
|
|
447
451
|
entry.voxelIndices.forEach(function (idx) {
|
|
452
|
+
if (nv.drawBitmap[idx] === 0)
|
|
453
|
+
return; // already erased — skip
|
|
454
|
+
presentIndices.add(idx);
|
|
448
455
|
var z = Math.floor(idx / (dx * dy));
|
|
449
456
|
var rem = idx - z * dx * dy;
|
|
450
457
|
var y = Math.floor(rem / dx);
|
|
@@ -463,7 +470,9 @@ export function capturePolylineDraftFromClick(nv) {
|
|
|
463
470
|
if (z > z2)
|
|
464
471
|
z2 = z;
|
|
465
472
|
});
|
|
466
|
-
|
|
473
|
+
if (strokeVoxels.length === 0)
|
|
474
|
+
return null;
|
|
475
|
+
var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, presentIndices);
|
|
467
476
|
return {
|
|
468
477
|
kind: "freehand",
|
|
469
478
|
baseBitmap: baseBitmap,
|
|
@@ -44,6 +44,7 @@ export function notifyPolylineChange(nv, count) {
|
|
|
44
44
|
export function resetPolylineState(nv) {
|
|
45
45
|
nv._cloudMrPolylineVertices = [];
|
|
46
46
|
nv._cloudMrPolylineBaseBitmap = null;
|
|
47
|
+
nv._cloudMrPolylinePrevBaseBitmap = null;
|
|
47
48
|
nv._cloudMrPolylineSessionStartBitmap = null;
|
|
48
49
|
nv._cloudMrPolylineAxCorSag = -1;
|
|
49
50
|
nv._cloudMrLastPolylineClickMs = null;
|
|
@@ -90,12 +91,20 @@ export function addPolylineVertex(nv) {
|
|
|
90
91
|
now - nv._cloudMrLastPolylineClickMs <= DOUBLE_CLICK_MS;
|
|
91
92
|
nv._cloudMrLastPolylineClickMs = now;
|
|
92
93
|
if (isDoubleClick) {
|
|
93
|
-
//
|
|
94
|
+
// The first click of the double-click already placed an extra vertex.
|
|
95
|
+
// Roll it back using the snapshot saved before that click, then close.
|
|
96
|
+
if (nv._cloudMrPolylinePrevBaseBitmap) {
|
|
97
|
+
verts.pop();
|
|
98
|
+
nv._cloudMrPolylineBaseBitmap = nv._cloudMrPolylinePrevBaseBitmap;
|
|
99
|
+
nv.drawBitmap.set(nv._cloudMrPolylinePrevBaseBitmap);
|
|
100
|
+
nv.refreshDrawing(false, false);
|
|
101
|
+
}
|
|
94
102
|
return POLYLINE_CLOSE;
|
|
95
103
|
}
|
|
96
104
|
if (verts.length === 0) {
|
|
97
105
|
nv._cloudMrPolylineSessionStartBitmap = nv.drawBitmap.slice();
|
|
98
106
|
nv._cloudMrPolylineBaseBitmap = nv._cloudMrPolylineSessionStartBitmap.slice();
|
|
107
|
+
nv._cloudMrPolylinePrevBaseBitmap = null;
|
|
99
108
|
nv._cloudMrPolylineAxCorSag = axCorSagFromMouse(nv);
|
|
100
109
|
nv.drawPenAxCorSag = nv._cloudMrPolylineAxCorSag;
|
|
101
110
|
verts.push(pt);
|
|
@@ -108,6 +117,8 @@ export function addPolylineVertex(nv) {
|
|
|
108
117
|
return false;
|
|
109
118
|
}
|
|
110
119
|
nv.drawPenAxCorSag = nv._cloudMrPolylineAxCorSag;
|
|
120
|
+
// Save the pre-segment bitmap so a subsequent double-click can roll back.
|
|
121
|
+
nv._cloudMrPolylinePrevBaseBitmap = nv._cloudMrPolylineBaseBitmap;
|
|
111
122
|
nv.drawPenLine(pt, prev, penValue);
|
|
112
123
|
verts.push(pt);
|
|
113
124
|
nv._cloudMrPolylineBaseBitmap = nv.drawBitmap.slice();
|
|
@@ -10,12 +10,71 @@ var __assign = (this && this.__assign) || function () {
|
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
13
14
|
import "./tk-dual-range.css";
|
|
14
15
|
var clamp = function (v, lo, hi) { return Math.max(lo, Math.min(hi, v)); };
|
|
16
|
+
// Display REAL-space values; use scientific notation for small non-zero values
|
|
17
|
+
// so they don't display as "0.000". Use type="text" because type="number" can
|
|
18
|
+
// show "0" for very small values instead of scientific notation.
|
|
19
|
+
function fmt(v, precision) {
|
|
20
|
+
if (!Number.isFinite(v))
|
|
21
|
+
return "";
|
|
22
|
+
return v !== 0 && Math.abs(v) < 0.01
|
|
23
|
+
? Number(v).toExponential(precision)
|
|
24
|
+
: v.toFixed(precision);
|
|
25
|
+
}
|
|
26
|
+
function parse(s) {
|
|
27
|
+
var n = Number(s);
|
|
28
|
+
return Number.isFinite(n) ? n : NaN;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A text input that holds a local string draft while the user is typing,
|
|
32
|
+
* committing only on blur or Enter. This lets the user type multi-digit and
|
|
33
|
+
* decimal values without mid-entry validation snapping the field.
|
|
34
|
+
* Shows a small red "Out of range" message when the committed value falls
|
|
35
|
+
* outside [lo, hi].
|
|
36
|
+
*/
|
|
37
|
+
function DeferredInput(_a) {
|
|
38
|
+
var committed = _a.committed, onCommit = _a.onCommit, lo = _a.lo, hi = _a.hi, className = _a.className;
|
|
39
|
+
var _b = useState(committed), draft = _b[0], setDraft = _b[1];
|
|
40
|
+
var _c = useState(false), focused = _c[0], setFocused = _c[1];
|
|
41
|
+
var _d = useState(null), error = _d[0], setError = _d[1];
|
|
42
|
+
// Sync external changes into the field only when the user is not typing.
|
|
43
|
+
useEffect(function () {
|
|
44
|
+
if (!focused)
|
|
45
|
+
setDraft(committed);
|
|
46
|
+
}, [committed, focused]);
|
|
47
|
+
var commit = function (value) {
|
|
48
|
+
var n = Number(value);
|
|
49
|
+
if (!Number.isFinite(n)) {
|
|
50
|
+
setError("\"".concat(value, "\" is not a valid number"));
|
|
51
|
+
}
|
|
52
|
+
else if (n < lo || n > hi) {
|
|
53
|
+
setError("".concat(value.trim(), " is out of range"));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
setError(null);
|
|
57
|
+
}
|
|
58
|
+
onCommit(value);
|
|
59
|
+
// After commit the parent re-formats, so sync back to avoid a stale draft.
|
|
60
|
+
setDraft(committed);
|
|
61
|
+
};
|
|
62
|
+
return (_jsxs("div", __assign({ className: "tkdr__input-wrap" }, { children: [_jsx("input", { className: "".concat(className !== null && className !== void 0 ? className : "").concat(error ? " tkdr__num--error" : ""), type: "text", inputMode: "decimal", value: draft, onChange: function (e) { return setDraft(e.target.value); }, onFocus: function () {
|
|
63
|
+
setFocused(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
}, onBlur: function (e) {
|
|
66
|
+
setFocused(false);
|
|
67
|
+
commit(e.target.value);
|
|
68
|
+
}, onKeyDown: function (e) {
|
|
69
|
+
if (e.key === "Enter") {
|
|
70
|
+
e.target.blur();
|
|
71
|
+
}
|
|
72
|
+
} }), error && _jsx("span", __assign({ className: "tkdr__error" }, { children: error }))] })));
|
|
73
|
+
}
|
|
15
74
|
export default function TKDualRange(_a) {
|
|
16
75
|
var _b;
|
|
17
76
|
var _c = _a.name, name = _c === void 0 ? "Values" : _c, minDomain = _a.minDomain, maxDomain = _a.maxDomain, valueLow = _a.valueLow, valueHigh = _a.valueHigh, onChangeLow = _a.onChangeLow, onChangeHigh = _a.onChangeHigh, _d = _a.transform, transform = _d === void 0 ? function (x) { return x; } : _d, _e = _a.inverse, inverse = _e === void 0 ? function (y) { return y; } : _e, step = _a.step, _f = _a.precision, precision = _f === void 0 ? 3 : _f, _g = _a.accentColor, accentColor = _g === void 0 ? "#580f8b" : _g;
|
|
18
|
-
// Map domain & current values into RENDER space
|
|
77
|
+
// Map domain & current values into RENDER space
|
|
19
78
|
var tMin = transform(minDomain);
|
|
20
79
|
var tMax = transform(maxDomain);
|
|
21
80
|
var tLow = transform(valueLow);
|
|
@@ -23,7 +82,7 @@ export default function TKDualRange(_a) {
|
|
|
23
82
|
var span = Math.max(1e-12, tMax - tMin);
|
|
24
83
|
var pct = function (t) { return ((t - tMin) / span) * 100; };
|
|
25
84
|
var s = step !== null && step !== void 0 ? step : Math.max(span * 0.001, Number.EPSILON);
|
|
26
|
-
// Keep ends from crossing; clamp in REAL space against the other end
|
|
85
|
+
// Keep ends from crossing; clamp in REAL space against the other end.
|
|
27
86
|
var handleLowRender = function (nextRender) {
|
|
28
87
|
var nextReal = clamp(inverse(nextRender), minDomain, valueHigh);
|
|
29
88
|
onChangeLow(nextReal);
|
|
@@ -32,37 +91,13 @@ export default function TKDualRange(_a) {
|
|
|
32
91
|
var nextReal = clamp(inverse(nextRender), valueLow, maxDomain);
|
|
33
92
|
onChangeHigh(nextReal);
|
|
34
93
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// type="number" can show "0" for very small values instead of scientific notation.
|
|
38
|
-
var fmt = function (v) {
|
|
39
|
-
return Number.isFinite(v)
|
|
40
|
-
? v !== 0 && Math.abs(v) < 0.01
|
|
41
|
-
? Number(v).toExponential(precision)
|
|
42
|
-
: v.toFixed(precision)
|
|
43
|
-
: "";
|
|
44
|
-
};
|
|
45
|
-
var parse = function (s) {
|
|
46
|
-
var n = Number(s);
|
|
47
|
-
return Number.isFinite(n) ? n : NaN;
|
|
48
|
-
};
|
|
49
|
-
return (_jsxs("div", __assign({ className: "tkdr" }, { children: [_jsxs("div", __assign({ className: "tkdr__row tkdr__row--ends" }, { children: [_jsxs("div", __assign({ className: "tkdr__group" }, { children: [_jsx("span", __assign({ className: "tkdr__hint" }, { children: "Min" })), _jsx("input", { className: "tkdr__num", type: "text", inputMode: "decimal", value: fmt(valueLow), onChange: function (e) {
|
|
50
|
-
var n = parse(e.target.value);
|
|
51
|
-
if (!Number.isFinite(n))
|
|
52
|
-
return;
|
|
53
|
-
onChangeLow(clamp(n, minDomain, valueHigh));
|
|
54
|
-
}, onBlur: function (e) {
|
|
55
|
-
var n = parse(e.target.value);
|
|
94
|
+
return (_jsxs("div", __assign({ className: "tkdr" }, { children: [_jsxs("div", __assign({ className: "tkdr__row tkdr__row--ends" }, { children: [_jsxs("div", __assign({ className: "tkdr__group" }, { children: [_jsx("span", __assign({ className: "tkdr__hint" }, { children: "Min" })), _jsx(DeferredInput, { className: "tkdr__num", committed: fmt(valueLow, precision), lo: minDomain, hi: valueHigh, onCommit: function (raw) {
|
|
95
|
+
var n = parse(raw);
|
|
56
96
|
if (!Number.isFinite(n))
|
|
57
97
|
return;
|
|
58
98
|
onChangeLow(clamp(n, minDomain, valueHigh));
|
|
59
|
-
} })] })), _jsxs("div", __assign({ className: "tkdr__group" }, { children: [_jsx("span", __assign({ className: "tkdr__hint" }, { children: "Max" })), _jsx(
|
|
60
|
-
var n = parse(
|
|
61
|
-
if (!Number.isFinite(n))
|
|
62
|
-
return;
|
|
63
|
-
onChangeHigh(clamp(n, valueLow, maxDomain));
|
|
64
|
-
}, onBlur: function (e) {
|
|
65
|
-
var n = parse(e.target.value);
|
|
99
|
+
} })] })), _jsxs("div", __assign({ className: "tkdr__group" }, { children: [_jsx("span", __assign({ className: "tkdr__hint" }, { children: "Max" })), _jsx(DeferredInput, { className: "tkdr__num", committed: fmt(valueHigh, precision), lo: valueLow, hi: maxDomain, onCommit: function (raw) {
|
|
100
|
+
var n = parse(raw);
|
|
66
101
|
if (!Number.isFinite(n))
|
|
67
102
|
return;
|
|
68
103
|
onChangeHigh(clamp(n, valueLow, maxDomain));
|
|
@@ -140,3 +140,25 @@
|
|
|
140
140
|
font-weight: 400;
|
|
141
141
|
font-family: 'Inter', 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
/* wrapper that stacks the input + error text */
|
|
145
|
+
.tkdr__input-wrap {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
align-items: flex-start;
|
|
149
|
+
gap: 2px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* red outline when the value is invalid */
|
|
153
|
+
.tkdr__num--error {
|
|
154
|
+
border-color: #d32f2f;
|
|
155
|
+
outline-color: #d32f2f;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* small red validation message */
|
|
159
|
+
.tkdr__error {
|
|
160
|
+
font-size: 0.7rem;
|
|
161
|
+
color: #d32f2f;
|
|
162
|
+
line-height: 1;
|
|
163
|
+
white-space: nowrap;
|
|
164
|
+
}
|
|
@@ -1469,7 +1469,15 @@ Niivue.prototype.groupLabelsFromSelection = function (
|
|
|
1469
1469
|
};
|
|
1470
1470
|
|
|
1471
1471
|
Niivue.prototype.resetScene = function () {
|
|
1472
|
+
// Reset 2D pan + zoom
|
|
1472
1473
|
this.scene.pan2Dxyzmm = [0, 0, 0, 1];
|
|
1474
|
+
// Reset 3D camera to Niivue defaults (azimuth=110, elevation=10, scale=1)
|
|
1475
|
+
if (typeof this.setRenderAzimuthElevation === "function") {
|
|
1476
|
+
this.setRenderAzimuthElevation(110, 10);
|
|
1477
|
+
}
|
|
1478
|
+
if (typeof this.setScale === "function") {
|
|
1479
|
+
this.setScale(1.0);
|
|
1480
|
+
}
|
|
1473
1481
|
this.drawScene();
|
|
1474
1482
|
};
|
|
1475
1483
|
|
|
@@ -1490,18 +1498,18 @@ Niivue.prototype.recenter = function () {
|
|
|
1490
1498
|
};
|
|
1491
1499
|
|
|
1492
1500
|
Niivue.prototype.resetZoom = function () {
|
|
1493
|
-
//
|
|
1494
|
-
// this.scene.pan2Dxyzmm[1] = 0;
|
|
1495
|
-
// this.scene.pan2Dxyzmm[2] = 0;
|
|
1496
|
-
|
|
1501
|
+
// Reset 2D zoom (pan stays, only zoom component adjusted)
|
|
1497
1502
|
const zoom = 1;
|
|
1498
|
-
|
|
1499
1503
|
const zoomChange = this.scene.pan2Dxyzmm[3] - zoom;
|
|
1500
1504
|
this.scene.pan2Dxyzmm[3] = zoom;
|
|
1501
1505
|
const mm = this.frac2mm(this.scene.crosshairPos);
|
|
1502
1506
|
this.scene.pan2Dxyzmm[0] += zoomChange * mm[0];
|
|
1503
1507
|
this.scene.pan2Dxyzmm[1] += zoomChange * mm[1];
|
|
1504
1508
|
this.scene.pan2Dxyzmm[2] += zoomChange * mm[2];
|
|
1509
|
+
// Reset 3D zoom to default scale
|
|
1510
|
+
if (typeof this.setScale === "function") {
|
|
1511
|
+
this.setScale(1.0);
|
|
1512
|
+
}
|
|
1505
1513
|
this.drawScene();
|
|
1506
1514
|
};
|
|
1507
1515
|
|