cloudmr-ux 4.8.5 → 4.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -636,20 +636,9 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
636
636
|
if (next.kind === "polyline") {
|
|
637
637
|
var count = (_c = (_b = next.vertices) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
|
|
638
638
|
if (count >= 2) {
|
|
639
|
-
// Redraw segments in the new color, then update the base so the
|
|
640
|
-
// rubber-band preview also uses the new color.
|
|
641
639
|
redrawPolylineDraft(nv, __assign(__assign({}, next), { filled: false }));
|
|
642
640
|
if (nv.drawBitmap)
|
|
643
641
|
nv._cloudMrPolylineBaseBitmap = nv.drawBitmap.slice();
|
|
644
|
-
// Re-apply fill on top when polygon is closeable (base stays segments-only).
|
|
645
|
-
if (count >= 3) {
|
|
646
|
-
nv.drawPenAxCorSag = next.axCorSag;
|
|
647
|
-
nv.drawPenFillPts = next.vertices.map(function (v) { return __spreadArray([], v, true); });
|
|
648
|
-
nv._cloudMrSkipNextUndoBitmap = true;
|
|
649
|
-
nv.drawPenFilled();
|
|
650
|
-
nv.refreshDrawing(false, false);
|
|
651
|
-
nv.drawScene();
|
|
652
|
-
}
|
|
653
642
|
}
|
|
654
643
|
}
|
|
655
644
|
else if (next.kind === "freehand") {
|
|
@@ -943,8 +932,8 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
943
932
|
}
|
|
944
933
|
}
|
|
945
934
|
function applyPenDraftHandler(_a) {
|
|
946
|
-
var _b, _c, _d, _e, _f;
|
|
947
|
-
var
|
|
935
|
+
var _b, _c, _d, _e, _f, _g;
|
|
936
|
+
var _h = _a === void 0 ? {} : _a, _j = _h.keepTool, keepTool = _j === void 0 ? false : _j;
|
|
948
937
|
var draft = penDraftRef.current;
|
|
949
938
|
if (!draft)
|
|
950
939
|
return;
|
|
@@ -973,9 +962,37 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
973
962
|
setPolylineVertexCount(0);
|
|
974
963
|
}
|
|
975
964
|
else if (draft._registryId && nv._cloudMrPolylineRegistry) {
|
|
976
|
-
// Was a committed polyline reopened as a freehand draft (move-only).
|
|
977
|
-
//
|
|
978
|
-
|
|
965
|
+
// Was a committed polyline reopened as a freehand draft (move-only, possibly recolored).
|
|
966
|
+
// Re-register with the current penValue and voxel positions so future clicks
|
|
967
|
+
// open it with the correct (possibly changed) color.
|
|
968
|
+
var entryIdx = nv._cloudMrPolylineRegistry.findIndex(function (e) { return e.id === draft._registryId; });
|
|
969
|
+
if (entryIdx >= 0) {
|
|
970
|
+
var old = nv._cloudMrPolylineRegistry[entryIdx];
|
|
971
|
+
// Collect the newly committed voxel positions using the draft's current penValue.
|
|
972
|
+
var newIndices = new Set();
|
|
973
|
+
if (nv.drawBitmap && draft.baseBitmap) {
|
|
974
|
+
var pv = draft.penValue;
|
|
975
|
+
for (var i = 0; i < nv.drawBitmap.length; i++) {
|
|
976
|
+
if (nv.drawBitmap[i] === pv && draft.baseBitmap[i] !== pv)
|
|
977
|
+
newIndices.add(i);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// Fall back to stroke voxel positions if bitmap diff returned nothing.
|
|
981
|
+
if (newIndices.size === 0 && draft.strokeVoxels && ((_g = nv.back) === null || _g === void 0 ? void 0 : _g.dims)) {
|
|
982
|
+
var dx = nv.back.dims[1];
|
|
983
|
+
var dy = nv.back.dims[2];
|
|
984
|
+
for (var _i = 0, _k = draft.strokeVoxels; _i < _k.length; _i++) {
|
|
985
|
+
var _l = _k[_i], x = _l[0], y = _l[1], z = _l[2];
|
|
986
|
+
newIndices.add(x + y * dx + z * dx * dy);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (newIndices.size > 0) {
|
|
990
|
+
nv._cloudMrPolylineRegistry[entryIdx] = __assign(__assign({}, old), { penValue: draft.penValue, voxelIndices: newIndices });
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
nv._cloudMrPolylineRegistry = nv._cloudMrPolylineRegistry.filter(function (e) { return e.id !== draft._registryId; });
|
|
994
|
+
}
|
|
995
|
+
}
|
|
979
996
|
}
|
|
980
997
|
setPenDraft(null);
|
|
981
998
|
penDraftRef.current = null;
|
|
@@ -1051,27 +1068,23 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1051
1068
|
nv.opts.deferFreehandCommit = false;
|
|
1052
1069
|
nv.opts.polylinePenMode = false;
|
|
1053
1070
|
nvSetDrawingEnabled(false);
|
|
1071
|
+
// Sync palette to the reopened ROI's color so the user can change it.
|
|
1072
|
+
if (draft.penValue != null) {
|
|
1073
|
+
setDrawPen(draft.penValue);
|
|
1074
|
+
nv.setPenValue(draft.penValue, false);
|
|
1075
|
+
}
|
|
1054
1076
|
};
|
|
1055
1077
|
nv.onPolylineChange = function (count) {
|
|
1056
|
-
var _a
|
|
1078
|
+
var _a;
|
|
1057
1079
|
setPolylineVertexCount(count);
|
|
1058
1080
|
if (drawShapeToolRef.current === "polyline" && count >= 2) {
|
|
1059
|
-
var draft = polylineDraftFromNv(nv, { filled:
|
|
1081
|
+
var draft = polylineDraftFromNv(nv, { filled: false });
|
|
1060
1082
|
if (draft) {
|
|
1061
|
-
if (count >= 3) {
|
|
1062
|
-
// Show fill live on canvas after each vertex commit.
|
|
1063
|
-
// Save/restore _cloudMrPolylineBaseBitmap (segments-only) so the
|
|
1064
|
-
// rubber-band preview still works correctly on mouse move.
|
|
1065
|
-
var savedBase = (_a = nv._cloudMrPolylineBaseBitmap) === null || _a === void 0 ? void 0 : _a.slice();
|
|
1066
|
-
redrawPolylineDraft(nv, draft);
|
|
1067
|
-
if (savedBase)
|
|
1068
|
-
nv._cloudMrPolylineBaseBitmap = savedBase;
|
|
1069
|
-
}
|
|
1070
1083
|
setPenDraft(draft);
|
|
1071
1084
|
penDraftRef.current = draft;
|
|
1072
1085
|
}
|
|
1073
1086
|
}
|
|
1074
|
-
else if (((
|
|
1087
|
+
else if (((_a = penDraftRef.current) === null || _a === void 0 ? void 0 : _a.kind) === "polyline") {
|
|
1075
1088
|
setPenDraft(null);
|
|
1076
1089
|
penDraftRef.current = null;
|
|
1077
1090
|
}
|
|
@@ -1294,6 +1307,11 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1294
1307
|
nv.opts.penType = draft.penType;
|
|
1295
1308
|
nv.opts.deferShapeCommit = true;
|
|
1296
1309
|
nvSetDrawingEnabled(false);
|
|
1310
|
+
// Sync palette to the reopened shape's color so the user can change it.
|
|
1311
|
+
if (draft.penValue != null) {
|
|
1312
|
+
setDrawPen(draft.penValue);
|
|
1313
|
+
nv.setPenValue(draft.penValue, false);
|
|
1314
|
+
}
|
|
1297
1315
|
};
|
|
1298
1316
|
nv.onApplyActiveDraft = function () {
|
|
1299
1317
|
applyActiveDraftIfAny();
|
|
@@ -1309,34 +1327,25 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1309
1327
|
return undefined;
|
|
1310
1328
|
}
|
|
1311
1329
|
var canvas = document.getElementById("niiCanvas");
|
|
1330
|
+
if (!canvas) {
|
|
1331
|
+
return undefined;
|
|
1332
|
+
}
|
|
1312
1333
|
var applyOnRightClick = function (event) {
|
|
1313
1334
|
var _a;
|
|
1314
1335
|
event.preventDefault();
|
|
1315
1336
|
event.stopPropagation();
|
|
1316
1337
|
(_a = nv.onApplyActiveDraft) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
1317
1338
|
};
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
// Any mousedown outside the canvas exits editing mode.
|
|
1323
|
-
// Clicks on the canvas are handled by Niivue's own mouseUpListener,
|
|
1324
|
-
// so we skip them here to avoid a double-apply race.
|
|
1325
|
-
var onDocMouseDown = function (event) {
|
|
1326
|
-
var _a;
|
|
1327
|
-
var target = event.target;
|
|
1328
|
-
var onCanvas = target === canvas || (canvas && canvas.contains(target));
|
|
1329
|
-
if (onCanvas)
|
|
1330
|
-
return;
|
|
1331
|
-
// Left or right click anywhere outside the canvas → apply draft.
|
|
1332
|
-
(_a = nv.onApplyActiveDraft) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
1339
|
+
var onMouseDown = function (event) {
|
|
1340
|
+
if (event.button === 2) {
|
|
1341
|
+
applyOnRightClick(event);
|
|
1342
|
+
}
|
|
1333
1343
|
};
|
|
1334
|
-
|
|
1344
|
+
canvas.addEventListener("mousedown", onMouseDown, true);
|
|
1345
|
+
canvas.addEventListener("contextmenu", applyOnRightClick, true);
|
|
1335
1346
|
return function () {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1339
|
-
document.removeEventListener("mousedown", onDocMouseDown, true);
|
|
1347
|
+
canvas.removeEventListener("mousedown", onMouseDown, true);
|
|
1348
|
+
canvas.removeEventListener("contextmenu", applyOnRightClick, true);
|
|
1340
1349
|
};
|
|
1341
1350
|
}, [shapeDraft, penDraft]);
|
|
1342
1351
|
React.useEffect(function () {
|
|
@@ -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
|
+
}
|