@aics/vole-app 3.5.0 → 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 (38) hide show
  1. package/es/aics-image-viewer/components/App/index.js +10 -0
  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/ControlPanel/CopySettingsButton.js +177 -147
  5. package/es/aics-image-viewer/components/ControlPanel/index.js +4 -1
  6. package/es/aics-image-viewer/components/Toolbar/styles.css +0 -10
  7. package/es/aics-image-viewer/components/ViewerCanvasWrapper/index.js +2 -1
  8. package/es/aics-image-viewer/components/dimension_sliders/AxisClipSliders.js +21 -8
  9. package/es/aics-image-viewer/components/dimension_sliders/RotationSliders.js +5 -7
  10. package/es/aics-image-viewer/components/dimension_sliders/SliderRows.js +8 -6
  11. package/es/aics-image-viewer/components/shared/ControlPanelRow/index.js +4 -2
  12. package/es/aics-image-viewer/components/shared/ControlPanelRow/styles.css +4 -3
  13. package/es/aics-image-viewer/components/useVolume.js +58 -24
  14. package/es/aics-image-viewer/shared/constants.js +24 -0
  15. package/es/aics-image-viewer/shared/utils/parseSnapshot.js +28 -2
  16. package/es/aics-image-viewer/shared/utils/parseUrl.js +41 -139
  17. package/es/aics-image-viewer/shared/utils/test/camera.test.js +1 -1
  18. package/es/aics-image-viewer/shared/utils/test/parseUrl.test.js +20 -20
  19. package/es/aics-image-viewer/shared/utils/test/storage.test.js +2 -2
  20. package/es/aics-image-viewer/shared/utils/test/viewerChannelSettings.test.js +1 -1
  21. package/es/aics-image-viewer/state/test/deserialize.test.js +1 -1
  22. package/es/aics-image-viewer/state/test/reset.test.js +1 -1
  23. package/es/aics-image-viewer/state/test/serialize.test.js +1 -0
  24. package/package.json +26 -44
  25. package/type-declarations/aics-image-viewer/components/ChannelsWidget.d.ts +3 -0
  26. package/type-declarations/aics-image-viewer/components/ChannelsWidgetRow.d.ts +3 -0
  27. package/type-declarations/aics-image-viewer/components/ControlPanel/CopySettingsButton.d.ts +2 -0
  28. package/type-declarations/aics-image-viewer/components/ViewerCanvasWrapper/index.d.ts +1 -0
  29. package/type-declarations/aics-image-viewer/components/dimension_sliders/AxisClipSliders.d.ts +1 -0
  30. package/type-declarations/aics-image-viewer/components/dimension_sliders/SliderRows.d.ts +1 -2
  31. package/type-declarations/aics-image-viewer/components/shared/ControlPanelRow/index.d.ts +1 -0
  32. package/type-declarations/aics-image-viewer/components/useVolume.d.ts +3 -0
  33. package/type-declarations/aics-image-viewer/shared/constants.d.ts +1 -0
  34. package/type-declarations/aics-image-viewer/shared/utils/parseSnapshot.d.ts +2 -0
  35. package/type-declarations/aics-image-viewer/shared/utils/parseUrl.d.ts +1 -8
  36. package/es/Globals.d.js +0 -0
  37. package/es/aics-image-viewer/shared/utils/firebase/index.js +0 -109
  38. package/type-declarations/aics-image-viewer/shared/utils/firebase/index.d.ts +0 -69
@@ -103,6 +103,7 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
103
103
  view3d.setAxisPosition(AXIS_MARGIN_DEFAULT[0], axisY);
104
104
  view3d.setTimestepIndicatorPosition(SCALE_BAR_MARGIN_DEFAULT[0], scaleBarY);
105
105
  view3d.setScaleBarPosition(scaleBarX, scaleBarY);
106
+ view3d.setLowResIndicatorPosition(scaleBarY);
106
107
  };
107
108
  var App = function App(props) {
108
109
  var _props$showError, _props$viewerChannelS, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4, _props$imageDownloadH, _props$parentImageDow;
@@ -338,12 +339,20 @@ var App = function App(props) {
338
339
  showError(error, image);
339
340
  onImageTitleChange === null || onImageTitleChange === void 0 || onImageTitleChange(undefined);
340
341
  }, [showError, onImageTitleChange]);
