@aics/vole-app 3.4.4 → 3.6.0

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 (72) hide show
  1. package/es/aics-image-viewer/components/App/index.js +15 -5
  2. package/es/aics-image-viewer/components/ChannelsWidget.js +3 -1
  3. package/es/aics-image-viewer/components/ChannelsWidgetRow.js +29 -16
  4. package/es/aics-image-viewer/components/ColorPicker/index.js +9 -1
  5. package/es/aics-image-viewer/components/ControlPanel/CopySettingsButton.js +445 -0
  6. package/es/aics-image-viewer/components/ControlPanel/index.js +16 -5
  7. package/es/aics-image-viewer/components/ControlPanel/styles.css +29 -2
  8. package/es/aics-image-viewer/components/StyleProvider/index.js +17 -2
  9. package/es/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.js +1 -1
  10. package/es/aics-image-viewer/components/Toolbar/index.js +1 -1
  11. package/es/aics-image-viewer/components/Toolbar/styles.css +0 -10
  12. package/es/aics-image-viewer/components/{CellViewerCanvasWrapper → ViewerCanvasWrapper}/index.js +3 -2
  13. package/es/aics-image-viewer/components/dimension_sliders/AxisClipSliders.js +21 -8
  14. package/es/aics-image-viewer/components/dimension_sliders/RotationSliders.js +6 -8
  15. package/es/aics-image-viewer/components/dimension_sliders/SliderRows.js +8 -6
  16. package/es/aics-image-viewer/components/shared/ContextualAlert.js +101 -0
  17. package/es/aics-image-viewer/components/shared/ControlPanelRow/index.js +4 -2
  18. package/es/aics-image-viewer/components/shared/ControlPanelRow/styles.css +4 -3
  19. package/es/aics-image-viewer/components/useVolume.js +59 -25
  20. package/es/aics-image-viewer/shared/constants.js +25 -1
  21. package/es/aics-image-viewer/shared/types.js +1 -1
  22. package/es/aics-image-viewer/shared/utils/controlPointsToLut.js +4 -0
  23. package/es/aics-image-viewer/shared/utils/parseSnapshot.js +205 -0
  24. package/es/aics-image-viewer/shared/utils/{urlParsing.js → parseUrl.js} +55 -191
  25. package/es/aics-image-viewer/shared/utils/permissions.js +46 -0
  26. package/es/aics-image-viewer/shared/utils/test/camera.test.js +1 -2
  27. package/es/aics-image-viewer/shared/utils/test/{urlParsing.test.js → parseUrl.test.js} +28 -25
  28. package/es/aics-image-viewer/shared/utils/test/storage.test.js +2 -2
  29. package/es/aics-image-viewer/shared/utils/test/viewerChannelSettings.test.js +1 -1
  30. package/es/aics-image-viewer/state/deserialize.js +443 -331
  31. package/es/aics-image-viewer/state/reset.js +1 -1
  32. package/es/aics-image-viewer/state/serialize.js +168 -78
  33. package/es/aics-image-viewer/state/subscribers.js +1 -1
  34. package/es/aics-image-viewer/state/test/deserialize.test.js +63 -77
  35. package/es/aics-image-viewer/state/test/reset.test.js +2 -2
  36. package/es/aics-image-viewer/state/test/serialize.test.js +42 -43
  37. package/es/aics-image-viewer/state/test/test_data.js +119 -1
  38. package/es/aics-image-viewer/state/types.js +157 -245
  39. package/es/aics-image-viewer/state/util.js +39 -1
  40. package/es/index.js +4 -2
  41. package/package.json +26 -44
  42. package/type-declarations/aics-image-viewer/components/App/types.d.ts +3 -3
  43. package/type-declarations/aics-image-viewer/components/ChannelsWidget.d.ts +3 -0
  44. package/type-declarations/aics-image-viewer/components/ChannelsWidgetRow.d.ts +3 -0
  45. package/type-declarations/aics-image-viewer/components/ControlPanel/CopySettingsButton.d.ts +11 -0
  46. package/type-declarations/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.d.ts +1 -1
  47. package/type-declarations/aics-image-viewer/components/{CellViewerCanvasWrapper → ViewerCanvasWrapper}/index.d.ts +4 -3
  48. package/type-declarations/aics-image-viewer/components/dimension_sliders/AxisClipSliders.d.ts +7 -6
  49. package/type-declarations/aics-image-viewer/components/dimension_sliders/SliderRows.d.ts +1 -2
  50. package/type-declarations/aics-image-viewer/components/shared/ContextualAlert.d.ts +29 -0
  51. package/type-declarations/aics-image-viewer/components/shared/ControlPanelRow/index.d.ts +1 -0
  52. package/type-declarations/aics-image-viewer/components/useVolume.d.ts +3 -0
  53. package/type-declarations/aics-image-viewer/shared/constants.d.ts +2 -2
  54. package/type-declarations/aics-image-viewer/shared/types.d.ts +2 -2
  55. package/type-declarations/aics-image-viewer/shared/utils/controlPointsToLut.d.ts +1 -1
  56. package/type-declarations/aics-image-viewer/shared/utils/parseSnapshot.d.ts +49 -0
  57. package/type-declarations/aics-image-viewer/shared/utils/{urlParsing.d.ts → parseUrl.d.ts} +3 -24
  58. package/type-declarations/aics-image-viewer/shared/utils/permissions.d.ts +7 -0
  59. package/type-declarations/aics-image-viewer/shared/utils/viewerChannelSettings.d.ts +1 -1
  60. package/type-declarations/aics-image-viewer/state/deserialize.d.ts +70 -35
  61. package/type-declarations/aics-image-viewer/state/reset.d.ts +1 -1
  62. package/type-declarations/aics-image-viewer/state/serialize.d.ts +44 -10
  63. package/type-declarations/aics-image-viewer/state/types.d.ts +265 -159
  64. package/type-declarations/aics-image-viewer/state/util.d.ts +3 -1
  65. package/type-declarations/index.d.ts +5 -3
  66. package/es/Globals.d.js +0 -0
  67. package/es/aics-image-viewer/shared/enums.js +0 -18
  68. package/es/aics-image-viewer/shared/utils/firebase/index.js +0 -109
  69. package/type-declarations/aics-image-viewer/shared/enums.d.ts +0 -15
  70. package/type-declarations/aics-image-viewer/shared/utils/firebase/index.d.ts +0 -69
  71. package/type-declarations/aics-image-viewer/shared/utils/math.d.ts +0 -1
  72. /package/es/aics-image-viewer/components/{CellViewerCanvasWrapper → ViewerCanvasWrapper}/styles.css +0 -0
@@ -1,6 +1,6 @@
1
- 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); }
2
1
  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; }
3
2
  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; }
3
+ 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); }
4
4
  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; }
5
5
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
6
6
  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); }
@@ -11,34 +11,19 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
11
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
12
12
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
13
13
  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; }
14
- import { ImageType, RenderMode, ViewMode } from "../shared/enums";
14
+ import { clamp, identity } from "lodash";
15
15
  import { controlPointsToRamp, parseLutSetting } from "../shared/utils/controlPointsToLut";
16
16
  import { removeUndefinedProperties } from "../shared/utils/datatypes";
17
- import { clamp } from "../shared/utils/math";
18
- import { CameraTransformKeys, ViewerChannelSettingKeys, ViewerStateKeys } from "./types";
17
+ import { CameraTransformKeys, ChannelStateSnapshotKeys, ImageType, ImageTypeSnapshot, RenderMode, RenderModeSnapshot, ViewerStateSnapshotKeys, ViewMode, ViewModeSnapshot } from "./types";
19
18
  var DEFAULT_CONTROL_POINT_COLOR = [255, 255, 255];
