@aics/vole-app 3.3.1 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/es/aics-image-viewer/components/App/index.js +1 -0
  2. package/es/aics-image-viewer/components/BottomPanel/index.js +48 -19
  3. package/es/aics-image-viewer/components/BottomPanel/styles.css +16 -7
  4. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +39 -19
  5. package/es/aics-image-viewer/components/ControlPanel/index.js +2 -0
  6. package/es/aics-image-viewer/components/dimension_sliders/AxisClipSliders.js +148 -0
  7. package/es/aics-image-viewer/components/dimension_sliders/RotationSliders.js +167 -0
  8. package/es/aics-image-viewer/components/dimension_sliders/SliderRows.js +167 -0
  9. package/es/aics-image-viewer/components/{AxisClipSliders → dimension_sliders}/styles.css +7 -1
  10. package/es/aics-image-viewer/components/shared/NumericInput/index.js +4 -2
  11. package/es/aics-image-viewer/components/shared/SliderRow/index.js +2 -0
  12. package/es/aics-image-viewer/components/shared/SliderRow/styles.css +2 -1
  13. package/es/aics-image-viewer/shared/constants.js +1 -1
  14. package/es/aics-image-viewer/shared/utils/camera.js +122 -0
  15. package/es/aics-image-viewer/shared/utils/test/camera.test.js +127 -0
  16. package/es/aics-image-viewer/shared/utils/test/urlParsing.test.js +23 -702
  17. package/es/aics-image-viewer/shared/utils/urlParsing.js +14 -828
  18. package/es/aics-image-viewer/state/deserialize.js +437 -0
  19. package/es/aics-image-viewer/state/serialize.js +145 -0
  20. package/es/aics-image-viewer/state/test/deserialize.test.js +432 -0
  21. package/es/aics-image-viewer/state/test/serialize.test.js +97 -0
  22. package/es/aics-image-viewer/state/test/test_data.js +168 -0
  23. package/es/aics-image-viewer/state/types.js +265 -1
  24. package/package.json +2 -1
  25. package/type-declarations/aics-image-viewer/components/App/types.d.ts +1 -1
  26. package/type-declarations/aics-image-viewer/components/BottomPanel/index.d.ts +6 -3
  27. package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +1 -0
  28. package/type-declarations/aics-image-viewer/components/{AxisClipSliders/index.d.ts → dimension_sliders/AxisClipSliders.d.ts} +2 -3
  29. package/type-declarations/aics-image-viewer/components/dimension_sliders/RotationSliders.d.ts +6 -0
  30. package/type-declarations/aics-image-viewer/components/dimension_sliders/SliderRows.d.ts +31 -0
  31. package/type-declarations/aics-image-viewer/components/shared/NumericInput/index.d.ts +1 -0
  32. package/type-declarations/aics-image-viewer/components/shared/SliderRow/index.d.ts +3 -1
  33. package/type-declarations/aics-image-viewer/shared/utils/camera.d.ts +19 -0
  34. package/type-declarations/aics-image-viewer/shared/utils/urlParsing.d.ts +1 -307
  35. package/type-declarations/aics-image-viewer/state/deserialize.d.ts +70 -0
  36. package/type-declarations/aics-image-viewer/state/serialize.d.ts +20 -0
  37. package/type-declarations/aics-image-viewer/state/types.d.ts +216 -0
  38. package/es/aics-image-viewer/components/AxisClipSliders/index.js +0 -295
