@aics/vole-app 3.4.4 → 3.5.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 (55) hide show
  1. package/es/aics-image-viewer/components/App/index.js +5 -5
  2. package/es/aics-image-viewer/components/ColorPicker/index.js +9 -1
  3. package/es/aics-image-viewer/components/ControlPanel/CopySettingsButton.js +415 -0
  4. package/es/aics-image-viewer/components/ControlPanel/index.js +12 -4
  5. package/es/aics-image-viewer/components/ControlPanel/styles.css +29 -2
  6. package/es/aics-image-viewer/components/StyleProvider/index.js +17 -2
  7. package/es/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.js +1 -1
  8. package/es/aics-image-viewer/components/Toolbar/index.js +1 -1
  9. package/es/aics-image-viewer/components/{CellViewerCanvasWrapper → ViewerCanvasWrapper}/index.js +1 -1
  10. package/es/aics-image-viewer/components/dimension_sliders/RotationSliders.js +1 -1
  11. package/es/aics-image-viewer/components/shared/ContextualAlert.js +101 -0
  12. package/es/aics-image-viewer/components/useVolume.js +1 -1
  13. package/es/aics-image-viewer/shared/constants.js +1 -1
  14. package/es/aics-image-viewer/shared/types.js +1 -1
  15. package/es/aics-image-viewer/shared/utils/controlPointsToLut.js +4 -0
  16. package/es/aics-image-viewer/shared/utils/parseSnapshot.js +179 -0
  17. package/es/aics-image-viewer/shared/utils/{urlParsing.js → parseUrl.js} +14 -52
  18. package/es/aics-image-viewer/shared/utils/permissions.js +46 -0
  19. package/es/aics-image-viewer/shared/utils/test/camera.test.js +0 -1
  20. package/es/aics-image-viewer/shared/utils/test/{urlParsing.test.js → parseUrl.test.js} +8 -5
  21. package/es/aics-image-viewer/state/deserialize.js +443 -331
  22. package/es/aics-image-viewer/state/reset.js +1 -1
  23. package/es/aics-image-viewer/state/serialize.js +168 -78
  24. package/es/aics-image-viewer/state/subscribers.js +1 -1
  25. package/es/aics-image-viewer/state/test/deserialize.test.js +62 -76
  26. package/es/aics-image-viewer/state/test/reset.test.js +1 -1
  27. package/es/aics-image-viewer/state/test/serialize.test.js +41 -43
  28. package/es/aics-image-viewer/state/test/test_data.js +119 -1
  29. package/es/aics-image-viewer/state/types.js +157 -245
  30. package/es/aics-image-viewer/state/util.js +39 -1
  31. package/es/index.js +4 -2
  32. package/package.json +1 -1
  33. package/type-declarations/aics-image-viewer/components/App/types.d.ts +3 -3
  34. package/type-declarations/aics-image-viewer/components/ControlPanel/CopySettingsButton.d.ts +9 -0
  35. package/type-declarations/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.d.ts +1 -1
  36. package/type-declarations/aics-image-viewer/components/{CellViewerCanvasWrapper → ViewerCanvasWrapper}/index.d.ts +3 -3
  37. package/type-declarations/aics-image-viewer/components/dimension_sliders/AxisClipSliders.d.ts +6 -6
  38. package/type-declarations/aics-image-viewer/components/shared/ContextualAlert.d.ts +29 -0
  39. package/type-declarations/aics-image-viewer/shared/constants.d.ts +1 -2
  40. package/type-declarations/aics-image-viewer/shared/types.d.ts +2 -2
  41. package/type-declarations/aics-image-viewer/shared/utils/controlPointsToLut.d.ts +1 -1
  42. package/type-declarations/aics-image-viewer/shared/utils/parseSnapshot.d.ts +47 -0
  43. package/type-declarations/aics-image-viewer/shared/utils/{urlParsing.d.ts → parseUrl.d.ts} +2 -16
  44. package/type-declarations/aics-image-viewer/shared/utils/permissions.d.ts +7 -0
  45. package/type-declarations/aics-image-viewer/shared/utils/viewerChannelSettings.d.ts +1 -1
  46. package/type-declarations/aics-image-viewer/state/deserialize.d.ts +70 -35
  47. package/type-declarations/aics-image-viewer/state/reset.d.ts +1 -1
  48. package/type-declarations/aics-image-viewer/state/serialize.d.ts +44 -10
  49. package/type-declarations/aics-image-viewer/state/types.d.ts +265 -159
  50. package/type-declarations/aics-image-viewer/state/util.d.ts +3 -1
  51. package/type-declarations/index.d.ts +5 -3
  52. package/es/aics-image-viewer/shared/enums.js +0 -18
  53. package/type-declarations/aics-image-viewer/shared/enums.d.ts +0 -15
  54. package/type-declarations/aics-image-viewer/shared/utils/math.d.ts +0 -1
  55. /package/es/aics-image-viewer/components/{CellViewerCanvasWrapper → ViewerCanvasWrapper}/styles.css +0 -0