20
19
  var DEFAULT_CONTROL_POINT_COLOR_CODE = "1";
21
20
  var FLOAT_REGEX = /-?[0-9]*\.?[0-9]+/;
22
21
 
23
- /** Match colon-separated pairs of alphanumeric strings */
24
- var LUT_REGEX = /^-?[a-z0-9.]*:[ ]*-?[a-z0-9.]*$/;
25
-
26
22
  /**
27
- * Match colon-separated pairs of numeric strings, representing histogram bin
28
- * indices or intensity values.
23
+ * A valid lut specifier for `ViewerChannelSettings` is a float optionally prefixed with one of `v`, `p`, or `m`, or
24
+ * the string `autoij`.
29
25
  */
30
- var RAMP_REGEX = new RegExp("^".concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, "$"));
31
-
32
- /**
33
- * Match comma-separated triplet of numeric strings.
34
- */
35
- var SLICE_REGEX = new RegExp("^".concat(FLOAT_REGEX.source, ",").concat(FLOAT_REGEX.source, ",").concat(FLOAT_REGEX.source, "$"));
36
-
37
- /**
38
- * Matches a sequence of three comma-separated min:max number pairs, representing
39
- * the x, y, and z axes.
40
- */
41
- var REGION_REGEX = new RegExp("^(".concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, ")(,").concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, "){2}$"));
26
+ var LUT_VALUE_REGEX = new RegExp("^([vpm]?".concat(FLOAT_REGEX.source, "|autoij)?$"));
42
27
  var HEX_COLOR_REGEX = new RegExp("(([0-9a-fA-F]{6})|".concat(DEFAULT_CONTROL_POINT_COLOR_CODE, ")"));
43
28
 
44
29
  /** Represents control points specified by bin indices. */