342
+ var onScrub = useCallback(function (isScrubbing, image) {
343
+ if (isScrubbing) {
344
+ void view3d.enableScrubIndicator(image)["catch"](onError);
345
+ } else {
346
+ view3d.disableScrubIndicator();
347
+ }
348
+ }, [view3d, onError]);
341
349
  var volume = useVolume(scenes, {
342
350
  viewerChannelSettings: props.viewerChannelSettings,
343
351
  onCreateImage: onCreateImage,
344
352
  onChannelLoaded: onChannelLoaded,
345
353
  onChangeScene: onChangeScene,
346
354
  onError: onError,
355
+ onScrub: onScrub,
347
356
  maskChannelName: maskChannelName
348
357
  });
349
358
  var image = volume.image,
@@ -603,6 +612,7 @@ var App = function App(props) {
603
612
  numScenes: numScenes,
604
613
  playControls: volume.playControls,
605
614
  playingAxis: volume.playingAxis,
615
+ setScrubbingAxis: volume.setScrubbingAxis,
606
616
  appHeight: props.appHeight,
607
617
  visibleControls: visibleControls,
608
618
  clippingPanelOpen: clippingPanelOpen,
@@ -145,7 +145,9 @@ var ChannelsWidget = function ChannelsWidget(props) {
145
145
  channelDataForChannel: channelDataChannels[channelIndex],
146
146
  name: getDisplayName(channelName, channelIndex, viewerChannelSettings),
147
147
  onColorChangeComplete: props.onColorChangeComplete,
148
- saveIsosurface: props.saveIsosurface
148
+ saveIsosurface: props.saveIsosurface,
149
+ getDropdownContainer: props.getDropdownContainer,
150
+ scrollContainer: props.scrollContainer
149
151
  });
150
152
  };
151
153
  var rows = channelDataChannels && Object.entries(channelGroupedByType).filter(function (_ref3) {
@@ -4,12 +4,13 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
4
4
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
- import { Button, Checkbox } from "antd";
7
+ import { RightOutlined } from "@ant-design/icons";
8
+ import { Checkbox } from "antd";
8
9
  import React, { useCallback, useState } from "react";
9
10
  import { colorArrayToObject, colorObjectToArray } from "../shared/utils/colorRepresentations";
10
11
  import { select, useViewerState } from "../state/store";
12
+ import CopySettingsButton from "./ControlPanel/CopySettingsButton";
11
13
  import ControlPanelRow from "./shared/ControlPanelRow";
12
- import ViewerIcon from "./shared/ViewerIcon";
13
14
  import TfEditor from "./TfEditor";
14
15
  var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
15
16
  var index = props.index,
@@ -28,6 +29,11 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
28
29
  setControlsOpen = _useState2[1];
29
30
  // Don't show controls in single-channel mode
30
31
  var controlsOpen = _controlsOpen && !singleChannelMode;
32
+ var toggleControlsOpen = useCallback(function () {
33
+ return setControlsOpen(function (open) {
34
+ return !open;
35
+ });
36
+ }, []);
31
37
  var onClickChannel = useCallback(function () {
32
38
  if (singleChannelMode) {
33
39
  changeViewerSetting("singleChannelIndex", index);
@@ -70,18 +76,12 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
70
76
  }, "Vol"), /*#__PURE__*/React.createElement(Checkbox, {
71
77
  checked: channelState.isosurfaceEnabled,
72
78
  onChange: isosurfaceCheckHandler
73
- }, "Surf"), /*#__PURE__*/React.createElement(Button, {
74
- icon: /*#__PURE__*/React.createElement(ViewerIcon, {
75
- type: "preferences",
76
- style: {
77
- fontSize: "16px"
78
- }
79
- }),
80
- onClick: function onClick() {
81
- return setControlsOpen(!controlsOpen);
82
- },
83
- title: "Open channel settings",
84
- type: "text"
79
+ }, "Surf"), /*#__PURE__*/React.createElement(CopySettingsButton, {
80
+ hide: props.controlPanelCollapsed,
81
+ getDropdownContainer: props.getDropdownContainer,
82
+ scrollContainer: props.scrollContainer,
83
+ hideImportExport: true,
84
+ channelIndex: props.index
85
85
  }));
86
86
  var renderControls = function renderControls() {
87
87
  var showEditor = singleChannelMode ? singleChannelIndex === index : channelState.volumeEnabled || channelState.isosurfaceEnabled;
@@ -126,13 +126,26 @@ var ChannelsWidgetRow = function ChannelsWidgetRow(props) {
126
126
  var rowClass = controlsOpen ? "" : " controls-closed";
127
127
  return /*#__PURE__*/React.createElement(ControlPanelRow, {
128
128
  key: index,
129
- title: props.name,
129
+ title: /*#__PURE__*/React.createElement("span", {
130
+ onClick: toggleControlsOpen,
131
+ style: {
132
+ cursor: "pointer"
133
+ }
134
+ }, props.name),
130
135
  color: colorArrayToObject(channelState.color),
131
136
  onColorChange: onColorChange,
132
137
  onColorChangeComplete: props.onColorChangeComplete,
133
138
  onClick: onClickChannel,
134
139
  className: rowClass,
135
- highlight: thisChannelOnly
140
+ highlight: thisChannelOnly,
141
+ icon: /*#__PURE__*/React.createElement(RightOutlined, {
142
+ style: {
143
+ transition: "transform 0.3s",
144
+ transform: controlsOpen ? "rotate(90deg)" : "rotate(0deg)",
145
+ cursor: "pointer"
146
+ },
147
+ onClick: toggleControlsOpen
148
+ })
136
149
  }, visibilityControls, controlsOpen && /*#__PURE__*/React.createElement("div", {
137
150
  style: {
138
151
  width: "100%"
@@ -10,21 +10,21 @@ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try {
10
10
  function _OverloadYield(e, d) { this.v = e, this.k = d; }
11
11
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
12
12
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
13
+ 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; }
14
+ 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; }
15
+ 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; }
16
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
17
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13
18
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
14
19
  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."); }
15
20
  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; } }
16
21
  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; }
17
22
  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; } }
18
23
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
19
- 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; }
20
- 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; }
21
- 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; }
22
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
23
- 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); }
24
24
  import { DragOutlined, EllipsisOutlined, ExclamationCircleOutlined, WarningOutlined } from "@ant-design/icons";