@@ -20,18 +20,18 @@ import { Layout } from "antd";
20
20
  import { debounce, isEqual } from "lodash";
21
21
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
22
  import { AXIS_MARGIN_DEFAULT, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, getDefaultViewerChannelSettings, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
23
- import { ImageType, ViewMode } from "../../shared/enums";
24
23
  import { controlPointsToRamp, initializeLut } from "../../shared/utils/controlPointsToLut";
25
24
  import { useConstructor } from "../../shared/utils/hooks";
26
25
  import { findFirstChannelMatch } from "../../shared/utils/viewerChannelSettings";
27
26
  import { select, useViewerState } from "../../state/store";
28
27
  import { subscribeImageToState, subscribeViewToState } from "../../state/subscribers";
28
+ import { ImageType, ViewMode } from "../../state/types";
29
29
  import useVolume, { ImageLoadStatus } from "../useVolume";
30
- import CellViewerCanvasWrapper from "../CellViewerCanvasWrapper";
31
30
  import ControlPanel from "../ControlPanel";
32
31
  import { useErrorAlert } from "../ErrorAlert";
33
32
  import StyleProvider from "../StyleProvider";
34
33
  import Toolbar from "../Toolbar";
34
+ import CellViewerCanvasWrapper from "../ViewerCanvasWrapper";
35
35
  import ChannelUpdater from "./ChannelUpdater";
36
36
  import "../../assets/styles/globals.css";
37
37
  import "./styles.css";
@@ -105,7 +105,7 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
105
105
  view3d.setScaleBarPosition(scaleBarX, scaleBarY);
106
106
  };
107
107
  var App = function App(props) {
108
- var _props$showError, _props$viewerChannelS, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
108
+ 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;
109
109
  props = _objectSpread(_objectSpread({}, defaultProps), props);
110
110
 
111
111
  // State management /////////////////////////////////////////////////////////
@@ -582,8 +582,8 @@ var App = function App(props) {
582
582
  margin: props.canvasMargin
583
583
  }
584
584
  }, /*#__PURE__*/React.createElement(Content, null, /*#__PURE__*/React.createElement(Toolbar, {
585
- fovDownloadHref: props.parentImageDownloadHref,
586
- cellDownloadHref: props.imageDownloadHref,
585
+ cellDownloadHref: (_props$imageDownloadH = props.imageDownloadHref) !== null && _props$imageDownloadH !== void 0 ? _props$imageDownloadH : defaultProps.imageDownloadHref,
586
+ fovDownloadHref: (_props$parentImageDow = props.parentImageDownloadHref) !== null && _props$parentImageDow !== void 0 ? _props$parentImageDow : defaultProps.parentImageDownloadHref,
587
587
  hasParentImage: !!props.parentImageUrl,
588
588
  hasCellId: !!props.cellId,
589
589
  canPathTrace: view3d ? view3d.hasWebGL2() : false,
@@ -17,6 +17,13 @@ var DEFAULT_COLOR = {
17
17
  b: "19",
18
18
  a: "1"
19
19
  };
20
+ var PRESET_COLOR_HEX = ["#ffffff", "#ff0000", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff", "#c0c0c0"];
21
+ var PRESET_COLORS = PRESET_COLOR_HEX.map(function (color) {
22
+ return {
23
+ color: color,
24
+ title: color.toUpperCase()
25
+ };
26
+ });
20
27
  var ColorPicker = function ColorPicker(props) {
21
28
  var _React$useState = React.useState(false),
22
29
  _React$useState2 = _slicedToArray(_React$useState, 2),
@@ -87,7 +94,8 @@ var ColorPicker = function ColorPicker(props) {
87
94
  color: currentColor,
88
95
  onChange: handleChange,
89
96
  onChangeComplete: handleChangeComplete,
90
- disableAlpha: props.disableAlpha
97
+ disableAlpha: props.disableAlpha,
98
+ presetColors: PRESET_COLORS
91
99
  })) : null));
92
100
  };
