@aics/vole-app 2.13.0 → 2.13.2

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.
@@ -3,9 +3,6 @@ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArra
3
3
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
4
  function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
5
5
  function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
6
- function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
7
- function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
8
- function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
9
6
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
10
7
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
11
8
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -18,22 +15,18 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
18
15
  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; } }
19
16
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
20
17
  // 3rd Party Imports
21
- import { LoadSpec, PrefetchDirection, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, View3d, VolumeFileFormat, VolumeLoaderContext } from "@aics/vole-core";
18
+ import { RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, View3d } from "@aics/vole-core";
22
19
  import { Layout } from "antd";
23
20
  import { debounce, isEqual } from "lodash";
24
21
  import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
25
- import { Box3, Vector3 } from "three";
26
- import { AXIS_MARGIN_DEFAULT, CACHE_MAX_SIZE, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, DTYPE_RANGE, getDefaultChannelColor, getDefaultViewerState, QUEUE_MAX_LOW_PRIORITY_SIZE, QUEUE_MAX_SIZE, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
22
+ import { AXIS_MARGIN_DEFAULT, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, DTYPE_RANGE, getDefaultViewerState, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
27
23
  import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
28
24
  import { activeAxisMap } from "../../shared/types";
29
25
  import { colorArrayToFloats } from "../../shared/utils/colorRepresentations";
30
26
  import { controlPointsToRamp, initializeLut, rampToControlPoints, remapControlPointsForChannel } from "../../shared/utils/controlPointsToLut";
31
- import { useConstructor, useStateWithGetter } from "../../shared/utils/hooks";
32
- import PlayControls from "../../shared/utils/playControls";
33
- import SceneStore from "../../shared/utils/sceneStore";
27
+ import { useConstructor } from "../../shared/utils/hooks";
34
28
  import { alphaSliderToImageValue, brightnessSliderToImageValue, densitySliderToImageValue, gammaSliderToImageValues } from "../../shared/utils/sliderValuesToImageValues";
35
- import { getDisplayName, makeChannelIndexGrouping } from "../../shared/utils/viewerChannelSettings";
36
- import { initializeOneChannelSetting } from "../../shared/utils/viewerState";
29
+ import useVolume, { ImageLoadStatus } from "../useVolume";
37
30
  import CellViewerCanvasWrapper from "../CellViewerCanvasWrapper";
38
31
  import ControlPanel from "../ControlPanel";
39
32
  import { useErrorAlert } from "../ErrorAlert";
@@ -81,12 +74,6 @@ var defaultProps = {
81
74
  canvasMargin: "0 0 0 0",
82
75
  view3dRef: undefined
83
76
  };
84
- var axisToLoaderPriority = {
85
- t: PrefetchDirection.T_PLUS,
86
- z: PrefetchDirection.Z_PLUS,
87
- y: PrefetchDirection.Y_PLUS,
88
- x: PrefetchDirection.X_PLUS
89
- };
90
77
  var CLIPPING_PANEL_ANIMATION_DURATION_MS = 300;
91
78
  var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, hasTime, hasScenes, isMode3d) {
92
79
  // The height of the clipping panel includes the button, but we're trying to put these elements next to the button
@@ -116,59 +103,41 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
116
103
  view3d.setTimestepIndicatorPosition(SCALE_BAR_MARGIN_DEFAULT[0], scaleBarY);
117
104
  view3d.setScaleBarPosition(scaleBarX, scaleBarY);
118
105
  };
119
-
120
- // TODO this component, specifically its volume loading behavior, is way out of compliance with react-hooks lints
121
- // and will need a larger refactoring pass to fix that.
122
- /* eslint-disable react-hooks/exhaustive-deps */
123
- /* eslint-disable react-hooks/rules-of-hooks */
124
106
  var App = function App(props) {
125
- var _scenes$length, _scenes, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
107
+ var _getCurrentViewerChan, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
126
108
  props = _objectSpread(_objectSpread({}, defaultProps), props);
127
109
 
128
110
  // State management /////////////////////////////////////////////////////////
129
111
  var viewerState = useContext(ViewerStateContext).ref;
130
112
  var _viewerState$current = viewerState.current,
113
+ imageType = _viewerState$current.imageType,
114
+ viewMode = _viewerState$current.viewMode,
131
115
  channelSettings = _viewerState$current.channelSettings,
132
- setChannelSettings = _viewerState$current.setChannelSettings,
133
- changeViewerSetting = _viewerState$current.changeViewerSetting,
134
116
  changeChannelSetting = _viewerState$current.changeChannelSetting,
135
117
  applyColorPresets = _viewerState$current.applyColorPresets,
136
118
  setSavedViewerChannelSettings = _viewerState$current.setSavedViewerChannelSettings,
137
119
  getCurrentViewerChannelSettings = _viewerState$current.getCurrentViewerChannelSettings,
138
120
  getChannelsAwaitingReset = _viewerState$current.getChannelsAwaitingReset,
139
- getChannelsAwaitingResetOnLoad = _viewerState$current.getChannelsAwaitingResetOnLoad,
140
121
  onResetChannel = _viewerState$current.onResetChannel;
122
+ var _props = props,
123
+ onControlPanelToggle = _props.onControlPanelToggle,
124
+ metadata = _props.metadata,
125
+ metadataFormatter = _props.metadataFormatter;
141
126
  useMemo(function () {
142
127
  if (props.viewerChannelSettings) {
143
128
  setSavedViewerChannelSettings(props.viewerChannelSettings);
144
129
  }
145
- }, [props.viewerChannelSettings]);
130
+ }, [props.viewerChannelSettings, setSavedViewerChannelSettings]);
146
131
  var view3d = useConstructor(function () {
147
132
  return new View3d();
148
133
  });
149
134
  if (props.view3dRef !== undefined) {
150
135
  props.view3dRef.current = view3d;
151
136
  }
152
- var loadContext = useConstructor(function () {
153
- return new VolumeLoaderContext(CACHE_MAX_SIZE, QUEUE_MAX_SIZE, QUEUE_MAX_LOW_PRIORITY_SIZE);
154
- });
155
-
156
- // install loadContext into view3d
157
- view3d.loaderContext = loadContext;
158
- var loader = useRef();
159
- var _useState = useState(null),
160
- _useState2 = _slicedToArray(_useState, 2),
161
- image = _useState2[0],
162
- setImage = _useState2[1];
163
- var imageUrlRef = useRef("");
164
137
  var _useErrorAlert = useErrorAlert(),
165
138
  _useErrorAlert2 = _slicedToArray(_useErrorAlert, 2),
166
139
  errorAlert = _useErrorAlert2[0],