@@ -0,0 +1,437 @@
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
+ 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
+ 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; }
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
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
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); }
7
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
8
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
9
+ 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; } }
10
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
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
+ 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
+ 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";
15
+ import { removeUndefinedProperties } from "../shared/utils/datatypes";
16
+ import { clamp } from "../shared/utils/math";
17
+ import { CameraTransformKeys, ViewerChannelSettingKeys, ViewerStateKeys } from "./types";
18
+ var DEFAULT_CONTROL_POINT_COLOR = [255, 255, 255];
19
+ var DEFAULT_CONTROL_POINT_COLOR_CODE = "1";
20
+ var FLOAT_REGEX = /-?[0-9]*\.?[0-9]+/;
21
+
22
+ /** Match colon-separated pairs of alphanumeric strings */
23
+ var LUT_REGEX = /^-?[a-z0-9.]*:[ ]*-?[a-z0-9.]*$/;
24
+
25
+ /**
26
+ * Match colon-separated pairs of numeric strings, representing histogram bin
27
+ * indices or intensity values.
28
+ */
29
+ var RAMP_REGEX = new RegExp("^".concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, "$"));
30
+
31
+ /**
32
+ * Match comma-separated triplet of numeric strings.
33
+ */
34
+ var SLICE_REGEX = new RegExp("^".concat(FLOAT_REGEX.source, ",").concat(FLOAT_REGEX.source, ",").concat(FLOAT_REGEX.source, "$"));
35
+
36
+ /**
37
+ * Matches a sequence of three comma-separated min:max number pairs, representing
38
+ * the x, y, and z axes.
39
+ */
40
+ var REGION_REGEX = new RegExp("^(".concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, ")(,").concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, "){2}$"));
41
+ var HEX_COLOR_REGEX = new RegExp("(([0-9a-fA-F]{6})|".concat(DEFAULT_CONTROL_POINT_COLOR_CODE, ")"));
42
+
43
+ /** Represents control points specified by bin indices. */
44
+ var CONTROL_POINT_REGEX = new RegExp("(".concat(FLOAT_REGEX.source, ":").concat(FLOAT_REGEX.source, ":").concat(HEX_COLOR_REGEX.source, ")"));
45
+ var HEX_COLOR_STR_REGEX = new RegExp("^".concat(HEX_COLOR_REGEX.source, "$"));
46
+
47
+ /**
48
+ * LEGACY: Matches a COMMA-separated list of control points, where each control point is represented
49
+ * by a triplet of `{x}:{opacity}:{hex color}`.
50
+ * The hex color can be replaced with `1` to represent white (`ffffff`).
51
+ */
52
+ export var LEGACY_CONTROL_POINTS_REGEX = new RegExp("^".concat(CONTROL_POINT_REGEX.source, "(,").concat(CONTROL_POINT_REGEX.source, ")*$"));
53
+
54
+ /**
55
+ * Matches a COLON-separated list of control points, where each control point is
56
+ * represented by a triplet of `{x}:{opacity}:{hex color}`.
57
+ * - `x` is a value that will either be parsed as a histogram bin index (legacy,
58
+ * for `ControlPointsLegacy`) or intensity value (for `ControlPoints`),
59
+ * depending on on which field is being parsed.
60
+ * - Opacity is a float in the [0, 1] range.
61
+ * - The hex color is a 6-digit hex color (e.g. `ffeecc`), and can be replaced
62
+ * with `1` to represent white (`ffffff`).
63
+ */
64
+ export var CONTROL_POINTS_REGEX = new RegExp("^".concat(CONTROL_POINT_REGEX.source, "(:").concat(CONTROL_POINT_REGEX.source, ")*$"));
65
+
66
+ /**
67
+ * Parse a string list of comma-separated key:value pairs into
68
+ * a key-value object.
69
+ *
70
+ * @param data The string to parse. Expected to be in the format
71
+ * "key1:value1,key2:value2,...". Commas in keys or values
72
+ * must be encoded using `encodeURIComponent`.
73
+ * @returns An object with the parsed key-value pairs. Key and value strings
74
+ * will be decoded using `decodeURIComponent`.
75
+ */
76
+ export function parseKeyValueList(data) {
77
+ if (data === "") {
78
+ return {};
79
+ }
80
+ var result = {};
81
+ var keyValuePairs = data.split(",");
82
+ var _iterator = _createForOfIteratorHelper(keyValuePairs),
83
+ _step;
84
+ try {
85
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
86
+ var pair = _step.value;
87
+ var splitIndex = pair.indexOf(":");
88
+ if (splitIndex === -1) {
89
+ continue;
90
+ }
91
+ var key = pair.slice(0, splitIndex);
92
+ var value = pair.slice(splitIndex + 1);
93
+ result[decodeURIComponent(key).trim()] = decodeURIComponent(value).trim();
94
+ }
95
+ } catch (err) {
96
+ _iterator.e(err);
97
+ } finally {
98
+ _iterator.f();
99
+ }
100
+ return result;
101
+ }
102
+
103
+ /**
104
+ * Parses a string to a float and clamps the result to the [min, max] range.
105
+ * Returns `undefined` if the string is undefined or NaN.
106
+ * @param value String to parse as a float. Will be parsed with `Number.parseFloat`.
107
+ * @param min Minimum value, inclusive.
108
+ * @param max Maximum value, inclusive.
109
+ * @returns
110
+ * - The parsed number, clamped to the [min, max] range.
111
+ * - `undefined` if the string is undefined or NaN.
112
+ */
113
+ export function parseStringFloat(value, min, max) {
114
+ if (value === undefined) {
115
+ return undefined;
116
+ }
117
+ var number = Number.parseFloat(value);
118
+ return Number.isNaN(number) ? undefined : clamp(number, min, max);
119
+ }
120
+
121
+ /**
122
+ * Parses a string to an integer and clamps the result to the [min, max] range.
123
+ * @param value String to parse as a float. Assumes base 10, parses with `Number.parseInt(value, 10)`.
124
+ * @param min Minimum value, inclusive.
125
+ * @param max Maximum value, inclusive.
126
+ * @returns
127
+ * - The parsed number, clamped to the [min, max] range.
128
+ * - `undefined` if the string is undefined or NaN.
129
+ */
130
+ export function parseStringInt(value, min, max) {
131
+ if (value === undefined) {
132
+ return undefined;
133
+ }
134
+ var number = Number.parseInt(value, 10);
135
+ if (Number.isNaN(number)) {
136
+ return undefined;
137
+ }
138
+ return clamp(number, min, max);
139
+ }
140
+
141
+ /**
142
+ * Parses a string to an enum value; if the string is not in the enum, returns the default value.
143
+ * @param value String to parse.
144
+ * @param enumValues Enum. Cannot be a `const enum`, as these are removed at compile time.
145
+ * @param defaultValue Default value to return if the string is not in the enum.
146
+ * @returns A value from the enum or the default value. Note that the return type includes `undefined`
147
+ * if the `defaultValue` is `undefined`.
148
+ */
149
+ export function parseStringEnum(value, enumValues) {
150
+ var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
151
+ if (value === undefined || !Object.values(enumValues).includes(value)) {
152
+ return defaultValue;
153
+ }
154
+ return value;
155
+ }
156
+
157
+ /**
158
+ * Parses a string boolean value ("1" as true, "0" as false), and returns `undefined` if the value is `undefined`.
159
+ */
160
+ function parseStringBoolean(value) {
161
+ if (value === undefined) {
162
+ return undefined;
163
+ }
164
+ return value === "1";
165
+ }
166
+ export function parseHexColorAsColorArray(hexColor) {
167
+ if (!hexColor || !HEX_COLOR_STR_REGEX.test(hexColor)) {
168
+ return undefined;
169
+ }
170
+ // if (hexColor in COLOR_CODES) {
171
+ // return COLOR_CODES[hexColor];
172
+ // }
173
+ if (hexColor === DEFAULT_CONTROL_POINT_COLOR_CODE) {
174
+ return DEFAULT_CONTROL_POINT_COLOR;
175
+ }
176
+ var r = Number.parseInt(hexColor.slice(0, 2), 16);
177
+ var g = Number.parseInt(hexColor.slice(2, 4), 16);
178
+ var b = Number.parseInt(hexColor.slice(4, 6), 16);
179
+ return [r, g, b];
180
+ }
181
+ function parseStringSlice(region) {
182
+ if (!region || !SLICE_REGEX.test(region)) {
183
+ return undefined;
184
+ }
185
+ var _region$split$map = region.split(",").map(function (val) {
186
+ return parseStringFloat(val, 0, 1);
187
+ }),
188
+ _region$split$map2 = _slicedToArray(_region$split$map, 3),
189
+ x = _region$split$map2[0],
190
+ y = _region$split$map2[1],
191
+ z = _region$split$map2[2];
192
+ if (x === undefined || y === undefined || z === undefined) {
193
+ return undefined;
194
+ }
195
+ return {
196
+ x: x,
197
+ y: y,
198
+ z: z
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Parses an array of three numbers from a string.
204
+ * @param stringArr The string to parse. Should be three numbers separated by a separator.
205
+ * @param options Optional parameters for parsing:
206
+ * - `min`: Minimum value for each number. Default is negative infinity.
207
+ * - `max`: Maximum value for each number. Default is positive infinity.
208
+ * - `separator`: Separator between numbers. Default is `,`.
209
+ * @returns
210
+ * - undefined if the string is undefined or could not be parsed.
211
+ * - An array of three numbers, clamped to the [min, max] range.
212
+ */
213
+ function parseThreeNumberArray(stringArr, options) {
214
+ var _options$min, _options$max, _options$separator;
215
+ if (!stringArr) {
216
+ return undefined;
217
+ }
218
+ var min = (_options$min = options === null || options === void 0 ? void 0 : options.min) !== null && _options$min !== void 0 ? _options$min : Number.NEGATIVE_INFINITY;
219
+ var max = (_options$max = options === null || options === void 0 ? void 0 : options.max) !== null && _options$max !== void 0 ? _options$max : Number.POSITIVE_INFINITY;
220
+ var separator = (_options$separator = options === null || options === void 0 ? void 0 : options.separator) !== null && _options$separator !== void 0 ? _options$separator : ",";
221
+ var _stringArr$split$map = stringArr.split(separator).map(function (val) {
222
+ return parseStringFloat(val, min, max);
223
+ }),
224
+ _stringArr$split$map2 = _slicedToArray(_stringArr$split$map, 3),
225
+ x = _stringArr$split$map2[0],
226
+ y = _stringArr$split$map2[1],
227
+ z = _stringArr$split$map2[2];
228
+ if (x === undefined || y === undefined || z === undefined) {
229
+ return undefined;
230
+ }
231
+ return [x, y, z];
232
+ }
233
+ function parseStringRegion(region) {
234
+ if (!region || !REGION_REGEX.test(region)) {
235
+ return undefined;
236
+ }
237
+ var _region$split$map3 = region.split(",").map(function (axis) {
238
+ // each is a min/max pair
239
+ var _axis$split$map = axis.split(":").map(function (val) {
240
+ return parseStringFloat(val, 0, 1);
241
+ }),
242
+ _axis$split$map2 = _slicedToArray(_axis$split$map, 2),
243
+ min = _axis$split$map2[0],
244
+ max = _axis$split$map2[1];
245
+ if (min === undefined || max === undefined) {
246
+ return undefined;
247
+ }
248
+ // Ensure sorted order
249
+ return min < max ? [min, max] : [max, min];
250
+ }),
251
+ _region$split$map4 = _slicedToArray(_region$split$map3, 3),
252
+ x = _region$split$map4[0],
253
+ y = _region$split$map4[1],
254
+ z = _region$split$map4[2];
255
+ // Check for undefined values
256
+ if (x === undefined || y === undefined || z === undefined) {
257
+ return undefined;
258
+ }
259
+ return {
260
+ x: x,
261
+ y: y,
262
+ z: z
263
+ };
264
+ }
265
+ function parseCameraState(cameraSettings) {
266
+ if (!cameraSettings) {
267
+ return undefined;
268
+ }
269
+ var parsedCameraSettings = parseKeyValueList(cameraSettings);
270
+ var result = {
271
+ position: parseThreeNumberArray(parsedCameraSettings[CameraTransformKeys.Position], {
272
+ separator: ":"
273
+ }),
274
+ target: parseThreeNumberArray(parsedCameraSettings[CameraTransformKeys.Target], {
275
+ separator: ":"
276
+ }),
277
+ up: parseThreeNumberArray(parsedCameraSettings[CameraTransformKeys.Up], {
278
+ separator: ":"
279
+ }),
280
+ // Orthographic scales cannot be negative
281
+ orthoScale: parseStringFloat(parsedCameraSettings[CameraTransformKeys.OrthoScale], 0, Infinity),
282
+ fov: parseStringFloat(parsedCameraSettings[CameraTransformKeys.Fov], 0, 180)
283
+ };
284
+ return removeUndefinedProperties(result);
285
+ }
286
+ export function deserializeViewerState(params) {
287
+ var result = {
288
+ maskAlpha: parseStringInt(params[ViewerStateKeys.Mask], 0, 100),
289
+ imageType: parseStringEnum(params[ViewerStateKeys.Image], ImageType),
290
+ showAxes: parseStringBoolean(params[ViewerStateKeys.Axes]),
291
+ showBoundingBox: parseStringBoolean(params[ViewerStateKeys.BoundingBox]),
292
+ boundingBoxColor: parseHexColorAsColorArray(params[ViewerStateKeys.BoundingBoxColor]),
293
+ backgroundColor: parseHexColorAsColorArray(params[ViewerStateKeys.BackgroundColor]),
294
+ autorotate: parseStringBoolean(params[ViewerStateKeys.Autorotate]),
295
+ brightness: parseStringFloat(params[ViewerStateKeys.Brightness], 0, 100),
296
+ density: parseStringFloat(params[ViewerStateKeys.Density], 0, 100),
297
+ levels: parseThreeNumberArray(params[ViewerStateKeys.Levels], {
298
+ min: 0,
299
+ max: 255
300
+ }),
301
+ interpolationEnabled: parseStringBoolean(params[ViewerStateKeys.Interpolation]),
302
+ region: parseStringRegion(params[ViewerStateKeys.Region]),
303
+ slice: parseStringSlice(params[ViewerStateKeys.Slice]),
304
+ time: parseStringInt(params[ViewerStateKeys.Time], 0, Number.POSITIVE_INFINITY),
305
+ scene: parseStringInt(params[ViewerStateKeys.Scene], 0, Number.POSITIVE_INFINITY),
306
+ renderMode: parseStringEnum(params[ViewerStateKeys.Mode], RenderMode),
307
+ singleChannelMode: parseStringBoolean(params[ViewerStateKeys.SingleChannelMode]),
308
+ singleChannelIndex: parseStringInt(params[ViewerStateKeys.SingleChannelIndex], 0, Number.POSITIVE_INFINITY),
309
+ useExactScaleLevel: parseStringBoolean(params[ViewerStateKeys.UseExactScaleLevel]),
310
+ scaleLevelIndex: parseStringInt(params[ViewerStateKeys.ScaleLevelIndex], 0, Number.MAX_SAFE_INTEGER),
311
+ cameraState: parseCameraState(params[ViewerStateKeys.CameraState])
312
+ };
313
+
314
+ // Handle viewmode, since they use different mappings
315
+ // TODO: Allow lowercase
316
+ if (params.view) {
317
+ var viewParamToViewMode = {
318
+ "3D": ViewMode.threeD,
319
+ Z: ViewMode.xy,
320
+ Y: ViewMode.xz,
321
+ X: ViewMode.yz
322
+ };
323
+ var allowedViews = Object.keys(viewParamToViewMode);
324
+ var view;
325
+ if (allowedViews.includes(params.view.toUpperCase())) {
326
+ view = params.view.toUpperCase();
327
+ } else {
328
+ view = "3D";
329
+ }
330
+ result.viewMode = viewParamToViewMode[view];
331
+ }
332
+ return removeUndefinedProperties(result);
333
+ }
334
+ function parseControlPoints(controlPoints) {
335
+ if (!(controlPoints && (CONTROL_POINTS_REGEX.test(controlPoints) || LEGACY_CONTROL_POINTS_REGEX.test(controlPoints)))) {
336
+ return undefined;
337
+ }
338
+
339
+ // Parse raw control point data from the string into an array of [x, opacity, color] triplets.
340
+ var controlPointStrings;
341
+ if (LEGACY_CONTROL_POINTS_REGEX.test(controlPoints)) {
342
+ // Legacy format uses commas to separate control points.
343
+ controlPointStrings = controlPoints.split(",").map(function (cp) {
344
+ return cp.split(":");
345
+ });
346
+ } else {
347
+ // New format is all colon-separated, where every three elements represent a control point.
348
+ controlPointStrings = controlPoints.split(":").reduce(function (acc, _val, i, array) {
349
+ if ((i + 1) % 3 === 0) {
350
+ acc.push([array[i - 2], array[i - 1], array[i]]);
351
+ }
352
+ return acc;
353
+ }, []);
354
+ }
355
+ var newControlPoints = controlPointStrings.map(function (cp) {
356
+ var _parseStringFloat, _parseStringFloat2, _parseHexColorAsColor;
357
+ var _cp = _slicedToArray(cp, 3),
358
+ x = _cp[0],
359
+ opacity = _cp[1],
360
+ color = _cp[2];
361
+ return {
362
+ x: (_parseStringFloat = parseStringFloat(x, -Infinity, Infinity)) !== null && _parseStringFloat !== void 0 ? _parseStringFloat : 0,
363
+ opacity: (_parseStringFloat2 = parseStringFloat(opacity, 0, 1)) !== null && _parseStringFloat2 !== void 0 ? _parseStringFloat2 : 1.0,
364
+ color: (_parseHexColorAsColor = parseHexColorAsColorArray(color)) !== null && _parseHexColorAsColor !== void 0 ? _parseHexColorAsColor : DEFAULT_CONTROL_POINT_COLOR
365
+ };
366
+ });
367
+ // Sort control points by x value
368
+ return newControlPoints.sort(function (a, b) {
369
+ return a.x - b.x;
370
+ });
371
+ }
372
+
373
+ /**
374
+ * Parses a ViewerChannelSetting from a JSON object.
375
+ * @param channelIndex Index of the channel, to be turned into a `match` value.
376
+ * @param jsonState The serialized ViewerChannelSetting to parse, as an object.
377
+ * @returns A ViewerChannelSetting object.
378
+ */
379
+ export function deserializeViewerChannelSetting(channelIndex, jsonState) {
380
+ // Missing/undefined fields should be handled downstream.
381
+ var result = {
382
+ match: channelIndex,
383
+ enabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.VolumeEnabled]),
384
+ surfaceEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.SurfaceEnabled]),
385
+ isovalue: parseStringFloat(jsonState[ViewerChannelSettingKeys.IsosurfaceValue], -Infinity, Infinity),
386
+ keepIntensityRange: parseStringBoolean(jsonState[ViewerChannelSettingKeys.KeepRange]),
387
+ surfaceOpacity: parseStringFloat(jsonState[ViewerChannelSettingKeys.IsosurfaceAlpha], 0, 1),
388
+ colorizeEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.Colorize]),
389
+ colorizeAlpha: parseStringFloat(jsonState[ViewerChannelSettingKeys.ColorizeAlpha], 0, 1),
390
+ controlPointsEnabled: parseStringBoolean(jsonState[ViewerChannelSettingKeys.ControlPointsEnabled])
391
+ };
392
+ if (jsonState[ViewerChannelSettingKeys.Color] && HEX_COLOR_STR_REGEX.test(jsonState.col)) {
393
+ result.color = jsonState[ViewerChannelSettingKeys.Color];
394
+ }
395
+ if (jsonState[ViewerChannelSettingKeys.Lut] && LUT_REGEX.test(jsonState.lut)) {
396
+ var _jsonState$ViewerChan = jsonState[ViewerChannelSettingKeys.Lut].split(":"),
397
+ _jsonState$ViewerChan2 = _slicedToArray(_jsonState$ViewerChan, 2),
398
+ min = _jsonState$ViewerChan2[0],
399
+ max = _jsonState$ViewerChan2[1];
400
+ result.intensity = _objectSpread(_objectSpread({}, result.intensity), {}, {
401
+ lut: [min.trim(), max.trim()]
402
+ });
403
+ }
404
+ if (jsonState[ViewerChannelSettingKeys.Ramp]) {
405
+ if (RAMP_REGEX.test(jsonState[ViewerChannelSettingKeys.Ramp])) {
406
+ var _jsonState$ViewerChan3 = jsonState[ViewerChannelSettingKeys.Ramp].split(":"),
407
+ _jsonState$ViewerChan4 = _slicedToArray(_jsonState$ViewerChan3, 2),
408
+ _min = _jsonState$ViewerChan4[0],
409
+ _max = _jsonState$ViewerChan4[1];
410
+ result.intensity = _objectSpread(_objectSpread({}, result.intensity), {}, {
411
+ ramp: [Number.parseFloat(_min), Number.parseFloat(_max)]
412
+ });
413
+ }
414
+ } else if (jsonState[ViewerChannelSettingKeys.RampLegacy]) {
415
+ if (RAMP_REGEX.test(jsonState[ViewerChannelSettingKeys.RampLegacy])) {
416
+ var _jsonState$ViewerChan5 = jsonState[ViewerChannelSettingKeys.RampLegacy].split(":"),
417
+ _jsonState$ViewerChan6 = _slicedToArray(_jsonState$ViewerChan5, 2),
418
+ _min2 = _jsonState$ViewerChan6[0],
419
+ _max2 = _jsonState$ViewerChan6[1];
420
+ result.ramp = [Number.parseFloat(_min2), Number.parseFloat(_max2)];
421
+ }
422
+ }
423
+ if (jsonState[ViewerChannelSettingKeys.ControlPoints]) {
424
+ var parsedResult = parseControlPoints(jsonState[ViewerChannelSettingKeys.ControlPoints]);
425
+ if (parsedResult) {
426
+ result.intensity = _objectSpread(_objectSpread({}, result.intensity), {}, {
427
+ controlPoints: parsedResult
428
+ });
429
+ }
430
+ } else if (jsonState[ViewerChannelSettingKeys.ControlPointsLegacy]) {
431
+ var _parsedResult = parseControlPoints(jsonState[ViewerChannelSettingKeys.ControlPointsLegacy]);
432
+ if (_parsedResult) {
433
+ result.controlPoints = _parsedResult;
434
+ }
435
+ }
436
+ return result;
437
+ }
@@ -0,0 +1,145 @@
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
+ 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; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
4
+ 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); }
5
+ import { isEqual } from "lodash";
6
+ import { getDefaultCameraState, getDefaultChannelState, getDefaultViewerState } from "../shared/constants";
7
+ import { ViewMode } from "../shared/enums";
8
+ import { removeMatchingProperties, removeUndefinedProperties } from "../shared/utils/datatypes";
9
+ import { CameraTransformKeys, ViewerChannelSettingKeys, ViewerStateKeys } from "./types";
10
+ var ENCODED_COLON_REGEX = /%3A/g;
11
+ var DEFAULT_CONTROL_POINT_COLOR = [255, 255, 255];
12
+ var DEFAULT_CONTROL_POINT_COLOR_CODE = "1";
13
+ export function objectToKeyValueList(obj) {
14
+ var keyValuePairs = [];
15
+ for (var key in obj) {
16
+ var value = obj[key];
17
+ if (value === undefined) {
18
+ continue;
19
+ }
20
+ // Allow colon separators to remain unencoded to save URL character length.
21
+ var escapedValue = encodeURIComponent(value.trim()).replace(ENCODED_COLON_REGEX, ":");
22
+ keyValuePairs.push("".concat(encodeURIComponent(key.trim()), ":").concat(escapedValue));
23
+ }
24
+ return keyValuePairs.join(",");
25
+ }
26
+ function colorArrayToHex(color) {
27
+ return color.map(function (c) {
28
+ return c.toString(16).padStart(2, "0");
29
+ }).join("").toLowerCase();
30
+ }
31
+
32
+ /**
33
+ * Formats a float or integer value to a string with a maximum precision for float values.
34
+ * @param value The number to format.
35
+ * @param maxPrecision The maximum number of significant digits to display for float values.
36
+ * Default is 7.
37
+ * @returns
38
+ * - For integers, the integer value as a string.
39
+ * - For floats, the float value as a string with a maximum of `maxPrecision` significant digits
40
+ * and any trailing zeroes removed.
41
+ *
42
+ * @example
43
+ * ```
44
+ * formatFloat(1.23456, 3) // "1.23"
45
+ * formatFloat(123456, 3) // "123456"
46
+ * formatFloat(1.3999999999999999, 3) // "1.4"
47
+ * ```
48
+ */
49
+ function formatFloat(value) {
50
+ var maxPrecision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 7;
51
+ if (Number.isInteger(value)) {
52
+ return value.toString();
53
+ }
54
+ return Number(value.toPrecision(maxPrecision)).toString();
55
+ }
56
+ function perAxisToArray(perAxis) {
57
+ return [perAxis.x, perAxis.y, perAxis.z];
58
+ }
59
+
60
+ /** Serializes a region into a `x1:x2,y1:y2,z1:z2` string format. */
61
+ function serializeRegion(region) {
62
+ return perAxisToArray(region).map(function (axis) {
63
+ return axis.map(function (val) {
64
+ return formatFloat(val);
65
+ }).join(":");
66
+ }).join(",");
67
+ }
68
+
69
+ /** Serializes a slice parameter into a `x,y,z` string format. */
70
+ function serializeSlice(slice) {
71
+ return perAxisToArray(slice).map(function (val) {
72
+ return formatFloat(val);
73
+ }).join(",");
74
+ }
75
+ function serializeBoolean(value) {
76
+ if (value === undefined) {
77
+ return undefined;
78
+ }
79
+ return value ? "1" : "0";
80
+ }
81
+ export function serializeCameraState(cameraState, removeDefaults) {
82
+ var viewMode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ViewMode.threeD;
83
+ if (removeDefaults) {
84
+ // Note that we use the `getDefaultCameraState()` to get the defaults here,
85
+ // instead of `getDefaultViewerState().cameraState`. The latter is undefined, which signals
86
+ // that the camera should not be modified for URLs that don't specify it.
87
+ cameraState = removeMatchingProperties(cameraState, getDefaultCameraState(viewMode));
88
+ if (Object.keys(cameraState).length === 0) {
89
+ return undefined;
90
+ }
91
+ }
92
+ var cameraString = objectToKeyValueList(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, CameraTransformKeys.Position, cameraState.position && cameraState.position.map(function (value) {
93
+ return formatFloat(value);
94
+ }).join(":")), CameraTransformKeys.Target, cameraState.target && cameraState.target.map(function (value) {
95
+ return formatFloat(value);
96
+ }).join(":")), CameraTransformKeys.Up, cameraState.up && cameraState.up.map(function (value) {
97
+ return formatFloat(value);
98
+ }).join(":")), CameraTransformKeys.OrthoScale, cameraState.orthoScale === undefined ? undefined : formatFloat(cameraState.orthoScale)), CameraTransformKeys.Fov, cameraState.fov === undefined ? undefined : formatFloat(cameraState.fov)));
99
+ return cameraString === "" ? undefined : cameraString;
100
+ }
101
+ function serializeControlPoints(controlPoints) {
102
+ return controlPoints.map(function (cp) {
103
+ var x = formatFloat(cp.x);
104
+ var opacity = formatFloat(cp.opacity);
105
+ // Default control-point color is encoded as DEFAULT_CONTROL_POINT_COLOR_CODE ("1").
106
+ var color = isEqual(cp.color, DEFAULT_CONTROL_POINT_COLOR) ? DEFAULT_CONTROL_POINT_COLOR_CODE : colorArrayToHex(cp.color);
107
+ return "".concat(x, ":").concat(opacity, ":").concat(color);
108
+ }).join(":");
109
+ }
110
+
111
+ /**
112
+ * Serializes a single viewer channel setting into a dictionary of URL parameters
113
+ * (`ViewerChannelStateParams`).
114
+ * @param channelSetting The channel state object to serialize.
115
+ * @param removeDefaults Whether to remove properties that match the output of `getDefaultChannelState`.
116
+ * @returns A `ViewerChannelSettingParams` object with the serialized parameters. Undefined values are removed.
117
+ */
118
+ export function serializeViewerChannelSetting(channelSetting, removeDefaults) {
119
+ var _channelSetting$isova, _channelSetting$opaci, _channelSetting$color, _channelSetting$ramp, _removeUndefinedPrope;
120
+ if (removeDefaults) {
121
+ channelSetting = removeMatchingProperties(channelSetting, getDefaultChannelState());
122
+ }
123
+ return removeUndefinedProperties((_removeUndefinedPrope = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_removeUndefinedPrope, ViewerChannelSettingKeys.VolumeEnabled, serializeBoolean(channelSetting.volumeEnabled)), ViewerChannelSettingKeys.SurfaceEnabled, serializeBoolean(channelSetting.isosurfaceEnabled)), ViewerChannelSettingKeys.IsosurfaceValue, (_channelSetting$isova = channelSetting.isovalue) === null || _channelSetting$isova === void 0 ? void 0 : _channelSetting$isova.toString()), ViewerChannelSettingKeys.IsosurfaceAlpha, (_channelSetting$opaci = channelSetting.opacity) === null || _channelSetting$opaci === void 0 ? void 0 : _channelSetting$opaci.toString()), ViewerChannelSettingKeys.Colorize, serializeBoolean(channelSetting.colorizeEnabled)), ViewerChannelSettingKeys.ColorizeAlpha, (_channelSetting$color = channelSetting.colorizeAlpha) === null || _channelSetting$color === void 0 ? void 0 : _channelSetting$color.toString()), ViewerChannelSettingKeys.Color, channelSetting.color && colorArrayToHex(channelSetting.color)), ViewerChannelSettingKeys.ControlPoints, channelSetting.controlPoints && serializeControlPoints(channelSetting.controlPoints)), ViewerChannelSettingKeys.ControlPointsEnabled, serializeBoolean(channelSetting.useControlPoints)), ViewerChannelSettingKeys.Ramp, (_channelSetting$ramp = channelSetting.ramp) === null || _channelSetting$ramp === void 0 ? void 0 : _channelSetting$ramp.join(":")), _defineProperty(_removeUndefinedPrope, ViewerChannelSettingKeys.KeepRange, serializeBoolean(channelSetting.keepIntensityRange))));
124
+ }
125
+
126
+ /**
127
+ * Serializes a `ViewerState` object into a dictionary of URL parameters.
128
+ * @param state The `ViewerState` to serialize.
129
+ * @param removeDefaults If true, remove properties that match the output of `getDefaultViewerState`.
130
+ * @returns A `ViewerStateParams` object with the serialized parameters. Undefined values are removed.
131
+ */
132
+ export function serializeViewerState(state, removeDefaults) {
133
+ var _state$maskAlpha, _state$brightness, _state$density, _state$levels, _state$time, _state$scene, _state$singleChannelI, _state$scaleLevelInde, _result;
134
+ if (removeDefaults) {
135
+ state = removeMatchingProperties(state, getDefaultViewerState());
136
+ // special case: if there's an explicit scale level but it's not being used, no reason to include it
137
+ if (state.scaleLevelIndex !== undefined && state.useExactScaleLevel === undefined) {
138
+ delete state.scaleLevelIndex;
139
+ }
140
+ }
141
+ var result = (_result = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_result, ViewerStateKeys.Mode, state.renderMode), ViewerStateKeys.Mask, (_state$maskAlpha = state.maskAlpha) === null || _state$maskAlpha === void 0 ? void 0 : _state$maskAlpha.toString()), ViewerStateKeys.Image, state.imageType), ViewerStateKeys.Axes, serializeBoolean(state.showAxes)), ViewerStateKeys.BoundingBox, serializeBoolean(state.showBoundingBox)), ViewerStateKeys.BoundingBoxColor, state.boundingBoxColor && colorArrayToHex(state.boundingBoxColor)), ViewerStateKeys.BackgroundColor, state.backgroundColor && colorArrayToHex(state.backgroundColor)), ViewerStateKeys.Autorotate, serializeBoolean(state.autorotate)), ViewerStateKeys.Brightness, (_state$brightness = state.brightness) === null || _state$brightness === void 0 ? void 0 : _state$brightness.toString()), ViewerStateKeys.Density, (_state$density = state.density) === null || _state$density === void 0 ? void 0 : _state$density.toString()), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_result, ViewerStateKeys.Interpolation, serializeBoolean(state.interpolationEnabled)), ViewerStateKeys.Region, state.region && serializeRegion(state.region)), ViewerStateKeys.Slice, state.slice && serializeSlice(state.slice)), ViewerStateKeys.Levels, (_state$levels = state.levels) === null || _state$levels === void 0 ? void 0 : _state$levels.join(",")), ViewerStateKeys.Time, (_state$time = state.time) === null || _state$time === void 0 ? void 0 : _state$time.toString()), ViewerStateKeys.Scene, (_state$scene = state.scene) === null || _state$scene === void 0 ? void 0 : _state$scene.toString()), ViewerStateKeys.SingleChannelMode, serializeBoolean(state.singleChannelMode)), ViewerStateKeys.SingleChannelIndex, (_state$singleChannelI = state.singleChannelIndex) === null || _state$singleChannelI === void 0 ? void 0 : _state$singleChannelI.toString()), ViewerStateKeys.UseExactScaleLevel, serializeBoolean(state.useExactScaleLevel)), ViewerStateKeys.ScaleLevelIndex, (_state$scaleLevelInde = state.scaleLevelIndex) === null || _state$scaleLevelInde === void 0 ? void 0 : _state$scaleLevelInde.toString()), _defineProperty(_result, ViewerStateKeys.CameraState, state.cameraState && serializeCameraState(state.cameraState, removeDefaults, state.viewMode)));
142
+ var viewModeToViewParam = _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, ViewMode.threeD, "3D"), ViewMode.xy, "Z"), ViewMode.xz, "Y"), ViewMode.yz, "X");
143
+ result[ViewerStateKeys.View] = state.viewMode && viewModeToViewParam[state.viewMode];
144
+ return removeUndefinedProperties(result);
145
+ }