93
101
  export default ColorPicker;
@@ -0,0 +1,415 @@
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 _regeneratorRuntime() { "use strict"; var r = _regenerator(), e = r.m(_regeneratorRuntime), t = (Object.getPrototypeOf ? Object.getPrototypeOf(e) : e.__proto__).constructor; function n(r) { var e = "function" == typeof r && r.constructor; return !!e && (e === t || "GeneratorFunction" === (e.displayName || e.name)); } var o = { "throw": 1, "return": 2, "break": 3, "continue": 3 }; function a(r) { var e, t; return function (n) { e || (e = { stop: function stop() { return t(n.a, 2); }, "catch": function _catch() { return n.v; }, abrupt: function abrupt(r, e) { return t(n.a, o[r], e); }, delegateYield: function delegateYield(r, o, a) { return e.resultName = o, t(n.d, _regeneratorValues(r), a); }, finish: function finish(r) { return t(n.f, r); } }, t = function t(r, _t, o) { n.p = e.prev, n.n = e.next; try { return r(_t, o); } finally { e.next = n.n; } }), e.resultName && (e[e.resultName] = n.v, e.resultName = void 0), e.sent = n.v, e.next = n.n; try { return r.call(this, e); } finally { n.p = e.prev, n.n = e.next; } }; } return (_regeneratorRuntime = function _regeneratorRuntime() { return { wrap: function wrap(e, t, n, o) { return r.w(a(e), t, n, o && o.reverse()); }, isGeneratorFunction: n, mark: r.m, awrap: function awrap(r, e) { return new _OverloadYield(r, e); }, AsyncIterator: _regeneratorAsyncIterator, async: function async(r, e, t, o, u) { return (n(e) ? _regeneratorAsyncGen : _regeneratorAsync)(a(r), e, t, o, u); }, keys: _regeneratorKeys, values: _regeneratorValues }; })(); }
3
+ function _regeneratorValues(e) { if (null != e) { var t = e["function" == typeof Symbol && Symbol.iterator || "@@iterator"], r = 0; if (t) return t.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) return { next: function next() { return e && r >= e.length && (e = void 0), { value: e && e[r++], done: !e }; } }; } throw new TypeError(_typeof(e) + " is not iterable"); }
4
+ function _regeneratorKeys(e) { var n = Object(e), r = []; for (var t in n) r.unshift(t); return function e() { for (; r.length;) if ((t = r.pop()) in n) return e.value = t, e.done = !1, e; return e.done = !0, e; }; }
5
+ function _regeneratorAsync(n, e, r, t, o) { var a = _regeneratorAsyncGen(n, e, r, t, o); return a.next().then(function (n) { return n.done ? n.value : a.next(); }); }
6
+ function _regeneratorAsyncGen(r, e, t, o, n) { return new _regeneratorAsyncIterator(_regenerator().w(r, e, t, o), n || Promise); }
7
+ function _regeneratorAsyncIterator(t, e) { function n(r, o, i, f) { try { var c = t[r](o), u = c.value; return u instanceof _OverloadYield ? e.resolve(u.v).then(function (t) { n("next", t, i, f); }, function (t) { n("throw", t, i, f); }) : e.resolve(u).then(function (t) { c.value = t, i(c); }, function (t) { return n("throw", t, i, f); }); } catch (t) { f(t); } } var r; this.next || (_regeneratorDefine2(_regeneratorAsyncIterator.prototype), _regeneratorDefine2(_regeneratorAsyncIterator.prototype, "function" == typeof Symbol && Symbol.asyncIterator || "@asyncIterator", function () { return this; })), _regeneratorDefine2(this, "_invoke", function (t, o, i) { function f() { return new e(function (e, r) { n(t, i, e, r); }); } return r = r ? r.then(f, f) : f(); }, !0); }
8
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
9
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
10
+ function _OverloadYield(e, d) { this.v = e, this.k = d; }
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
+ 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 _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
14
+ 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
+ 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
+ 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
+ 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
+ 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
+ import { DragOutlined, EllipsisOutlined, ExclamationCircleOutlined, WarningOutlined } from "@ant-design/icons";
25
+ import { Alert, Button, Checkbox, Dropdown, Modal, Tooltip, Upload } from "antd";
26
+ import React from "react";
27
+ import { channelStatesToSnapshot, isStoreSnapshot, snapshotToChannelStates } from "../../shared/utils/parseSnapshot";
28
+ import { queryPasteDenied } from "../../shared/utils/permissions";
29
+ import { useViewerState } from "../../state/store";
30
+ import { cloneChannelState } from "../../state/util";
31
+ import { useContextualAlert } from "../shared/ContextualAlert";
32
+ var ImportModalState = /*#__PURE__*/function (ImportModalState) {
33
+ ImportModalState[ImportModalState["Closed"] = 0] = "Closed";
34
+ ImportModalState[ImportModalState["Import"] = 1] = "Import";
35
+ ImportModalState[ImportModalState["Warning"] = 2] = "Warning";
36
+ return ImportModalState;
37
+ }(ImportModalState || {});
38
+ var importSettings = function importSettings(settingsString) {
39
+ var _useViewerState$getSt = useViewerState.getState(),
40
+ channelSettings = _useViewerState$getSt.channelSettings,
41
+ replaceAllChannelSettings = _useViewerState$getSt.replaceAllChannelSettings;
42
+ var parsed;
43
+ try {
44
+ parsed = JSON.parse(settingsString);
45
+ } catch (_unused) {
46
+ parsed = undefined;
47
+ }
48
+ if (!isStoreSnapshot(parsed)) {
49
+ return {
50
+ success: false
51
+ };
52
+ }
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
+ };
73
+ };
74
+ var PartialMatchMessage = function PartialMatchMessage(props) {
75
+ var matchedCount = props.matchedCount,
76
+ unmatched = props.unmatched;
77
+ var unmatchedCount = unmatched.length;
78
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", null, "Settings applied to ", matchedCount, " channel", matchedCount > 1 ? "s" : "", ". Could not find a match for", " ", unmatchedCount, " channel name", unmatchedCount > 1 ? "s" : "", ":"), /*#__PURE__*/React.createElement("ul", null, unmatched.map(function (channelName, index) {
79
+ return /*#__PURE__*/React.createElement("li", {
80
+ key: index
81
+ }, channelName);
82
+ })));
83
+ };
84
+ var SuccessMessage = function SuccessMessage(_ref) {
85
+ var channelCount = _ref.channelCount,
86
+ undo = _ref.undo;
87
+ return /*#__PURE__*/React.createElement(React.Fragment, null, "Settings applied to ", channelCount, " channel", channelCount > 1 ? "s" : "", " -", " ", /*#__PURE__*/React.createElement(Button, {
88
+ type: "link",
89
+ style: {
90
+ padding: 0,
91
+ height: "unset"
92
+ },
93
+ onClick: undo
94
+ }, "Undo"));
95
+ };
96
+ var CopySettingsButton = function CopySettingsButton(props) {
97
+ var imageName = props.imageName,
98
+ scrollContainer = props.scrollContainer,
99
+ hide = props.hide,
100
+ getDropdownContainer = props.getDropdownContainer;
101
+ var buttonRef = React.useRef(null);
102
+ var _React$useState = React.useState(true),
103
+ _React$useState2 = _slicedToArray(_React$useState, 2),
104
+ includeColor = _React$useState2[0],
105
+ setIncludeColor = _React$useState2[1];
106
+ var _React$useState3 = React.useState(false),
107
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
108
+ pasteDenied = _React$useState4[0],
109
+ setPasteDenied = _React$useState4[1];
110
+ var _React$useState5 = React.useState(false),
111
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
112
+ dropdownOpen = _React$useState6[0],
113
+ setDropdownOpen = _React$useState6[1];
114
+ var _React$useState7 = React.useState(ImportModalState.Closed),
115
+ _React$useState8 = _slicedToArray(_React$useState7, 2),
116
+ importModalState = _React$useState8[0],
117
+ setImportModalState = _React$useState8[1];
118
+ var _useContextualAlert = useContextualAlert(buttonRef.current, {
119
+ scrollContainer: scrollContainer,
120
+ hide: hide,
121
+ timeout: 8000
122
+ }),
123
+ _useContextualAlert2 = _slicedToArray(_useContextualAlert, 2),
124
+ alert = _useContextualAlert2[0],
125
+ showContextualAlert = _useContextualAlert2[1];
126
+ 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];
134
+ var showModalAlert = React.useCallback(function (content, alertType) {
135
+ setModalAlert(content);
136
+ setModalAlertType(alertType);
137
+ }, []);
138
+ var undoRef = React.useRef();
139
+ var undo = React.useCallback(function () {
140
+ var _undoRef$current;
141
+ (_undoRef$current = undoRef.current) === null || _undoRef$current === void 0 || _undoRef$current.call(undoRef);
142
+ showModalAlert(undefined);
143
+ showContextualAlert(undefined);
144
+ }, [showContextualAlert, showModalAlert]);
145
+
146
+ // On first render, check if the user has disabled clipboard access
147
+ var firstRenderRef = React.useRef(false);
148
+ if (firstRenderRef.current) {
149
+ queryPasteDenied().then(setPasteDenied);
150
+ firstRenderRef.current = true;
151
+ }
152
+ var pastePrompt = pasteDenied && /*#__PURE__*/React.createElement(Tooltip, {
153
+ title: "You must grant access to the clipboard",
154
+ placement: "right"
155
+ }, /*#__PURE__*/React.createElement(ExclamationCircleOutlined, null));
156
+ var onClickExport = React.useCallback(function () {
157
+ setDropdownOpen(false);
158
+ var _useViewerState$getSt2 = useViewerState.getState(),
159
+ channelSettings = _useViewerState$getSt2.channelSettings;
160
+ var serialized = channelStatesToSnapshot(channelSettings, includeColor ? undefined : ["color"]);
161
+ var stateText = JSON.stringify(serialized);
162
+ var link = document.createElement("a");
163
+ link.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(stateText));
164
+ var isoDate = new Date().toISOString().split("T")[0];
165
+ var imgName = imageName !== null && imageName !== void 0 ? imageName : "settings";
166
+ link.setAttribute("download", "".concat(isoDate, "_").concat(imgName, ".json"));
167
+ link.style.display = "none";
168
+ document.body.appendChild(link);
169
+ link.click();
170
+ document.body.removeChild(link);
171
+ }, [imageName, includeColor]);
172
+ var onClickPaste = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
173
+ var clipboard, importResult, unmatched, matchedCount, unmatchedCount;
174
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
175
+ while (1) switch (_context.prev = _context.next) {
176
+ case 0:
177
+ setDropdownOpen(false);
178
+
179
+ // Try to read the clipboard
180
+ clipboard = undefined;
181
+ _context.prev = 2;
182
+ _context.next = 5;
183
+ return navigator.clipboard.readText();
184
+ case 5:
185
+ clipboard = _context.sent;
186
+ _context.next = 13;
187
+ break;
188
+ case 8:
189
+ _context.prev = 8;
190
+ _context.t0 = _context["catch"](2);
191
+ showContextualAlert("Could not read clipboard", "error");
192
+ // If paste failed, check if it was because the user was asked to grant clipboard access and said no
193
+ queryPasteDenied().then(setPasteDenied);
194
+ return _context.abrupt("return");
195
+ 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
+ }
218
+ } 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
+ }
231
+ }
232
+ case 21:
233
+ case "end":
234
+ return _context.stop();
235
+ }
236
+ }, _callee, null, [[2, 8]]);
237
+ })), [showContextualAlert, showModalAlert, undo]);
238
+ var onImportFile = React.useCallback(/*#__PURE__*/function () {
239
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(_ref3) {
240
+ var file, onSuccess, onError, text, importResult, matchedCount, unmatched, unmatchedCount;
241
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
242
+ while (1) switch (_context2.prev = _context2.next) {
243
+ case 0:
244
+ file = _ref3.file, onSuccess = _ref3.onSuccess, onError = _ref3.onError;
245
+ _context2.next = 3;
246
+ return file.text();
247
+ case 3:
248
+ text = _context2.sent;
249
+ importResult = importSettings(text);
250
+ if (importResult.success) {
251
+ _context2.next = 9;
252
+ break;
253
+ }
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
+ }
274
+ } 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
+ }
286
+ }
287
+ onSuccess === null || onSuccess === void 0 || onSuccess(undefined);
288
+ case 14:
289
+ case "end":
290
+ return _context2.stop();
291
+ }
292
+ }, _callee2);
293
+ }));
294
+ return function (_x) {
295
+ return _ref4.apply(this, arguments);
296
+ };
297
+ }(), [showContextualAlert, showModalAlert, undo]);
298
+ var items = [{
299
+ key: 0,
300
+ label: "Copy",
301
+ onClick: function onClick() {
302
+ setDropdownOpen(false);
303
+ try {
304
+ var _useViewerState$getSt3 = useViewerState.getState(),
305
+ channelSettings = _useViewerState$getSt3.channelSettings;
306
+ var serialized = channelStatesToSnapshot(channelSettings, includeColor ? undefined : ["color"]);
307
+ navigator.clipboard.writeText(JSON.stringify(serialized));
308
+ showContextualAlert("Settings copied");
309
+ } catch (_unused3) {
310
+ showContextualAlert("Could not copy settings", "error");
311
+ }
312
+ }
313
+ }, {
314
+ key: 1,
315
+ label: "Export",
316
+ onClick: onClickExport
317
+ }, {
318
+ key: 2,
319
+ label: /*#__PURE__*/React.createElement("div", {
320
+ style: {
321
+ display: "flex",
322
+ justifyContent: "space-between"
323
+ }
324
+ }, /*#__PURE__*/React.createElement("span", null, "Paste"), pastePrompt),
325
+ disabled: pasteDenied,
326
+ 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,
340
+ className: "import-dropdown-menu-item-include-color",
341
+ label: /*#__PURE__*/React.createElement(Checkbox, {
342
+ checked: includeColor
343
+ }, "Include color setting"),
344
+ onClick: function onClick(_ref5) {
345
+ var domEvent = _ref5.domEvent;
346
+ domEvent.stopPropagation();
347
+ domEvent.preventDefault();
348
+ setIncludeColor(function (includeColor) {
349
+ return !includeColor;
350
+ });
351
+ }
352
+ }];
353
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Dropdown, {
354
+ menu: {
355
+ items: items
356
+ },
357
+ trigger: ["click"],
358
+ overlayStyle: {
359
+ minWidth: 100
360
+ },
361
+ getPopupContainer: getDropdownContainer,
362
+ open: dropdownOpen,
363
+ onOpenChange: function onOpenChange(open, _ref6) {
364
+ var source = _ref6.source;
365
+ if (open || source === "trigger") {
366
+ setDropdownOpen(open);
367
+ }
368
+ }
369
+ }, /*#__PURE__*/React.createElement(Button, {
370
+ type: "text",
371
+ size: "large",
372
+ ref: buttonRef
373
+ }, /*#__PURE__*/React.createElement(EllipsisOutlined, null))), /*#__PURE__*/React.createElement(Modal, {
374
+ closable: true,
375
+ title: importModalState === ImportModalState.Warning ? "Settings applied with exceptions" : "Import channel settings",
376
+ className: "modal-settings-import",
377
+ open: importModalState !== ImportModalState.Closed,
378
+ onCancel: function onCancel() {
379
+ return setImportModalState(ImportModalState.Closed);
380
+ },
381
+ footer: importModalState === ImportModalState.Warning && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
382
+ onClick: function onClick() {
383
+ undo();
384
+ setImportModalState(ImportModalState.Closed);
385
+ }
386
+ }, "Undo all"), /*#__PURE__*/React.createElement(Button, {
387
+ type: "primary",
388
+ onClick: function onClick() {
389
+ return setImportModalState(ImportModalState.Closed);
390
+ }
391
+ }, "Ok")),
392
+ getContainer: getDropdownContainer
393
+ }, importModalState === ImportModalState.Import && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("p", null, "Upload a saved .json settings file"), /*#__PURE__*/React.createElement(Upload.Dragger, {
394
+ showUploadList: false,
395
+ customRequest: onImportFile,
396
+ accept: ".json"
397
+ }, /*#__PURE__*/React.createElement(DragOutlined, null), " Drag and drop here or click to browse")), modalAlert !== undefined && /*#__PURE__*/React.createElement(Alert, {
398
+ showIcon: true,
399
+ style: {
400
+ marginTop: 15
401
+ },
402
+ message: modalAlert,
403
+ icon: modalAlertType === "error" ? /*#__PURE__*/React.createElement(WarningOutlined, {
404
+ style: {
405
+ fontSize: 21
406
+ }
407
+ }) : /*#__PURE__*/React.createElement(ExclamationCircleOutlined, {
408
+ style: {
409
+ fontSize: 21
410
+ }
411
+ }),
412
+ type: modalAlertType
413
+ })), alert);
414
+ };
415
+ export default CopySettingsButton;
@@ -17,6 +17,7 @@ import CustomizeWidget from "../CustomizeWidget";
17
17
  import GlobalVolumeControls from "../GlobalVolumeControls";