167
- _showError = _useErrorAlert2[1];
168
- var showError = function showError(error) {
169
- _showError(error);
170
- setSendingQueryRequest(false);
171
- };
140
+ showError = _useErrorAlert2[1];
172
141
  useEffect(function () {
173
142
  // Get notifications of loading errors which occur after the initial load, e.g. on time change or new channel load
174
143
  view3d.setLoadErrorHandler(function (_vol, e) {
@@ -177,142 +146,67 @@ var App = function App(props) {
177
146
  return function () {
178
147
  return view3d.setLoadErrorHandler(undefined);
179
148
  };
180
- }, [view3d]);
181
- var hasRawImage = !!(props.rawData && props.rawDims);
182
- var numScenes = hasRawImage ? 1 : (_scenes$length = (_scenes = props.imageUrl.scenes) === null || _scenes === void 0 ? void 0 : _scenes.length) !== null && _scenes$length !== void 0 ? _scenes$length : 1;
183
- var numSlices = (_image$imageInfo$volu = image === null || image === void 0 ? void 0 : image.imageInfo.volumeSize) !== null && _image$imageInfo$volu !== void 0 ? _image$imageInfo$volu : {
184
- x: 0,
185
- y: 0,
186
- z: 0
187
- };
188
- var numSlicesLoaded = (_image$imageInfo$subr = image === null || image === void 0 ? void 0 : image.imageInfo.subregionSize) !== null && _image$imageInfo$subr !== void 0 ? _image$imageInfo$subr : {
189
- x: 0,
190
- y: 0,
191
- z: 0
192
- };
193
- var numTimesteps = (_image$imageInfo$time = image === null || image === void 0 ? void 0 : image.imageInfo.times) !== null && _image$imageInfo$time !== void 0 ? _image$imageInfo$time : 1;
194
-
195
- // State for image loading/reloading
196
-
197
- /** `true` when a channel's data has been loaded for the current image. */
198
- var hasChannelLoadedRef = useRef([]);
199
- // `true` when image data has been requested, but no data has been received yet
200
- var _useState3 = useState(false),
201
- _useState4 = _slicedToArray(_useState3, 2),
202
- sendingQueryRequest = _useState4[0],
203
- setSendingQueryRequest = _useState4[1];
204
- // `true` when all channels of the current image are loaded
205
- var _useState5 = useState(false),
206
- _useState6 = _slicedToArray(_useState5, 2),
207
- imageLoaded = _useState6[0],
208
- setImageLoaded = _useState6[1];
209
- // tracks which channels have been loaded
210
- var _useStateWithGetter = useStateWithGetter([]),
211
- _useStateWithGetter2 = _slicedToArray(_useStateWithGetter, 3),
212
- channelVersions = _useStateWithGetter2[0],
213
- setChannelVersions = _useStateWithGetter2[1],
214
- getChannelVersions = _useStateWithGetter2[2];
215
- // we need to keep track of channel ranges for remapping
216
- var channelRangesRef = useRef([]);
217
- var _useState7 = useState({}),
218
- _useState8 = _slicedToArray(_useState7, 2),
219
- channelGroupedByType = _useState8[0],
220
- setChannelGroupedByType = _useState8[1];
221
- var _useState9 = useState(function () {
222
- return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
223
- }),
224
- _useState10 = _slicedToArray(_useState9, 2),
225
- controlPanelClosed = _useState10[0],
226
- setControlPanelClosed = _useState10[1];
227
- // Only allow auto-close once while the screen is too narrow.
228
- var _useState11 = useState(false),
229
- _useState12 = _slicedToArray(_useState11, 2),
230
- hasAutoClosedControlPanel = _useState12[0],
231
- setHasAutoClosedControlPanel = _useState12[1];
232
- var _useState13 = useState(true),
233
- _useState14 = _slicedToArray(_useState13, 2),
234
- clippingPanelOpen = _useState14[0],
235
- setClippingPanelOpen = _useState14[1];
236
- var clippingPanelOpenTimeout = useRef(0);
237
-
238
- // `PlayControls` manages playing through time and spatial axes, which isn't practical with "pure" React
239
- // Playback state goes here, but the play/pause buttons that mainly control this class are down in `AxisClipSliders`
240
- var playControls = useConstructor(function () {
241
- return new PlayControls();
242
- });
243
- var _useState15 = useState(null),
244
- _useState16 = _slicedToArray(_useState15, 2),
245
- playingAxis = _useState16[0],
246
- setPlayingAxis = _useState16[1];
247
- playControls.onPlayingAxisChanged = function (axis) {
248
- var _loader$current, _loader$current2;
249
- (_loader$current = loader.current) === null || _loader$current === void 0 || _loader$current.setPrefetchPriority(axis ? [axisToLoaderPriority[axis]] : []);
250
- (_loader$current2 = loader.current) === null || _loader$current2 === void 0 || _loader$current2.syncMultichannelLoading(axis ? true : false);
251
- if (image) {
252
- if (axis === null) {
253
- // Playback has stopped - reset scale level bias
254
- view3d.setScaleLevelBias(image, 0);
255
- } else {
256
- // Playback has started - unless entire axis is in memory (typical in X and Y), downlevel to speed things up
257
- var shouldDownlevel = axis === "t" || numSlices[axis] !== numSlicesLoaded[axis];
258
- view3d.setScaleLevelBias(image, shouldDownlevel ? 1 : 0);
149
+ }, [view3d, showError]);
150
+ var imageUrlRef = useRef("");
151
+ var scenesRef = useRef([]);
152
+ var _props2 = props,
153
+ imageUrl = _props2.imageUrl,
154
+ parentImageUrl = _props2.parentImageUrl,
155
+ rawData = _props2.rawData,
156
+ rawDims = _props2.rawDims;
157
+ var scenes = useMemo(function () {
158
+ if (rawData && rawDims) {
159
+ return [{
160
+ data: rawData,
161
+ metadata: rawDims
162
+ }];
163
+ } else {
164
+ var _scenes;
165
+ var showParentImage = imageType === ImageType.fullField && parentImageUrl !== undefined;
166
+ var path = showParentImage ? parentImageUrl : imageUrl;
167
+ // Don't reload if we're already looking at this image
168
+ if (isEqual(path, imageUrlRef.current)) {
169
+ return scenesRef.current;
259
170
  }
171
+ imageUrlRef.current = path;
172
+ var result = (_scenes = path.scenes) !== null && _scenes !== void 0 ? _scenes : [path];
173
+ scenesRef.current = result;
174
+ return result;
260
175
  }
261
- setPlayingAxis(axis);
262
- };
176
+ }, [imageUrl, parentImageUrl, rawData, rawDims, imageType]);
177
+ var maskChannelName = (_getCurrentViewerChan = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan === void 0 ? void 0 : _getCurrentViewerChan.maskChannelName;
263
178
 
264
- // These last state functions are only ever used within this component - no need for a `useCallback`
265
-
266
- var getOneChannelSetting = function getOneChannelSetting(channelName, settings) {
267
- return (settings || viewerState.current.channelSettings).find(function (channel) {
268
- return channel.name === channelName;
269
- });
270
- };
271
- var setAllChannelsUnloaded = function setAllChannelsUnloaded(numberOfChannels) {
272
- setChannelVersions(new Array(numberOfChannels).fill(0));
273
- };
274
- var setOneChannelLoaded = function setOneChannelLoaded(index) {
275
- var newVersions = getChannelVersions().slice();
276
- newVersions[index]++;
277
- setChannelVersions(newVersions);
278
- };
279
-
280
- // Image loading/initialization functions ///////////////////////////////////
281
-
282
- /**
283
- * Updates a channel's ramp and control points after new data has been loaded.
284
- *
285
- * Also handles initializing the ramp/control points on initial load and resetting
286
- * them when the channel is reset.
287
- */
288
- var updateChannelTransferFunction = function updateChannelTransferFunction(aimg, thisChannelsSettings, channelIndex) {
289
- var thisChannel = aimg.getChannel(channelIndex);
290
-
291
- // If this is the first load of this image, auto-generate initial LUTs
292
- if (!hasChannelLoadedRef.current[channelIndex] || !thisChannelsSettings.controlPoints || !thisChannelsSettings.ramp || getChannelsAwaitingResetOnLoad().has(channelIndex)) {
293
- var viewerChannelSettings = getCurrentViewerChannelSettings();
294
- var _initializeLut = initializeLut(aimg, channelIndex, viewerChannelSettings),
295
- ramp = _initializeLut.ramp,
296
- controlPoints = _initializeLut.controlPoints;
179
+ // we need to keep track of channel ranges for remapping control points
180
+ var channelRangesRef = useRef([]);
181
+ var onChannelLoaded = useCallback(function (image, channelIndex, isInitialLoad) {
182
+ // TODO this was once a search by name - is that still necessary or will the index always be correct?
183
+ var thisChannelSettings = channelSettings[channelIndex];
184
+ var _viewerState$current2 = viewerState.current,
185
+ getChannelsAwaitingResetOnLoad = _viewerState$current2.getChannelsAwaitingResetOnLoad,
186
+ getCurrentViewerChannelSettings = _viewerState$current2.getCurrentViewerChannelSettings,
187
+ changeChannelSetting = _viewerState$current2.changeChannelSetting;
188
+ var ramp = thisChannelSettings.ramp,
189
+ controlPoints = thisChannelSettings.controlPoints;
190
+ var thisChannel = image.getChannel(channelIndex);
191
+ if (isInitialLoad || !controlPoints || !ramp || getChannelsAwaitingResetOnLoad().has(channelIndex)) {
192
+ // This channel needs its LUT initialized
193
+ var _initializeLut = initializeLut(image, channelIndex, getCurrentViewerChannelSettings()),
194
+ _ramp = _initializeLut.ramp,
195
+ _controlPoints = _initializeLut.controlPoints;
196
+ var dtype = thisChannel.dtype;
297
197
  changeChannelSetting(channelIndex, {
298
- controlPoints: controlPoints,
299
- ramp: controlPointsToRamp(ramp),
198
+ controlPoints: _controlPoints,
199
+ ramp: controlPointsToRamp(_ramp),
300
200
  // set the default range of the transfer function editor to cover the full range of the data type
301
- plotMin: DTYPE_RANGE[thisChannel.dtype].min,
302
- plotMax: DTYPE_RANGE[thisChannel.dtype].max
201
+ plotMin: DTYPE_RANGE[dtype].min,
202
+ plotMax: DTYPE_RANGE[dtype].max
303
203
  });
304
- onResetChannel(channelIndex);
305
204
  } else {
306
- // try not to update lut from here if we are in play mode
307
- // if (playingAxis !== null) {
308
- // do nothing here?
309
- // tell gui that we have updated control pts?
310
- //changeChannelSetting(channelIndex, "controlPoints", aimg.getChannel(channelIndex).lut.controlPoints);
311
- // }
205
+ // This channel has already been initialized, but its LUT was just remapped and we need to update some things
312
206
  var oldRange = channelRangesRef.current[channelIndex];
313
- if (thisChannelsSettings.useControlPoints) {
207
+ if (thisChannelSettings.useControlPoints) {
314
208
  // control points were just automatically remapped - update in state
315
- var rampControlPoints = rampToControlPoints(thisChannelsSettings.ramp);
209
+ var rampControlPoints = rampToControlPoints(thisChannelSettings.ramp);
316
210
  // now manually remap ramp using the channel's old range
317
211
  var remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
318
212
  changeChannelSetting(channelIndex, {
@@ -321,76 +215,52 @@ var App = function App(props) {
321
215
  });
322
216
  } else {
323
217
  // ramp was just automatically remapped - update in state
324
- var _ramp = controlPointsToRamp(thisChannel.lut.controlPoints);
218
+ var _ramp2 = controlPointsToRamp(thisChannel.lut.controlPoints);
325
219
  // now manually remap control points using the channel's old range
326
- var _controlPoints = thisChannelsSettings.controlPoints;
327
- var remappedControlPoints = remapControlPointsForChannel(_controlPoints, oldRange, thisChannel);
220
+ var _controlPoints2 = thisChannelSettings.controlPoints;
221
+ var remappedControlPoints = remapControlPointsForChannel(_controlPoints2, oldRange, thisChannel);
328
222
  changeChannelSetting(channelIndex, {
329
223
  controlPoints: remappedControlPoints,
330
- ramp: _ramp
224
+ ramp: _ramp2
331
225
  });
332
226
  }
333
227
  }
334
- };
335
- var onChannelDataLoaded = function onChannelDataLoaded(aimg, thisChannelsSettings, channelIndex) {
336
- var _getCurrentViewerChan;
337
- var thisChannel = aimg.getChannel(channelIndex);
338
- updateChannelTransferFunction(aimg, thisChannelsSettings, channelIndex);
339
228
 
340
229
  // save the channel's new range for remapping next time
341
230
  channelRangesRef.current[channelIndex] = [thisChannel.rawMin, thisChannel.rawMax];
342
- view3d.updateLuts(aimg);
343
- view3d.onVolumeData(aimg, [channelIndex]);
344
- view3d.setVolumeChannelEnabled(aimg, channelIndex, thisChannelsSettings.volumeEnabled);
345
- if (aimg.channelNames[channelIndex] === ((_getCurrentViewerChan = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan === void 0 ? void 0 : _getCurrentViewerChan.maskChannelName)) {
346
- view3d.setVolumeChannelAsMask(aimg, channelIndex);
231
+ view3d.updateLuts(image);
232
+ view3d.onVolumeData(image, [channelIndex]);
233
+ view3d.setVolumeChannelEnabled(image, channelIndex, thisChannelSettings.volumeEnabled);
234
+ if (image.channelNames[channelIndex] === maskChannelName) {
235
+ view3d.setVolumeChannelAsMask(image, channelIndex);
347
236
  }
348
- hasChannelLoadedRef.current[channelIndex] = true;
349
-
350
- // when any channel data has arrived:
351
- setSendingQueryRequest(false);
352
- setOneChannelLoaded(channelIndex);
353
- if (aimg.isLoaded()) {
354
- view3d.updateActiveChannels(aimg);
355
- setImageLoaded(true);
356
- playControls.onImageLoaded();
237
+ if (image.isLoaded()) {
238
+ view3d.updateActiveChannels(image);
357
239
  }
358
- };
359
- var setChannelStateForNewImage = function setChannelStateForNewImage(channelNames) {
360
- var grouping = makeChannelIndexGrouping(channelNames, getCurrentViewerChannelSettings());
361
- setChannelGroupedByType(grouping);
240
+ }, [view3d, channelSettings, maskChannelName, viewerState]);
241
+ var volume = useVolume(scenes, {
242
+ onChannelLoaded: onChannelLoaded,
243
+ onError: showError,
244
+ maskChannelName: maskChannelName
245
+ });
246
+ var image = volume.image,
247
+ setTime = volume.setTime,
248
+ setScene = volume.setScene;
362
249
 
363
- // compare each channel's new displayName to the old displayNames currently in state:
364
- // same number of channels, and each channel has same displayName
365
- var allNamesAreEqual = channelNames.every(function (name, idx) {
366
- var _channelSettings$idx;
367
- var displayName = getDisplayName(name, idx, getCurrentViewerChannelSettings());
368
- return displayName === ((_channelSettings$idx = channelSettings[idx]) === null || _channelSettings$idx === void 0 ? void 0 : _channelSettings$idx.displayName);
369
- });
370
- if (allNamesAreEqual) {
371
- var _newChannelSettings = channelNames.map(function (channel, index) {
372
- return _objectSpread(_objectSpread({}, channelSettings[index]), {}, {
373
- name: channel
374
- });
375
- });
376
- setChannelSettings(_newChannelSettings);
377
- return _newChannelSettings;
250
+ // add the image to the viewer on load
251
+ useEffect(function () {
252
+ if (image === null) {
253
+ return;
378
254
  }
379
- var newChannelSettings = channelNames.map(function (channel, index) {
380
- var color = getDefaultChannelColor(index);
381
- return initializeOneChannelSetting(channel, index, color, getCurrentViewerChannelSettings());
382
- });
383
- setChannelSettings(newChannelSettings);
384
- return newChannelSettings;
385
- };
386
- var placeImageInViewer = function placeImageInViewer(aimg, newChannelSettings) {
387
- setImage(aimg);
388
- var channelSetting = newChannelSettings || channelSettings;
389
- view3d.removeAllVolumes();
390
- view3d.addVolume(aimg, {
255
+ channelRangesRef.current = new Array(image.channelNames.length).fill(undefined);
256
+ var channelSettings = viewerState.current.channelSettings;
257
+ view3d.addVolume(image, {
391
258
  // Immediately passing down channel parameters isn't strictly necessary, but keeps things looking consistent on load
392
- channels: aimg.channelNames.map(function (name) {
393
- var ch = getOneChannelSetting(name, channelSetting);
259
+ channels: image.channelNames.map(function (name) {
260
+ // TODO do we really need to be searching by name here?
261
+ var ch = channelSettings.find(function (channel) {
262
+ return channel.name === name;
263
+ });
394
264
  if (!ch) {
395
265
  return {};
396
266
  }
@@ -403,151 +273,47 @@ var App = function App(props) {
403
273
  };
404
274
  })
405
275
  });
406
- var mode3d = viewerSettings.viewMode === ViewMode.threeD;
407
- setIndicatorPositions(view3d, clippingPanelOpen, aimg.imageInfo.times > 1, numScenes > 1, mode3d);
408
- imageLoadHandlers.current.forEach(function (effect) {
409
- return effect(aimg);
410
- });
411
- playControls.stepAxis = function (axis) {
412
- if (axis === "t") {
413
- changeViewerSetting("time", (viewerState.current.time + 1) % aimg.imageInfo.times);
414
- } else {
415
- var max = aimg.imageInfo.volumeSize[axis];
416
- var current = viewerState.current.slice[axis] * max;
417
- changeViewerSetting("slice", _objectSpread(_objectSpread({}, viewerState.current.slice), {}, _defineProperty({}, axis, (current + 1) % max / max)));
418
- }
419
- };
420
- playControls.getVolumeIsLoaded = function () {
421
- return aimg.isLoaded();
422
- };
423
- view3d.updateActiveChannels(aimg);
424
- // make sure we pick up whether the image needs to be in single-slice mode
425
- view3d.setCameraMode(viewerSettings.viewMode);
276
+ view3d.updateActiveChannels(image);
277
+ return view3d.removeAllVolumes.bind(view3d);
278
+ }, [image, view3d, viewerState]);
279
+ var hasRawImage = !!(props.rawData && props.rawDims);
280
+ var numScenes = hasRawImage ? 1 : (_scenes$length = (_scenes2 = props.imageUrl.scenes) === null || _scenes2 === void 0 ? void 0 : _scenes2.length) !== null && _scenes$length !== void 0 ? _scenes$length : 1;
281
+ var numSlices = (_image$imageInfo$volu = image === null || image === void 0 ? void 0 : image.imageInfo.volumeSize) !== null && _image$imageInfo$volu !== void 0 ? _image$imageInfo$volu : {
282
+ x: 1,
283
+ y: 1,
284
+ z: 1
426
285
  };
427
- var openImage = /*#__PURE__*/function () {
428
- var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
429
- var _getCurrentViewerChan2;
430
- var _props, imageUrl, parentImageUrl, rawData, rawDims, scene, scenePaths, _scenes2, showParentImage, path, loadSpec, options, aimg, channelNames, newChannelSettings, requiredLoadSpec, requiredChannelsToLoad, maskChannelName, maskChannelIndex, slice;
431
- return _regeneratorRuntime().wrap(function _callee$(_context) {
432
- while (1) switch (_context.prev = _context.next) {
433
- case 0:
434
- _props = props, imageUrl = _props.imageUrl, parentImageUrl = _props.parentImageUrl, rawData = _props.rawData, rawDims = _props.rawDims;
435
- scene = viewerState.current.scene;
436
- if (!(rawData && rawDims)) {
437
- _context.next = 6;
438
- break;
439
- }
440
- scenePaths = [{
441
- data: rawData,
442
- metadata: rawDims
443
- }];
444
- _context.next = 12;
445
- break;
446
- case 6:
447
- showParentImage = viewerState.current.imageType === ImageType.fullField && parentImageUrl !== undefined;
448
- path = showParentImage ? parentImageUrl : imageUrl; // Don't reload if we're already looking at this image
449
- if (!isEqual(path, imageUrlRef.current)) {
450
- _context.next = 10;
451
- break;
452
- }
453
- return _context.abrupt("return");
454
- case 10:
455
- imageUrlRef.current = path;
456
- scenePaths = (_scenes2 = path.scenes) !== null && _scenes2 !== void 0 ? _scenes2 : [path];
457
- case 12:
458
- setSendingQueryRequest(true);
459
- setImageLoaded(false);
460
- hasChannelLoadedRef.current = [];
461
- loadSpec = new LoadSpec();
462
- loadSpec.time = viewerState.current.time;
463
- options = {};
464
- if (rawData && rawDims) {
465
- options.fileType = VolumeFileFormat.DATA;
466
- options.rawArrayOptions = {
467
- data: rawData,
468
- metadata: rawDims
469
- };
470
- }
471
- _context.prev = 19;
472
- loader.current = new SceneStore(loadContext, scenePaths);
473
- _context.next = 23;
474
- return loader.current.createVolume(scene, loadSpec, function (v, channelIndex) {
475
- // NOTE: this callback runs *after* `onNewVolumeCreated` below, for every loaded channel
476
- // TODO is this search by name necessary or will the `channelIndex` passed to the callback always match state?
477
- var thisChannelSettings = viewerState.current.channelSettings[channelIndex];
478
- onChannelDataLoaded(v, thisChannelSettings, channelIndex);
479
- });
480
- case 23:
481
- aimg = _context.sent;
482
- _context.next = 30;
483
- break;
484
- case 26:
485
- _context.prev = 26;
486
- _context.t0 = _context["catch"](19);
487
- showError(_context.t0);
488
- throw _context.t0;
489
- case 30:
490
- channelNames = aimg.imageInfo.channelNames;
491
- newChannelSettings = setChannelStateForNewImage(channelNames); // order is important:
492
- // we need to remove the old volume before triggering channels unloaded,
493
- // which may cause calls on View3d to the old volume.
494
- view3d.removeAllVolumes();
495
- setAllChannelsUnloaded(channelNames.length);
496
- placeImageInViewer(aimg, newChannelSettings);
497
- channelRangesRef.current = new Array(channelNames.length).fill(undefined);
498
- requiredLoadSpec = new LoadSpec();
499
- requiredLoadSpec.time = viewerState.current.time;
500
-
501
- // make the currently enabled channels "required":
502
- // find all enabled indices in newChannelSettings:
503
- requiredChannelsToLoad = newChannelSettings ? newChannelSettings.map(function (channel, index) {
504
- return channel.volumeEnabled ? index : -1;
505
- }).filter(function (index) {
506
- return index >= 0;
507
- }) : []; // add mask channel to required channels, if specified
508
- maskChannelName = (_getCurrentViewerChan2 = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan2 === void 0 ? void 0 : _getCurrentViewerChan2.maskChannelName;
509
- if (maskChannelName) {
510
- maskChannelIndex = channelNames.indexOf(maskChannelName);
511
- if (maskChannelIndex >= 0 && !requiredChannelsToLoad.includes(maskChannelIndex)) {
512
- requiredChannelsToLoad.push(maskChannelIndex);
513
- }
514
- }
515
- requiredLoadSpec.channels = requiredChannelsToLoad;
516
-
517
- // When in 2D Z-axis view mode, we restrict the subregion to only the current slice. This is
518
- // to match an optimization that volume viewer does by loading Z-slices at a higher resolution,
519
- // and ensures the very first volume that is loaded is the same as the one that
520
- // will be shown whenever we switch back to the same viewer settings (2D Z-axis view mode).
521
- // (We don't do this for ZX and YZ modes because we assume that the data won't be chunked along the
522
- // X or Y axes in ways that would improve loading resolution, and we load the full 3D volume instead.)
523
- if (viewerSettings.viewMode === ViewMode.xy) {
524
- slice = viewerSettings.slice;
525
- requiredLoadSpec.subregion = new Box3(new Vector3(0, 0, slice.z), new Vector3(1, 1, slice.z));
526
- }
286
+ var numSlicesLoaded = (_image$imageInfo$subr = image === null || image === void 0 ? void 0 : image.imageInfo.subregionSize) !== null && _image$imageInfo$subr !== void 0 ? _image$imageInfo$subr : {
287
+ x: 0,
288
+ y: 0,
289
+ z: 0
290
+ };
291
+ var numTimesteps = (_image$imageInfo$time = image === null || image === void 0 ? void 0 : image.imageInfo.times) !== null && _image$imageInfo$time !== void 0 ? _image$imageInfo$time : 1;
527
292
 
528
- // initiate loading only after setting up new channel settings,
529
- // in case the loader callback fires before the state is set
530
- loader.current.loadScene(scene, aimg, requiredLoadSpec)["catch"](function (e) {
531
- showError(e);
532
- throw e;
533
- });
534
- case 44:
535
- case "end":
536
- return _context.stop();
537
- }
538
- }, _callee, null, [[19, 26]]);
539
- }));
540
- return function openImage() {
541
- return _ref.apply(this, arguments);
542
- };
543
- }();
293
+ // const [channelGroupedByType, setChannelGroupedByType] = useState<ChannelGrouping>({});
294
+ var _useState = useState(function () {
295
+ return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
296
+ }),
297
+ _useState2 = _slicedToArray(_useState, 2),
298
+ controlPanelClosed = _useState2[0],
299
+ setControlPanelClosed = _useState2[1];
300
+ // Only allow auto-close once while the screen is too narrow.
301
+ var _useState3 = useState(false),
302
+ _useState4 = _slicedToArray(_useState3, 2),
303
+ hasAutoClosedControlPanel = _useState4[0],
304
+ setHasAutoClosedControlPanel = _useState4[1];
305
+ var _useState5 = useState(true),
306
+ _useState6 = _slicedToArray(_useState5, 2),
307
+ clippingPanelOpen = _useState6[0],
308
+ setClippingPanelOpen = _useState6[1];
309
+ var clippingPanelOpenTimeout = useRef(0);
544
310
 
545
311
  // Imperative callbacks /////////////////////////////////////////////////////
546
312
 
547
313
  var viewerSettings = viewerState.current;
548
314
  var saveIsosurface = useCallback(function (channelIndex, type) {
549
315
  if (image) view3d.saveChannelIsosurface(image, channelIndex, type);
550
- }, [image]);
316
+ }, [image, view3d]);
551
317
  var saveScreenshot = useCallback(function () {
552
318
  view3d.capture(function (dataUrl) {
553
319
  var anchor = document.createElement("a");
@@ -555,36 +321,8 @@ var App = function App(props) {
555
321
  anchor.download = "screenshot.png";
556
322
  anchor.click();
557
323
  });
558
- }, []);
559
- var onClippingPanelOpenChange = useCallback(function (panelOpen) {
560
- if (panelOpen === clippingPanelOpen) {
561
- return;
562
- }
563
- var hasTime = numTimesteps > 1;
564
- var hasScenes = numScenes > 1;
565
- var mode3d = viewerSettings.viewMode === ViewMode.threeD;
566
- setClippingPanelOpen(panelOpen);
567
- setIndicatorPositions(view3d, panelOpen, hasTime, hasScenes, mode3d);
568
-
569
- // Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
570
- if (panelOpen) {
571
- view3d.setShowScaleBar(false);
572
- view3d.setShowTimestepIndicator(false);
573
- view3d.setShowAxis(false);
574
- window.clearTimeout(clippingPanelOpenTimeout.current);
575
- clippingPanelOpenTimeout.current = window.setTimeout(function () {
576
- view3d.setShowScaleBar(true);
577
- view3d.setShowTimestepIndicator(true);
578
- if (viewerSettings.showAxes) {
579
- view3d.setShowAxis(true);
580
- }
581
- }, CLIPPING_PANEL_ANIMATION_DURATION_MS);
582
- }
583
- }, [view3d, numTimesteps, numScenes, viewerSettings.viewMode, viewerSettings.showAxes, clippingPanelOpen]);
324
+ }, [view3d]);
584
325
  var getMetadata = useCallback(function () {
585
- var _props2 = props,
586
- metadata = _props2.metadata,
587
- metadataFormatter = _props2.metadataFormatter;
588
326
  var imageMetadata = image === null || image === void 0 ? void 0 : image.imageMetadata;
589
327
  if (imageMetadata && metadataFormatter) {
590
328
  imageMetadata = metadataFormatter(imageMetadata);
@@ -608,7 +346,28 @@ var App = function App(props) {
608
346
  var _sceneMeta;
609
347
  return (_sceneMeta = sceneMeta) !== null && _sceneMeta !== void 0 ? _sceneMeta : {};
610
348
  }
611
- }, [props.metadata, props.metadataFormatter, image]);
349
+ }, [metadata, metadataFormatter, image, numScenes, viewerState]);
350
+ useEffect(function () {
351
+ var hasTime = numTimesteps > 1;
352
+ var hasScenes = numScenes > 1;
353
+ var mode3d = viewerSettings.viewMode === ViewMode.threeD;
354
+ setIndicatorPositions(view3d, clippingPanelOpen, hasTime, hasScenes, mode3d);
355
+
356
+ // Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
357
+ if (clippingPanelOpen) {
358
+ view3d.setShowScaleBar(false);
359
+ view3d.setShowTimestepIndicator(false);
360
+ view3d.setShowAxis(false);
361
+ window.clearTimeout(clippingPanelOpenTimeout.current);
362
+ clippingPanelOpenTimeout.current = window.setTimeout(function () {
363
+ view3d.setShowScaleBar(true);
364
+ view3d.setShowTimestepIndicator(true);
365
+ if (viewerSettings.showAxes) {
366
+ view3d.setShowAxis(true);
367
+ }
368
+ }, CLIPPING_PANEL_ANIMATION_DURATION_MS);
369
+ }
370
+ }, [view3d, numTimesteps, numScenes, viewerSettings.viewMode, viewerSettings.showAxes, clippingPanelOpen]);
612
371
 
613
372
  // Effects //////////////////////////////////////////////////////////////////
614
373
 
@@ -630,19 +389,9 @@ var App = function App(props) {
630
389
  return window.removeEventListener("resize", onResizeDebounced);
631
390
  };
632
391
  }, [hasAutoClosedControlPanel]);
633
-
634
- // one-time init after view3d exists and before we start loading images
635
- useEffect(function () {
636
- view3d.setCameraMode(viewerSettings.viewMode);
637
- }, []);
638
-
639
- // Hook to trigger image load: on mount, when image source props/state change (`cellId`, `imageType`, `rawData`, etc)
640
- useEffect(function () {
641
- openImage();
642
- }, [props.imageUrl, props.cellId, viewerSettings.imageType, props.rawDims, props.rawData]);
643
392
  useEffect(function () {
644
- return props.onControlPanelToggle && props.onControlPanelToggle(controlPanelClosed);
645
- }, [controlPanelClosed, props.onControlPanelToggle]);
393
+ return onControlPanelToggle && onControlPanelToggle(controlPanelClosed);
394
+ }, [controlPanelClosed, onControlPanelToggle]);
646
395
  useEffect(function () {
647
396
  // delayed for the animation to finish
648
397
  window.setTimeout(function () {
@@ -653,44 +402,39 @@ var App = function App(props) {
653
402
  /** Custom effect hook for viewer updates that depend on `image`, so we don't have to repeatedly null-check it */
654
403
  var useImageEffect = function useImageEffect(effect, deps) {
655
404
  useEffect(function () {
656
- if (image && imageLoaded) {
405
+ if (image && volume.imageLoadStatus === ImageLoadStatus.LOADED) {
657
406
  return effect(image);
658
407
  }
659
- }, [].concat(_toConsumableArray(deps), [image, imageLoaded]));
660
- };
661
- var imageLoadHandlers = useRef([]);
662
- imageLoadHandlers.current = [];
663
- /** `ImageEffect`s that also run right on image creation, so the image doesn't first render with default settings */
664
- var useImageLoadEffect = function useImageLoadEffect(effect, deps) {
665
- useImageEffect(effect, deps);
666
- imageLoadHandlers.current.push(effect);
408
+ // react-hooks will check that `deps` match `effect`'s dependencies, so we can safely exclude `effect` here
409
+ // eslint-disable-next-line react-hooks/exhaustive-deps
410
+ }, [].concat(_toConsumableArray(deps), [image, volume.imageLoadStatus]));
667
411
  };
668
412
 
669
413
  // Effects to imperatively sync `viewerSettings` to `view3d`
670
414
  useImageEffect(function (_currentImage) {
671
415
  view3d.setCameraMode(viewerSettings.viewMode);
672
416
  view3d.resize(null);
673
- }, [viewerSettings.viewMode]);
417
+ }, [viewerSettings.viewMode, view3d]);
674
418
  useImageEffect(function (_currentImage) {
675
419
  if (viewerSettings.cameraState) {
676
420
  view3d.setCameraState(viewerSettings.cameraState);
677
421
  }
678
- }, [viewerSettings.cameraState]);
422
+ }, [viewerSettings.cameraState, view3d]);
679
423
  useImageEffect(function (_currentImage) {
680
424
  return view3d.setAutoRotate(viewerSettings.autorotate);
681
- }, [viewerSettings.autorotate]);
425
+ }, [viewerSettings.autorotate, view3d]);
682
426
  useImageEffect(function (_currentImage) {
683
427
  return view3d.setShowAxis(viewerSettings.showAxes);
684
- }, [viewerSettings.showAxes]);
428
+ }, [viewerSettings.showAxes, view3d]);
685
429
  useImageEffect(function (_currentImage) {
686
430
  return view3d.setBackgroundColor(colorArrayToFloats(viewerSettings.backgroundColor));
687
- }, [viewerSettings.backgroundColor]);
431
+ }, [viewerSettings.backgroundColor, view3d]);
688
432
  useImageEffect(function (currentImage) {
689
433
  return view3d.setBoundingBoxColor(currentImage, colorArrayToFloats(viewerSettings.boundingBoxColor));
690
- }, [viewerSettings.boundingBoxColor]);
434
+ }, [viewerSettings.boundingBoxColor, view3d]);
691
435
  useImageEffect(function (currentImage) {
692
436
  return view3d.setShowBoundingBox(currentImage, viewerSettings.showBoundingBox);
693
- }, [viewerSettings.showBoundingBox]);
437
+ }, [viewerSettings.showBoundingBox, view3d]);
694
438
  useImageEffect(function (image) {
695
439
  // Check whether any channels are marked to be reset and apply it.
696
440
  var channelsAwaitingReset = getChannelsAwaitingReset();
@@ -706,64 +450,52 @@ var App = function App(props) {
706
450
  onResetChannel(i);
707
451
  }
708
452
  }
709
- }, [channelSettings]);
710
- useImageLoadEffect(function (currentImage) {
453
+ }, [changeChannelSetting, channelSettings, getChannelsAwaitingReset, getCurrentViewerChannelSettings, onResetChannel]);
454
+ useImageEffect(function (currentImage) {
711
455
  var renderMode = viewerSettings.renderMode;
712
456
  view3d.setMaxProjectMode(currentImage, renderMode === RenderMode.maxProject);
713
457
  view3d.setVolumeRenderMode(renderMode === RenderMode.pathTrace ? RENDERMODE_PATHTRACE : RENDERMODE_RAYMARCH);
714
458
  view3d.updateActiveChannels(currentImage);
715
- }, [viewerSettings.renderMode]);
459
+ }, [viewerSettings.renderMode, view3d]);
716
460
  useImageEffect(function (currentImage) {
717
461
  view3d.updateMaskAlpha(currentImage, alphaSliderToImageValue(viewerSettings.maskAlpha));
718
462
  view3d.updateActiveChannels(currentImage);
719
- }, [viewerSettings.maskAlpha]);
720
- useImageLoadEffect(function (_currentImage) {
721
- var isPathTracing = viewerSettings.renderMode === RenderMode.pathTrace;
722
- var brightness = brightnessSliderToImageValue(viewerSettings.brightness, isPathTracing);
463
+ }, [viewerSettings.maskAlpha, view3d]);
464
+ useImageEffect(function (_currentImage) {
465
+ var brightness = brightnessSliderToImageValue(viewerSettings.brightness);
723
466
  view3d.updateExposure(brightness);
724
- }, [viewerSettings.brightness]);
725
- useImageLoadEffect(function (currentImage) {
726
- var isPathTracing = viewerSettings.renderMode === RenderMode.pathTrace;
727
- var density = densitySliderToImageValue(viewerSettings.density, isPathTracing);
467
+ }, [viewerSettings.brightness, view3d]);
468
+ useImageEffect(function (currentImage) {
469
+ var density = densitySliderToImageValue(viewerSettings.density);
728
470
  view3d.updateDensity(currentImage, density);
729
- }, [viewerSettings.density]);
730
- useImageLoadEffect(function (currentImage) {
471
+ }, [viewerSettings.density, view3d]);
472
+ useImageEffect(function (currentImage) {
731
473
  var imageValues = gammaSliderToImageValues(viewerSettings.levels);
732
474
  view3d.setGamma(currentImage, imageValues.min, imageValues.scale, imageValues.max);
733
- }, [viewerSettings.levels]);
475
+ }, [viewerSettings.levels, view3d]);
734
476
 