@@ -64,6 +49,147 @@ export var LEGACY_CONTROL_POINTS_REGEX = new RegExp("^".concat(CONTROL_POINT_REG
64
49
  */
65
50
  export var CONTROL_POINTS_REGEX = new RegExp("^".concat(CONTROL_POINT_REGEX.source, "(:").concat(CONTROL_POINT_REGEX.source, ")*$"));
66
51
 
52
+ /** An array of exactly `N` `T`s. */
53
+ // adapted from https://github.com/microsoft/TypeScript/pull/40002
54
+
55
+ // MARK: Destringifiers
56
+
57
+ /**
58
+ * Creates a function that parses a string into a list of exactly `length` items split by `delimiter`,
59
+ * with each item further parsed by `itemParser`; or returns `undefined` if parsing failed.
60
+ */
61
+ var tupleParser = function tupleParser(length) {
62
+ var itemParser = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : identity;
63
+ var delimiter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ":";
64
+ return function (stringified) {
65
+ var split = stringified.split(delimiter);
66
+ if (split.length !== length) {
67
+ return undefined;
68
+ }
69
+ var result = [];
70
+ var _iterator = _createForOfIteratorHelper(split),
71
+ _step;
72
+ try {
73
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
74
+ var value = _step.value;
75
+ var parsedValue = itemParser(value.trim());
76
+ if (parsedValue === undefined || Number.isNaN(parsedValue)) {
77
+ return undefined;
78
+ }
79
+ result.push(parsedValue);
80
+ }
81
+ } catch (err) {
82
+ _iterator.e(err);
83
+ } finally {
84
+ _iterator.f();
85
+ }
86
+ return result;
87
+ };
88
+ };
89
+ function parseControlPointSnapshots(controlPoints) {
90
+ if (!(controlPoints && (CONTROL_POINTS_REGEX.test(controlPoints) || LEGACY_CONTROL_POINTS_REGEX.test(controlPoints)))) {
91
+ return undefined;
92
+ }
93
+
94
+ // Parse raw control point data from the string into an array of [x, opacity, color] triplets.
95
+ var controlPointStrings;
96
+ if (LEGACY_CONTROL_POINTS_REGEX.test(controlPoints)) {
97
+ // Legacy format uses commas to separate control points.
98
+ controlPointStrings = controlPoints.split(",").map(function (cp) {
99
+ return cp.split(":");
100
+ });
101
+ } else {
102
+ // New format is all colon-separated, where every three elements represent a control point.
103
+ controlPointStrings = controlPoints.split(":").reduce(function (acc, _val, i, array) {
104
+ if ((i + 1) % 3 === 0) {
105
+ acc.push([array[i - 2], array[i - 1], array[i]]);
106
+ }
107
+ return acc;
108
+ }, []);
109
+ }
110
+ var newControlPoints = controlPointStrings.map(function (cp) {
111
+ var _cp = _slicedToArray(cp, 3),
112
+ x = _cp[0],
113
+ opacity = _cp[1],
114
+ color = _cp[2];
115
+ return {
116
+ x: Number.parseFloat(x),
117
+ opacity: Number.parseFloat(opacity),
118
+ color: color
119
+ };
120
+ });
121
+ return newControlPoints;
122
+ }
123
+ var parseBoolean = function parseBoolean(value) {
124
+ return value === "1" ? true : value === "0" ? false : undefined;
125
+ };
126
+ export var enumParser = function enumParser(enumValues) {
127
+ var variants = Object.values(enumValues);
128
+ var variantsLowercase = Object.fromEntries(variants.map(function (v) {
129
+ return [v.toLowerCase(), v];
130
+ }));
131
+ return function (value) {
132
+ return typeof value === "string" ? variantsLowercase[value.toLowerCase()] : undefined;
133
+ };
134
+ };
135
+ var parseViewMode = enumParser(ViewModeSnapshot);
136
+ var parseRenderMode = enumParser(RenderModeSnapshot);
137
+ var parseImageType = enumParser(ImageTypeSnapshot);
138
+
139
+ /**
140
+ * Helper function for parsing all keys of a stringified object back to their
141
+ * proper types.
142
+ *
143
+ * Accepts an object with string keys, and a map of parsers that convert the
144
+ * keys to their proper types, or return `undefined` when parsing fails.
145
+ */
146
+ var destringify = function destringify(stringified, parsers) {
147
+ var result = {};
148
+ for (var _i = 0, _Object$keys = Object.keys(parsers); _i < _Object$keys.length; _i++) {
149
+ var k = _Object$keys[_i];
150
+ var key = k;
151
+ var parser = parsers[key];
152
+ var stringValue = stringified[key];
153
+ if (stringValue !== undefined) {
154
+ var parsed = parser(stringValue);
155
+ if (parsed !== undefined && !Number.isNaN(parsed)) {
156
+ result[key] = parsed;
157
+ }
158
+ }
159
+ }
160
+ return result;
161
+ };
162
+
163
+ /**
164
+ * Converts a `CameraStateStringified` into a `CameraStateSnapshot` by parsing each key from its stringified
165
+ * representation to its type in `CameraStateSnapshot`.
166
+ */
167
+ export var destringifyCameraStateSnapshot = function destringifyCameraStateSnapshot(stringified) {
168
+ return destringify(stringified, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, CameraTransformKeys.Position, tupleParser(3, Number.parseFloat)), CameraTransformKeys.Target, tupleParser(3, Number.parseFloat)), CameraTransformKeys.Up, tupleParser(3, Number.parseFloat)), CameraTransformKeys.OrthoScale, Number.parseFloat), CameraTransformKeys.Fov, Number.parseFloat));
169
+ };
170
+
171
+ /**
172
+ * Converts a `ViewerStateStringified` into a `ViewerStateSnapshot` by parsing each key from its stringified
173
+ * representation to its type in `ViewerStateSnapshot`.
174
+ */
175
+ export var destringifyViewerStateSnapshot = function destringifyViewerStateSnapshot(stringified) {
176
+ var _destringify2;
177
+ return destringify(stringified, (_destringify2 = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_destringify2, ViewerStateSnapshotKeys.View, parseViewMode), ViewerStateSnapshotKeys.Mode, parseRenderMode), ViewerStateSnapshotKeys.MaskOpacity, Number.parseFloat), ViewerStateSnapshotKeys.ImageType, parseImageType), ViewerStateSnapshotKeys.ShowAxes, parseBoolean), ViewerStateSnapshotKeys.ShowBoundingBox, parseBoolean), ViewerStateSnapshotKeys.BoundingBoxColor, identity), ViewerStateSnapshotKeys.BackgroundColor, identity), ViewerStateSnapshotKeys.Autorotate, parseBoolean), ViewerStateSnapshotKeys.Brightness, Number.parseFloat), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_destringify2, ViewerStateSnapshotKeys.Density, Number.parseFloat), ViewerStateSnapshotKeys.Levels, tupleParser(3, Number.parseFloat, ",")), ViewerStateSnapshotKeys.Interpolation, parseBoolean), ViewerStateSnapshotKeys.Region, tupleParser(3, tupleParser(2, Number.parseFloat), ",")), ViewerStateSnapshotKeys.Slice, tupleParser(3, Number.parseFloat, ",")), ViewerStateSnapshotKeys.Time, Number.parseInt), ViewerStateSnapshotKeys.Scene, Number.parseInt), ViewerStateSnapshotKeys.CameraState, function (cameraString) {
178
+ return destringifyCameraStateSnapshot(parseKeyValueList(cameraString));
179
+ }), ViewerStateSnapshotKeys.SingleChannelMode, parseBoolean), ViewerStateSnapshotKeys.SingleChannelIndex, Number.parseInt), _defineProperty(_defineProperty(_destringify2, ViewerStateSnapshotKeys.UseExactScaleLevel, parseBoolean), ViewerStateSnapshotKeys.ScaleLevelIndex, Number.parseInt)));
180
+ };
181
+
182
+ /**
183
+ * Converts a `ChannelStateStringified` into a `ChannelStateSnapshot` by parsing each key from its stringified
184
+ * representation to its type in `ChannelStateSnapshot`.
185
+ */
186
+ export var destringifyChannelStateSnapshot = function destringifyChannelStateSnapshot(stringified) {
187
+ var _destringify3;
188
+ return destringify(stringified, (_destringify3 = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_destringify3, ChannelStateSnapshotKeys.Color, identity), ChannelStateSnapshotKeys.Colorize, parseBoolean), ChannelStateSnapshotKeys.ColorizeAlpha, Number.parseFloat), ChannelStateSnapshotKeys.IsosurfaceAlpha, Number.parseFloat), ChannelStateSnapshotKeys.Lut, tupleParser(2)), ChannelStateSnapshotKeys.ControlPoints, parseControlPointSnapshots), ChannelStateSnapshotKeys.ControlPointsLegacy, parseControlPointSnapshots), ChannelStateSnapshotKeys.Ramp, tupleParser(2, Number.parseFloat)), ChannelStateSnapshotKeys.RampLegacy, tupleParser(2, Number.parseFloat)), ChannelStateSnapshotKeys.ControlPointsEnabled, parseBoolean), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_destringify3, ChannelStateSnapshotKeys.VolumeEnabled, parseBoolean), ChannelStateSnapshotKeys.SurfaceEnabled, parseBoolean), ChannelStateSnapshotKeys.IsosurfaceValue, Number.parseFloat), ChannelStateSnapshotKeys.KeepRange, parseBoolean)));
189
+ };
190
+
191
+ // MARK: Parsing Helpers
192
+
67
193
  /**
68
194
  * Parse a string list of comma-separated key:value pairs into
69
195
  * a key-value object.
@@ -80,11 +206,11 @@ export function parseKeyValueList(data) {
80
206
  }
81
207
  var result = {};
82
208
  var keyValuePairs = data.split(",");
83
- var _iterator = _createForOfIteratorHelper(keyValuePairs),
84
- _step;
209
+ var _iterator2 = _createForOfIteratorHelper(keyValuePairs),
210
+ _step2;
85
211
  try {
86
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
87
- var pair = _step.value;
212
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
213
+ var pair = _step2.value;
88
214
  var splitIndex = pair.indexOf(":");
89
215
  if (splitIndex === -1) {
90
216
  continue;
@@ -94,78 +220,14 @@ export function parseKeyValueList(data) {
94
220
  result[decodeURIComponent(key).trim()] = decodeURIComponent(value).trim();
95
221
  }
96
222
  } catch (err) {
97
- _iterator.e(err);
223
+ _iterator2.e(err);
98
224
  } finally {
99
- _iterator.f();
225
+ _iterator2.f();
100
226
  }
101
227
  return result;
102
228
  }
103
-
104
- /**
105
- * Parses a string to a float and clamps the result to the [min, max] range.
106
- * Returns `undefined` if the string is undefined or NaN.
107
- * @param value String to parse as a float. Will be parsed with `Number.parseFloat`.
108
- * @param min Minimum value, inclusive.
109
- * @param max Maximum value, inclusive.
110
- * @returns
111
- * - The parsed number, clamped to the [min, max] range.
112
- * - `undefined` if the string is undefined or NaN.
113
- */
114
- export function parseStringFloat(value, min, max) {
115
- if (value === undefined) {
116
- return undefined;
117
- }
118
- var number = Number.parseFloat(value);
119
- return Number.isNaN(number) ? undefined : clamp(number, min, max);
120
- }
121
-
122
- /**
123
- * Parses a string to an integer and clamps the result to the [min, max] range.
124
- * @param value String to parse as a float. Assumes base 10, parses with `Number.parseInt(value, 10)`.
125
- * @param min Minimum value, inclusive.
126
- * @param max Maximum value, inclusive.
127
- * @returns
128
- * - The parsed number, clamped to the [min, max] range.
129
- * - `undefined` if the string is undefined or NaN.
130
- */
131
- export function parseStringInt(value, min, max) {
132
- if (value === undefined) {
133
- return undefined;
134
- }
135
- var number = Number.parseInt(value, 10);
136
- if (Number.isNaN(number)) {
137
- return undefined;
138
- }
139
- return clamp(number, min, max);
140
- }
141
-
142
- /**
143
- * Parses a string to an enum value; if the string is not in the enum, returns the default value.
144
- * @param value String to parse.
145
- * @param enumValues Enum. Cannot be a `const enum`, as these are removed at compile time.
146
- * @param defaultValue Default value to return if the string is not in the enum.
147
- * @returns A value from the enum or the default value. Note that the return type includes `undefined`
148
- * if the `defaultValue` is `undefined`.
149
- */
150
- export function parseStringEnum(value, enumValues) {
151
- var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
152
- if (value === undefined || !Object.values(enumValues).includes(value)) {
153
- return defaultValue;
154
- }
155
- return value;
156
- }
157
-
158
- /**
159
- * Parses a string boolean value ("1" as true, "0" as false), and returns `undefined` if the value is `undefined`.
160
- */
161
- function parseStringBoolean(value) {
162
- if (value === undefined) {
163
- return undefined;
164
- }
165
- return value === "1";
166
- }
167
229
  export function parseHexColorAsColorArray(hexColor) {
168
- if (!hexColor || !HEX_COLOR_STR_REGEX.test(hexColor)) {
230
+ if (typeof hexColor !== "string" || !HEX_COLOR_STR_REGEX.test(hexColor)) {
169
231
  return undefined;
170
232
  }
171
233
  // if (hexColor in COLOR_CODES) {
@@ -179,257 +241,267 @@ export function parseHexColorAsColorArray(hexColor) {
179
241
  var b = Number.parseInt(hexColor.slice(4, 6), 16);
180
242
  return [r, g, b];
181
243
  }
182
- function parseStringSlice(region) {
183
- if (!region || !SLICE_REGEX.test(region)) {
244
+
245
+ /** Verifies that `value` is a `number`; if it is, clamps it between `min` and `max`. */
246
+ export var validateNumber = function validateNumber(value) {
247
+ var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -Infinity;
248
+ var max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Infinity;
249
+ return typeof value === "number" && !Number.isNaN(value) ? clamp(value, min, max) : undefined;
250
+ };
251
+
252
+ /** Verifies that `value` is a `number`; if it is, truncates it to an integer and clamps it between `min` and `max`. */
253
+ export var validateInt = function validateInt(value) {
254
+ var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -Infinity;
255
+ var max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Infinity;
256
+ return typeof value === "number" && !Number.isNaN(value) ? clamp(Math.trunc(value), min, max) : undefined;
257
+ };
258
+
259
+ /** Verifies that a value is a `boolean`. */
260
+ var validateBoolean = function validateBoolean(value) {
261
+ return typeof value === "boolean" ? value : undefined;
262
+ };
263
+
264
+ /** Verifies that `value` is a list of length `length` and that all its items are valid per `validator`. */
265
+ var validateTuple = function validateTuple(value, length, validator) {
266
+ if (!Array.isArray(value) || value.length !== length) {
184
267
  return undefined;
185
268
  }
186
- var _region$split$map = region.split(",").map(function (val) {
187
- return parseStringFloat(val, 0, 1);
188
- }),
189
- _region$split$map2 = _slicedToArray(_region$split$map, 3),
190
- x = _region$split$map2[0],
191
- y = _region$split$map2[1],
192
- z = _region$split$map2[2];
193
- if (x === undefined || y === undefined || z === undefined) {
269
+ var result = [];
270
+ var _iterator3 = _createForOfIteratorHelper(value),
271
+ _step3;
272
+ try {
273
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
274
+ var v = _step3.value;
275
+ var validated = validator(v);
276
+ if (validated === undefined) {
277
+ return undefined;
278
+ }
279
+ result.push(validated);
280
+ }
281
+ } catch (err) {
282
+ _iterator3.e(err);
283
+ } finally {
284
+ _iterator3.f();
285
+ }
286
+ return result;
287
+ };
288
+
289
+ /**
290
+ * Verifies that `value` is a three-element array whose items are valid per `validator`;
291
+ * if it is, converts it to an object with the form `{ x, y, z }`
292
+ */
293
+ var validateXYZ = function validateXYZ(value, validator) {
294
+ var tuple = validateTuple(value, 3, validator);
295
+ if (tuple === undefined) {
194
296
  return undefined;
195
297
  }
298
+ var _tuple = _slicedToArray(tuple, 3),
299
+ x = _tuple[0],
300
+ y = _tuple[1],
301
+ z = _tuple[2];
196
302
  return {
197
303
  x: x,
198
304
  y: y,
199
305
  z: z
200
306
  };
201
- }
307
+ };
202
308
 
203
309
  /**
204
- * Parses an array of three numbers from a string.
205
- * @param stringArr The string to parse. Should be three numbers separated by a separator.
206
- * @param options Optional parameters for parsing:
207
- * - `min`: Minimum value for each number. Default is negative infinity.
208
- * - `max`: Maximum value for each number. Default is positive infinity.
209
- * - `separator`: Separator between numbers. Default is `,`.
210
- * @returns
211
- * - undefined if the string is undefined or could not be parsed.
212
- * - An array of three numbers, clamped to the [min, max] range.
310
+ * Verifies that `value` is a two-element array of numbers, clamps both numbers between `min` and `max`,
311
+ * then sorts the numbers.
213
312
  */
214
- function parseThreeNumberArray(stringArr, options) {
215
- var _options$min, _options$max, _options$separator;
216
- if (!stringArr) {
313
+ var validateSortedPair = function validateSortedPair(value, min, max) {
314
+ var tuple = validateTuple(value, 2, function (number) {
315
+ return validateNumber(number, min, max);
316
+ });
317
+ if (tuple === undefined) {
217
318
  return undefined;
218
319
  }
219
- var min = (_options$min = options === null || options === void 0 ? void 0 : options.min) !== null && _options$min !== void 0 ? _options$min : Number.NEGATIVE_INFINITY;
220
- var max = (_options$max = options === null || options === void 0 ? void 0 : options.max) !== null && _options$max !== void 0 ? _options$max : Number.POSITIVE_INFINITY;
221
- var separator = (_options$separator = options === null || options === void 0 ? void 0 : options.separator) !== null && _options$separator !== void 0 ? _options$separator : ",";
222
- var _stringArr$split$map = stringArr.split(separator).map(function (val) {
223
- return parseStringFloat(val, min, max);
224
- }),
225
- _stringArr$split$map2 = _slicedToArray(_stringArr$split$map, 3),
226
- x = _stringArr$split$map2[0],
227
- y = _stringArr$split$map2[1],
228
- z = _stringArr$split$map2[2];
229
- if (x === undefined || y === undefined || z === undefined) {
320
+ var _tuple2 = _slicedToArray(tuple, 2),
321
+ minVal = _tuple2[0],
322
+ maxVal = _tuple2[1];
323
+ return minVal > maxVal ? [maxVal, minVal] : [minVal, maxVal];
324
+ };
325
+
326
+ /** Verifies that `value` is an object with `string` keys. */
327
+ var validateRecord = function validateRecord(value) {
328
+ if (_typeof(value) !== "object" || Array.isArray(value) || value === null) {
230
329
  return undefined;
231
330
  }
232
- return [x, y, z];
233
- }
234
- function parseStringRegion(region) {
235
- if (!region || !REGION_REGEX.test(region)) {
331
+ if (!Object.keys(value).every(function (key) {
332
+ return typeof key === "string";
333
+ })) {
236
334
  return undefined;
237
335
  }
238
- var _region$split$map3 = region.split(",").map(function (axis) {
239
- // each is a min/max pair
240
- var _axis$split$map = axis.split(":").map(function (val) {
241
- return parseStringFloat(val, 0, 1);
242
- }),
243
- _axis$split$map2 = _slicedToArray(_axis$split$map, 2),
244
- min = _axis$split$map2[0],
245
- max = _axis$split$map2[1];
246
- if (min === undefined || max === undefined) {
247
- return undefined;
248
- }
249
- // Ensure sorted order
250
- return min < max ? [min, max] : [max, min];
251
- }),
252
- _region$split$map4 = _slicedToArray(_region$split$map3, 3),
253
- x = _region$split$map4[0],
254
- y = _region$split$map4[1],
255
- z = _region$split$map4[2];
256
- // Check for undefined values
257
- if (x === undefined || y === undefined || z === undefined) {
336
+ return value;
337
+ };
338
+
339
+ /** Verifies that `value` is a valid lut specifier per `ViewerChannelSettings`. */
340
+ var validateLutValue = function validateLutValue(value) {
341
+ if (typeof value === "number") {
342
+ return clamp(value, 0, 255);
343
+ }
344
+ if (typeof value !== "string") {
258
345
  return undefined;
259
346
  }
260
- return {
261
- x: x,
262
- y: y,
263
- z: z
347
+ var result = value.trim().toLowerCase();
348
+ if (LUT_VALUE_REGEX.test(result)) {
349
+ return result;
350
+ }
351
+ return undefined;
352
+ };
353
+ var enumSnapshotConverter = function enumSnapshotConverter(map) {
354
+ var entries = Object.entries(map);
355
+ var lowercaseMap = Object.fromEntries(entries.map(function (_ref) {
356
+ var _ref2 = _slicedToArray(_ref, 2),
357
+ s = _ref2[0],
358
+ e = _ref2[1];
359
+ return [s.toLowerCase(), e];
360
+ }));
361
+ return function (value) {
362
+ return typeof value === "string" ? lowercaseMap[value.toLowerCase()] : undefined;
264
363
  };
265
- }
266
- function parseCameraState(cameraSettings) {
267
- if (!cameraSettings) {
364
+ };
365
+ var snapshotToViewMode = enumSnapshotConverter(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, ViewModeSnapshot.threeD, ViewMode.threeD), ViewModeSnapshot.xy, ViewMode.xy), ViewModeSnapshot.xz, ViewMode.xz), ViewModeSnapshot.yz, ViewMode.yz));
366
+ var snapshotToRenderMode = enumSnapshotConverter(_defineProperty(_defineProperty(_defineProperty({}, RenderModeSnapshot.volumetric, RenderMode.volumetric), RenderModeSnapshot.maxProject, RenderMode.maxProject), RenderModeSnapshot.pathTrace, RenderMode.pathTrace));
367
+ var snapshotToImageType = enumSnapshotConverter(_defineProperty(_defineProperty({}, ImageTypeSnapshot.segmentedCell, ImageType.segmentedCell), ImageTypeSnapshot.fullField, ImageType.fullField));
368
+
369
+ // MARK: Parsers
370
+
371
+ /** Parses a `CameraStateSnapshot` to a `CameraState`. */
372
+ function snapshotToCameraState(snapshot) {
373
+ if (snapshot === undefined) {
268
374
  return undefined;
269
375
  }
270
- var parsedCameraSettings = parseKeyValueList(cameraSettings);
271
376
  var result = {
272
- position: parseThreeNumberArray(parsedCameraSettings[CameraTransformKeys.Position], {
273
- separator: ":"
274
- }),
275
- target: parseThreeNumberArray(parsedCameraSettings[CameraTransformKeys.Target], {
276
- separator: ":"
277
- }),
278
- up: parseThreeNumberArray(parsedCameraSettings[CameraTransformKeys.Up], {
279
- separator: ":"
280
- }),
377
+ position: validateTuple(snapshot[CameraTransformKeys.Position], 3, validateNumber),
378
+ target: validateTuple(snapshot[CameraTransformKeys.Target], 3, validateNumber),
379
+ up: validateTuple(snapshot[CameraTransformKeys.Up], 3, validateNumber),
281
380
  // Orthographic scales cannot be negative
282
- orthoScale: parseStringFloat(parsedCameraSettings[CameraTransformKeys.OrthoScale], 0, Infinity),
283
- fov: parseStringFloat(parsedCameraSettings[CameraTransformKeys.Fov], 0, 180)
381
+ orthoScale: validateNumber(snapshot[CameraTransformKeys.OrthoScale], 0),
382
+ fov: validateNumber(snapshot[CameraTransformKeys.Fov], 0, 180)
284
383
  };
285
384
  return removeUndefinedProperties(result);
286
385
  }
287
- export function deserializeViewerState(params) {
386
+
387
+ /** Parses a `ViewerStateSnapshot` to a `ViewerState`. */
388
+ export function snapshotToViewerState(snapshot) {
288
389
  var result = {
289
- maskAlpha: parseStringInt(params[ViewerStateKeys.Mask], 0, 100),
290
- imageType: parseStringEnum(params[ViewerStateKeys.Image], ImageType),
291
- showAxes: parseStringBoolean(params[ViewerStateKeys.Axes]),
292
- showBoundingBox: parseStringBoolean(params[ViewerStateKeys.BoundingBox]),
293
- boundingBoxColor: parseHexColorAsColorArray(params[ViewerStateKeys.BoundingBoxColor]),
294
- backgroundColor: parseHexColorAsColorArray(params[ViewerStateKeys.BackgroundColor]),
295
- autorotate: parseStringBoolean(params[ViewerStateKeys.Autorotate]),
296
- brightness: parseStringFloat(params[ViewerStateKeys.Brightness], 0, 100),
297
- density: parseStringFloat(params[ViewerStateKeys.Density], 0, 100),
298
- levels: parseThreeNumberArray(params[ViewerStateKeys.Levels], {
299
- min: 0,
300
- max: 255
390
+ viewMode: snapshotToViewMode(snapshot[ViewerStateSnapshotKeys.View]),
391
+ maskAlpha: validateNumber(snapshot[ViewerStateSnapshotKeys.MaskOpacity], 0, 100),
392
+ imageType: snapshotToImageType(snapshot[ViewerStateSnapshotKeys.ImageType]),
393
+ showAxes: validateBoolean(snapshot[ViewerStateSnapshotKeys.ShowAxes]),
394
+ showBoundingBox: validateBoolean(snapshot[ViewerStateSnapshotKeys.ShowBoundingBox]),
395
+ boundingBoxColor: parseHexColorAsColorArray(snapshot[ViewerStateSnapshotKeys.BoundingBoxColor]),
396
+ backgroundColor: parseHexColorAsColorArray(snapshot[ViewerStateSnapshotKeys.BackgroundColor]),
397
+ autorotate: validateBoolean(snapshot[ViewerStateSnapshotKeys.Autorotate]),
398
+ brightness: validateNumber(snapshot[ViewerStateSnapshotKeys.Brightness], 0, 100),
399
+ density: validateNumber(snapshot[ViewerStateSnapshotKeys.Density], 0, 100),
400
+ levels: validateTuple(snapshot[ViewerStateSnapshotKeys.Levels], 3, function (value) {
401
+ return validateNumber(value, 0, 255);
301
402
  }),
302
- interpolationEnabled: parseStringBoolean(params[ViewerStateKeys.Interpolation]),
303
- region: parseStringRegion(params[ViewerStateKeys.Region]),
304
- slice: parseStringSlice(params[ViewerStateKeys.Slice]),
305
- time: parseStringInt(params[ViewerStateKeys.Time], 0, Number.POSITIVE_INFINITY),
306
- scene: parseStringInt(params[ViewerStateKeys.Scene], 0, Number.POSITIVE_INFINITY),
307
- renderMode: parseStringEnum(params[ViewerStateKeys.Mode], RenderMode),
308
- singleChannelMode: parseStringBoolean(params[ViewerStateKeys.SingleChannelMode]),
309
- singleChannelIndex: parseStringInt(params[ViewerStateKeys.SingleChannelIndex], 0, Number.POSITIVE_INFINITY),
310
- useExactScaleLevel: parseStringBoolean(params[ViewerStateKeys.UseExactScaleLevel]),
311
- scaleLevelIndex: parseStringInt(params[ViewerStateKeys.ScaleLevelIndex], 0, Number.MAX_SAFE_INTEGER),
312
- cameraState: parseCameraState(params[ViewerStateKeys.CameraState])
403
+ interpolationEnabled: validateBoolean(snapshot[ViewerStateSnapshotKeys.Interpolation]),
404
+ region: validateXYZ(snapshot[ViewerStateSnapshotKeys.Region], function (value) {
405
+ return validateSortedPair(value, 0, 1);
406
+ }),
407
+ slice: validateXYZ(snapshot[ViewerStateSnapshotKeys.Slice], function (value) {
408
+ return validateNumber(value, 0, 1);
409
+ }),
410
+ time: validateInt(snapshot[ViewerStateSnapshotKeys.Time], 0, Number.POSITIVE_INFINITY),
411
+ scene: validateInt(snapshot[ViewerStateSnapshotKeys.Scene], 0, Number.POSITIVE_INFINITY),
412
+ renderMode: snapshotToRenderMode(snapshot[ViewerStateSnapshotKeys.Mode]),
413
+ singleChannelMode: validateBoolean(snapshot[ViewerStateSnapshotKeys.SingleChannelMode]),
414
+ singleChannelIndex: validateInt(snapshot[ViewerStateSnapshotKeys.SingleChannelIndex], 0, Number.POSITIVE_INFINITY),
415
+ useExactScaleLevel: validateBoolean(snapshot[ViewerStateSnapshotKeys.UseExactScaleLevel]),
416
+ scaleLevelIndex: validateInt(snapshot[ViewerStateSnapshotKeys.ScaleLevelIndex], 0, Number.MAX_SAFE_INTEGER),
417
+ cameraState: snapshotToCameraState(validateRecord(snapshot[ViewerStateSnapshotKeys.CameraState]))
313
418
  };
314
-
315
- // Handle viewmode, since they use different mappings
316
- // TODO: Allow lowercase
317
- if (params.view) {
318
- var viewParamToViewMode = {
319
- "3D": ViewMode.threeD,
320
- Z: ViewMode.xy,
321
- Y: ViewMode.xz,
322
- X: ViewMode.yz
323
- };
324
- var allowedViews = Object.keys(viewParamToViewMode);
325
- var view;
326
- if (allowedViews.includes(params.view.toUpperCase())) {
327
- view = params.view.toUpperCase();
328
- } else {
329
- view = "3D";
330
- }
331
- result.viewMode = viewParamToViewMode[view];
332
- }
333
419
  return removeUndefinedProperties(result);
334
420
  }
335
- function parseControlPoints(controlPoints) {
336
- if (!(controlPoints && (CONTROL_POINTS_REGEX.test(controlPoints) || LEGACY_CONTROL_POINTS_REGEX.test(controlPoints)))) {
337
- return undefined;
338
- }
339
421
 
340
- // Parse raw control point data from the string into an array of [x, opacity, color] triplets.
341
- var controlPointStrings;
342
- if (LEGACY_CONTROL_POINTS_REGEX.test(controlPoints)) {
343
- // Legacy format uses commas to separate control points.
344
- controlPointStrings = controlPoints.split(",").map(function (cp) {
345
- return cp.split(":");
346
- });
347
- } else {
348
- // New format is all colon-separated, where every three elements represent a control point.
349
- controlPointStrings = controlPoints.split(":").reduce(function (acc, _val, i, array) {
350
- if ((i + 1) % 3 === 0) {
351
- acc.push([array[i - 2], array[i - 1], array[i]]);
422
+ /** Attempts to parse an array of `ControlPointSnapshot`s to an array of `ControlPoint`s. */
423
+ function snapshotToControlPoints(controlPoints) {
424
+ var result = [];
425
+ var _iterator4 = _createForOfIteratorHelper(controlPoints),
426
+ _step4;
427
+ try {
428
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
429
+ var _parseHexColorAsColor;
430
+ var point = _step4.value;
431
+ var x = validateNumber(point.x);
432
+ var opacity = validateNumber(point.opacity, 0, 1);
433
+ var color = (_parseHexColorAsColor = parseHexColorAsColorArray(point.color)) !== null && _parseHexColorAsColor !== void 0 ? _parseHexColorAsColor : [].concat(DEFAULT_CONTROL_POINT_COLOR);
434
+ if (x === undefined || opacity === undefined || color === undefined) {
435
+ return undefined;
352
436
  }
353
- return acc;
354
- }, []);
437
+ result.push({
438
+ x: x,
439
+ opacity: opacity,
440
+ color: color
441
+ });
442
+ }
443
+ // Sort control points by x value
444
+ } catch (err) {
445
+ _iterator4.e(err);
446
+ } finally {
447
+ _iterator4.f();
355
448
  }
356
- var newControlPoints = controlPointStrings.map(function (cp) {
357
- var _parseStringFloat, _parseStringFloat2, _parseHexColorAsColor;
358
- var _cp = _slicedToArray(cp, 3),
359
- x = _cp[0],
360
- opacity = _cp[1],
361
- color = _cp[2];
362
- return {
363
- x: (_parseStringFloat = parseStringFloat(x, -Infinity, Infinity)) !== null && _parseStringFloat !== void 0 ? _parseStringFloat : 0,
364
- opacity: (_parseStringFloat2 = parseStringFloat(opacity, 0, 1)) !== null && _parseStringFloat2 !== void 0 ? _parseStringFloat2 : 1.0,
365
- color: (_parseHexColorAsColor = parseHexColorAsColorArray(color)) !== null && _parseHexColorAsColor !== void 0 ? _parseHexColorAsColor : DEFAULT_CONTROL_POINT_COLOR
366
- };
367
- });
368
- // Sort control points by x value
369
- return newControlPoints.sort(function (a, b) {
449
+ return result.sort(function (a, b) {
370
450
  return a.x - b.x;
371
451
  });
372
452
  }
373
453
 
374
454
  /**
375
- * Parses a ViewerChannelSetting from a JSON object.
376
- * @param channelIndex Index of the channel, to be turned into a `match` value.
377
- * @param jsonState The serialized ViewerChannelSetting to parse, as an object.
378
- * @returns A ViewerChannelSetting object.
455
+ * Parses a `ChannelStateSnapshot` to a `ViewerChannelSetting`.
456
+ * @param channelId Either the index or the name of the channel, to be turned into a `match` value.
457
+ * @param jsonState The serialized `ViewerChannelSetting` to parse, as an object.
458
+ * @returns A `ViewerChannelSetting` object.
379
459
  */
380
- export function deserializeViewerChannelSetting(channelIndex, jsonState) {
460
+ export function snapshotToViewerChannelSetting(channelId, jsonState) {
381
461
  // Missing/undefined fields should be handled downstream.
382
462
  var result = {
383
- match: channelIndex,
384
- enabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.VolumeEnabled]),
385
- surfaceEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.SurfaceEnabled]),
386
- isovalue: parseStringFloat(jsonState[ViewerChannelSettingKeys.IsosurfaceValue], -Infinity, Infinity),
387
- keepIntensityRange: parseStringBoolean(jsonState[ViewerChannelSettingKeys.KeepRange]),
388
- surfaceOpacity: parseStringFloat(jsonState[ViewerChannelSettingKeys.IsosurfaceAlpha], 0, 1),
389
- colorizeEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.Colorize]),
390
- colorizeAlpha: parseStringFloat(jsonState[ViewerChannelSettingKeys.ColorizeAlpha], 0, 1),
391
- controlPointsEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.ControlPointsEnabled])
463
+ match: channelId,
464
+ enabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.VolumeEnabled]),
465
+ surfaceEnabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.SurfaceEnabled]),
466
+ isovalue: validateNumber(jsonState[ChannelStateSnapshotKeys.IsosurfaceValue], -Infinity, Infinity),
467
+ keepIntensityRange: validateBoolean(jsonState[ChannelStateSnapshotKeys.KeepRange]),
468
+ surfaceOpacity: validateNumber(jsonState[ChannelStateSnapshotKeys.IsosurfaceAlpha], 0, 1),
469
+ colorizeEnabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.Colorize]),
470
+ colorizeAlpha: validateNumber(jsonState[ChannelStateSnapshotKeys.ColorizeAlpha], 0, 1),
471
+ controlPointsEnabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.ControlPointsEnabled])
392
472
  };