18
18
  import MetadataViewer from "../MetadataViewer";
19
19
  import ViewerIcon from "../shared/ViewerIcon";
20
+ import CopySettingsButton from "./CopySettingsButton";
20
21
  import "./styles.css";
21
22
  var ControlTab = /*#__PURE__*/function (ControlTab) {
22
23
  ControlTab[ControlTab["Channels"] = 0] = "Channels";
@@ -38,6 +39,7 @@ function ControlPanel(props) {
38
39
  var singleChannelMode = useViewerState(select("singleChannelMode"));
39
40
  var changeViewerSetting = useViewerState(select("changeViewerSetting"));
40
41
  var containerRef = React.useRef(null);
42
+ var columnRef = React.useRef(null);
41
43
  var getDropdownContainer = function getDropdownContainer() {
42
44
  var _containerRef$current;
43
45
  return (_containerRef$current = containerRef.current) !== null && _containerRef$current !== void 0 ? _containerRef$current : document.body;
@@ -184,10 +186,16 @@ function ControlPanel(props) {
184
186
  className: "control-panel-col",
185
187
  style: {
186
188
  flex: "0 0 450px"
187
- }
188
- }, /*#__PURE__*/React.createElement("h2", {
189
- className: "control-panel-title"
190
- }, ControlTabNames[tab]), visibleControls.colorPresetsDropdown && tab === ControlTab.Channels && renderChannelSettingsHeader(), hasImage && /*#__PURE__*/React.createElement("div", {
189
+ },
190
+ ref: columnRef
191
+ }, /*#__PURE__*/React.createElement("div", {
192
+ className: "control-panel-title-container"
193
+ }, /*#__PURE__*/React.createElement("h2", null, ControlTabNames[tab]), tab === ControlTab.Channels && /*#__PURE__*/React.createElement(CopySettingsButton, {
194
+ imageName: props.imageName,
195
+ scrollContainer: columnRef.current,
196
+ hide: props.collapsed,
197
+ getDropdownContainer: getDropdownContainer
198
+ })), visibleControls.colorPresetsDropdown && tab === ControlTab.Channels && renderChannelSettingsHeader(), hasImage && /*#__PURE__*/React.createElement("div", {
191
199
  className: "control-panel-content"
192
200
  }, tab === ControlTab.Channels && /*#__PURE__*/React.createElement(ChannelsWidget, {
193
201
  channelDataChannels: props.channelDataChannels,
@@ -57,12 +57,18 @@
57
57
  display:none;
58
58
  }
59
59
 
60
- .control-panel-col-container .control-panel-col .control-panel-title{
60
+ .control-panel-col-container .control-panel-col .control-panel-title-container{
61
61
  margin:16px;
62
62
  max-height:24px;
63
- font-size:1.4em;
63
+ display:flex;
64
+ align-items:center;
65
+ justify-content:space-between;
64
66
  }
65
67
 
68
+ .control-panel-col-container .control-panel-col .control-panel-title-container > *{
69
+ font-size:1.4em;
70
+ }
71
+
66
72
  .control-panel-col-container .control-panel-col .channel-settings-header{
67
73
  margin:0 16px 16px;
68
74
  display:flex;
@@ -177,3 +183,24 @@
177
183
  font-size:10px;
178
184
  top:calc(100% + 3px);
179
185
  }
186
+
187
+ .control-panel-col-container .ant-modal.modal-settings-import .ant-upload.ant-upload-drag{
188
+ transition-property:background-color, border-color;
189
+ }
190
+
191
+ .control-panel-col-container .ant-modal.modal-settings-import .ant-upload.ant-upload-drag:hover{
192
+ background-color:#ffffff20;
193
+ }
194
+
195
+ .control-panel-col-container .ant-dropdown .import-dropdown-menu-item-include-color{
196
+ white-space:nowrap;
197
+ padding-bottom:9px;
198
+ }
199
+
200
+ .control-panel-col-container .ant-dropdown .import-dropdown-menu-item-include-color.ant-dropdown-menu-item-active{
201
+ background-color:transparent;
202
+ }
203
+
204
+ .control-panel-col-container .ant-dropdown .ant-dropdown-menu-item-divider{
205
+ background-color:var(--color-controlpanel-border);
206
+ }