735
- // `time` is special: because syncing it requires a load, it cannot be dependent on `image`
477
+ // `time` and `scene` have their own special handlers via `volume`, since they both trigger loads
736
478
  useEffect(function () {
737
- if (image) {
738
- setSendingQueryRequest(true);
739
- setAllChannelsUnloaded(image.numChannels);
740
- view3d.setTime(image, viewerSettings.time);
741
- }
742
- }, [viewerSettings.time]);
479
+ return setTime(view3d, viewerSettings.time);
480
+ }, [view3d, viewerSettings.time, setTime]);
743
481
  useEffect(function () {
744
- if (image) {
745
- var _loader$current3;
746
- setSendingQueryRequest(true);
747
- (_loader$current3 = loader.current) === null || _loader$current3 === void 0 || _loader$current3.loadScene(viewerSettings.scene, image)["catch"](function (e) {
748
- return showError(e);
749
- });
750
- }
751
- }, [viewerSettings.scene]);
752
- useImageLoadEffect(function (currentImage) {
482
+ return setScene(viewerSettings.scene);
483
+ }, [viewerSettings.scene, setScene]);
484
+ useImageEffect(function (currentImage) {
753
485
  return view3d.setInterpolationEnabled(currentImage, viewerSettings.interpolationEnabled);
754
- }, [viewerSettings.interpolationEnabled]);
755
- useImageLoadEffect(function (currentImage) {
486
+ }, [viewerSettings.interpolationEnabled, view3d]);
487
+ useImageEffect(function (currentImage) {
756
488
  var _props$transform;
757
489
  return view3d.setVolumeTranslation(currentImage, ((_props$transform = props.transform) === null || _props$transform === void 0 ? void 0 : _props$transform.translation) || [0, 0, 0]);
758
- }, [(_props$transform2 = props.transform) === null || _props$transform2 === void 0 ? void 0 : _props$transform2.translation]);
759
- useImageLoadEffect(function (currentImage) {
490
+ }, [(_props$transform2 = props.transform) === null || _props$transform2 === void 0 ? void 0 : _props$transform2.translation, view3d]);
491
+ useImageEffect(function (currentImage) {
760
492
  var _props$transform3;
761
493
  return view3d.setVolumeRotation(currentImage, ((_props$transform3 = props.transform) === null || _props$transform3 === void 0 ? void 0 : _props$transform3.rotation) || [0, 0, 0]);
762
- }, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation]);
763
- var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis, _ref2, slice) {
764
- var _ref3 = _slicedToArray(_ref2, 2),
765
- minval = _ref3[0],
766
- maxval = _ref3[1];
494
+ }, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation, view3d]);
495
+ var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis, _ref, slice, viewMode) {
496
+ var _ref2 = _slicedToArray(_ref, 2),
497
+ minval = _ref2[0],
498
+ maxval = _ref2[1];
767
499
  useImageEffect(
768
500
  // Logic to determine axis clipping range, for each of x,y,z,3d slider:
769
501
  // if slider was same as active axis view mode: [viewerSettings.slice[axis], viewerSettings.slice[axis] + 1.0/volumeSize[axis]]
@@ -773,30 +505,30 @@ var App = function App(props) {
773
505
  var isOrthoAxis = false;
774
506
  var axismin = 0.0;
775
507
  var axismax = 1.0;
776
- if (viewerSettings.viewMode === ViewMode.threeD) {
508
+ if (viewMode === ViewMode.threeD) {
777
509
  axismin = minval;
778
510
  axismax = maxval;
779
511
  isOrthoAxis = false;
780
512
  } else {
781
- isOrthoAxis = activeAxisMap[viewerSettings.viewMode] === axis;
513
+ isOrthoAxis = activeAxisMap[viewMode] === axis;
782
514
  var oneSlice = 1 / currentImage.imageInfo.volumeSize[axis];
783
515
  axismin = isOrthoAxis ? slice : 0.0;
784
516
  axismax = isOrthoAxis ? slice + oneSlice : 1.0;
785
- if (axis === "z" && viewerSettings.viewMode === ViewMode.xy) {
517
+ if (axis === "z" && viewMode === ViewMode.xy) {
786
518
  view3d.setZSlice(currentImage, Math.floor(slice * currentImage.imageInfo.volumeSize.z));
787
- if (!currentImage.isLoaded()) {
788
- setImageLoaded(false);
789
- }
790
519
  }
791
520
  }
792
521
  // view3d wants the coordinates in the -0.5 to 0.5 range
793
522
  view3d.setAxisClip(currentImage, axis, axismin - 0.5, axismax - 0.5, isOrthoAxis);
794
- view3d.setCameraMode(viewerSettings.viewMode);
795
- }, [minval, maxval, slice, viewerSettings.viewMode]);
523
+ view3d.setCameraMode(viewMode);
524
+ // TODO under some circumstances, this effect will trigger a load. Ideally, this would be reflected in the load
525
+ // state managed by `useVolume`. This is complicated by the fact that the relevant methods (`setAxisClip` and
526
+ // `setZSlice`) don't provide a channel load callback like other load-triggering methods (e.g. `setTime`).
527
+ }, [axis, minval, maxval, slice, viewMode]);
796
528
  };