393
- if (jsonState[ViewerChannelSettingKeys.Color] && HEX_COLOR_STR_REGEX.test(jsonState.col)) {
394
- result.color = jsonState[ViewerChannelSettingKeys.Color];
473
+ var color = jsonState[ChannelStateSnapshotKeys.Color];
474
+ if (typeof color === "string" && HEX_COLOR_STR_REGEX.test(color)) {
475
+ result.color = color;
395
476
  }
396
- if (jsonState[ViewerChannelSettingKeys.Lut] && LUT_REGEX.test(jsonState.lut)) {
397
- var _jsonState$ViewerChan = jsonState[ViewerChannelSettingKeys.Lut].split(":"),
398
- _jsonState$ViewerChan2 = _slicedToArray(_jsonState$ViewerChan, 2),
399
- min = _jsonState$ViewerChan2[0],
400
- max = _jsonState$ViewerChan2[1];
477
+ var lut = validateTuple(jsonState[ChannelStateSnapshotKeys.Lut], 2, validateLutValue);
478
+ if (lut !== undefined) {
401
479
  result.intensity = _objectSpread(_objectSpread({}, result.intensity), {}, {
402
- lut: [min.trim(), max.trim()]
480
+ lut: lut
403
481
  });
404
482
  }
405
- if (jsonState[ViewerChannelSettingKeys.Ramp]) {
406
- if (RAMP_REGEX.test(jsonState[ViewerChannelSettingKeys.Ramp])) {
407
- var _jsonState$ViewerChan3 = jsonState[ViewerChannelSettingKeys.Ramp].split(":"),
408
- _jsonState$ViewerChan4 = _slicedToArray(_jsonState$ViewerChan3, 2),
409
- _min = _jsonState$ViewerChan4[0],
410
- _max = _jsonState$ViewerChan4[1];
483
+ if (jsonState[ChannelStateSnapshotKeys.Ramp]) {
484
+ var ramp = validateSortedPair(jsonState[ChannelStateSnapshotKeys.Ramp]);
485
+ if (ramp !== undefined) {
411
486
  result.intensity = _objectSpread(_objectSpread({}, result.intensity), {}, {
412
- ramp: [Number.parseFloat(_min), Number.parseFloat(_max)]
487
+ ramp: ramp
413
488
  });
414
489
  }
415
- } else if (jsonState[ViewerChannelSettingKeys.RampLegacy]) {
416
- if (RAMP_REGEX.test(jsonState[ViewerChannelSettingKeys.RampLegacy])) {
417
- var _jsonState$ViewerChan5 = jsonState[ViewerChannelSettingKeys.RampLegacy].split(":"),
418
- _jsonState$ViewerChan6 = _slicedToArray(_jsonState$ViewerChan5, 2),
419
- _min2 = _jsonState$ViewerChan6[0],
420
- _max2 = _jsonState$ViewerChan6[1];
421
- result.ramp = [Number.parseFloat(_min2), Number.parseFloat(_max2)];
490
+ } else if (jsonState[ChannelStateSnapshotKeys.RampLegacy]) {
491
+ var _ramp = validateSortedPair(jsonState[ChannelStateSnapshotKeys.RampLegacy]);
492
+ if (_ramp !== undefined) {
493
+ result.ramp = _ramp;
422
494
  }
423
495
  }
424
- if (jsonState[ViewerChannelSettingKeys.ControlPoints]) {
425
- var parsedResult = parseControlPoints(jsonState[ViewerChannelSettingKeys.ControlPoints]);
496
+ if (Array.isArray(jsonState[ChannelStateSnapshotKeys.ControlPoints])) {
497
+ var parsedResult = snapshotToControlPoints(jsonState[ChannelStateSnapshotKeys.ControlPoints]);
426
498
  if (parsedResult) {
427
499
  result.intensity = _objectSpread(_objectSpread({}, result.intensity), {}, {
428
500
  controlPoints: parsedResult
429
501
  });
430
502
  }
431
- } else if (jsonState[ViewerChannelSettingKeys.ControlPointsLegacy]) {
432
- var _parsedResult = parseControlPoints(jsonState[ViewerChannelSettingKeys.ControlPointsLegacy]);
503
+ } else if (Array.isArray(jsonState[ChannelStateSnapshotKeys.ControlPointsLegacy])) {
504
+ var _parsedResult = snapshotToControlPoints(jsonState[ChannelStateSnapshotKeys.ControlPointsLegacy]);
433
505
  if (_parsedResult) {
434
506
  result.controlPoints = _parsedResult;
435
507
  }
@@ -438,10 +510,10 @@ export function deserializeViewerChannelSetting(channelIndex, jsonState) {
438
510
  }
439
511
 
440
512
  /**
441
- * Parses a `ViewerChannelStateParams` object into a partial `ChannelState`.
513
+ * Parses a `ChannelStateSnapshot` object into a partial `ChannelState`.
442
514
  *
443
- * This is used to convert raw URL params into internal channel state fields,
444
- * leaving absent or invalid values undefined.
515
+ * This is used to convert from serialized formats (URL params, JSON) into
516
+ * internal channel state fields, leaving absent or invalid values undefined.
445
517
  *
446
518
  * This function optionally accepts the target channel's `Histogram`. This
447
519
  * argument is required to parse the following params correctly:
@@ -455,68 +527,59 @@ export function deserializeViewerChannelSetting(channelIndex, jsonState) {
455
527
  * If `histogram` is left undefined, e.g. because the channel has not yet been
456
528
  * loaded, these params are ignored.
457
529
  */
458
- export function deserializeChannelState(jsonState, histogram) {
530
+ export function snapshotToChannelState(jsonState, histogram) {
459
531
  var result = {
460
- volumeEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.VolumeEnabled]),
461
- isosurfaceEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.SurfaceEnabled]),
462
- isovalue: parseStringFloat(jsonState[ViewerChannelSettingKeys.IsosurfaceValue], -Infinity, Infinity),
463
- keepIntensityRange: parseStringBoolean(jsonState[ViewerChannelSettingKeys.KeepRange]),
464
- opacity: parseStringFloat(jsonState[ViewerChannelSettingKeys.IsosurfaceAlpha], 0, 1),
465
- colorizeEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.Colorize]),
466
- colorizeAlpha: parseStringFloat(jsonState[ViewerChannelSettingKeys.ColorizeAlpha], 0, 1),
467
- useControlPoints: parseStringBoolean(jsonState[ViewerChannelSettingKeys.ControlPointsEnabled]),
468
- color: parseHexColorAsColorArray(jsonState[ViewerChannelSettingKeys.Color])
532
+ volumeEnabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.VolumeEnabled]),
533
+ isosurfaceEnabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.SurfaceEnabled]),
534
+ isovalue: validateNumber(jsonState[ChannelStateSnapshotKeys.IsosurfaceValue], -Infinity, Infinity),
535
+ keepIntensityRange: validateBoolean(jsonState[ChannelStateSnapshotKeys.KeepRange]),
536
+ opacity: validateNumber(jsonState[ChannelStateSnapshotKeys.IsosurfaceAlpha], 0, 1),
537
+ colorizeEnabled: validateBoolean(jsonState[ChannelStateSnapshotKeys.Colorize]),
538
+ colorizeAlpha: validateNumber(jsonState[ChannelStateSnapshotKeys.ColorizeAlpha], 0, 1),
539
+ useControlPoints: validateBoolean(jsonState[ChannelStateSnapshotKeys.ControlPointsEnabled]),
540
+ color: parseHexColorAsColorArray(jsonState[ChannelStateSnapshotKeys.Color])
469
541
  };
