@aics/vole-app 2.12.0 → 2.13.1

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.
Files changed (28) hide show
  1. package/es/aics-image-viewer/components/App/ChannelUpdater.js +11 -12
  2. package/es/aics-image-viewer/components/App/index.js +216 -466
  3. package/es/aics-image-viewer/components/AxisClipSliders/index.js +12 -12
  4. package/es/aics-image-viewer/components/BottomPanel/index.js +12 -28
  5. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +3 -12
  6. package/es/aics-image-viewer/components/ChannelsWidgetRow/index.js +2 -2
  7. package/es/aics-image-viewer/components/ChannelsWidgetRow/styles.css +5 -1
  8. package/es/aics-image-viewer/components/ErrorAlert/index.js +1 -1
  9. package/es/aics-image-viewer/components/TfEditor/index.js +75 -35
  10. package/es/aics-image-viewer/components/TfEditor/styles.css +2 -3
  11. package/es/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.js +1 -1
  12. package/es/aics-image-viewer/components/Toolbar/index.js +34 -30
  13. package/es/aics-image-viewer/components/ViewerStateProvider/index.js +11 -13
  14. package/es/aics-image-viewer/components/useVolume.js +327 -0
  15. package/es/aics-image-viewer/shared/constants.js +10 -1
  16. package/es/aics-image-viewer/shared/utils/hooks.js +3 -26
  17. package/es/aics-image-viewer/shared/utils/sliderValuesToImageValues.js +15 -9
  18. package/es/aics-image-viewer/shared/utils/viewerState.js +1 -1
  19. package/package.json +3 -2
  20. package/type-declarations/aics-image-viewer/components/BottomPanel/index.d.ts +2 -2
  21. package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +2 -2
  22. package/type-declarations/aics-image-viewer/components/TfEditor/index.d.ts +1 -1
  23. package/type-declarations/aics-image-viewer/components/Toolbar/index.d.ts +1 -1
  24. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/types.d.ts +1 -1
  25. package/type-declarations/aics-image-viewer/components/useVolume.d.ts +49 -0
  26. package/type-declarations/aics-image-viewer/shared/constants.d.ts +4 -1
  27. package/type-declarations/aics-image-viewer/shared/utils/hooks.d.ts +0 -2
  28. package/type-declarations/aics-image-viewer/shared/utils/sliderValuesToImageValues.d.ts +6 -3