797
- usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x);
798
- usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y);
799
- usePerAxisClippingUpdater("z", viewerSettings.region.z, viewerSettings.slice.z);
529
+ usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x, viewMode);
530
+ usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y, viewMode);
531
+ usePerAxisClippingUpdater("z", viewerSettings.region.z, viewerSettings.slice.z, viewMode);
800
532
 
801
533
  // Rendering ////////////////////////////////////////////////////////////////
802
534
 
@@ -805,7 +537,7 @@ var App = function App(props) {
805
537
  }, [props.visibleControls]);
806
538
  var pixelSize = useMemo(function () {
807
539
  return image ? image.imageInfo.physicalPixelSize.toArray() : [1, 1, 1];
808
- }, [image === null || image === void 0 ? void 0 : image.imageInfo.physicalPixelSize]);
540
+ }, [image]);
809
541
  var resetCamera = useMemo(function () {
810
542
  return view3d.resetCamera.bind(view3d);
811
543
  }, [view3d]);
@@ -821,7 +553,7 @@ var App = function App(props) {
821
553
  index: index,
822
554
  view3d: view3d,
823
555
  image: image,
824
- version: channelVersions[index]
556
+ version: volume.channelVersions[index]
825
557
  });
826
558
  }), /*#__PURE__*/React.createElement(Sider, {
827
559
  className: "control-panel-holder",
@@ -840,7 +572,7 @@ var App = function App(props) {
840
572
  hasImage: !!image,
841
573
  pixelSize: pixelSize,
842
574
  channelDataChannels: image === null || image === void 0 ? void 0 : image.channels,
843
- channelGroupedByType: channelGroupedByType
575
+ channelGroupedByType: volume.channelGroupedByType
844
576
  // functions
845
577
  ,
846
578
  setCollapsed: setControlPanelClosed,
@@ -865,17 +597,17 @@ var App = function App(props) {
865
597
  }), /*#__PURE__*/React.createElement(CellViewerCanvasWrapper, {
866
598
  view3d: view3d,
867
599
  image: image,
868
- loadingImage: sendingQueryRequest,
600
+ loadingImage: volume.imageLoadStatus === ImageLoadStatus.REQUESTED,
869
601
  numSlices: numSlices,
870
602
  numSlicesLoaded: numSlicesLoaded,
871
603
  numTimesteps: numTimesteps,
872
604
  numScenes: numScenes,
873
- playControls: playControls,
874
- playingAxis: playingAxis,
605
+ playControls: volume.playControls,
606
+ playingAxis: volume.playingAxis,
875
607
  appHeight: props.appHeight,
876
608
  visibleControls: visibleControls,
877
609
  clippingPanelOpen: clippingPanelOpen,
878
- onClippingPanelOpenChange: onClippingPanelOpenChange
610
+ onClippingPanelOpenChange: setClippingPanelOpen
879
611
  })))));
880
612
  };
881
613
  export default App;