470
- var lutSerialized = jsonState[ViewerChannelSettingKeys.Lut];
542
+ var lutRaw = validateTuple(jsonState[ChannelStateSnapshotKeys.Lut], 2, validateLutValue);
471
543
  var pointsFromLut = undefined;
472
- if (histogram !== undefined && lutSerialized !== undefined && LUT_REGEX.test(lutSerialized)) {
473
- var _lutSerialized$split = lutSerialized.split(":"),
474
- _lutSerialized$split2 = _slicedToArray(_lutSerialized$split, 2),
475
- min = _lutSerialized$split2[0],
476
- max = _lutSerialized$split2[1];
477
- var lut = parseLutSetting(histogram, [min.trim(), max.trim()]);
544
+ if (histogram !== undefined && lutRaw !== undefined) {
545
+ var lut = parseLutSetting(histogram, lutRaw);
478
546
  pointsFromLut = lut === null || lut === void 0 ? void 0 : lut.controlPoints.map(function (point) {
479
547
  return _objectSpread(_objectSpread({}, point), {}, {
480
548
  x: histogram.getValueFromBinIndex(point.x)
481
549
  });
482
550
  });
483
551
  }
484
- if (jsonState[ViewerChannelSettingKeys.Ramp]) {
485
- if (RAMP_REGEX.test(jsonState[ViewerChannelSettingKeys.Ramp])) {
486
- var _jsonState$ViewerChan7 = jsonState[ViewerChannelSettingKeys.Ramp].split(":"),
487
- _jsonState$ViewerChan8 = _slicedToArray(_jsonState$ViewerChan7, 2),
488
- _min3 = _jsonState$ViewerChan8[0],
489
- _max3 = _jsonState$ViewerChan8[1];
490
- result.ramp = [Number.parseFloat(_min3), Number.parseFloat(_max3)];
552
+ if (jsonState[ChannelStateSnapshotKeys.Ramp]) {
553
+ var ramp = validateSortedPair(jsonState[ChannelStateSnapshotKeys.Ramp]);
554
+ if (ramp !== undefined) {
555
+ result.ramp = ramp;
491
556
  }
492
- } else if (jsonState[ViewerChannelSettingKeys.RampLegacy]) {
493
- if (histogram !== undefined) {
494
- if (RAMP_REGEX.test(jsonState[ViewerChannelSettingKeys.RampLegacy])) {
495
- var _jsonState$ViewerChan9 = jsonState[ViewerChannelSettingKeys.RampLegacy].split(":"),
496
- _jsonState$ViewerChan10 = _slicedToArray(_jsonState$ViewerChan9, 2),
497
- rawMin = _jsonState$ViewerChan10[0],
498
- rawMax = _jsonState$ViewerChan10[1];
499
- var _min4 = histogram.getValueFromBinIndex(Number.parseFloat(rawMin));
500
- var _max4 = histogram.getValueFromBinIndex(Number.parseFloat(rawMax));
501
- result.ramp = [_min4, _max4];
502
- }
557
+ } else if (jsonState[ChannelStateSnapshotKeys.RampLegacy]) {
558
+ var _ramp2 = validateSortedPair(jsonState[ChannelStateSnapshotKeys.RampLegacy]);
559
+ if (histogram !== undefined && _ramp2 !== undefined) {
560
+ var _ramp3 = _slicedToArray(_ramp2, 2),
561
+ rawMin = _ramp3[0],
562
+ rawMax = _ramp3[1];
563
+ var min = histogram.getValueFromBinIndex(rawMin);
564
+ var max = histogram.getValueFromBinIndex(rawMax);
565
+ result.ramp = [min, max];
503
566
  }
504
567
  } else if (pointsFromLut !== undefined) {
505
568
  result.ramp = controlPointsToRamp(pointsFromLut);
506
569
  }
507
- if (jsonState[ViewerChannelSettingKeys.ControlPoints]) {
508
- var parsedResult = parseControlPoints(jsonState[ViewerChannelSettingKeys.ControlPoints]);
570
+ if (Array.isArray(jsonState[ChannelStateSnapshotKeys.ControlPoints])) {
571
+ var parsedResult = snapshotToControlPoints(jsonState[ChannelStateSnapshotKeys.ControlPoints]);
509
572
  if (parsedResult) {
510
573
  result.controlPoints = parsedResult;
511
574
  }
512
- } else if (jsonState[ViewerChannelSettingKeys.ControlPointsLegacy]) {
575
+ } else if (Array.isArray(jsonState[ChannelStateSnapshotKeys.ControlPointsLegacy])) {
513
576
  if (histogram !== undefined) {
514
- var _parsedResult2 = parseControlPoints(jsonState[ViewerChannelSettingKeys.ControlPointsLegacy]);
577
+ var _parsedResult2 = snapshotToControlPoints(jsonState[ChannelStateSnapshotKeys.ControlPointsLegacy]);
515
578
  if (_parsedResult2) {
516
- result.controlPoints = _parsedResult2.map(function (_ref) {
517
- var opacity = _ref.opacity,
518
- color = _ref.color,
519
- x = _ref.x;
579
+ result.controlPoints = _parsedResult2.map(function (_ref3) {
580
+ var opacity = _ref3.opacity,
581
+ color = _ref3.color,
582
+ x = _ref3.x;
520
583
  return {
521
584
  opacity: opacity,
522
585
  color: color,
@@ -529,4 +592,53 @@ export function deserializeChannelState(jsonState, histogram) {
529
592
  result.controlPoints = pointsFromLut;
530
593
  }
531
594
  return removeUndefinedProperties(result);
532
- }
595
+ }
596
+
597
+ /**
598
+ * Parses a stringified viewer state snapshot (`ViewerStateStringified`) to a
599
+ * `ViewerState`.
600
+ *
601
+ * This is equivalent to (and implemented by) calling
602
+ * `destringifyViewerStateSnapshot`, then `snapshotToViewerState`.
603
+ */
604
+ export var stringSnapshotToViewerState = function stringSnapshotToViewerState(stringified) {
605
+ return snapshotToViewerState(destringifyViewerStateSnapshot(stringified));
606
+ };
607
+
608
+ /**
609
+ * Parses a stringified channel state snapshot (`ChannelStateStringified`) to a
610
+ * `ViewerChannelSetting`.
611
+ *
612
+ * This is equivalent to (and implemented by) calling
613
+ * `destringifyChannelStateSnapshot`, then `snapshotToViewerChannelSetting`.
614
+ *
615
+ * @param channelIndex Index of the channel, to be turned into a `match` value.
616
+ * @param jsonState The serialized `ViewerChannelSetting` to parse, as an object.
617
+ * @returns A `ViewerChannelSetting` object.
618
+ */
619
+ export var stringSnapshotToViewerChannelSetting = function stringSnapshotToViewerChannelSetting(channelIndex, jsonState) {
620
+ return snapshotToViewerChannelSetting(channelIndex, destringifyChannelStateSnapshot(jsonState));
621
+ };
622
+
623
+ /**
624
+ * Parses a stringified channel state snapshot (`ChannelStateStringified`)
625
+ * into a partial `ChannelState`.
626
+ *
627
+ * This is equivalent to (and implemented by) calling
628
+ * `destringifyChannelStateSnapshot`, then `snapshotToChannelState`.
629
+ *
630
+ * This function optionally accepts the target channel's `Histogram`. This
631
+ * argument is required to parse the following params correctly:
632
+ *
633
+ * - `lut`, which contains instructions for how to set the channel's
634
+ * intensities *relative to its intensity distribution*.
635
+ * - `rmp`, the legacy ramp parameter represented as histogram bin indices
636
+ * - `cps`, the legacy control points parameter where `x` values are
637
+ * represented as histogram bin indices
638
+ *
639
+ * If `histogram` is left undefined, e.g. because the channel has not yet been
640
+ * loaded, these params are ignored.
641
+ */
642
+ export var stringSnapshotToChannelState = function stringSnapshotToChannelState(jsonState, histogram) {
643
+ return snapshotToChannelState(destringifyChannelStateSnapshot(jsonState), histogram);
644
+ };