@@ -43,7 +43,7 @@ var SliderRow = function SliderRow(_ref) {
43
43
  className: "axis-slider-container"
44
44
  }, /*#__PURE__*/React.createElement("span", {
45
45
  className: "slider-name"
46
- }, label), max === 0 ? /*#__PURE__*/React.createElement("i", null, "No values to adjust") : /*#__PURE__*/React.createElement("span", {
46
+ }, label), max <= min ? /*#__PURE__*/React.createElement("i", null, "No values to adjust") : /*#__PURE__*/React.createElement("span", {
47
47
  className: "axis-slider"
48
48
  }, /*#__PURE__*/React.createElement(SmarterSlider, {
49
49
  className: isRange ? "" : "slider-single-handle",
@@ -77,7 +77,7 @@ var SliderRow = function SliderRow(_ref) {
77
77
  onChange: wrappedOnChange,
78
78
  onStart: onStart,
79
79
  onEnd: onEnd
80
- })), max > 0 && /*#__PURE__*/React.createElement("span", {
80
+ })), max > min && /*#__PURE__*/React.createElement("span", {
81
81
  className: "slider-values"
82
82
  }, /*#__PURE__*/React.createElement(NumericInput, {
83
83
  min: min,
@@ -97,6 +97,9 @@ var SliderRow = function SliderRow(_ref) {
97
97
  };
98
98
  /** Wrapper around `SliderRow` that adds a play button and accounts for the case where not all of an axis is loaded */
99
99
  var PlaySliderRow = function PlaySliderRow(props) {
100
+ var onChange = props.onChange,
101
+ onStart = props.onStart,
102
+ onEnd = props.onEnd;
100
103
  // In partially-loaded axes, stores the displayed value of the slider while the user is sliding it
101
104
  var _useState = useState(props.val),
102
105
  _useState2 = _slicedToArray(_useState, 2),
@@ -108,27 +111,24 @@ var PlaySliderRow = function PlaySliderRow(props) {
108
111
  sliderHeld = _useState4[0],
109
112
  setSliderHeld = _useState4[1];
110
113
  var wrappedOnChange = useCallback(function (_ref2) {
111
- var _props$onChange;
112
114
  var _ref3 = _slicedToArray(_ref2, 1),
113
115
  val = _ref3[0];
114
- return (_props$onChange = props.onChange) === null || _props$onChange === void 0 ? void 0 : _props$onChange.call(props, val);
115
- }, [props.onChange]);
116
+ return onChange === null || onChange === void 0 ? void 0 : onChange(val);
117
+ }, [onChange]);
116
118
  var wrappedSetValReadout = useCallback(function (_ref4) {
117
119
  var _ref5 = _slicedToArray(_ref4, 1),
118
120
  val = _ref5[0];
119
121
  return setValReadout(val);
120
122
  }, []);
121
123
  var wrappedOnStart = useCallback(function () {
122
- var _props$onStart;
123
124
  setValReadout(props.val);
124
125
  setSliderHeld(true);
125
- (_props$onStart = props.onStart) === null || _props$onStart === void 0 || _props$onStart.call(props);
126
- }, [props.onStart]);
126
+ onStart === null || onStart === void 0 || onStart();
127
+ }, [onStart, props.val]);
127
128
  var wrappedOnEnd = useCallback(function () {
128
- var _props$onEnd;
129
129
  setSliderHeld(false);
130
- (_props$onEnd = props.onEnd) === null || _props$onEnd === void 0 || _props$onEnd.call(props);
131
- }, [props.onEnd]);
130
+ onEnd === null || onEnd === void 0 || onEnd();
131
+ }, [onEnd]);
132
132
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SliderRow, {
133
133
  label: props.label,
134
134
  vals: [props.val],
@@ -181,7 +181,7 @@ var AxisClipSliders = function AxisClipSliders(props) {
181
181
  // Pause when view mode or volume size has changed
182
182
  useEffect(function () {
183
183
  return props.playControls.pause();
184
- }, [props.mode, props.image]);
184
+ }, [props.mode, props.image, props.playControls]);
185
185
  var handlePlayPause = function handlePlayPause(axis, willPlay) {
186
186
  if (willPlay) {
187
187
  props.playControls.play(axis);
@@ -5,41 +5,26 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
7
  import { Button, Drawer } from "antd";
8
- import React, { useEffect, useState } from "react";
8
+ import React, { useCallback, useState } from "react";
9
9
  import ViewerIcon from "../shared/ViewerIcon";
10
10
  import "./styles.css";
11
11
  var BottomPanel = function BottomPanel(_ref) {
12
12
  var children = _ref.children,
13
+ openProp = _ref.open,
13
14
  title = _ref.title,
14
15
  height = _ref.height,
15
- onVisibleChange = _ref.onVisibleChange,
16
- onVisibleChangeEnd = _ref.onVisibleChangeEnd;
16
+ onOpenChange = _ref.onOpenChange;
17
17
  var _useState = useState(true),
18
18
  _useState2 = _slicedToArray(_useState, 2),
19
- isVisible = _useState2[0],
20
- setIsVisible = _useState2[1];
21
-
22
- // Call `onVisibleChange` on mount
23
- useEffect(function () {
24
- onVisibleChange === null || onVisibleChange === void 0 || onVisibleChange(isVisible);
25
- if (!isVisible) {
26
- onVisibleChangeEnd === null || onVisibleChangeEnd === void 0 || onVisibleChangeEnd(isVisible);
19
+ openState = _useState2[0],
20
+ setOpenState = _useState2[1];
21
+ var open = openProp !== null && openProp !== void 0 ? openProp : openState;
22
+ var toggleDrawer = useCallback(function () {
23
+ if (openProp === undefined) {
24
+ setOpenState(!open);
27
25
  }
28
- }, []);
29
-
30
- // Treat changes in height as a change in visibility if the panel is open
31
- useEffect(function () {
32
- onVisibleChange === null || onVisibleChange === void 0 || onVisibleChange(isVisible);
33
- window.setTimeout(function () {
34
- return onVisibleChangeEnd === null || onVisibleChangeEnd === void 0 ? void 0 : onVisibleChangeEnd(isVisible);
35
- }, 300);
36
- }, [height]);
37
- var toggleDrawer = function toggleDrawer() {
38
- setIsVisible(!isVisible);
39
- if (onVisibleChange) {
40
- onVisibleChange(!isVisible);
41
- }
42
- };
26
+ onOpenChange === null || onOpenChange === void 0 || onOpenChange(!open);
27
+ }, [open, openProp, onOpenChange]);
43
28
  var optionsButton = /*#__PURE__*/React.createElement(Button, {
44
29
  className: "options-button",
45
30
  size: "small",
@@ -58,10 +43,9 @@ var BottomPanel = function BottomPanel(_ref) {
58
43
  placement: "bottom",
59
44
  closable: false,
60
45
  getContainer: false,
61
- open: isVisible,
46
+ open: open,
62
47
  mask: false,
63
48
  title: optionsButton,
64
- afterOpenChange: onVisibleChangeEnd,
65
49
  height: height !== null && height !== void 0 ? height : 190
66
50
  }, /*#__PURE__*/React.createElement("div", {
67
51
  className: "drawer-body-wrapper"
@@ -16,13 +16,7 @@ var ViewerWrapper = function ViewerWrapper(props) {
16
16
  var view3dviewerRef = /*#__PURE__*/React.createRef();
17
17
  React.useEffect(function () {
18
18
  view3dviewerRef.current.appendChild(props.view3d.getDOMElement());
19
- props.view3d.setAutoRotate(props.autorotate);
20
- }, []);
21
-
22
- // TODO necessary?
23
- React.useEffect(function () {
24
- props.view3d.resize(null);
25
- });
19
+ }, [props.view3d, view3dviewerRef]);
26
20
  var renderOverlay = function renderOverlay() {
27
21
  // Don't show spinner during playback - we may be constantly loading new data, it'll block the view!
28
22
  var showSpinner = props.loadingImage && !props.playingAxis;
@@ -62,11 +56,8 @@ var ViewerWrapper = function ViewerWrapper(props) {
62
56
  style: STYLES.view3d
63
57
  }), /*#__PURE__*/React.createElement(BottomPanel, {
64
58
  title: "Clipping",
65
- onVisibleChange: function onVisibleChange(visible) {
66
- var _props$onClippingPane;
67
- (_props$onClippingPane = props.onClippingPanelVisibleChange) === null || _props$onClippingPane === void 0 || _props$onClippingPane.call(props, visible, numTimesteps > 1, numScenes > 1, viewMode === ViewMode.threeD);
68
- },
69
- onVisibleChangeEnd: props.onClippingPanelVisibleChangeEnd,
59
+ open: props.clippingPanelOpen,
60
+ onOpenChange: props.onClippingPanelOpenChange,
70
61
  height: clippingPanelTall ? CLIPPING_PANEL_HEIGHT_TALL : CLIPPING_PANEL_HEIGHT_DEFAULT
71
62
  }, visibleControls.axisClipSliders && !!props.image && /*#__PURE__*/React.createElement(AxisClipSliders, {
72
63
  mode: viewMode,
@@ -94,7 +94,7 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
94
94
  colorizeAlpha = channelState.colorizeAlpha,
95
95
  useControlPoints = channelState.useControlPoints,
96
96
  ramp = channelState.ramp,
97
- lockPlotToDataRange = channelState.lockPlotToDataRange,
97
+ plotMin = channelState.plotMin,
98
98
  plotMax = channelState.plotMax;
99
99
  return /*#__PURE__*/React.createElement(TfEditor, {
100
100
  id: "TFEditor" + index,
@@ -107,7 +107,7 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
107
107
  colorizeAlpha: colorizeAlpha,
108
108
  useControlPoints: useControlPoints,
109
109
  ramp: ramp,
110
- lockPlotToDataRange: lockPlotToDataRange,
110
+ plotMin: plotMin,
111
111
  plotMax: plotMax
112
112
  });
113
113
  };
@@ -30,9 +30,13 @@
30
30
  display:flex;
31
31
  justify-content:start;
32
32
  align-items:end;
33
- gap:6px;
33
+ gap:5px;
34
34
  }
35
35
 
36
+ .channel-rows-list .ant-list-item.channel-row .button-row .ant-btn.ant-btn-sm{
37
+ padding:12px 8px;
38
+ }
39
+
36
40
  .channel-rows-list .ant-list-item.channel-row.controls-closed .ant-btn-text:not(:focus-visible){
37
41
  color:var(--color-controlpanel-text);
38
42
  }
@@ -119,7 +119,7 @@ export var useErrorAlert = function useErrorAlert() {
119
119
  return [].concat(_toConsumableArray(prev), [errorSeenCount]);
120
120
  });
121
121
  seenErrors.set(errorTitle, errorSeenCount);
122
- }, []);
122
+ }, [seenErrors]);
123
123
  var onSkipError = React.useCallback(function () {
124
124
  setErrorList(function (prev) {
125
125
  return prev.slice(1);
@@ -250,7 +250,9 @@ var clamp = function clamp(value, min, max) {
250
250
  return Math.min(Math.max(value, min), max);
251
251
  };
252
252
  var TfEditor = function TfEditor(props) {
253
- var changeChannelSetting = props.changeChannelSetting;
253
+ var changeChannelSetting = props.changeChannelSetting,
254
+ plotMin = props.plotMin,
255
+ plotMax = props.plotMax;
254
256
  var innerWidth = props.width - TFEDITOR_MARGINS.left - TFEDITOR_MARGINS.right;
255
257
  var innerHeight = props.height - TFEDITOR_MARGINS.top - TFEDITOR_MARGINS.bottom;
256
258
  var _useState = useState(null),
@@ -294,22 +296,27 @@ var TfEditor = function TfEditor(props) {
294
296
  var _props$channelData = props.channelData,
295
297
  rawMin = _props$channelData.rawMin,
296
298
  rawMax = _props$channelData.rawMax,
297
- dtype = _props$channelData.dtype;
299
+ dtype = _props$channelData.dtype,
300
+ histogram = _props$channelData.histogram;
298
301
  var typeRange = DTYPE_RANGE[dtype];
299
302
 
300
303
  // d3 scales define the mapping between data and screen space (and do the heavy lifting of generating plot axes)
301
304
  /** `xScale` is in raw intensity range, not U8 range. We use `u8ToAbsolute` and `absoluteToU8` to translate to U8. */
302
- var _useMemo = useMemo(function () {
303
- var domain = props.lockPlotToDataRange ? [rawMin, rawMax] : [typeRange.min, props.plotMax];
304
- var scale = d3.scaleLinear().domain(domain).range([0, innerWidth]);
305
- var plotMinU8 = absoluteToU8(domain[0], props.channelData);
306
- var plotMaxU8 = absoluteToU8(domain[1], props.channelData);
307
- return [scale, plotMinU8, plotMaxU8];
308
- }, [innerWidth, rawMin, rawMax, typeRange, props.lockPlotToDataRange, props.plotMax]),
309
- _useMemo2 = _slicedToArray(_useMemo, 3),
310
- xScale = _useMemo2[0],
311
- plotMinU8 = _useMemo2[1],
312
- plotMaxU8 = _useMemo2[2];
305
+ var xScale = useMemo(function () {
306
+ return d3.scaleLinear().domain([plotMin, plotMax]).range([0, innerWidth]);
307
+ }, [innerWidth, plotMin, plotMax]);
308
+ var plotMinU8 = useMemo(function () {
309
+ return absoluteToU8(plotMin, {
310
+ rawMin: rawMin,
311
+ rawMax: rawMax
312
+ });
313
+ }, [plotMin, rawMin, rawMax]);
314
+ var plotMaxU8 = useMemo(function () {
315
+ return absoluteToU8(plotMax, {
316
+ rawMin: rawMin,
317
+ rawMax: rawMax
318
+ });
319
+ }, [plotMax, rawMin, rawMax]);
313
320
  var yScale = useMemo(function () {
314
321
  return d3.scaleLinear().domain([0, 1]).range([innerHeight, 0]);
315
322
  }, [innerHeight]);
@@ -454,12 +461,15 @@ var TfEditor = function TfEditor(props) {
454
461
  var areaPath = useMemo(function () {
455
462
  var _areaGenerator;
456
463
  var areaGenerator = d3.area().x(function (d) {
457
- return xScale(controlPointToAbsolute(d, props.channelData));
464
+ return xScale(controlPointToAbsolute(d, {
465
+ rawMin: rawMin,
466
+ rawMax: rawMax
467
+ }));
458
468
  }).y0(function (d) {
459
469
  return yScale(d.opacity);
460
470
  }).y1(innerHeight).curve(d3.curveLinear);
461
471
  return (_areaGenerator = areaGenerator(controlPointsToRender)) !== null && _areaGenerator !== void 0 ? _areaGenerator : undefined;
462
- }, [controlPointsToRender, xScale, yScale, innerHeight]);
472
+ }, [controlPointsToRender, xScale, yScale, innerHeight, rawMin, rawMax]);
463
473
 
464
474
  /** d3-generated svg data string representing the "basic mode" min/max slider handles */
465
475
  var sliderHandlePath = useMemo(function () {
@@ -503,11 +513,11 @@ var TfEditor = function TfEditor(props) {
503
513
  if (el === null) {
504
514
  return;
505
515
  }
506
- var numBins = props.channelData.histogram.getNumBins();
516
+ var numBins = histogram.getNumBins();
507
517
  if (numBins < 1) {
508
518
  return;
509
519
  }
510
- var _getHistogramBinLengt = getHistogramBinLengths(props.channelData.histogram),
520
+ var _getHistogramBinLengt = getHistogramBinLengths(histogram),
511
521
  binLengths = _getHistogramBinLengt.binLengths,
512
522
  max = _getHistogramBinLengt.max;
513
523
  var start = Math.max(0, Math.ceil(plotMinU8));
@@ -519,18 +529,21 @@ var TfEditor = function TfEditor(props) {
519
529
  .data(binLengthsToRender) // bind the histogram bins to this selection
520
530
  .join("rect") // ensure we have exactly as many bound `rect` elements in the DOM as we have histogram bins
521
531
  .attr("class", "bar").attr("width", barWidth).attr("x", function (_len, idx) {
522
- return xScale(u8ToAbsolute(idx + start, props.channelData));
532
+ return xScale(u8ToAbsolute(idx + start, {
533
+ rawMin: rawMin,
534
+ rawMax: rawMax
535
+ }));
523
536
  }) // set position and height from data
524
537
  .attr("y", function (len) {
525
538
  return binScale(len);
526
539
  }).attr("height", function (len) {
527
540
  return innerHeight - binScale(len);
528
541
  });
529
- }, [xScale, props.channelData, props.channelData.histogram, innerWidth, innerHeight, plotMinU8, plotMaxU8]);
542
+ }, [xScale, rawMin, rawMax, histogram, innerWidth, innerHeight, plotMinU8, plotMaxU8]);
530
543
  var applyTFGenerator = useCallback(function (generator) {
531
544
  setSelectedPointIdx(null);
532
545
  lastColorRef.current = TFEDITOR_DEFAULT_COLOR;
533
- var lut = TF_GENERATORS[generator](props.channelData.histogram);
546
+ var lut = TF_GENERATORS[generator](histogram);
534
547
  if (props.useControlPoints) {
535
548
  setControlPoints(lut.controlPoints.map(function (cp) {
536
549
  return _objectSpread(_objectSpread({}, cp), {}, {
@@ -540,12 +553,13 @@ var TfEditor = function TfEditor(props) {
540
553
  } else {
541
554
  setRamp(controlPointsToRamp(lut.controlPoints));
542
555
  }
543
- }, [props.channelData.histogram, props.useControlPoints]);
556
+ }, [histogram, props.useControlPoints, setControlPoints, setRamp]);
544
557
  var createTFGeneratorButton = function createTFGeneratorButton(generator, name, description) {
545
558
  return /*#__PURE__*/React.createElement(Tooltip, {
546
559
  title: description,
547
560
  placement: "top"
548
561
  }, /*#__PURE__*/React.createElement(Button, {
562
+ size: "small",
549
563
  onClick: function onClick() {
550
564
  return applyTFGenerator(generator);
551
565
  }
@@ -572,14 +586,14 @@ var TfEditor = function TfEditor(props) {
572
586
  onContextMenu: handleControlPointContextMenu
573
587
  });
574
588
  }) : null;
575
- // move selected control point to the end so it's not occluded by other nearby points
589
+ // move selected control point to the end so it's drawn last and not occluded by other nearby points
576
590
  if (controlPointCircles !== null && selectedPointIdx !== null) {
577
591
  controlPointCircles.push(controlPointCircles.splice(selectedPointIdx, 1)[0]);
578
592
  }
579
593
  var viewerModeString = props.useControlPoints ? "advanced" : "basic";
580
594
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
581
595
  className: "button-row"
582
- }, createTFGeneratorButton("resetXF", "None", "Reset transfer function to full range."), createTFGeneratorButton("auto98XF", "Default", "Ramp from 50th percentile to 98th."), createTFGeneratorButton("auto2XF", "IJ Auto", "Emulates ImageJ's \"auto\" button."), createTFGeneratorButton("bestFitXF", "Auto 2", "Ramp over the middle 80% of data."), /*#__PURE__*/React.createElement(Checkbox, {
596
+ }, createTFGeneratorButton("auto98XF", "Default", "Ramp from 50th percentile to 98th"), createTFGeneratorButton("auto2XF", "IJ Auto", "Emulates ImageJ's \"auto\" button"), createTFGeneratorButton("resetXF", "Auto 1", "Ramp over the full data range (0% to 100%)"), createTFGeneratorButton("bestFitXF", "Auto 2", "Ramp over the middle 80% of data"), /*#__PURE__*/React.createElement(Checkbox, {
583
597
  checked: props.useControlPoints,
584
598
  onChange: function onChange(e) {
585
599
  return changeChannelSetting({
@@ -591,7 +605,7 @@ var TfEditor = function TfEditor(props) {
591
605
  }
592
606
  }, "Advanced")), !props.useControlPoints && /*#__PURE__*/React.createElement("div", {
593
607
  className: "tf-editor-control-row ramp-row"
594
- }, "Ramp min/max", /*#__PURE__*/React.createElement(InputNumber, {
608
+ }, "Levels min/max", /*#__PURE__*/React.createElement(InputNumber, {
595
609
  value: u8ToAbsolute(props.ramp[0], props.channelData),
596
610
  onChange: function onChange(v) {
597
611
  return v !== null && setRamp([absoluteToU8(v, props.channelData), props.ramp[1]]);
@@ -691,26 +705,52 @@ var TfEditor = function TfEditor(props) {
691
705
  }
692
706
  }))))), /*#__PURE__*/React.createElement("div", {
693
707
  className: "tf-editor-control-row plot-range-row"
694
- }, /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement(Checkbox, {
695
- checked: props.lockPlotToDataRange,
696
- onChange: function onChange(e) {
697
- return changeChannelSetting({
698
- lockPlotToDataRange: e.target.checked
699
- });
700
- }
701
- }, "Lock to data range")), !props.lockPlotToDataRange && /*#__PURE__*/React.createElement("span", null, "Plot max", " ", /*#__PURE__*/React.createElement(InputNumber, {
702
- value: props.plotMax,
708
+ }, "Plot min/max", /*#__PURE__*/React.createElement(InputNumber, {
709
+ value: plotMin,
703
710
  onChange: function onChange(v) {
704
711
  return v !== null && changeChannelSetting({
705
- plotMax: v
712
+ plotMin: v,
713
+ plotMax: Math.max(v + 1, plotMax)
706
714
  });
707
715
  },
708
716
  formatter: numberFormatter,
709
717
  min: typeRange.min,
718
+ max: typeRange.max - 1,
719
+ size: "small",
720
+ controls: false
721
+ }), /*#__PURE__*/React.createElement(InputNumber, {
722
+ value: plotMax,
723
+ onChange: function onChange(v) {
724
+ return v !== null && changeChannelSetting({
725
+ plotMax: v,
726
+ plotMin: Math.min(v - 1, plotMin)
727
+ });
728
+ },
729
+ formatter: numberFormatter,
730
+ min: typeRange.min + 1,
710
731
  max: typeRange.max,
711
732
  size: "small",
712
733
  controls: false
713
- }))), /*#__PURE__*/React.createElement(SliderRow, {
734
+ }), /*#__PURE__*/React.createElement(Button, {
735
+ size: "small",
736
+ style: {
737
+ marginLeft: "12px"
738
+ },
739
+ onClick: function onClick() {
740
+ return changeChannelSetting({
741
+ plotMin: rawMin,
742
+ plotMax: rawMax
743
+ });
744
+ }
745
+ }, "Fit to data"), /*#__PURE__*/React.createElement(Button, {
746
+ size: "small",
747
+ onClick: function onClick() {
748
+ return changeChannelSetting({
749
+ plotMin: typeRange.min,
750
+ plotMax: typeRange.max
751
+ });
752
+ }
753
+ }, "Full range")), /*#__PURE__*/React.createElement(SliderRow, {
714
754
  label: /*#__PURE__*/React.createElement(Checkbox, {
715
755
  checked: props.colorizeEnabled,
716
756
  onChange: function onChange(e) {
@@ -25,21 +25,20 @@
25
25
  color:var(--color-controlpanel-text);
26
26
  align-items:center;
27
27
  min-height:24px;
28
+ gap:5px;
28
29
  }
29
30
 
30
31
  .channel-row .tf-editor-control-row.ramp-row{
31
- gap:6px;
32
32
  margin-top:15px;
33
33
  }
34
34
 
35
35
  .channel-row .tf-editor-control-row.plot-range-row{
36
- justify-content:space-between;
37
36
  margin-bottom:20px;
38
37
  }
39
38
 
40
39
  .channel-row .tf-editor-control-row .ant-input-number{
41
40
  border-color:var(--color-button-tertiary-outline);
42
- width:72px;
41
+ width:64px;
43
42
  }
44
43
 
45
44
  .channel-row .tf-editor-control-row .ant-input-number input{
@@ -1,5 +1,5 @@
1
- import React from "react";
2
1
  import { Radio } from "antd";
2
+ import React from "react";
3
3
  import { ViewMode } from "../../shared/enums";
4
4
  var viewModes = [ViewMode.threeD, ViewMode.xy, ViewMode.xz, ViewMode.yz];
5
5
  var ViewModeRadioButtons = function ViewModeRadioButtons(props) {
@@ -4,15 +4,15 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
4
4
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
- import { UndoOutlined } from "@ant-design/icons";
7
+ import { ReloadOutlined } from "@ant-design/icons";
8
8
  import { Button, Radio, Select, Tooltip } from "antd";
9
9
  import { debounce } from "lodash";
10
10
  import React from "react";
11
- import ViewModeRadioButtons from "./ViewModeRadioButtons";
12
- import DownloadButton from "./DownloadButton";
13
- import { connectToViewerState } from "../ViewerStateProvider";
14
11
  import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
15
12
  import ViewerIcon from "../shared/ViewerIcon";
13
+ import { connectToViewerState } from "../ViewerStateProvider";
14
+ import DownloadButton from "./DownloadButton";
15
+ import ViewModeRadioButtons from "./ViewModeRadioButtons";
16
16
  import "./styles.css";
17
17
  var RESIZE_DEBOUNCE_DELAY = 50;
18
18
  var visuallyHiddenStyle = {
@@ -55,34 +55,38 @@ var Toolbar = function Toolbar(props) {
55
55
  setScrollBtnLeft(barEl.scrollLeft > 0);
56
56
  setScrollBtnRight(barEl.scrollLeft < barEl.scrollWidth - barEl.clientWidth);
57
57
  }, []);
58
- var checkSize = React.useCallback(debounce(function () {
59
- if (!leftRef.current || !centerRef.current || !rightRef.current || !barRef.current) {
60
- return;
61
- }
62
- var leftRect = leftRef.current.getBoundingClientRect();
63
- var centerRect = centerRef.current.getBoundingClientRect();
64
- var rightRect = rightRef.current.getBoundingClientRect();
65
58
 
66
- // when calculating width required to leave scroll mode, add a bit of extra width to ensure that triggers
67
- // for entering and leaving scroll mode never overlap (causing toolbar to rapidly switch when resizing)
68
- var SCROLL_OFF_EXTRA_WIDTH = 15;
69
- if (scrollMode) {
70
- // Leave scroll mode if there is enough space for centered controls not to overlap left/right-aligned ones
71
- var barWidth = barRef.current.getBoundingClientRect().width;
72
- var requiredWidth = Math.max(leftRect.width, rightRect.width) * 2 + centerRect.width + SCROLL_OFF_EXTRA_WIDTH;
73
- if (barWidth > requiredWidth) {
74
- setScrollMode(false);
75
- }
76
- } else {
77
- // Enter scroll mode if centered controls are overlapping either left/right-aligned ones
78
- if (leftRect.right > centerRect.left || centerRect.right > rightRect.left) {
79
- setScrollMode(true);
59
+ // This is effectively a `useCallback` - it memoizes a function - but since we're feeding the whole function into
60
+ // lodash's `debounce`, using `useMemo` memoizes the transformation done by `debounce` as well.
61
+ var checkSize = React.useMemo(function () {
62
+ return debounce(function () {
63
+ if (!leftRef.current || !centerRef.current || !rightRef.current || !barRef.current) {
64
+ return;
80
65
  }
81
- }
82
- checkScrollBtnVisible();
83
- }, RESIZE_DEBOUNCE_DELAY), [scrollMode, checkScrollBtnVisible]);
84
- React.useEffect(checkSize, []);
66
+ var leftRect = leftRef.current.getBoundingClientRect();
67
+ var centerRect = centerRef.current.getBoundingClientRect();
68
+ var rightRect = rightRef.current.getBoundingClientRect();
69
+
70
+ // when calculating width required to leave scroll mode, add a bit of extra width to ensure that triggers
71
+ // for entering and leaving scroll mode never overlap (causing toolbar to rapidly switch when resizing)
72
+ var SCROLL_OFF_EXTRA_WIDTH = 15;
73
+ setScrollMode(function (mode) {
74
+ if (mode) {
75
+ // Leave scroll mode if there is enough space for centered controls not to overlap left/right-aligned ones
76
+ var barWidth = barRef.current.getBoundingClientRect().width;
77
+ var requiredWidth = Math.max(leftRect.width, rightRect.width) * 2 + centerRect.width + SCROLL_OFF_EXTRA_WIDTH;
78
+ return barWidth <= requiredWidth;
79
+ } else {
80
+ // Enter scroll mode if centered controls are overlapping either left/right-aligned ones
81
+ return leftRect.right > centerRect.left || centerRect.right > rightRect.left;
82
+ }
83
+ });
84
+ checkScrollBtnVisible();
85
+ }, RESIZE_DEBOUNCE_DELAY);
86
+ }, [checkScrollBtnVisible]);
85
87
  React.useEffect(function () {
88
+ // Make sure `checkSize` is run once on mount
89
+ checkSize();
86
90
  window.addEventListener("resize", checkSize);
87
91
  return function () {
88
92
  return window.removeEventListener("resize", checkSize);
@@ -157,7 +161,7 @@ var Toolbar = function Toolbar(props) {
157
161
  }, /*#__PURE__*/React.createElement(Button, {
158
162
  className: "ant-btn-icon-only btn-borderless",
159
163
  onClick: resetToSavedViewerState
160
- }, /*#__PURE__*/React.createElement(UndoOutlined, null), /*#__PURE__*/React.createElement("span", {
164
+ }, /*#__PURE__*/React.createElement(ReloadOutlined, null), /*#__PURE__*/React.createElement("span", {
161
165
  style: visuallyHiddenStyle
162
166
  }, "Reset to initial settings")))), /*#__PURE__*/React.createElement("div", {
163
167
  className: "viewer-toolbar-center",
@@ -11,6 +11,7 @@ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object
11
11
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
12
12
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13
13
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
14
+ import { isEqual } from "lodash";
14
15
  import React, { useCallback, useContext, useEffect, useMemo, useReducer, useRef } from "react";
15
16
  import { getDefaultViewerChannelSettings, getDefaultViewerState } from "../../shared/constants";
16
17
  import { RenderMode, ViewMode } from "../../shared/enums";
@@ -150,7 +151,7 @@ var ViewerStateProvider = function ViewerStateProvider(props) {
150
151
  });
151
152
  useEffect(function () {
152
153
  resetProvider.setSavedViewerState(props.viewerSettings || {});
153
- }, [props.viewerSettings]);
154
+ }, [props.viewerSettings, resetProvider]);
154
155
  var changeViewerSetting = useCallback(function (key, value) {
155
156
  return viewerDispatch({
156
157
  key: key,
@@ -178,20 +179,17 @@ var ViewerStateProvider = function ViewerStateProvider(props) {
178
179
  });
179
180
  }, []);
180
181
 
181
- // Sync viewer settings prop with state
182
- // React docs seem to be fine with syncing state with props directly in the render function, but that caused an
183
- // infinite render loop, so now it's in a `useMemo`:
184
182
  // https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
185
- useMemo(function () {
186
- if (props.viewerSettings) {
187
- for (var _i = 0, _arr = Object.keys(props.viewerSettings); _i < _arr.length; _i++) {
188
- var key = _arr[_i];
189
- if (viewerSettings[key] !== props.viewerSettings[key]) {
190
- changeViewerSetting(key, props.viewerSettings[key]);
191
- }
183
+ var prevViewerSettingsPropsRef = useRef(undefined);
184
+ if (props.viewerSettings && !isEqual(props.viewerSettings, prevViewerSettingsPropsRef.current)) {
185
+ for (var _i = 0, _arr = Object.keys(props.viewerSettings); _i < _arr.length; _i++) {
186
+ var key = _arr[_i];
187
+ if (viewerSettings[key] !== props.viewerSettings[key]) {
188
+ changeViewerSetting(key, props.viewerSettings[key]);
192
189
  }
193
190
  }
194
- }, [props.viewerSettings]);
191
+ prevViewerSettingsPropsRef.current = props.viewerSettings;
192
+ }
195
193
  var context = useMemo(function () {
196
194
  ref.current = _objectSpread(_objectSpread({}, viewerSettings), {}, {
197
195
  channelSettings: channelSettings,
@@ -214,7 +212,7 @@ var ViewerStateProvider = function ViewerStateProvider(props) {
214
212
  return {
215
213
  ref: ref
216
214
  };
217
- }, [viewerSettings, channelSettings]);
215
+ }, [viewerSettings, channelSettings, applyColorPresets, changeViewerSetting, changeChannelSetting, setChannelSettings, resetProvider]);
218
216
  return /*#__PURE__*/React.createElement(ViewerStateContext.Provider, {
219
217
  value: context
220
218
  }, props.children);