25
25
  import { Alert, Button, Checkbox, Dropdown, Modal, Tooltip, Upload } from "antd";
26
26
  import React from "react";
27
- import { channelStatesToSnapshot, isStoreSnapshot, snapshotToChannelStates } from "../../shared/utils/parseSnapshot";
27
+ import { channelStatesToSnapshot, isStoreSnapshot, singleChannelStateToSnapshot, snapshotToChannelStates } from "../../shared/utils/parseSnapshot";
28
28
  import { queryPasteDenied } from "../../shared/utils/permissions";
29
29
  import { useViewerState } from "../../state/store";
30
30
  import { cloneChannelState } from "../../state/util";
@@ -35,41 +35,17 @@ var ImportModalState = /*#__PURE__*/function (ImportModalState) {
35
35
  ImportModalState[ImportModalState["Warning"] = 2] = "Warning";
36
36
  return ImportModalState;
37
37
  }(ImportModalState || {});
38
- var importSettings = function importSettings(settingsString) {
39
- var _useViewerState$getSt = useViewerState.getState(),
40
- channelSettings = _useViewerState$getSt.channelSettings,
41
- replaceAllChannelSettings = _useViewerState$getSt.replaceAllChannelSettings;
38
+ var parseSnapshot = function parseSnapshot(snapshotString) {
42
39
  var parsed;
43
40
  try {
44
- parsed = JSON.parse(settingsString);
41
+ parsed = JSON.parse(snapshotString);
45
42
  } catch (_unused) {
46
43
  parsed = undefined;
47
44
  }
48
45
  if (!isStoreSnapshot(parsed)) {
49
- return {
50
- success: false
51
- };
46
+ return undefined;
52
47
  }
53
- var channelStates = snapshotToChannelStates(parsed);
54
- var channelCount = Object.keys(channelStates).length;
55
- var currentStates = channelSettings.map(cloneChannelState);
56
- var nextStates = channelSettings.map(function (state) {
57
- var result = _objectSpread(_objectSpread({}, cloneChannelState(state)), channelStates[state.name]);
58
- delete channelStates[state.name];
59
- return result;
60
- });
61
- replaceAllChannelSettings(nextStates);
62
- var undo = function undo() {
63
- return replaceAllChannelSettings(currentStates);
64
- };
65
- var unmatched = Object.keys(channelStates);
66
- var matchedCount = channelCount - unmatched.length;
67
- return {
68
- success: true,
69
- matchedCount: matchedCount,
70
- unmatched: unmatched,
71
- undo: undo
72
- };
48
+ return snapshotToChannelStates(parsed);
73
49
  };
74
50
  var PartialMatchMessage = function PartialMatchMessage(props) {
75
51
  var matchedCount = props.matchedCount,
@@ -84,7 +60,7 @@ var PartialMatchMessage = function PartialMatchMessage(props) {
84
60
  var SuccessMessage = function SuccessMessage(_ref) {
85
61
  var channelCount = _ref.channelCount,
86
62
  undo = _ref.undo;
87
- return /*#__PURE__*/React.createElement(React.Fragment, null, "Settings applied to ", channelCount, " channel", channelCount > 1 ? "s" : "", " -", " ", /*#__PURE__*/React.createElement(Button, {
63
+ return /*#__PURE__*/React.createElement(React.Fragment, null, "Settings applied", channelCount !== undefined && " to ".concat(channelCount, " channel").concat(channelCount > 1 ? "s" : ""), " -", " ", /*#__PURE__*/React.createElement(Button, {
88
64
  type: "link",
89
65
  style: {
90
66
  padding: 0,
@@ -124,13 +100,13 @@ var CopySettingsButton = function CopySettingsButton(props) {
124
100
  alert = _useContextualAlert2[0],
125
101
  showContextualAlert = _useContextualAlert2[1];
126
102
  var _React$useState9 = React.useState(undefined),
127
- _React$useState10 = _slicedToArray(_React$useState9, 2),
128
- modalAlert = _React$useState10[0],
129
- setModalAlert = _React$useState10[1];
130
- var _React$useState11 = React.useState(undefined),
131
- _React$useState12 = _slicedToArray(_React$useState11, 2),
132
- modalAlertType = _React$useState12[0],
133
- setModalAlertType = _React$useState12[1];
103
+ _React$useState0 = _slicedToArray(_React$useState9, 2),
104
+ modalAlert = _React$useState0[0],
105
+ setModalAlert = _React$useState0[1];
106
+ var _React$useState1 = React.useState(undefined),
107
+ _React$useState10 = _slicedToArray(_React$useState1, 2),
108
+ modalAlertType = _React$useState10[0],
109
+ setModalAlertType = _React$useState10[1];
134
110
  var showModalAlert = React.useCallback(function (content, alertType) {
135
111
  setModalAlert(content);
136
112
  setModalAlertType(alertType);
@@ -153,11 +129,106 @@ var CopySettingsButton = function CopySettingsButton(props) {
153
129
  title: "You must grant access to the clipboard",
154
130
  placement: "right"
155
131
  }, /*#__PURE__*/React.createElement(ExclamationCircleOutlined, null));
132
+ var applySnapshotText = React.useCallback(function (jsonTextSnapshot, sourceName, defaultModalAlerts) {
133
+ var showDefaultAlert = defaultModalAlerts ? showModalAlert : showContextualAlert;
134
+ var sourceNameCapitalized = sourceName.charAt(0).toUpperCase() + sourceName.slice(1);
135
+ var parsedStates = parseSnapshot(jsonTextSnapshot);
136
+ var _useViewerState$getSt = useViewerState.getState(),
137
+ channelSettings = _useViewerState$getSt.channelSettings,
138
+ replaceAllChannelSettings = _useViewerState$getSt.replaceAllChannelSettings;
139
+ var currentStates = channelSettings.map(cloneChannelState);
140
+ if (parsedStates === undefined) {
141
+ showDefaultAlert("".concat(sourceNameCapitalized, " does not contain channel settings"), "error");
142
+ return false;
143
+ }
144
+ var channelCount = Object.keys(parsedStates).length;
145
+ var states = currentStates.map(function (state) {
146
+ var result = _objectSpread(_objectSpread({}, cloneChannelState(state)), parsedStates[state.name]);
147
+ delete parsedStates[state.name];
148
+ return result;
149
+ });
150
+ var unmatched = Object.keys(parsedStates);
151
+ var matchedCount = channelCount - unmatched.length;
152
+ var unmatchedCount = unmatched.length;
153
+ replaceAllChannelSettings(states);
154
+ undoRef.current = function () {
155
+ return replaceAllChannelSettings(currentStates);
156
+ };
157
+ if (unmatchedCount > 0) {
158
+ if (matchedCount > 0) {
159
+ // Some channels in the text snapshot matched, some did not
160
+ setImportModalState(ImportModalState.Warning);
161
+ showModalAlert(/*#__PURE__*/React.createElement(PartialMatchMessage, {
162
+ matchedCount: matchedCount,
163
+ unmatched: unmatched
164
+ }), "warning");
165
+ } else {
166
+ // No channels in the text snapshot matched channels in the current image
167
+ showDefaultAlert("Channel names in ".concat(sourceName, " did not match names in image"), "error");
168
+ }
169
+ } else {
170
+ if (matchedCount > 0) {
171
+ // All channels matched!
172
+ setImportModalState(ImportModalState.Closed);
173
+ showContextualAlert(/*#__PURE__*/React.createElement(SuccessMessage, {
174
+ channelCount: matchedCount,
175
+ undo: undo
176
+ }));
177
+ } else {
178
+ // There were no channels in the text snapshot at all!
179
+ showDefaultAlert("".concat(sourceNameCapitalized, " does not contain channel settings"), "error");
180
+ }
181
+ }
182
+ return true;
183
+ }, [showContextualAlert, showModalAlert, undo]);
184
+ var applySingleChannelSnapshotText = React.useCallback(function (textSnapshot, sourceName, defaultModalAlerts, channelIndex) {
185
+ var showDefaultAlert = defaultModalAlerts ? showModalAlert : showContextualAlert;
186
+ var sourceNameCapitalized = sourceName.charAt(0).toUpperCase() + sourceName.slice(1);
187
+ var parsedStates = parseSnapshot(textSnapshot);
188
+ if (parsedStates === undefined) {
189
+ showDefaultAlert("".concat(sourceNameCapitalized, " does not contain a channel setting"), "error");
190
+ return false;
191
+ }
192
+ var states = Object.values(parsedStates);
193
+ if (states.length < 1) {
194
+ showDefaultAlert("".concat(sourceNameCapitalized, " does not contain a channel setting"), "error");
195
+ return false;
196
+ } else if (states.length > 1) {
197
+ showDefaultAlert("".concat(sourceNameCapitalized, " contains multiple channel settings and can't be applied to a single channel"), "error");
198
+ return false;
199
+ }
200
+ var state = states[0];
201
+ var _useViewerState$getSt2 = useViewerState.getState(),
202
+ channelSettings = _useViewerState$getSt2.channelSettings,
203
+ replaceAllChannelSettings = _useViewerState$getSt2.replaceAllChannelSettings;
204
+ var currentStates = channelSettings.map(cloneChannelState);
205
+ var newStates = channelSettings.map(function (currentState, index) {
206
+ if (index === channelIndex) {
207
+ return _objectSpread(_objectSpread({}, cloneChannelState(currentState)), state);
208
+ } else {
209
+ return cloneChannelState(currentState);
210
+ }
211
+ });
212
+ replaceAllChannelSettings(newStates);
213
+ undoRef.current = function () {
214
+ return replaceAllChannelSettings(currentStates);
215
+ };
216
+ showContextualAlert(/*#__PURE__*/React.createElement(SuccessMessage, {
217
+ undo: undo
218
+ }));
219
+ return true;
220
+ }, [showContextualAlert, showModalAlert, undo]);
156
221
  var onClickExport = React.useCallback(function () {
157
222
  setDropdownOpen(false);
158
- var _useViewerState$getSt2 = useViewerState.getState(),
159
- channelSettings = _useViewerState$getSt2.channelSettings;
160
- var serialized = channelStatesToSnapshot(channelSettings, includeColor ? undefined : ["color"]);
223
+ var _useViewerState$getSt3 = useViewerState.getState(),
224
+ channelSettings = _useViewerState$getSt3.channelSettings;
225
+ var serialized;
226
+ if (props.channelIndex !== undefined) {
227
+ var channel = channelSettings[props.channelIndex];
228
+ serialized = singleChannelStateToSnapshot(channel, includeColor ? undefined : ["color"]);
229
+ } else {
230
+ serialized = channelStatesToSnapshot(channelSettings, includeColor ? undefined : ["color"]);
231
+ }
161
232
  var stateText = JSON.stringify(serialized);
162
233
  var link = document.createElement("a");
163
234
  link.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(stateText));
@@ -168,9 +239,9 @@ var CopySettingsButton = function CopySettingsButton(props) {
168
239
  document.body.appendChild(link);
169
240
  link.click();
170
241
  document.body.removeChild(link);
171
- }, [imageName, includeColor]);
242
+ }, [imageName, includeColor, props.channelIndex]);
172
243
  var onClickPaste = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
173
- var clipboard, importResult, unmatched, matchedCount, unmatchedCount;
244
+ var clipboard;
174
245
  return _regeneratorRuntime().wrap(function _callee$(_context) {
175
246
  while (1) switch (_context.prev = _context.next) {
176
247
  case 0:
@@ -193,51 +264,20 @@ var CopySettingsButton = function CopySettingsButton(props) {
193
264
  queryPasteDenied().then(setPasteDenied);
194
265
  return _context.abrupt("return");
195
266
  case 13:
196
- importResult = importSettings(clipboard);
197
- if (importResult.success) {
198
- _context.next = 17;
199
- break;
200
- }
201
- showContextualAlert("Clipboard does not contain channel settings", "error");
202
- return _context.abrupt("return");
203
- case 17:
204
- unmatched = importResult.unmatched, matchedCount = importResult.matchedCount;
205
- unmatchedCount = unmatched.length;
206
- undoRef.current = function () {
207
- importResult.undo();
208
- showContextualAlert(undefined);
209
- showModalAlert(undefined);
210
- };
211
- if (unmatchedCount > 0) {
212
- if (matchedCount > 0) {
213
- showModalAlert(/*#__PURE__*/React.createElement(PartialMatchMessage, importResult), "warning");
214
- setImportModalState(ImportModalState.Warning);
215
- } else {
216
- showContextualAlert("Channel names in clipboard did not match names in image", "error");
217
- }
267
+ if (props.channelIndex !== undefined) {
268
+ applySingleChannelSnapshotText(clipboard, "clipboard", false, props.channelIndex);
218
269
  } else {
219
- if (matchedCount > 0) {
220
- showContextualAlert(/*#__PURE__*/React.createElement(React.Fragment, null, "Settings applied to ", matchedCount, " channel", matchedCount > 1 ? "s" : "", " -", " ", /*#__PURE__*/React.createElement(Button, {
221
- type: "link",
222
- style: {
223
- padding: 0,
224
- height: "unset"
225
- },
226
- onClick: undo
227
- }, "Undo")));
228
- } else {
229
- showContextualAlert("Clipboard does not contain channel settings", "error");
230
- }
270
+ applySnapshotText(clipboard, "clipboard", false);
231
271
  }
232
- case 21:
272
+ case 14:
233
273
  case "end":
234
274
  return _context.stop();
235
275
  }
236
276
  }, _callee, null, [[2, 8]]);
237
- })), [showContextualAlert, showModalAlert, undo]);
277
+ })), [applySingleChannelSnapshotText, applySnapshotText, props.channelIndex, showContextualAlert]);
238
278
  var onImportFile = React.useCallback(/*#__PURE__*/function () {
239
279
  var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(_ref3) {
240
- var file, onSuccess, onError, text, importResult, matchedCount, unmatched, unmatchedCount;
280
+ var file, onSuccess, onError, text, result;
241
281
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
242
282
  while (1) switch (_context2.prev = _context2.next) {
243
283
  case 0:
@@ -246,46 +286,17 @@ var CopySettingsButton = function CopySettingsButton(props) {
246
286
  return file.text();
247
287
  case 3:
248
288
  text = _context2.sent;
249
- importResult = importSettings(text);
250
- if (importResult.success) {
251
- _context2.next = 9;
252
- break;
289
+ if (props.channelIndex !== undefined) {
290
+ result = applySingleChannelSnapshotText(text, "file", true, props.channelIndex);
291
+ } else {
292
+ result = applySnapshotText(text, "file", true);
253
293
  }
254
- showModalAlert("File does not contain channel settings", "error");
255
- onError === null || onError === void 0 || onError(new Error());
256
- return _context2.abrupt("return");
257
- case 9:
258
- matchedCount = importResult.matchedCount, unmatched = importResult.unmatched;
259
- unmatchedCount = unmatched.length;
260
- undoRef.current = function () {
261
- importResult.undo();
262
- showContextualAlert(undefined);
263
- showModalAlert(undefined);
264
- };
265
- if (unmatchedCount > 0) {
266
- if (matchedCount > 0) {
267
- // Some channels in the file matched, some did not
268
- setImportModalState(ImportModalState.Warning);
269
- showModalAlert(/*#__PURE__*/React.createElement(PartialMatchMessage, importResult), "warning");
270
- } else {
271
- // No channels in the file matched channels in the current image
272
- showModalAlert("Channel names in file did not match names in image", "error");
273
- }
294
+ if (result) {
295
+ onSuccess === null || onSuccess === void 0 || onSuccess(undefined);
274
296
  } else {
275
- if (matchedCount > 0) {
276
- // All channels matched!
277
- setImportModalState(ImportModalState.Closed);
278
- showContextualAlert(/*#__PURE__*/React.createElement(SuccessMessage, {
279
- channelCount: matchedCount,
280
- undo: undo
281
- }));
282
- } else {
283
- // There were no channels in the file at all!
284
- showModalAlert("File does not contain channel settings", "error");
285
- }
297
+ onError === null || onError === void 0 || onError(new Error());
286
298
  }
287
- onSuccess === null || onSuccess === void 0 || onSuccess(undefined);
288
- case 14:
299
+ case 6:
289
300
  case "end":
290
301
  return _context2.stop();
291
302
  }
@@ -294,28 +305,31 @@ var CopySettingsButton = function CopySettingsButton(props) {
294
305
  return function (_x) {
295
306
  return _ref4.apply(this, arguments);
296
307
  };
297
- }(), [showContextualAlert, showModalAlert, undo]);
298
- var items = [{
299
- key: 0,
308
+ }(), [applySingleChannelSnapshotText, applySnapshotText, props.channelIndex]);
309
+ var copyItem = {
310
+ key: "Copy",
300
311
  label: "Copy",
301
312
  onClick: function onClick() {
302
313
  setDropdownOpen(false);
303
314
  try {
304
- var _useViewerState$getSt3 = useViewerState.getState(),
305
- channelSettings = _useViewerState$getSt3.channelSettings;
306
- var serialized = channelStatesToSnapshot(channelSettings, includeColor ? undefined : ["color"]);
315
+ var _useViewerState$getSt4 = useViewerState.getState(),
316
+ channelSettings = _useViewerState$getSt4.channelSettings;
317
+ var serialized;
318
+ if (props.channelIndex !== undefined) {
319
+ var channel = channelSettings[props.channelIndex];
320
+ serialized = singleChannelStateToSnapshot(channel, includeColor ? undefined : ["color"]);
321
+ } else {
322
+ serialized = channelStatesToSnapshot(channelSettings, includeColor ? undefined : ["color"]);
323
+ }
307
324
  navigator.clipboard.writeText(JSON.stringify(serialized));
308
325
  showContextualAlert("Settings copied");
309
326
  } catch (_unused3) {
310
327
  showContextualAlert("Could not copy settings", "error");
311
328
  }
312
329
  }
313
- }, {
314
- key: 1,
315
- label: "Export",
316
- onClick: onClickExport
317
- }, {
318
- key: 2,
330
+ };
331
+ var pasteItem = {
332
+ key: "Paste",
319
333
  label: /*#__PURE__*/React.createElement("div", {
320
334
  style: {
321
335
  display: "flex",
@@ -324,19 +338,9 @@ var CopySettingsButton = function CopySettingsButton(props) {
324
338
  }, /*#__PURE__*/React.createElement("span", null, "Paste"), pastePrompt),
325
339
  disabled: pasteDenied,
326
340
  onClick: onClickPaste
327
- }, {
328
- key: 3,
329
- label: "Import",
330
- onClick: function onClick() {
331
- setDropdownOpen(false);
332
- setImportModalState(ImportModalState.Import);
333
- showModalAlert(undefined);
334
- }
335
- }, {
336
- key: 4,
337
- type: "divider"
338
- }, {
339
- key: 5,
341
+ };
342
+ var includeColorItem = {
343
+ key: "IncludeColor",
340
344
  className: "import-dropdown-menu-item-include-color",
341
345
  label: /*#__PURE__*/React.createElement(Checkbox, {
342
346
  checked: includeColor
@@ -349,7 +353,33 @@ var CopySettingsButton = function CopySettingsButton(props) {
349
353
  return !includeColor;
350
354
  });
351
355
  }
352
- }];
356
+ };
357
+ var items;
358
+ if (props.hideImportExport) {
359
+ items = [copyItem, pasteItem, {
360
+ key: "divider",
361
+ type: "divider"
362
+ }, includeColorItem];
363
+ } else {
364
+ var exportItem = {
365
+ key: 1,
366
+ label: "Export",
367
+ onClick: onClickExport
368
+ };
369
+ var importItem = {
370
+ key: 3,
371
+ label: "Import",
372
+ onClick: function onClick() {
373
+ setDropdownOpen(false);
374
+ setImportModalState(ImportModalState.Import);
375
+ showModalAlert(undefined);
376
+ }
377
+ };
378
+ items = [copyItem, exportItem, pasteItem, importItem, {
379
+ key: "divider",
380
+ type: "divider"
381
+ }, includeColorItem];
382
+ }
353
383
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Dropdown, {
354
384
  menu: {
355
385
  items: items
@@ -204,7 +204,10 @@ function ControlPanel(props) {
204
204
  onColorChangeComplete: props.onColorChangeComplete,
205
205
  onApplyColorPresets: props.onApplyColorPresets,
206
206
  filterFunc: props.filterFunc,
207
- viewerChannelSettings: viewerChannelSettings
207
+ viewerChannelSettings: viewerChannelSettings,
208
+ getDropdownContainer: getDropdownContainer,
209
+ scrollContainer: columnRef.current,
210
+ controlPanelCollapsed: props.collapsed
208
211
  }), tab === ControlTab.Advanced && renderAdvancedSettings(), tab === ControlTab.Metadata && /*#__PURE__*/React.createElement(MetadataViewer, {
209
212
  metadata: props.metadata
210
213
  }))));
@@ -3,7 +3,6 @@
3
3
  width:100%;
4
4
  z-index:250;
5
5
  }
6
-
7
6
  .viewer-toolbar-container .viewer-toolbar-scroll-left,
8
7
  .viewer-toolbar-container .viewer-toolbar-scroll-right{
9
8
  height:100%;
@@ -17,28 +16,22 @@
17
16
  z-index:255;
18
17
  transition:color 0.3s;
19
18
  }
20
-
21
19
  .viewer-toolbar-container .viewer-toolbar-scroll-left:hover, .viewer-toolbar-container .viewer-toolbar-scroll-right:hover{
22
20
  color:white;
23
21
  }
24
-
25
22
  .viewer-toolbar-container .viewer-toolbar-scroll-left{
26
23
  left:0;
27
24
  }
28
-
29
25
  .viewer-toolbar-container .viewer-toolbar-scroll-right{
30
26
  right:0;
31
27
  }
32
-
33
28
  .viewer-toolbar-container.viewer-toolbar-scroll{
34
29
  border-bottom:1px solid var(--color-layout-dividers);
35
30
  }
36
-
37
31
  .viewer-toolbar-container.viewer-toolbar-scroll .viewer-toolbar-scroll-left,
38
32
  .viewer-toolbar-container.viewer-toolbar-scroll .viewer-toolbar-scroll-right{
39
33
  display:flex;
40
34
  }
41
-
42
35
  .viewer-toolbar-container.viewer-toolbar-scroll .viewer-toolbar{
43
36
  white-space:nowrap;
44
37
  justify-content:normal;
@@ -46,16 +39,13 @@
46
39
  overflow-x:scroll;
47
40
  scrollbar-width:none;
48
41
  }
49
-
50
42
  .viewer-toolbar-container.viewer-toolbar-scroll .viewer-toolbar::-webkit-scrollbar{
51
43
  display:none;
52
44
  }
53
-
54
45
  .viewer-toolbar-container.viewer-toolbar-scroll .viewer-toolbar .viewer-toolbar-left{
55
46
  position:static;
56
47
  margin-left:15px;
57
48
  }
58
-
59
49
  .viewer-toolbar-container.viewer-toolbar-scroll .viewer-toolbar .viewer-toolbar-right{
60
50
  position:static;
61
51
  margin-right:15px;
@@ -65,7 +65,8 @@ var ViewerWrapper = function ViewerWrapper(props) {
65
65
  time: time,
66
66
  scene: scene,
67
67
  playControls: props.playControls,
68
- playingAxis: props.playingAxis
68
+ playingAxis: props.playingAxis,
69
+ setScrubbingAxis: props.setScrubbingAxis
69
70
  })
70
71
  });
71
72
  }